Das komplette Frontend für die tutego-Kunden/Trainer/Serminarverwaltung ist in GWT implementiert. Die Codebase ist auf dem Level von GWT 1.1 und bis zur letzten Version hat sich viel geändert. Über Updates blogge ich regelmäßig. Es wurde Zeit neu zu betrachten, welche Teile meiner Software ich vielleicht umbauen kann. Viele Themen sind spannend, aber leider gibt im Netz kaum Dokus, und da meine Lösung läuft, war die Motivation nicht sonderlich hoch, Stunden zum Rumspielen zu investieren.
Eine der großen Neuerungen in GWT 2.0 ist die Möglichkeit, deklarativ Oberflächen aufzubauen. Im Mittelpunkt steht das UiBinder-Famework, was eine XML-Dateien zur Beschreibung eines Widget-Baum nutzt und in eine initialisierte GWT-Komponente überführt. Das Feature ist relativ gut dokumentiert und die Änderungen im Code sind klein. Daher habe ich mir ein paar Stunden für die Überführung einer View genommen.
Für jede View gibt es bei mir eine Klasse, die von einer GWT-Klasse wie Composite abgeleitet ist. Sie wird vom Presenter aufgebaut. Die View-Klasse deklariert eine Reihe von Objektvariablen. Die werden später initialisiert. Vorher sahen meine Programme etwas so aus:
// Contact person
HTML contactPersonLabel = new HTML( "<b>AnsprechpartnerIn</b> (Name, EMail, Telefon)" );
contactPersonLabel.getElement().getStyle().setMarginTop( 0.5, Unit.EM );
panel.add( contactPersonLabel );
panel.add( contactPersonTextBox = new FormTextBox() );
// tutego rate per day
HTML ratePerDayLabel = new HTML( "<b>tutego Tagessatz</b> (netto)" );
ratePerDayLabel.getElement().getStyle().setMarginTop( 0.5, Unit.EM );
panel.add( ratePerDayLabel );
panel.add( new HorizontalFlowPanel( ratePerDayTextField = new FormIntegerTextBox(), new HTML( " Euro" ) ) );
ratePerDayTextField.setWidth( "5em" );
Mit Styles arbeite ich auch, wobei ich dies nicht für das Mikrolayout verwende. Lokale Abstände initialisierte ich immer direkt.
Der UiBinder externalisiert die Beschreibung des Objektgraphen in eine XML-Datei. Es ist wichtig zu verstehen, dass der UiBinder keinen Template-Engine ist, also die XML-Datei kein XHTML ist, wo man a) einfach HTML schreiben kann und b) so etwas wie Wiederholungen/Fallunterscheidungen schreiben kann. Es ist “nur” eine Beschreibung des Objektgraphen und ausschließlich GWT-Widgets werden dort referenziert. (Auch wenn das bei einem SpanElement mit <div></div> auf den ersten Blick nicht so aussieht.)
Das GWT-Plugin für Eclipse ermöglicht gleich das Anlegen der XML-Datei und der Klasse, was für neue Views praktisch ist. Ich habe von Hand eine XML-Datei EditCustomerView.ui.xml aufgebaut, die so aussieht:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui=’urn:ui:com.google.gwt.uibinder‘
xmlns:g=’urn:import:com.google.gwt.user.client.ui‘ xmlns:t=’urn:import:traida.client.web.util‘>
<ui:with field=’res‘ type=’traida.client.resource.TraidaResources‘ />
<ui:style>
.bottomGap { margin-bottom: 1em; }
</ui:style>
<g:VerticalPanel>
<g:Label styleName='{res.style.header1}‘ ui:field=’header‘ />
<t:TitledPanel>
<g:HTML>Adresse und Kontakt</g:HTML>
<g:Anchor ui:field=’editContactAnchor‘>[Bearbeiten]</g:Anchor>
</t:TitledPanel>
<g:HTML styleName='{style.bottomGap}‘ ui:field=’businessCardHTML‘ />
<t:TitledPanel>
<g:HTML>Kurs(e)</g:HTML>
<g:Anchor ui:field=’addNewTrainingAnchor‘>[+]</g:Anchor>
</t:TitledPanel>
<t:VerticalFlowPanel styleName='{style.bottomGap}‘ ui:field=’trainingsPanel‘ />
<t:TitledPanel>
<g:HTML>Vermerk und Logo</g:HTML>
</t:TitledPanel>
…
</g:VerticalPanel>
</ui:UiBinder>
Am Anfang werden zwei Namensräume g und t aufgebaut, einmal für die Google-Komponenten, einmal für eigene. Eigene Komponenten können einfach verwendet werden, auch die Tastaturvervollständigung klappt, ist allerdings (nur bei mir?) ziemlich träge.
Wichtig sind die ui:field-Elemente. Sie entsprechen Variablen, die in einer Java-Klasse deklariert werden. Parallel zu der XML-Datei EditCustomerView.ui.xml gibt es eine andere zugehörige Java-Klasse EditCustomerView. Sie beginnt so:
class EditCustomerView extends Composite
{
interface EditCustomerViewUiBinder extends UiBinder<Widget, EditCustomerView> {} // 2
private static EditCustomerViewUiBinder uiBinder = GWT.create(EditCustomerViewUiBinder.class);
@UiField Label header; // 1
…
EditCustomerView()
{
initWidget( uiBinder.createAndBindUi( this ) ); // 3
setWidth( "800" );
…
}
}
Im Rumpf ist etwas Magie, und drei Details sind wichtig:
- Die Klasse hat für alle in der XML-Datei deklarierten ui:field-Dinge eine Objektvariable mit der entsprechenden Annotation @UiField. Die Typen müssen zusammenpassen, also Label zu <g:Label> usw. Der UiBinder instanziiert selbständig die Typen durch den Standardkonstruktor. Ist keiner vorhanden, muss man tricksen und eine Factory einführen.
- Zu den Generics beim UiBinder: das erste Typargument (hier Widget) ist das, was später als Ergebnis “rauskommt”. In der XML-Datei nutze ich ein VerticalPanel, sodass der Typ hätte eigentlich auch spezieller sein können, doch den spezielleren Typ brauche ich nicht. Das zweite Typargument ist die Klasse, die die @UiField-Variablen deklariert, also die eigene Klasse selbst.
- Der Aufruf createAndBindUi() baut den Objektbaum auf. Das Ergebnis der Methode ist vom Typ, der im ersten Generics zugewiesen wurde, also im Beispiel Widget.
Das zur Technik. Die Frage ist, ob sich die Umstellung gelohnt hat. Bisher habe ich eine View umgestellt, doch das Ergebnis sieht gut aus. Die hierarchische Gliederung ist übersichtlich, wobei mir die XML-Ansicht reicht und ich den Designer nicht brauche. Allerdings fände ich es besser, das Layout einfacher verändern zu können. Soll zum Beispiel der Abstand zur nächsten Zeile erholt werden, so kann man nicht einfach beim Element selbst ein CSS-Syle setzen, sondern muss ein <style> Element einführen, ihm einen Namen geben und dann zuweisen. Das ist mehr Schreibarbeit als nötig. Die anderen Views werde ich nun auch umstellen.
Ob ich die UiBinder für jede View einsetzen werde wird sich zeigen, da einige Views viel stäter aus HTML aufgebaut werden und die View im Prinzip nur einen Titel setzt und der Rest ist HTML.
Ein paar Links:
- http://code.google.com/intl/de/webtoolkit/doc/latest/DevGuideUiBinder.htm
- http://stackoverflow.com/questions/2052994/how-to-declare-dependent-style-names-with-uibinder
- http://aarendar.wordpress.com/2010/03/02/learning-gwt-uibinder-part-2-styles-and-annotations/