136 86 3MB
German Pages 319 Year 2009
MOSMANN
PRAXISBUCH WICKET
michael MOSMANN
PRAXISBUCH
WICKET PROFESSIONELLE WEB-2.0ANWENDUNGEN ENTWICKELN
Mosmann Praxisbuch Wicket
v
Bleiben Sie einfach auf dem Laufenden: www.hanser.de/newsletter Sofort anmelden und Monat für Monat die neuesten Infos und Updates erhalten.
Michael Mosmann
Praxisbuch Wicket Professionelle Web-2.0-Anwendungen entwickeln
Michael Mosmann, Lübeck Kontakt: [email protected]
Alle in diesem Buch enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autor und Verlag übernehmen infolgedessen keine juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen – oder Teilen davon – entsteht. Ebenso übernehmen Autor und Verlag keine Gewähr dafür, dass beschriebene Verfahren usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Buch berechtigt deshalb auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Bibliografische Information der Deutschen Nationalbibliothek: Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
Dieses Werk ist urheberrechtlich geschützt. Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren) – auch nicht für Zwecke der Unterrichtsgestaltung – reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.
© 2009 Carl Hanser Verlag München, www.hanser.de Lektorat: Margarete Metzger Copy editing: Jürgen Dubau, Freiburg Herstellung: Irene Weilhart Umschlagdesign: Marc Müller-Bremer, www.rebranding.de, München Umschlagrealisation: Stephan Rönigk Datenbelichtung, Druck und Bindung: Kösel, Krugzell Ausstattung patentrechtlich geschützt. Kösel FD 351, Patent-Nr. 0748702 Printed in Germany ISBN 978-3-446-41909-4
für meine Eltern
0
2
Inhalt Vorwort..............................................................................................................................XIII 1 1.1
1.2
1.3
2 2.1 2.2
2.3
Einleitung ................................................................................................................. 1 Warum Wicket? .......................................................................................................................3 1.1.1 Einfach, Konsistent, Offensichtlich ............................................................................4 1.1.2 Wiederverwendbarkeit ...............................................................................................4 1.1.3 Sauber getrennt...........................................................................................................5 1.1.4 Sicher..........................................................................................................................5 1.1.5 Effizient und skalierbar ..............................................................................................6 1.1.6 Komplett.....................................................................................................................6 1.1.7 Eine gute Wahl ...........................................................................................................6 Vorbereitung und Installation...................................................................................................7 1.2.1 Java, Maven und Eclipse ............................................................................................7 1.2.2 Versionskontrolle mit Subversion ..............................................................................7 Grundlagen einer Webanwendung ...........................................................................................8 1.3.1 Anwendungsschichten................................................................................................8 1.3.2 Verzeichnis und Paketstruktur..................................................................................11 1.3.3 Unit-Tests .................................................................................................................12 Aufsetzen der Teilprojekte .................................................................................... 15 Nomenklatur der Teilprojekte ................................................................................................15 Aufsetzen der Teilprojekte .....................................................................................................16 2.2.1 Projektbasis ParentPom ............................................................................................16 2.2.2 Teilprojekt Base .......................................................................................................20 2.2.3 Teilprojekte Datenbankkonfiguration.......................................................................20 2.2.4 Teilprojekt Persistenz ...............................................................................................22 2.2.5 Teilprojekt Applikationsschicht................................................................................24 2.2.6 Teilprojekt Webapp ..................................................................................................24 2.2.7 Teilprojekt ParentPom – Abschluss..........................................................................26 Erstellen von Eclipse-Projektdateien......................................................................................27
VII
Inhalt 3 3.1 3.2
3.3
3.4 3.5
4 4.1 4.2
4.3
4.4
5 5.1 5.2 5.3 5.4
VIII
Mit Leben füllen ..................................................................................................... 29 Konfiguration mit Spring ....................................................................................................... 29 Datenbankkonfiguration......................................................................................................... 30 3.2.1 Teilprojekt dbconfig................................................................................................. 30 3.2.2 Teilprojekt dbconfig-test .......................................................................................... 31 3.2.3 Teilprojekt dbconfig-schema-update........................................................................ 31 3.2.4 Schemagenerierung mit Hibernate ........................................................................... 32 Persistenz ............................................................................................................................... 33 3.3.1 Datenbankzugriff – Allgemeine Schnittstellendefinition ......................................... 33 3.3.2 Datenbankzugriff – Hilfsklassen .............................................................................. 34 3.3.3 Datenbankzugriff – User .......................................................................................... 35 3.3.4 Datenbankzugriff – Konfiguration ........................................................................... 37 3.3.5 Persistenz-Tests........................................................................................................ 38 3.3.6 Schema-Update ........................................................................................................ 40 Anwendungsschicht ............................................................................................................... 41 Präsentationsschicht............................................................................................................... 41 3.5.1 Hilfsklasse für Maven-Projekte................................................................................ 41 3.5.2 Wicket Web Application .......................................................................................... 42 3.5.3 Servlet-Konfiguration............................................................................................... 44 3.5.4 Spring-Konfiguration ............................................................................................... 46 3.5.5 Start der Anwendung................................................................................................ 46 Die Wicket-Architektur .......................................................................................... 49 Wicket und das HTTP-Protokoll............................................................................................ 49 Struktur .................................................................................................................................. 49 4.2.1 WebApplication ....................................................................................................... 50 4.2.2 Session ..................................................................................................................... 50 4.2.3 PageMap .................................................................................................................. 50 4.2.4 Page.......................................................................................................................... 50 4.2.5 PageStore ................................................................................................................. 51 4.2.6 Component ............................................................................................................... 51 Request-Behandlung .............................................................................................................. 51 4.3.1 Komponentenphasen ................................................................................................ 52 4.3.2 Nebenläufigkeit – Threads ....................................................................................... 52 Komponenten, Modelle, Markup ........................................................................................... 53 4.4.1 Komponenten ........................................................................................................... 53 4.4.2 Modelle .................................................................................................................... 53 4.4.3 Markup ..................................................................................................................... 53 Modelle ................................................................................................................... 55 Konverter ............................................................................................................................... 55 Einfache Modelle ................................................................................................................... 57 5.2.1 Modelle verändern ................................................................................................... 58 Modell-Hilfsklassen............................................................................................................... 60 Modelle und Serialisierung .................................................................................................... 61 5.4.1 DetachableModel – Dynamische Modelldaten......................................................... 61
Inhalt
5.5
5.6
6 6.1
6.2
6.3 6.4
6.5
7 7.1
7.2
5.4.2 Kaskadierung von Modellen.....................................................................................62 5.4.3 Automatische Kaskadierung von Modellen..............................................................65 5.4.4 Datenbankzugriffsmodelle........................................................................................66 Komplexe Modellklassen.......................................................................................................69 5.5.1 Zugriff auf Bean-Properties......................................................................................69 5.5.2 Die Klasse PropertyModel........................................................................................72 5.5.3 CompoundPropertyModel ........................................................................................74 Ausgelagerte Informationen...................................................................................................76 5.6.1 Einfacher Zugriff auf Ressourcen.............................................................................76 5.6.2 ResourceModel.........................................................................................................76 5.6.3 StringResourceModel ...............................................................................................78 Komponenten......................................................................................................... 81 Basisklasse Component..........................................................................................................81 6.1.1 Komponentenbaum ..................................................................................................81 6.1.2 Darstellungsphasen...................................................................................................83 6.1.3 Page, Session und Application .................................................................................84 6.1.4 Komponentenpfad ....................................................................................................84 6.1.5 Modelle ....................................................................................................................84 6.1.6 Feedback ..................................................................................................................85 Grundlagen der Vererbung.....................................................................................................85 6.2.1 Eine Seite mit eigenen Komponenten.......................................................................85 6.2.2 Vererbung für Fortgeschrittene................................................................................91 Style, Locale und Variation....................................................................................................94 6.3.1 Markup-Variationen .................................................................................................94 Sichtbarkeit ............................................................................................................................99 6.4.1 wicket:enclosure.....................................................................................................100 6.4.2 Empfehlung zur Anwendung..................................................................................101 Ajax......................................................................................................................................102 6.5.1 Ajax-Events............................................................................................................103 6.5.2 Einfache Event-Behandlung ...................................................................................104 6.5.3 Automatische Event-Behandlung ...........................................................................105 Basiskomponenten.............................................................................................. 109 Gruppierende Komponenten ................................................................................................109 7.1.1 Seiten......................................................................................................................109 7.1.2 Panel.......................................................................................................................117 7.1.3 Fragment ................................................................................................................119 7.1.4 Border.....................................................................................................................120 7.1.5 ComponentBorder ..................................................................................................125 7.1.6 WebMarkupContainer ............................................................................................126 Inhaltselemente ....................................................................................................................127 7.2.1 Label und MultiLineLabel......................................................................................127 7.2.2 Lokaler Konverter ..................................................................................................129 7.2.3 XML.......................................................................................................................130 7.2.4 Das wicket:message-Tag ........................................................................................131
IX
Inhalt
7.3
7.4
8 8.1
8.2
9 9.1 9.2 9.3 9.4
9.5
9.6 9.7 9.8 9.9
X
7.2.5 Image...................................................................................................................... 132 Links .................................................................................................................................... 137 7.3.1 Von A nach B......................................................................................................... 137 7.3.2 Ajax und Links....................................................................................................... 138 7.3.3 Link-Tricks............................................................................................................. 140 7.3.4 Externe Links ......................................................................................................... 141 7.3.5 Popups.................................................................................................................... 141 7.3.6 ResourceLink ......................................................................................................... 143 7.3.7 Formularlinks ......................................................................................................... 144 Behavior............................................................................................................................... 144 7.4.1 Darf es etwas JavaScript sein? ............................................................................... 144 7.4.2 Attribute anpassen .................................................................................................. 145 7.4.3 Attribute erweitern ................................................................................................. 147 7.4.4 Ajax und Formulare ............................................................................................... 148 Listen und Tabellen ............................................................................................. 149 Darstellung von Listen......................................................................................................... 149 8.1.1 RepeatingView....................................................................................................... 149 8.1.2 RefreshingView...................................................................................................... 150 8.1.3 ListView................................................................................................................. 152 8.1.4 PropertyListView ................................................................................................... 153 8.1.5 ColumnListView .................................................................................................... 154 DataProvider ........................................................................................................................ 156 8.2.1 DataView ............................................................................................................... 156 8.2.2 GridView................................................................................................................ 158 8.2.3 DataGridView ........................................................................................................ 159 8.2.4 DataTable ............................................................................................................... 161 8.2.5 DefaultDataTable ................................................................................................... 162 Formulare ............................................................................................................. 169 Voraussetzungen .................................................................................................................. 169 Feedback .............................................................................................................................. 170 Basisklasse für alle Beispiele ............................................................................................... 171 Formulare absenden ............................................................................................................. 172 9.4.1 Absenden mit Submit-Button ................................................................................. 172 9.4.2 Button-Komponente ............................................................................................... 173 9.4.3 Submit per Ajax ..................................................................................................... 174 9.4.4 POST und GET ...................................................................................................... 175 Textfelder............................................................................................................................. 176 9.5.1 Typangabe.............................................................................................................. 178 9.5.2 Automatische Typermittlung.................................................................................. 179 Label .................................................................................................................................... 181 CheckBox ............................................................................................................................ 182 RadioButton......................................................................................................................... 185 Auswahlfelder...................................................................................................................... 186 9.9.1 Select...................................................................................................................... 186
Inhalt
9.10
9.11
9.12
9.13
9.14 9.15
9.16 9.17 10 10.1
9.9.2 DropDownChoice...................................................................................................188 9.9.3 ListMultipleChoice.................................................................................................190 Dateien hochladen................................................................................................................192 9.10.1 FileUpload..............................................................................................................192 9.10.2 MultiFileUpload .....................................................................................................194 Gültigkeitsprüfung ...............................................................................................................195 9.11.1 StringValidator .......................................................................................................196 9.11.2 Minimum und Maximum .......................................................................................197 9.11.3 E-Mail ....................................................................................................................197 9.11.4 URL........................................................................................................................199 9.11.5 Eigene Validatoren .................................................................................................199 FormValidator......................................................................................................................201 9.12.1 Passwortprüfung.....................................................................................................201 9.12.2 Eigene Prüfung .......................................................................................................203 Ajax......................................................................................................................................205 9.13.1 AjaxFormSubmitBehavior......................................................................................205 9.13.2 AjaxFormValidatingBehavior ................................................................................207 9.13.3 AjaxComponentUpdatingBehavior ........................................................................207 9.13.4 OnChangeBehavior ................................................................................................209 9.13.5 AutoCompleteTextField .........................................................................................210 AjaxEditableLabel ...............................................................................................................212 Erweitertes Feedback ...........................................................................................................214 9.15.1 Feedback zum Formular .........................................................................................214 9.15.2 Feedback für die Komponente................................................................................215 9.15.3 Feedback als Rahmen .............................................................................................216 9.15.4 Feedback als Indikator............................................................................................217 9.15.5 Feedback per CSS ..................................................................................................218 Generierte Formulare ...........................................................................................................220 Verschachtelte Formulare ....................................................................................................222
10.2 10.3
Sessions und Security ........................................................................................ 225 Einfache Variante.................................................................................................................225 10.1.1 Eine eigene Session-Klasse ....................................................................................225 10.1.2 Geschützte Seiten ...................................................................................................226 10.1.3 Strategie..................................................................................................................226 10.1.4 WebApplication......................................................................................................227 10.1.5 Seiten......................................................................................................................228 Marker an Komponenten......................................................................................................231 Elemente ausblenden............................................................................................................233
11 11.1 11.2 11.3 11.4
Wicket in der Praxis............................................................................................. 235 Die Integration von Spring...................................................................................................235 Navigation............................................................................................................................237 CSS einbinden......................................................................................................................244 Eigene Basiskomponenten ...................................................................................................250
XI
Inhalt 11.5
11.6
11.7
11.8 11.9
12 12.1
Komponententausch............................................................................................................. 253 11.5.1 AjaxFallbackConfirmLink ..................................................................................... 254 11.5.2 Wizard.................................................................................................................... 256 Suchmaschinenoptimierung ................................................................................................. 258 11.6.1 Pfad für BookmarkablePages ................................................................................. 258 11.6.2 SessionTimeoutPage .............................................................................................. 262 11.6.3 SEO-Links.............................................................................................................. 264 11.6.4 Servlet-Filter .......................................................................................................... 268 11.6.5 Tracking mit Google Analytics .............................................................................. 271 Ressourcen........................................................................................................................... 274 11.7.1 Dynamisch erzeugte Grafiken ................................................................................ 274 11.7.2 Automatisch generierte Thumbnails....................................................................... 276 11.7.3 Download durch Formular ..................................................................................... 277 11.7.4 Shared Resources ................................................................................................... 278 11.7.5 RSS-Feed ............................................................................................................... 280 Links auf Seiten und Ressourcen ......................................................................................... 282 Optimierungen ..................................................................................................................... 284 11.9.1 Applikation............................................................................................................. 284 11.9.2 Konverter ............................................................................................................... 284 11.9.3 Debug..................................................................................................................... 284 11.9.4 Ressource ............................................................................................................... 285
12.2
Fehlersuche.......................................................................................................... 287 Häufige Fehlerquellen.......................................................................................................... 287 12.1.1 Komponenten fehlen .............................................................................................. 287 12.1.2 Komponente ist bereits vorhanden ......................................................................... 287 12.1.3 Ajax funktioniert nicht ........................................................................................... 288 Unit-Tests ............................................................................................................................ 288
13
Anfang oder Ende?.............................................................................................. 293
Register............................................................................................................................ 295
XII
Vorwort Ich beschäftige mich seit meiner frühen Jugend mit der Softwareentwicklung. Angefangen hat das auf einem ZX-Spektrum-Klon in Basic. Im Laufe der Jahre kamen so unterschiedliche Programmiersprachen und Betriebssysteme zusammen. Der Einstieg in die objektorientierte Programmierung kam mit C++. Die ersten Versuche, Anwendungen mit Benutzeroberflächen zu schreiben, habe ich auf einem Amiga unternommen. Die Anwendungen waren klein und der Nutzerkreis beschränkt. Im Juli 1999 fing ich bei Dr. Klein und Co. an und startete mit ersten Anwendungen, die im Internet zur Verfügung gestellt wurden. Das waren Java-Applets, die z.B. Kreditberechnungen ermöglichten. Nicht viel später entstanden die ersten Webanwendungen, die damals als Servlets und mit JavaServer Pages realisiert wurden. Die Anwendungen wurden komplexer und dieses Entwicklungsmodell stieß zunehmend an seine Grenzen. Mangels guter Alternativen entstanden so im Laufe der Jahre einige selbst entwickelte Anwendungsframeworks. Diese linderten zwar die Probleme etwas, beseitigten das grundlegende Problem aber nicht. Die Suche nach Alternativen ging weiter und so landete ich im März 2008 auf der Webseite von Wicket. Ich hatte mir bis dahin so einiges angesehen: GWT, Rails, Thinwire um nur einige zu nennen. Im Gegensatz zu diesen überzeugte mich Wicket von Anfang an. Das ist nun mehr als ein Jahr her und für mich hat sich durch Wicket einiges grundlegend verändert. Webanwendungen zu schreiben ist so einfach geworden, dass man bei neuen Features nicht darüber nachdenkt, wie man sie realisiert, sondern im ersten Moment nur, ob man sie realisiert. Wusste man bei den alten Anwendungen genau, welche Problemstellungen „Bauchschmerzen“ verursachten, sind diese Anforderungen mit Wicket keine Herausforderung und das Entwickeln mit Wicket macht einfach nur Spaß. Die Arbeit kommt vor den Vergnügen. Doch der Spaß sollte nicht lange auf sich warten lassen. In diesem Sinne: Let’s have some fun.
XIII
Vorwort
Danksagung Es gab eine Menge Menschen, die mich direkt und indirekt beim Schreiben dieses Buches unterstützt haben. Daher ist es unmöglich, nicht aus Versehen, den einen oder anderen zu vergessen. Die Reihenfolge sollte auf keinen Fall als Wertung missverstanden werden ☺. Ich danke natürlich meiner Frau, weil sie mich immer wieder angespornt und mir den Rücken frei gehalten hat. Ich danke dem Carl Hanser Verlag und da ganz besonders Frau Metzger, die immer sehr viel geduldiger und ruhiger war als ich. Vermutlich hätte ich sonst bereits das Handtuch geworfen. Dank gebührt auch Dirk Diebel, der einen Blick auf Auszüge dieses Buches werfen durfte und mir bestätigte, dass das nicht ganz unverständlich ist, was ich da zusammengeschrieben habe. Ohne Stephan Lamprecht wäre dieses Buch nie entstanden, weil er nicht nur den Kontakt zum Verlag hergestellt hat, sondern auch als erfahrender Autor immer wieder mit einem „Das ist ganz normal“ meine Seele gestreichelt hat. Vielen Dank an alle Wicket-Entwickler und die Wicket-Community für ein großartiges Framework und die großartige Unterstützung bei Problemen und Fragen. Und zum Schluss möchte ich noch allen danken, die in dieser Auflistung nicht vorkommen, aber mindestens das Gefühl haben dürfen, dass sie mir während dieser Zeit das Leben leichter gemacht haben. Michael Mosmann, August 2009
XIV
1 1 Einleitung Ich bin im Frühjahr 2008 auf Wicket aufmerksam geworden. Doch da hatte Wicket bereits eine längere Entwicklung hinter sich, denn es wird bereits seit 2005 entwickelt. 2006 wurde Wicket ein offizielles Apache-Projekt, und im Januar 2007 wurde dann das erste Release als Apache-Projekt veröffentlicht. Seit April 2008 setze ich Wicket mit viel Freude in Projekten ein. Die Idee zu diesem Buch entstand, nachdem ich ein paar Monate mit Wicket gearbeitet hatte und die Begeisterung für das Framework immer stärker zunahm. Ich hatte zu dem Zeitpunkt bereits einiges über Wicket gelesen, vermisste aber oft den Praxisbezug der Beispiele und Lösungen. Viele Wege führen nach Rom, aber nicht jeder ist kurz oder schnell. Und so habe ich am Anfang auch das eine oder andere Mal eine falsche Abbiegung genommen, die sich zwar nicht als Sackgasse entpuppte, aber teilweise einen gehörigen Umweg darstellte. Dieses Buch soll als Straßenkarte dienen, damit man sich auf den gut ausgebauten Straßen, die Wicket bietet, zurechtfindet. Wer sollte das Buch lesen? Wer Webanwendungen entwickelt oder entwickeln möchte und dabei auf Java als Programmiersprache setzt, wird nach der Lektüre dieses Buches sehr schnell Webanwendungen entwickeln können. Dabei werden alle Aspekte beleuchtet, die für das Erstellen einer Webanwendung wichtig und notwendig sind. Entwickler, die bereits Wicket einsetzen, profitieren von den praxisnahen Beispielen und bewährten Lösungen. Konventionen In diesem Buch gelten folgende Konventionen: Programmcode, Verzeichnisse und Dateinamen werden innerhalb des normalen Textes als code dargestellt. Quelltexte werden ohne besondere Überschrift als Codeblock dargestellt: public void javacode()
Bei der Auszeichnung mit Überschrift wird ein Dateiname angegeben:
1
1 Einleitung Listing 1.1 Beispiel.java public void andererCode()
Für HTML-Quelltexte, Property- und Markup-Dateien wird dieselbe Formatierung wie für Java-Quelltexte benutzt. Wenn durch die begrenzte Breite des Buches Text umbrochen werden muss und das an dieser Stelle nicht möglich ist, wird das unwillkürliche Zeilenende durch ein Leerzeichen und „\“ angegeben. Nur wenn das „\“-Zeichen an letzter Stelle steht, ist ein unwillkürlicher Zeilenumbruch gemeint. Die nächste Zeile wird dann um zwei Leerzeichen gegenüber dem Zeilenanfang eingerückt: Text=Der Text ist zu lang, als dass er auf eine Zeile passen \ würde, und beginnt auf der neuen Zeile mit zwei Leerzeichen, \ die ebenfalls ignoriert werden müssen. Ein \-Zeichen mittendrin ist in Ordnung.
Da sich viele Dinge immer wiederholen, wird im Laufe des Buches das eine oder andere gekürzt. Damit man erkennen kann, ob gekürzt wurde, erscheinen an der entsprechenden Stelle drei Punkte: ...
Wenn solche Kürzungen vorgenommen wurden, ist es ebenso wahrscheinlich, dass nur leichte Anpassungen an bereits bestehenden Quelltexten vorgenommen wurden. Diese werden der besseren Übersichtlichkeit halber entsprechend hervorgehoben: ... das stand hier schon doch das ist neu ...
Kommandos, die in der Konsole eingeben werden müssen, sind von Natur aus nie mehrzeilig. Es gelten daher dieselben Regeln zum Umbruch wie bei Quelltexten. Allerdings beginnen Kommandos immer mit einem Dollarzeichen, es sei denn, sie kommen innerhalb des Textes vor. Dann ergibt sich die Bedeutung aus dem Text: $ kommando
Java Alle Felder einer Klasse starten mit einem Unterstrich. Wenn eine Klasse als Bean benutzt wird, dann erfolgt der Zugriff auf das Feld durch eine passende set- und get-Methode. Listing 1.2 BeispielBean.java public class BeispielBean { String _titel; public String getTitel() { return _titel; } public void setTitel(String titel) { _titel=titel; } }
2
1.1 Warum Wicket? Im Folgenden wird diese ausführliche Schreibweise gekürzt. Dabei werden alle Felder mit den entsprechenden Typangaben aufgeführt und exemplarisch die Methodennamen für die ersten Felder angegeben. Listing 1.3 BeispielBean.java (gekürzt) public class BeispielBean { String _titel; getTitel(),setTitel(),... }
Importanweisungen werden nur aufgeführt, wenn die zu importierende Klasse nicht aus dem Beispielprojekt oder aus dem Wicket-Framework stammt und sich auch nicht in den Java-Standardbibliotheken befindet (z.B. import java.io.Serializable;). Die Kürzung wird durch ... gekennzeichnet. Markup Für die Darstellung von Komponenten nutzt Wicket einen Template-Mechanismus. Dabei kann einer Komponente eine HTML-Datei zugeordnet werden, in der alle nötigen Strukturen für die Darstellung enthalten sind. Diese Datei wird im folgenden Markup genannt. Version Das Buch bezieht sich auf die Wicket-Version 1.4, die mit dem Erscheinen dieses Buches in einer finalen Version verfügbar sein wird. Die Beispiele können begrenzt auch auf Wicket 1.3 übertragen werden. Wenn man nicht durch andere Abhängigkeiten gezwungen ist, Wicket noch in einer alten Version einzusetzen, sollte man spätestens jetzt den Schritt wagen und auf Wicket in der Version 1.4 migrieren. Online Da die Entwicklung von Wicket immer weiter geht, veröffentliche ich auf der Seite http://www.wicket-praxis.de/blog/ fortlaufend Erfahrungen und Tipps aus der praktischen Arbeit. Wenn Sie mit mir in Kontakt treten wollen, genügt eine E-Mail an: [email protected].
1.1
Warum Wicket? Für das Entwickeln von Webanwendungen stehen eine ganze Reihe von Frameworks und Technologien bereit. Die Auswahl fällt schwer, da man die meisten Anforderungen mit einer ganzen Reihe von Technologien umsetzen könnte. Wenn man dann die zu verwendende Programmiersprache auf Java eingrenzt, bleiben trotzdem noch einige Frameworks übrig, die um die Gunst der Programmierer wetteifern.
3
1 Einleitung Aus der umfangreichen Liste der Möglichkeiten habe ich Wicket ausgewählt. Dabei hat Wicket zunächst mit recht einfachen Mitteln mein Interesse geweckt: Es gibt eine sehr einfache und schnelle Möglichkeit, ein Testprojekt aufzusetzen. Dieses kann sofort gestartet werden und zeigt ein minimales Grundgerüst einer WicketAnwendung. Die bestehende Anwendung kann einfach verändert werden, was zu einem sehr schnellen Erfolgserlebnis führt. Die Lernkurve für die ersten Gehversuche ist sehr gering. Nachdem ich auf diese Weise sehr schnell einen ersten Eindruck gewinnen konnte, habe ich mich eingehender mit Wicket beschäftigt. Nach einer kurzen Einarbeitungsphase und einigen kleinen Beispielanwendungen war ich mir sicher, dass ich Wicket für meine zukünftigen Webprojekte einsetzen werde. Die Kriterien, die für Wicket sprechen, habe ich an eine Übersicht auf der Webseite des Wicket-Projektes 1 angelehnt, beschränke mich aber hier auf die für mich besonders wichtigen Aspekte.
1.1.1
Einfach, Konsistent, Offensichtlich
Ich empfehle als Einstieg eine kleine Beispielanwendung, die man sich über die „Quickstart“-Funktion 2 generieren lassen kann. Anhand dieser Anwendung kann man einige der folgenden Punkte recht schnell nachvollziehen: Alles kann in Java realisiert werden. Das Grundprinzip erinnert an Swing und ist leicht zu verstehen. Es müssen keine Konfigurationsdateien erstellt werden. Wicket-Anwendungen können einfach für Suchmaschinen optimiert werden. Hervorragende Unterstützung bei der Fehlersuche durch ausführliche Informationen im Entwicklungsmodus. Wicket benötigt keine speziellen Vorbereitungen oder Vorarbeiten. Wicket ist einfach nur Java. Wicket-Anwendungen können über Unit-Tests getestet werden. Es ist für mich einer der größten Vorteile, dass alles in Java ausgedrückt werden muss. Es gibt nur eine Stelle, an der ein Fehler auftreten kann: im Code. Mit den sehr ausführlichen Fehlermeldungen kann der Fehler sehr schnell eingegrenzt und behoben werden.
1.1.2
Wiederverwendbarkeit
Wicket ist zwar nicht das einzige Framework, bei dem man auch bei Komponenten auf Vererbung zurückgreifen kann, doch das einzige, bei dem das sehr einfach umzusetzen ist. 1 2
4
http://wicket.apache.org/introduction.html http://wicket.apache.org/quickstart.html
1.1 Warum Wicket? Das ermöglicht sehr leistungsfähige und gleichzeitig effektive Komponenten. Da Komponenten ihre Funktionalität vollständig in sich kapseln, kann man sie innerhalb derselben Anwendung beliebig oft nutzen, sodass komplexe Anwendungen sehr schnell realisiert werden können. Wicket-Komponenten können auch anwendungsunabhängig entwickelt werden. Alle notwendigen Daten und Informationen können in eine Java-Bibliothek ausgelagert und dann in verschiedensten Projekten eingesetzt werden. Dazu muss die Bibliothek nur in das Projekt eingebunden werden. Auf diese Weise profitiert man sogar in allen nachfolgenden Wicket-Projekten von der eigenen Entwicklungsarbeit, da man oft fast alle allgemeinen Komponenten wiederverwenden kann. So entsteht auch hier eine Bibliothek von Lösungen, wie man sie aus anderen Bereichen der Anwendungsentwicklung kennt.
1.1.3
Sauber getrennt
Wer Wicket zum ersten Mal testet und vorher schon Erfahrungen mit JSP, JSF oder ähnlichen Ansätzen gemacht hat, wundert sich, dass im Markup (HTML-Schnipsel, die zur Komponente gehören und für die Darstellung wichtig sind) keine Code-Bestandteile zu finden sind. Dieser Punkt irritiert so stark, dass er immer wieder z.B. in Mailinglisten thematisiert wird. Während der Arbeit mit Wicket offenbart dieses Konzept seine Vorteile: Im Markup befinden sich nur Referenzen auf Komponenten, aber kein Code oder spezielle Logik. Die Komplexität und der Funktionsreichtum der Anwendung werden nur durch Java als Programmiersprache und nicht durch eine in ihrer Ausdruckstärke eingeschränkten Auszeichnungssprache begrenzt. Programmcode entsteht auf diese Weise nur an einer Stelle und in einer Sprache. Die Markup-Dateien für die Komponenten können mit einfachen Textprogrammen oder mit HTML-Editoren bearbeitet werden. Da Wicket keine besondere Notation erfordert, gehen bei der Bearbeitung keine Informationen verloren, sodass die Gestaltung der Vorlagen z.B. durch einen Webdesigner angepasst werden kann. So wird die Arbeitsteilung zwischen Entwickler und Gestalter gut unterstützt. Im Zweifelsfall muss nur die versehentlich gelöschte Bezeichnung der Komponente wiederhergestellt werden. Auf alle positiven Skalierungseffekte im Entwicklungsprozess durch die Nutzung von Java als Programmiersprache und den darauf aufsetzenden unterstützenden Werkzeugen kann bei der Entwicklung von Anwendungen mit Wicket zurückgegriffen werden. Die Komponentenarchitektur unterstützt diesen Aspekt zusätzlich.
1.1.4
Sicher
Je nach verwendetem Framework sind Webanwendungen mehr oder weniger anfällig für Code-Injection 3-, XSS 4- und andere Attacken. 3 4
http://en.wikipedia.org/wiki/Code_injection http://en.wikipedia.org/wiki/Cross-site_scripting
5
1 Einleitung Wicket-Anwendungen sind sicher. In Wicket werden nur die Parameter der Nutzerinteraktion in der URL kodiert. Der Zustand der Anwendung wird serverseitig gespeichert, sodass eine Kodierung dieser Information in URL-Parameter nicht notwendig ist. Das Einschleusen von Schadcode über manipulierte Parameter ist somit weitestgehend ausgeschlossen. Möchte man den Zustand einer Anwendung durch URL-Parameter steuerbar gestalten, muss man diese Möglichkeit explizit bereitstellen. Da Wicket-Anwendungen vollständig in Java realisiert werden, stehen alle Sicherheitsaspekte der Java-Plattform zur Verfügung.
1.1.5
Effizient und skalierbar
Jede Abstraktion oder Zwischenschicht zieht ein anderes Laufzeitverhalten nach sich. Wie stark die Einschnitte in den verschiedenen Bereichen sind, hängt von der eingesetzten Lösung ab. Wicket gehört zu den leichtgewichtigen Lösungen. Der architekturelle Überbau hat dabei kaum Auswirkungen auf die Gesamtperformance. Da Wicket vollständig in Java realisiert wurde, können zudem zusätzlich zu den klassischen Lösungsansätzen für die Lastverteilung in Webanwendungen andere, auf die Lastverteilung von Java-Anwendungen spezialisierte Produkte eingesetzt werden (z.B. Terracotta 5).
1.1.6
Komplett
Webanwendungen können vollständig mit den in Wicket enthaltenen Komponenten umgesetzt werden. Man muss also nicht in Vorleistung gehen und sich ein eigenes Anwendungsgerüst schaffen. Es ist allerdings derart einfach, eigene Anpassungen vorzunehmen, eigene Komponenten zu entwickeln oder einfach fremde Komponenten zu benutzen, dass man sich schnell an die erweiterten Möglichkeiten gewöhnt und rückblickend feststellen muss, wie reduziert bisherige Lösungen und die damit realisierten Anwendungen waren.
1.1.7
Eine gute Wahl
Mit Wicket zu arbeiten macht Spaß. Die Komplexität von mit Wicket realisierbaren Anwendungen übersteigt die Komplexität solcher Anwendungen, die mit anderen Webtechnologien umgesetzt werden können, bei Weitem. Die Komponentenarchitektur gewährleistet dennoch eine große Übersichtlichkeit, sodass der Einarbeitungsaufwand sehr viel geringer ist, was sich gerade bei der Anpassung von bestehenden Anwendungen positiv bemerkbar macht. Wer Webanwendungen entwickelt, sollte Wicket also unbedingt ausprobieren.
5
6
http://www.terracotta.org/
1.2 Vorbereitung und Installation
1.2
Vorbereitung und Installation Um Webanwendungen mit Wicket schreiben zu können, bedarf es weniger Vorraussetzungen. Im Prinzip reichen Java und ein Texteditor. Wenn man ernsthaft Webanwendungen entwickeln möchte, kommt man aber um ein etwas komplizierteres Setup nicht herum. Dafür bekommt man dann aber auch eine Menge an nützlichen Funktionen und Hilfsmitteln.
1.2.1
Java, Maven und Eclipse
Java Für Wicket benötigt man ein Java Development Kit (JDK) ab Version 5. Eine Java Runtime Environment (JRE) reicht zum Entwickeln nicht aus. Unter Windows laden Sie die Installationsdateien einfach von der Javaseite 6 von Sun herunter und starten die Installation. Unter Linux sollte man Java über das integrierte Paketmanagement installieren. Apache Maven Damit man den Java-Compiler nicht immer von Hand in der Kommandozeile starten muss, haben sich im Laufe der Zeit unterschiedliche Ansätze und Lösungen für dieses Problem entwickelt. In diesem Buch und auch sonst bevorzuge ich Maven für das Build-Management. Für die Installation lädt man das passende Archiv von der Apache-Maven-Seite 7 herunter und folgt den Installationsanweisungen auf der Seite. Eclipse Für das Buch empfehle ich als Entwicklungsumgebung Eclipse 8. Dafür gibt es zwei Gründe: Eclipse ist kostenlos. Maven-Projekte können sehr einfach in Eclipse importiert werden. Alle definierten Abhängigkeiten werden dabei in die Eclipse-Projekte übernommen. Ich empfehle für dieses Buch Eclipse IDE for Java EE Developers ab Version 3.4. Ein Plugin für Maven kann installiert werden. Das ist aber nicht notwendig, weil Maven die Eclipse-Projekte ohne fremde Hilfe erzeugt.
1.2.2
Versionskontrolle mit Subversion
Ein regelmäßiges Backup ist ein gutes Ruhekissen. Auf diese Weise kann man jederzeit einen älteren Zustand wiederherstellen. Spätestens, wenn man mit mehreren Entwicklern an einem Projekt arbeitet, funktioniert das Backup-Prinzip nicht mehr. Ein Versionsver6
http://java.sun.com/javase/downloads/index.jsp http://maven.apache.org/ 8 http://www.eclipse.org/ 7
7
1 Einleitung waltungssystem (Version Control System, VCS) wird unumgänglich. Aber auch für den einzelnen Entwickler bringt der Einsatz eines VCS erhebliche Vorteile. Man kann in der Entwicklung gefahrlos verzweigen und diesen Entwicklungszweig wieder mit dem Hauptzweig zusammenführen. Man kann Dinge ausprobieren und jederzeit wieder auf einen älteren (nicht nur den letzten) Stand zurücksetzen. Subversion hat sich als Standard etabliert und kann über Plugins recht gut in Eclipse integriert werden. Empfehlungen Für die Arbeit an einem Projekt empfiehlt es sich unter Windows, sowohl das EclipsePlugin 9 als auch das Windows Explorer-Plugin TortoiseSVN 10 zu installieren. Unter Linux kann man alle Aufgaben über die Kommandozeile durchführen und sollte daher Subversion über die Paketverwaltung installieren. Da das Eclipse-Plugin eine eigene SubversionBibliothek mitbringt, ist darauf zu achten, dass die Subversion-Client-Version in beiden Fällen dieselbe ist, da sich das Ablageformat teilweise unterscheidet und der ältere Client dann nicht mehr auf die Daten zugreifen kann, wenn der neuere Client sie erst einmal in das neuere Format konvertiert hat. Wer keinen eigenen Subversion-Server aufsetzen möchte, kann auch auf SubversionHosting-Angebote 11 zurückgreifen.
1.3
Grundlagen einer Webanwendung Wicket ist im Gegensatz zu Grails oder Rails kein Framework, das alle Aspekte einer Webanwendung, z.B. den Datenbankzugriff, in einem Paket bündelt. Wir müssen uns daher selbst um die notwendigen Bibliotheken bemühen, was uns aber vor keine großen Herausforderungen stellen wird.
1.3.1
Anwendungsschichten
Das Buch hat den Anspruch, alle wesentlichen Aspekte der Entwicklungen von Webanwendungen abzubilden. Dazu gehört neben der Präsentationsschicht, bei der Wicket zum Einsatz kommt, die Schicht der Anwendungslogik (Business Logic) und die der Datenhaltung (Persistenz) (siehe den Wikipedia-Eintrag zur Drei-Schichten-Architektur 12). Da auch die Präsentationsschicht vollständig in Java realisiert wird, ist die Anbindung an die anderen Applikationsschichten besonders einfach. Das Zusammenfügen der verschiedenen Anwendungsschichten geschieht nicht mehr durch Programmcode, sondern durch den Einsatz eines Dependency Injection 13-Frameworks. 9
http://subclipse.tigris.org/ http://tortoisesvn.tigris.org/ 11 http://www.svnhostingcomparison.com/ 12 http://de.wikipedia.org/wiki/3-Tier#Drei-Schichten-Architektur 13 http://de.wikipedia.org/wiki/Dependency_Injection 10
8
1.3 Grundlagen einer Webanwendung 1.3.1.1 Persistenz mit Hibernate Alle Anwendungen arbeiten mit Daten. Daten werden klassisch in Datenbanksystemen abgelegt. Relationale Datenbanksysteme bilden Daten in Tabellen ab, die miteinander verknüpft werden können. In den Spalten einer Tabelle werden die verschiedenen Attribute eines Datensatzes abgelegt, jede Zeile steht für einen eigenständigen Datensatz. Objektorientierte Sprachen können mit Objekten und Beziehungen von Objekten untereinander arbeiten. Die Datenhaltung in Tabellen unterscheidet sich derart von den Möglichkeiten objektorientierter Datenhaltung, dass die Modelle nicht automatisch aufeinander abbildbar sind. Es gibt Speziallösungen, die Objekte und deren Beziehung in einer Objektdatenbank 14 abbilden können. Diese Speziallösungen konnten sich bis heute aus verschiedenen Gründen nicht durchsetzen. Daher wurde für die Anbindung von relationalen Datenbanksystemen OR-Mapper 15 entwickelt, die es sowohl für verschiedene Programmiersprachen als auch für unterschiedliche Datenbanksysteme gibt. Diese OR-Mapper übernehmen die Transformation der unterschiedlichen Modelle ineinander. Dabei wird meist ein Objekt auf eine Tabelle und die Eigenschaften des Objekts auf die Spalten einer Tabelle abgebildet. Diese Methode ist bei der Abbildung von komplexen Objektbeziehungen natürlich limitiert, hat gegenüber den Speziallösungen aber den entscheidenden Vorteil, dass auf bewährte Technologien zurückgegriffen werden kann. Für die Datenbankanbindung in Java gibt es JDBC mit den entsprechenden datenbankabhängigen Treibern. Der Zugriff erfolgt in Tabellenform. Im Prinzip sind damit alle Anforderungen an eine Datenbankschnittstelle erfüllt. Die Anbindung an eigene Applikationen ist dennoch recht kompliziert und mit vielen Fallstricken versehen. Eine objektorientierte Darstellung der Daten und deren Transformation in die Tabellenstruktur der Datenbank hat auf Seiten der Anwendungsentwicklung zu wesentlich schnelleren Ergebnissen geführt. Außerdem kann der Zugriff auf die Daten in Abhängigkeit von der Datenbank optimiert werden. OR-Mapper stellen daher neben der Transformation der Daten meist grundlegende Datenbankoperationen zur Verfügung. Warum Hibernate? In diesem Buch und auch sonst empfehle ich Hibernate als Persistenz-Framework. Die Wahl fiel auf Hibernate, weil es einfach zu benutzen und am Markt etabliert ist. Es zeichnet sich ähnlich wie Wicket durch eine gute Java-Integration aus. Natürlich kann man jedes andere Persistenz-Framework mit Wicket benutzen. Wicket bietet keine besondere Hibernate-Integration, sodass die aufgezeigten Lösungsvorschläge auch auf andere Frameworks abgebildet werden können.
14 15
http://de.wikipedia.org/wiki/Objektorientierte_Datenbank http://de.wikipedia.org/wiki/Objektrelationale_Abbildung
9
1 Einleitung Funktionsweise Mit Hibernate kann man Klassen und deren Attribute auf Datenbanktabellen abbilden. Beziehungen untereinander können ebenso abgebildet werden. Hibernate sorgt dabei für die Transformation der Daten und Abhängigkeiten in ein für die Datenbank verständliches Format. Die Informationen, auf welche Tabelle und auf welchen Spalten die Objekte abgebildet werden, können sowohl in der Klasse selbst (per Annotation) als auch in gesonderten Konfigurationsdateien abgelegt werden. Der Zugriff auf die Daten kann über eine objektorientierte Alternative zu SQL (HQL) oder über eine Schnittstelle erfolgen. Die Transformation in Objekte erfolgt transparent. 1.3.1.2 Dependency Injection mit dem Spring-Framework In klassischen objektorientierten Anwendungen ist jedes Objekt selbst dafür verantwortlich, die Abhängigkeiten aufzulösen sowie die notwendigen Ressourcen zu erzeugen und zu verwalten. Dazu muss jedes Objekt über Informationen seiner Umgebung verfügen. Dependency Injection verlagert die Verantwortung für das Finden und Verwalten von Ressourcen und Abhängigkeiten aus dem Objekt in ein Framework. Das Framework erzeugt über Konfigurationsdateien oder Metainformationen in den Klassen die Anwendungsmodule und löst die definierten Abhängigkeiten auf. Dieses Vorgehen reduziert die Abhängigkeit der Klasse zur Umgebung und zu konkreten Umsetzungen der abhängigen Module. Warum Spring? Das Spring-Framework wurde aus ähnlichen Gründen wie Hibernate ausgewählt. Und ähnlich wie bei Hibernate kann auch dieser Aspekt durch ein anderes Framework abgebildet werden. Wie gut die Ergebnisse in diesem Buch dann übertragbar sind, hängt sehr stark von dem verwendeten Framework ab. Spring benutzt für die Konfiguration XML-Dateien. In diesen Dateien werden die Objekte mit Namen versehen und die Konfiguration der Attribute vorgenommen. Die Abhängigkeiten von Objekten können über Referenzen und deren Übergabe in Attribute vorgenommen werden. Eine Anwendung lädt über Funktionen des Frameworks eine Spring-Konfiguration und kann dann über den Namen auf die Objekte bekannten Typs zugreifen. Spring ist somit einfach in jede Java-Anwendung integrierbar. 1.3.1.3 Anwendungslogik und Präsentation mit Wicket Die Architektur von Wicket folgt dem Model-View-Controller 16-Ansatz (Modell-Präsentation-Steuerung). Das Modell beinhaltet die darzustellenden Daten, der Controller übernimmt die Steuerung der Interaktionsmöglichkeiten, und in der Präsentationsschicht werden diese Daten dann dargestellt.
16
10
http://de.wikipedia.org/wiki/MVC
1.3 Grundlagen einer Webanwendung
1.3.2
Verzeichnis und Paketstruktur
Es zeigt sich, dass man nie zu früh anfangen kann, seine Softwareprojekte gut zu strukturieren. Ich lege daher für jede Anwendungsschicht mindestens ein eigenständiges Teilprojekt an. Das verkürzt den Entwicklungszyklus und verbessert die Übersichtlichkeit. 1.3.2.1 Projektverwaltung mit Maven Für das Erstellen der Teilprojekte und die Abhängigkeitsverwaltung setze ich Maven ein. Maven ist dabei ein Build-Tool mit vielfältigen Möglichkeiten. Alternativen zu Maven sind z.B. Ant 17 mit Ivy 18 oder Buildr 19. Build-Tools sind hauptsächlich für das Erstellen der ausführbaren Programme aus vorhandenen Quelltexten zuständig. Das Aufgabenspektrum hat sich spätestens seit Maven in der Version 1.0 grundlegend erweitert und umfasst seither vor allem auch die Abhängigkeitsverwaltung. Weitere Funktionalitäten können fast beliebig durch Plugins erweitert werden und reichen vom Erstellen von Dokumentationsseiten bis zum Deployment der Anwendung auf Produktivsystemen. Das Grundprinzip von Maven ist einfach. In einem Verzeichnis gibt es ein vorgeschriebenes Verzeichnislayout für ein Projekt: pom.xml src main java resources test java resources
Dazu benötigt Maven neben einer sehr einfach gehaltenen Projektdefinitionsdatei (pom.xml), die minimale Informationen zum Projekt enthält, die notwendigen JavaQuelltexte in standardisierten Verzeichnissen. Maven sucht dann für das Erstellen des Projekts z.B. im Verzeichnis src/main/java nach Java-Dateien und kompiliert diese. In src/test/java befinden sich Unit-Tests, die beim vollständigen Erstellen der Anwendung ausgeführt werden. In den jeweiligen resource-Verzeichnissen befinden sich alle anderen Dateien (Grafiken, Texte etc.). In der Projektdefinitionsdatei können auch die Abhängigkeiten zu den anderen Teilprojekten oder zu anderen Bibliotheken eingestellt werden, wobei Maven diese Abhängigkeiten selbstständig auflöst und die referenzierten Bibliotheken automatisch aus einem Verzeichnis herunterlädt und lokal vorhält. Wurde eine Bibliothek bereits einmal heruntergeladen, greift Maven auf die in einem lokalen Verzeichnis abgelegte Version zurück. Über Plugins ist es außerdem möglich, die Webanwendung direkt aus dem Projektverzeichnis heraus starten zu können. Auf diese Weise können sogar Anpassungen an der laufenden Anwendung vorgenommen werden. 17
http://ant.apache.org/ http://ant.apache.org/ivy/ 19 http://incubator.apache.org/buildr/ 18
11
1 Einleitung 1.3.2.2 Besser als Quickstart Wicket bietet auf der eigenen Webseite eine „Quickstart“-Funktion, die ein einfaches Wicket-Anwendungsgerüst erstellt, das sofort lauffähig ist. Für einen ersten Test sollte man sich mit „Quickstart“ ein Testprojekt erstellen lassen. Allerdings empfehle ich, dieses Projekt für eigene Entwicklungen nicht weiter zu verwenden. Dafür gibt es verschiedene Gründe. Der Wichtigste für mich: Das Projekt benutzt angepasste Einstellungen, die so nicht nötig sind und vom Standard abweichen. Außerdem ist es hilfreich, wenn man alle Anpassungen, die man im Laufe des Entwicklungsprozesses vorgenommen hat, selbst durchführt. Dann kann man z.B. dokumentieren, was die Anpassung bewirkt, was sich neben der besseren Dokumentation des eigenen Projekts positiv auf die Fehlersuche auswirkt.
1.3.3
Unit-Tests
Irren ist menschlich. Doch Fehler in Anwendungen sind ärgerlich. Fehler in der eigenen Anwendung kann man aber vermeiden. Denn je komplexer die Anwendungen werden, desto mehr Zeit benötigt man, um selbst triviale Fehler einzugrenzen. Eine Methode, die Anzahl der Fehler mit jedem Entwicklungsschritt nachhaltig einzudämmen, nennt sich „Test Driven Development“. Dabei geht es im Prinzip darum, für jede Zeile Programm wieder ein Programm zu schreiben, das die Funktionsfähigkeit und das Ergebnis überprüft. Was sich im ersten Moment etwas „verrückt“ anhört, stellt sich nach kurzer Zeit als unglaublich entlastendes Instrument dar. Wenn das Teilprojekt mit den automatisch ablaufenden Tests erstellt werden konnte, kann man sich ziemlich sicher sein, dass es genauso stabil läuft wie vor der letzten Anpassung. Unit-Tests (Modul- bzw. Komponententests) unterscheiden sich vom normalen Programmcode nur dadurch, dass die Namen der Klassen meist mit „Test“ anfangen und die Methoden der Klasse z.B. „testIrgendetwas“ heißen. Wenn man mit Maven ein Projekt kompiliert, dann werden die nach dieser Nomenklatur erstellten Klassen und Funktionen ausgeführt. Wenn dann in so einem Test ein Fehler auftritt und der Test somit fehlschlägt, kann das ganze Projekt nicht gebaut werden und wird daher auch nicht für andere Projekte, die dieses Teilprojekt eingebunden haben, zur Verfügung gestellt. So kann sichergestellt werden, dass alle Abhängigkeiten in einem fehlerfreien Zustand sind. Wenn in einem Test Fehler auftreten, kann man die Suche nach der Ursache sehr stark einschränken. Denselben Fehler innerhalb einer laufenden Anwendung zu isolieren, ist sehr viel aufwendiger. Außerdem bietet ein gut formulierter Test einen hervorragenden Sicherheitsfallschirm für den Fall, das man z.B. Optimierungen vornimmt oder die Funktionalität erweitert. Wenn die Tests nach einer solchen Anpassung immer noch funktionieren, kann man fast davon ausgehen, dass auch die Anwendung, die auf diese Funktionen aufbaut, immer noch funktioniert.
12
1.3 Grundlagen einer Webanwendung 1.3.3.1 Persistenz-Tests Auch mit Hibernate können Unit-Tests durchgeführt werden. Dabei kann eine temporäre Datenbank benutzt werden, die nur zur Laufzeit des Tests vorhanden ist und keinerlei Daten enthält. Das bedeutet, dass Nebeneffekte durch bereits bestehende Daten ausgeschlossen werden können. Somit kann man alle Datenbankoperationen (z.B. Löschen) ohne die Gefährdung von Produktivsystemen testen. 1.3.3.2 User-Interface-Tests Mit Wicket können Teile von Wicket-Anwendungen und -Komponenten getestet werden. Auch wenn die Testbarkeit sicher mit Einschränkungen behaftet ist, geht die Testabdeckung und die Einfachheit beim Erstellen eigener Test weit über das hinaus, was mit anderen Frameworks realisierbar ist. Das macht Wicket in diesem Bereich einmalig. Zusammenfassung Interessanterweise unterscheidet sich unsere Auswahl nur unwesentlich von den Bibliotheken, die auch Grails benutzt, sicherlich auch, weil wir ebenso wie das Grails-Projekt auf etablierte Standards setzen. Auch wenn der Aufwand für das Aufsetzen eines Projektes ungleich höher ist, bewahren wir so die Flexibilität, die es uns erlauben würde, z.B. ein anderes Persistence-Framework einzusetzen. Allerdings ist zu beachten, dass diese Aufwände nur einmal in einem Projekt anfallen und damit selten eine große Rolle spielen.
13
1 Einleitung
14
2 2 Aufsetzen der Teilprojekte Wir schreiben endlich unsere erste eigene Wicket-Anwendung. Die Strukturen, die im Folgenden erstellt werden, sollten sich durch leichte Modifikationen auf andere Projekte anwenden lassen. In den folgenden Abschnitten erstellen wir zuerst das notwendige Grundgerüst mit allen Abhängigkeiten zu externen Bibliotheken und eigenen Teilprojekten. Im zweiten Schritt hauchen wir dem Ganzen Leben ein. Als Erstes sollte man sich für dieses Projekt ein Verzeichnis anlegen, in dem künftig alle Teilprojekte und sonstige Daten beheimatet sind. Es empfiehlt sich, auf Leerzeichen in den Verzeichnisnamen zu verzichten, sonst kann es zu unnötigen Komplikationen kommen.
2.1
Nomenklatur der Teilprojekte Maven benutzt für das Auflösen von Abhängigkeiten drei Informationen, die in einem Projekt angegeben werden müssen: groupId: Bezeichnet eine Gruppe, in der die Artefakte zusammengefasst werden. Die GruppenID hat eher organisatorische Gründe und ist leider ungeeignet, Namenskollisionen zu vermeiden. Im Dateinamen der fertigen Bibliothek kommt dieser Bezeichner nicht vor, sodass es zu Dateinamenskollisionen kommen kann, wenn aus unterschiedlichen Gruppen Artefakte mit derselben Versionsnummer und artifactId benutzt werden. artifactId: Diese ID gibt dem Projekt seinen Namen. Diese ID findet sich später auch im Dateinamen der erzeugten Bibliothek und im Projektnamen in Eclipse wieder. version:
Version des Projekts. Eine gute Versionierung sollte eigentlich bei 0.1 anfangen. Ich persönlich finde es akzeptabel, in diesem Buch bei Version 1.0 anzufangen. Allerdings wird bis zur Fertigstellung
15
2 Aufsetzen der Teilprojekte die Versionsbezeichnung 1.0-SNAPSHOT lauten (das ist die Grundeinstellung von Maven). Dieses Versionierungsschema empfiehlt sich aus zwei Gründen. Zum einen wird auf diese Weise das Teilprojekt als „in Arbeit“ gekennzeichnet und sorgt zum anderen bei Maven dafür, dass beim Auflösen dieser Abhängigkeiten regelmäßig geprüft wird, ob eine Version mit einem neueren Zeitstempel existiert. Wenn man allein an einem Projekt entwickelt, wird sich da kein Unterschied bemerkbar machen. Wenn man aber im Team entwickelt und ein Continuous-Build-System 1 zum Einsatz kommt, ist nur so gewährleistet, dass man die aktuellste Version eines Teilprojektes benutzt. Da die groupId aus oben genannten Gründen nicht zur Strukturierung von Projekten herangezogen werden kann, handhabe ich die Bezeichnung der Projekte wie folgt: In groupId schreibe ich den Domainnamen des Projekts in umgekehrter Reihenfolge. Wenn es ein Unterprojekt für eine existierende Domain ist, hänge ich ein „projekt.“ an. Der Wert in artifactId fängt mit dem Wert aus groupId an und wird, gefolgt von „--“, um einen Namen für dieses Teilprojekt ergänzt. Der Verzeichnisname für dieses Teilprojekt entspricht dem Wert in artifactId. Für dieses Beispiel benutze ich als groupId den Wert „de.wicketpraxis“. Ein Teilprojekt hat demzufolge als artifactId den Wert „de.wicketpraxis--teilprojekt“. Das Verzeichnis, in dem alle Daten dieses Teilprojektes enthalten sind, hat ebenso diesen Namen.
2.2
Aufsetzen der Teilprojekte Beim Erstellen der Teilprojekte arbeiten wir uns von der Basis an aufwärts, wobei die höchste Schicht die Präsentationsschicht ist, in diesem Fall also Wicket. Ziel ist es, am Ende ein vollständiges Projekt mit allen notwendigen Teilprojekten zu erhalten. Im zweiten Schritt wird für jede Schicht eine beispielhafte Funktionalität realisiert, die das Funktionieren und das Zusammenspiel der verschiedenen Schichten veranschaulicht.
2.2.1
Projektbasis ParentPom
Als erstes benötigen wir ein Maven-Projekt, in dem alle teilprojektübergreifenden Einstellungen und Abhängigkeiten abgelegt werden. Dazu legen wir ein Projekt unter de.wicketpraxis–-parentPom an. Der Name parentPom wurde gewählt, weil in diesem Teilprojekt übergreifende Einstellungen vorgenommen werden können. Außerdem kann zum Schluss die ganze Anwendung über dieses Projekt erstellt werden, weil die Teilprojekte entsprechend konfiguriert wurden.
1
16
http://www.theasolutions.com/tutorials/scrum_agile.jsp
2.2 Aufsetzen der Teilprojekte Auf der Kommandozeile erstellt folgende Befehlssequenz ein passendes Projektverzeichnis (alles gehört in eine Eingabezeile und wurde nur zur besseren Übersicht mehrzeilig geschrieben): $ mvn archetype:create \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DgroupId=de.wicketpraxis \ -DartifactId=de.wicketpraxis--parentPom
Dabei legt Maven erfreulicher Weise gleich ein Verzeichnis passend zur artifactId an. In diesem Verzeichnis finden sich folgende Verzeichnisse: src main java resources test java resources
In main/java finden sich die Quelltexte der Anwendung. In main/resources wird alles abgelegt, was in diesem Projekt sonst noch an Daten benutzt wird. Dazu gehören z.B. Bilder oder Konfigurationsdateien. Unter test/java werden die Quelltexte für die Unit-Tests abgelegt, die in dem Test auf die Ressourcen unter test/resources zurückgreifen können. Programmcode und Daten aus dem test-Ordner werden nicht in der Bibliotheksdatei weitergegeben. Jetzt kann man mit $ mvn install
auf der Kommandozeile das Projekt erstellen lassen. Wenn alles richtig konfiguriert wurde und alle Tests erfolgreich abgeschlossen werden konnten, erscheint am Ende die Ausgabe: ... [INFO] ---------------------------------------------------------------[INFO] BUILD SUCCESSFUL [INFO] ---------------------------------------------------------------...
In dem generierten Projektverzeichnis befindet sich außerdem die Projektdatei pom.xml mit folgendem Inhalt:
4.0.0 de.wicketpraxis de.wicketpraxis--parentPom jar 1.0-SNAPSHOT de.wicketpraxis--parentPom http://maven.apache.org
junit junit 3.8.1 test
17
2 Aufsetzen der Teilprojekte Wie man erkennen kann, finden sich dort unsere Werte für groupId und artifactId wieder. Außerdem sind noch zwei Bereiche von besonderem Interesse. Der Parameter „packaging“ ist auf dem Wert „jar“ gesetzt, was bedeutet, dass aus diesem Projekt eine normale Bibliothek in einer JAR-Datei erzeugt wird. Der zweite interessante Aspekt ist im Bereich dependencies zu finden. Dort wird eine Bibliothek als Abhängigkeit definiert, die für das Ausführen der Unit-Tests vorhanden sein muss. Man kann auch hier wieder die drei relevanten Parameter für die Adressierung dieser Abhängigkeit erkennen. Zusätzlich ist der Parameter scope aufgeführt, bei dem der Wert test darauf hinweist, dass diese Bibliothek nur für die Unit-Tests benutzt wird. Jetzt passen wir die Projektbeschreibungsdatei wie folgt an: Listing 2.1 pom.xml
4.0.0 de.wicketpraxis de.wicketpraxis--parentPom 1.0-SNAPSHOT pom ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ Parent Pom) http://wicketpraxis.de
maven-compiler-plugin
1.5 1.5 UTF-8
org.apache.maven.plugins maven-resources-plugin
UTF-8
org.apache.maven.plugins maven-source-plugin
attach-sources verify
jar
junit junit
18
2.2 Aufsetzen der Teilprojekte ${junit.version} test
org.slf4j slf4j-jdk14 ${slf4j.version}
3.8.1 1.4.2
Diese Projektdefinition wird nachher von allen anderen Teilprojekten referenziert und definiert somit die Grundeinstellung für alle Teilprojekte. Folgende Anpassung habe ich vorgenommen: An der Stelle, wo steht, habe ich die Darstellung aus Platzgründen gekürzt. Die Angaben an dieser Stelle entsprechen den automatisch generierten. Der Parameter packaging muss für dieses Teilprojekt auf pom gesetzt werden. Die Platzhalter wie z.B. ${pom.groupId} werden durch die aktuell definierten Inhalte ersetzt. Damit erspart man sich einerseits Schreibaufwand und kann andererseits bestimmte Informationen an einer Stelle bündeln (z.B. die Versionen für die Abhängigkeiten im Bereich properties). Mit dem Compiler-Plugin (maven-compiler-plugin) wird die Java-Version auf 1.5 gesetzt. Damit benötigt die Anwendung mindestens ein Java 5 oder Java 6. Außerdem wird die Kodierung für die Quellcodedateien auf UTF-8 gesetzt. Das Maven-Resource-Plugin (maven-resources-plugin) sorgt dafür, dass alle Ressourcendateien mit UTF-8-Kodierung eingebunden werden. Es empfiehlt sich, mithilfe des Maven-Source-Plugins (maven-source-plugin) auch die Quelltexte zum Projekt in ein Archiv packen zu lassen. So kann man später in der Entwicklungsumgebung sehr einfach auf die passenden Quelltexte zugreifen, ohne dass man das Projekt geöffnet haben muss. Für dieses Projekt benutzen wir junit für die Unit-Tests. Es muss mindestens eine Bibliothek für Unit-Tests definiert sein, sonst können Projekte nicht gebaut werden. Java bietet ein integriertes Logging-Framework. Außerdem gibt es noch von Apache das log4j-Logging-Framework. Das slf4j-Projekt bietet die Möglichkeit, jederzeit das verwendete Framework austauschen zu können, ohne dass man Anwendungscode anpassen muss. Außerdem bietet slf4j eine praktische Schnittstelle, um parametrisierte Log-Ausgaben generieren zu können. Damit ist dieses Teilprojekt fertig gestellt. Jetzt müssen wir dieses Projekt als Basisprojekt in alle weiteren Teilprojekte einbinden. Davor sollten wir es kurz mit mvn install erstellen.
19
2 Aufsetzen der Teilprojekte
2.2.2
Teilprojekt Base
Im Laufe einer langjährigen Entwicklung sammeln sich immer mehr Funktionen, die keinem Projekt direkt zugerechnet werden können, sich aber einer ungemeinen Nützlichkeit im Projektalltag erfreuen. Damit diese Funktionen sich gar nicht erst in den Untiefen eines Projekts verlieren, empfehle ich, ein Teilprojekt anzulegen, in dem solche allgemeinen Funktionen dann eine passende Heimat haben. Dieses Teilprojekt erstellen wir wieder mit: $ mvn archetype:create \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DgroupId=de.wicketpraxis \ -DartifactId=de.wicketpraxis--base
Die generierte Projektdatei wird vollständig ersetzt, da wir viele Einstellungen bereits in dem übergeordneten Projekt getroffen haben und diese deshalb nicht noch einmal angegeben werden müssen. Listing 2.2 pom.xml
4.0.0
de.wicketpraxis de.wicketpraxis--parentPom 1.0-SNAPSHOT
${pom.groupId}--base ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ Base)
Damit die nötigen Informationen vom Basisprojekt übernommen werden können, muss es wie im Bereich parent referenziert werden.
2.2.3
Teilprojekte Datenbankkonfiguration
Für die Datenbankanbindung werden drei Teilprojekte angelegt. Auch wenn der Aufwand im ersten Moment übertrieben wirkt, ist nur durch die Teilung eine saubere Abhängigkeitsstruktur erreichbar. Das erklärt sich sehr viel besser, wenn wir nachher diese Projekte einbinden und nutzen. 2.2.3.1 Konfigurationsprojekt für die Produktivdatenbank Wir legen ein Teilprojekt mit der artifactId de.wicketpraxis--dbconfig an. Wir passen die Projektdatei an und fügen die Abhängigkeiten für den Datenbanktreiber ein. Als Produktivdatenbank soll eine MySQL-Datenbank zum Einsatz kommen. Daher wird die Bibliothek für diesen Datenbanktreiber eingebunden. Listing 2.3 pom.xml
4.0.0
20
2.2 Aufsetzen der Teilprojekte
...
${pom.groupId}--dbconfig ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ DB Config)
mysql mysql-connector-java ${mysql.version}
5.1.6
2.2.3.2 Konfigurationsprojekt Schema-Update Hibernate bietet die Möglichkeit, aus der konfigurierten Datenbankzugriffschicht automatisch das Datenbankschema zu erzeugen. Dabei nimmt Hibernate ebenfalls datenbankspezifische Optimierungen vor, auf die man durchaus zurückgreifen sollte. Wenn man nicht gerade über umfangreiches datenbankspezifisches Wissen verfügt, dürfte das Know-how aus dem Hibernate-Projekt zu den besseren Ergebnissen führen. Auf die automatische Schemagenerierung greifen wir bei der Testdatenbank ebenfalls zurück. Dieses Projekt dient nur dazu, andere Zugangsdaten für die Datenbank bereitzustellen, die mit anderen Rechten ausgestattet die entsprechenden Tabellen anlegen kann. Außerdem können wir so verhindern, dass diese Zugangsdaten auf Produktivsystemen auftauchen. Wir erstellen das Teilprojekt mit der artifactId de.wicketpraxis--dbconfigschema-update. Wir definieren eine Abhängigkeit zum Projekt mit der Produktdaten-
bankkonfiguration, sodass derselbe Datenbanktreiber benutzt wird. Listing 2.4 pom.xml
4.0.0
...
${pom.groupId}--dbconfig-schema-update ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ DB Config Schema Update)
${pom.groupId} ${pom.groupId}--dbconfig ${pom.version}
Wenn sich der Datenbanktreiber ändern sollte, muss natürlich in beiden Fällen die noch zu erstellende Konfiguration angepasst werden.
21
2 Aufsetzen der Teilprojekte 2.2.3.3 Konfigurationsprojekt für die Testdatenbank Für den Datenbanktest wird auf eine Datenbank zurückgegriffen, die ohne Installation auskommt und innerhalb einer beliebigen Java-Anwendung gestartet werden kann. Es ist aber ebenso praktikabel, auch während der Entwicklung auf diese Datenbank statt auf eine lokale MySQL-Installation zurückzugreifen. Damit erspart man sich Installations- und Konfigurationsaufwand. Wir erstellen das Teilprojekt mit der artifactId de.wicketpraxis– -dbconfig-test und passen die Projektdatei an. Listing 2.5 pom.xml
4.0.0
...
${pom.groupId}--dbconfig-test ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ DB Config Test)
hsqldb hsqldb ${hsqldb.version}
1.8.0.1
Ich benutze die HSQL-Datenbank für die Testumgebung. Es können natürlich beliebige andere Datenbanken benutzt werden, für die Hibernate Unterstützung anbietet.
2.2.4
Teilprojekt Persistenz
Nachdem wir nun alle Datenbankkonfigurationsprojekte angelegt (aber noch nicht konfiguriert) haben, legen wir nun das Teilprojekt (artifactId de.wicketpraxis-persistence) an, das den Datenbankzugriff regelt. Diese Projektdatei wird etwas umfangreicher, denn wir müssen die anderen Teilprojekte einbinden. Listing 2.6 pom.xml
4.0.0
...
${pom.groupId}--persistence ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ Persistence)
${pom.groupId}
22
2.2 Aufsetzen der Teilprojekte ${pom.groupId}--dbconfig ${pom.version}
${pom.groupId} ${pom.groupId}--dbconfig-test ${pom.version} test
${pom.groupId} ${pom.groupId}--dbconfig-schema-update ${pom.version} test
org.springframework spring ${springframework.version}
org.springframework spring-test ${springframework.version} test
org.springframework spring-jdbc ${springframework.version} test
org.apache.wicket wicket ${wicket.version}
org.apache.wicket wicket-spring ${wicket.version}
org.apache.wicket wicket-extensions ${wicket.version}
wicketpraxis.com
WicketPraxis Webapp
contextConfigLocation /WEB-INF/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
de.wicketpraxis.webapp org.apache.wicket.protocol.http.WicketFilter
applicationClassName de.wicketpraxis.web.WicketPraxisApplication
configuration deployment
de.wicketpraxis.hibernate.osv
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
de.wicketpraxis.hibernate.osv /*
de.wicketpraxis.webapp /*
An erster Stelle wird eine Spring-Konfiguration im selben Verzeichnis angegeben (/WEBINF/applicationContext.xml). Auf diese Konfiguration wird sowohl von Wicket als
auch durch den OpenSessionInViewFilter zugegriffen. Der ContextLoaderListener stellt einen aus der Konfiguration ermittelten ApplicationContext bereit und sorgt beim Beenden des Servers für das Herunterfahren des Context. Im WicketFilter geben wir nun unsere eigene Wicket-Anwendung an und setzen den Startmodus auf deployment. Damit wird das Anwendungsarchiv standardmäßig mit diesem Modus erstellt. Das Öffnen und Schließen der Session für den Datenbankzugriff über Hibernate übernimmt der OpenSessionInViewFilter. Die Reihenfolge der Filter im Bereich filter-mapping ist daher
45
3 Mit Leben füllen wichtig. Die Anfrage durchläuft den erstgenannten Filter vor dem nächsten. Damit ist sichergestellt, dass die Datenbankverbindung geöffnet ist, wenn die Anfrage durch Wicket behandelt wird.
3.5.4
Spring-Konfiguration
Wir erstellen jetzt im selben Verzeichnis die referenzierte Konfigurationsdatei für den ApplicationContext. Dabei könnten wir zwar direkt unsere app.xml einbinden. Ich empfehle aber einen Umweg, sodass a) jede Schicht die Konfigurationsdatei an einer nachvollziehbaren Position ablegt und b) in der Konfigurationsdatei für den Servlet-Container keine zusätzlichen Einstellungen abgelegt werden. Für den Fall, das Unit-Tests für die Präsentationsschicht durchgeführt werden müssten, wäre es sehr schwierig (wenn nicht unmöglich), an diese Datei heranzukommen. Listing 3.22 applicationContext.xml
In diesem binden wir die noch zu erstellende Datei webapp.xml aus dem Ressourcenpfad ein. Danach erstellen wir diese Datei im Verzeichnis src/main/resources/de/wicketpraxis/web/. Listing 3.23 webapp.xml
Die Anwendung ist damit vollständig konfiguriert und kann jetzt gestartet werden.
3.5.5
Start der Anwendung
Wir haben jetzt alles zusammen. Alle Teilprojekte sollten, wenn noch nicht geschehen, über das ParentPom-Projekt erstellt (mvn install) werden. Dann wechseln wir in das Webapp-Projekt und starten die Webanwendung mit: $ mvn jetty:run –Dwicket.configuration=development
Der eingebettete Webserver sollte starten und den erfolgreichen Start mit folgender Meldung quittieren: ... INFO: [WicketPraxisApplication] Started Wicket version 1.4 in development mode ******************************************************************** *** WARNING: Wicket is running in DEVELOPMENT mode. *** *** ^^^^^^^^^^^ *** *** Do NOT deploy to your live server(s) without changing this. *** *** See Application#getConfigurationType() for more information. *** ******************************************************************** 2009-01-13 19:18:17.161::INFO: Started [email protected]:8080 [INFO] Started Jetty Server
46
3.5 Präsentationsschicht Damit wurde der Server erfolgreich gestartet. Man öffnet nun mit dem Browser die Seite http://localhost:8080/. Dort erscheint eine Fehlermeldung mit einem Link, der auf unsere Webanwendung verweist. Nach einem Klick sollte man ein Ergebnis wie in Abbildung 3.1 sehen.
Abbildung 3.1 Startseite mit Daten aus der Datenbank
Wenn man wie ich schon einen Eintrag in die Datenbank getätigt hat, sieht man in der Ergebnistabelle auch schon einen Eintrag. Damit ist die Anwendung funktionsfähig, und jede der notwendigen Schichten ist erstellt und eingebunden. Hinweis
Wicket unterscheidet zwischen den Modi Deployment und Development. Alle erweiterten Informationen, die im Development-Modus angezeigt werden, werden durch den Modus Deployment deaktiviert. Für die Entwicklung sollte der Development-Modus gewählt werden, weil das die Fehlersuche erheblich vereinfacht.
47
3 Mit Leben füllen
48
4 4 Die Wicket-Architektur Nachdem wir nun unsere erste kleine Webanwendung erstellt haben, möchte ich etwas genauer auf die Architektur von Wicket eingehen, bevor wir uns in den nächsten Kapiteln mit den einzelnen Komponenten beschäftigen.
4.1
Wicket und das HTTP-Protokoll Eine Webanwendung benutzt das HTTP-Protokoll, um mit dem Browser zu kommunizieren. Über dieses Protokoll wird eine Anfrage an den Server geschickt und die Antwort an den Browser übermittelt. Das Protokoll dient dem Transport der Daten und ist selbst zustandslos. Das bedeutet, dass eine Anfrage an den Server in keiner Beziehung zu einer vorangegangenen Antwort stehen muss. Diese Zustandslosigkeit des Protokolls verursacht eine Reihe von Problemen, die in den verschiedenen Webframeworks unterschiedlich gelöst werden. Wicket orientiert sich dabei stark an einer Desktop-Anwendung, sodass man als Entwickler nur selten damit konfrontiert wird, dass es sich immer noch um eine Webanwendung handelt. Dennoch unterstützt Wicket den Entwickler mit einer Reihe von Schnittstellen, die weiterhin den Zugriff auf das Transportprotokoll ermöglichen oder davon abstrahiert bestimmte Funktionen zur Verfügung stellt.
4.2
Struktur Wicket besitzt verschiedene Elemente, die in einer Webanwendung zusammenspielen. Ich werde jedes im Folgenden etwas eingehender erläutern, sodass sich ein Gesamtbild ergibt, was für das Verständnis von Wicket hilfreich ist.
49
4 Die Wicket-Architektur
4.2.1
WebApplication
Wie wir bereits gesehen haben, müssen wir für unsere eigene Anwendung eine von WebApplication abgeleitete Klasse erstellen. Von dieser Klasse wird auf dem Server nur eine Instanz pro Anwendung erzeugt. Die init-Methode wird beim Start nur einmal ausgeführt, sodass an dieser Stelle alle Einstellungen vorgenommen werden können, die für die Anwendung gelten sollen. Außerdem gibt es verschiedene Methoden, die man mit eigenen Implementierungen überladen und so die Anwendung an die eigenen Bedürfnisse anpassen kann.
4.2.2
Session
Jeder Nutzer, der auf die Anwendung zugreift bekommt eine Session zugewiesen. Das ist notwendig, da Wicket alle Informationen, die für eine Nutzerinteraktion notwendig sind, in dieser Session speichert. Während einige andere Frameworks versuchen, den Zustand einer Anwendung in URL-Parametern abzulegen, überträgt Wicket in dieser Phase nur die vom Nutzer durchgeführte Aktion. Die Daten einer Session werden in einem SessionStore gespeichert. Normalerweise werden die Daten in einer HttpSession aus dem javax.servlet-Paket gespeichert. Man könnte allerdings auch seinen eigenen SessionStore implementieren.
4.2.3
PageMap
Jede Session hat mindestens eine PageMap. In dieser PageMap sind die Seiten (Page) abgelegt, die der Nutzer aufgerufen hat. Neben der zuletzt besuchten Version einer Seite finden sich in der PageMap auch ältere Versionen der Seite wieder. Dadurch kann der Nutzer im Browser auf die letzten Seiten zurückspringen, ohne dass es zu Fehlern kommt, weil Wicket den Zustand der Anwendung zu diesem Zeitpunkt wiederherstellen kann. Wenn ein Nutzer mehr als ein Browserfenster geöffnet hat (z.B. mit einem Popup), legt Wicket mehr als eine PageMap an. Auch wenn diese Vorgehensweise dafür sorgt, dass jederzeit die korrekten Funktionen aufgerufen werden, muss man sich als Entwickler nicht mit solchen Details beschäftigen, denn dieser Vorgang geschieht automatisch und vollkommen transparent.
4.2.4
Page
Eine Seite (Page) ist eine Komponente, die sich von anderen Wicket-Komponenten nur dadurch unterscheidet, dass alle anderen Komponenten immer zu einer Seite gehören. Die Seite ist damit die oberste Komponente im Komponentenbaum. Man kann sich eine Page auch als Browserfenster vorstellen. Die Darstellung einer Seite liefert als Ergebnis die HTML-Daten, die der Browser dann darstellt.
50
4.3 Request-Behandlung
4.2.5
PageStore
Wicket hält die aktuellste Seite der PageMap im Speicher vor. Alle anderen Seiten werden im PageStore abgelegt. Im Standardfall wird dazu die Seite serialisiert und in einem DiskPageStore, also auf der Festplatte abgelegt. Bei Bedarf wird die Seite wieder aus dem PageStore geladen. Auf diese Weise hält sich der Speicherverbrauch in Grenzen, ohne an Funktionalität einzubüßen.
4.2.6
Component
Eine Komponente ist die Basiseinheit einer Wicket-Anwendung. Alle Wicket-Komponenten sind von dieser Klasse abgeleitet.
4.3
Request-Behandlung Wicket kapselt die bei einer Webanwendung beteiligten HttpRequest- und HttpResponse-Klassen. Diese werden durch einen RequestCycle abgearbeitet, wobei die Behandlung der verschiedenen Phasen an einen RequestCycleProcessor übergeben werden. Im RequestCycle werden folgende Phasen durchlaufen: PREPARE_REQUEST: Startet die Request-Verarbeitung. RESOLVE_TARGET: Ermittelt das Ziel dieser Abfrage (RequestTarget) durch den RequestCycleProcessor. PROCESS_EVENTS: Die Event-Behandlung durch den RequestCycleProcessor wird
gestartet. RESPOND: Der Response wird durch den RequestCycleProcessor erstellt. DETACH_REQUEST: Alle temporären Daten werden gelöscht, indem für jede Komponente die Methode detach() aufgerufen wird. Danach wird die Seite in der PageMap abgelegt. Zusätzlich wird die Seite serialisiert, damit sie im PageStore abgelegt werden kann. DONE: Die Abarbeitung ist abgeschlossen, der nächste Request kann verarbeitet werden. Hinweis
Wenn an dieser Stelle eine Komponente noch Referenzen auf Objekte hat, die nicht serialisiert werden können, kann Wicket die ganze Seite nicht serialisieren und somit nicht in den PageStore schreiben. Das kann zu Problemen führen, wenn man später auf diese Seite zugreifen möchte. Wenn zu einem späteren Zeitpunkt auf diese Seite zugegriffen wird, kann Wicket diese nicht im PageStore finden und gibt eine Fehlermeldung aus.
Der RequestCycleProcessor stellt dabei folgende Funktionen bereit: resolve(RequestCycle, RequestParameters): Die URL und die URL-Parameter
werden dekodiert und das RequestTarget ermittelt.
51
4 Die Wicket-Architektur processEvents(RequestCycle): Wenn das RequestTarget ermittelt wurde, werden die Events verarbeitet. Dabei werden dann Events wie der Klick auf einen Link oder das Abschicken eines Formulars ausgewertet (IRequestListener z.B. IlinkListener). respond(RequestCycle): Nachdem die Events verarbeitet wurden, wird das Ergebnis gerendert und an den Browser zurückgeschickt. respond(RuntimeException, RequestCycle): Wenn ein Fehler aufgetreten ist, der
nicht abgefangen wurde, wird diese Methode aufgerufen, um auf diesen Zustand geeignet zu reagieren. Im Entwicklungsmodus wird z.B. eine andere, mit mehr Informationen versehene Fehlerseite dargestellt. Was dabei an den Browser zurückgeschickt wird, hängt natürlich davon ab, ob die Anfrage eine Seite oder ein Bild zurückliefern soll, oder ob das Ergebnis für eine Ajax-Anfrage aufbereitet werden muss.
4.3.1
Komponentenphasen
Jede Komponente hat neben dem Lebenszyklus, der mit dem Erstellen der Komponente über einen Konstruktor beginnt und mit dem Bereinigen durch den Garbage Collector endet, auch einen Request-Zyklus. Dabei sind die wesentlichen Phasen: Request-Behandlung: Die durch den Request beschriebene Aktion wird durchgeführt (Abschnitt 4.3.1.1). onBeforeRender: Wenn die Komponente sichtbar ist, dann wird, bevor die Komponente dargestellt wird, die onBeforeRender-Methode aufgerufen. Dabei kann z.B. die Sichtbarkeit manipuliert werden, was Einfluss auf diesen und den nächsten Schritt hat. onRender: wird aufgerufen, wenn die Komponente dargestellt wird. onAfterRender: wird immer aufgerufen, auch wenn die Komponente unsichtbar ist. onDetach: wird danach aufgerufen und sorgt dafür, dass die temporären Daten gelöscht werden können, damit die Session nur soviel Platz wie nötig belegt.
4.3.1.1 Request-Behandlung Die durch den Request ausgelösten Aktionen werden durchgeführt. Dabei werden zuerst die Komponenten gesucht, bei der eine Aktion ausgelöst wurde. Wenn die Komponente gefunden wurde, wird die Aktion ausgelöst, die dann z.B. im Fall eines Links dazu führt, dass irgendwann onClick() aufgerufen wird. Bei Formularen werden die durch den Request übergebenen Werte der Formularkomponenten verarbeitet.
4.3.2
Nebenläufigkeit – Threads
Pro Request wird ein Thread ausgeführt, sodass man sich normalerweise keine Gedanken um dieses Thema machen muss – mit einer wichtigen Ausnahme: die Session. Da ein Nut-
52
4.4 Komponenten, Modelle, Markup zer einer Session zugewiesen ist, kann es passieren, dass der Zugriff auf die Session gleichzeitig erfolgt. Daher muss man selbst sicherstellen, dass der Zugriff synchronisiert erfolgt.
4.4
Komponenten, Modelle, Markup Wicket ist ein MVC-Framework. MVC steht dabei für Model, View und Controller. Das Model stellt die Daten bereit, die durch den Controller verändert werden können und die durch eine View dargestellt werden.
4.4.1
Komponenten
Die kleinste Einheit einer Wicket-Anwendung ist die Komponente. Eine Komponente übernimmt dabei die Funktion eines Controllers, wobei das Framework dafür sorgt, dass die Aktion des Nutzers der richtigen Komponente zugeordnet werden kann und die Komplexität dieser Verarbeitung vollständig kapselt.
4.4.2
Modelle
Wicket orientiert sich dabei an dem Programmiermodell einer Desktop-Anwendung. In diesem Modell informiert der Controller die View darüber, ob sich etwas geändert hat und die Komponente neu gezeichnet werden muss. Da bei Webanwendungen immer (es sei denn, Ajax kommt zum Einsatz) die ganze Seite dargestellt werden muss, entfällt das Benachrichtigen der View über die Veränderung. Es werden immer alle Komponenten mit den zugehörigen Daten dargestellt. Wenn Wicket über die Modelländerungen informiert wird, zieht Wicket diese Informationen heran, um für diesen neuen Zustand eine neue Version der Seite anzulegen. Die alte Version mit alten Daten wird im PageStore abgelegt.
4.4.3
Markup
Eine Wicket-Komponente besteht aus einer Java-Klasse, die von einer Wicket-Basisklasse geerbt hat, und einer dazugehörigen Markup-Datei, die denselben Namen besitzt, in src/main/resources im selben Unterverzeichnis wie die Klasse abgelegt werden muss und die Endung html besitzt. Das bedeutet, dass eine Klasse StartPage im Package de.wicketpraxis.pages von der Klasse WebPage abgeleitet ist und eine Markup-Datei im Verzeichnis de/wicketpraxis/pages/ innerhalb des Ressourcenverzeichnisses src/main/resources mit den Dateinamen StartPage.html benötigt.
53
5 5 Modelle Es ist sicher ungewöhnlich, dass man bei einem MVC-Framework mit der Erklärung der Modelle anfängt. Dass ich es trotzdem tue, hat zwei Gründe: Einerseits gibt es kaum Komponenten, die nicht auf ein Modell zugreifen, um etwas darzustellen. Andererseits ist die häufigste Frage in Bezug auf Wicket: Wie stelle ich etwas dar? Und bevor man diese Frage beantworten kann, muss man erst klären, woher dieses Etwas kommt. Die meisten Komponenten greifen auf ein Modell zu, um die Modelldaten direkt oder indirekt darzustellen. Dazu greifen die Komponenten über sehr einfache Methoden auf die Daten zu. Die einfachste Komponente Label konvertiert die Daten des Modells in einen String und stellt diesen dann dar. Das IModel-Interface, das alle Modelle implementieren müssen, definiert nur drei Methoden: setObject(T): setzt die Daten. setObject(T): gibt die Daten zurück. detach(): Wenn ein Modell einer Komponente zugeordnet wurde, wird bei einem Aufruf der detach()-Methode der Komponente auch die detach()-Methode des Modells aufgerufen. Das bedeutet, dass Modelle beliebige Daten enthalten können.
5.1
Konverter Die Daten, die ein Modell zurückliefern kann, können beliebigen Typs sein. Damit diese Daten sinnvoll dargestellt werden können, liefert Wicket für die wichtigsten Datentypen Konverter mit, welche die Daten in Text und Text in Daten umwandeln können, wie es z.B. bei Formularen benötigt wird. Auch wenn Wicket für die wichtigsten Datentypen passende Konverter mitbringt, kann es notwendig sein, eigene Konverter für unbekannte Datentypen oder die bestehenden Konverter für einen oder mehrere Datentypen bereitzustellen. Dazu überschreibt man am besten die newConverterLocator()-Methode der WebApplication-Klasse.
55
5 Modelle Listing 5.1 WicketPraxisApplication.java ... @Override protected IConverterLocator newConverterLocator() { return new CustomConverterLocator(super.newConverterLocator()); } ...
Da wir nicht alle Konverter neu definieren möchten, sondern bei fast allen Typen auf die bewährten Konverter zurückgreifen wollen, sollte unsere ConverterLocator-Klasse auf die Konverter der Standardklasse zurückgreifen. Listing 5.2 CustomConverterLocator.java package de.wicketpraxis.web.converter; ... public class CustomConverterLocator implements IconverterLocator { IConverterLocator _fallback; Map, \ IConverter>(); { _customMap.put(Some.class, new SomeClassConverter()); } public CustomConverterLocator(IConverterLocator fallback) { _fallback=fallback; } public IConverter getConverter(Class type) { IConverter ret=_customMap.get(type); if (ret==null) ret=_fallback.getConverter(type); return ret; } }
Das IConverter-Interface ist ähnlich einfach wie das IModel-Interface. Die Methode convertToString ist für die Umwandlung zur Darstellung verantwortlich. Die Methode convertToObject konvertiert im besten Fall einen String in den gewünschten Typ. Interessant ist hierbei, dass mit Locale auch die gewünschte Spracheinstellung übergeben wird. Listing 5.3 IConverter.java package org.apache.wicket.util.convert; ... public interface IConverter extends IClusterable { Object convertToObject(String value, Locale locale); String convertToString(Object value, Locale locale); }
56
5.2 Einfache Modelle
5.2
Einfache Modelle Genug der Theorie. Schauen wir uns an einem ersten Beispiel an, wie sich das alles zusammenfügt. Dass dabei noch nicht alles bekannt ist, liegt in der Natur der Sache. In diesem ersten Beispiel möchten wir die aktuelle Uhrzeit auf der Seite anzeigen. Dazu erstellen wir eine Seite (das bedeutet: eine neue Klasse, die von WebPage abgeleitet wird). Listing 5.4 SimpleModelPage.java package de.wicketpraxis.web.thema.models; ... public class SimpleModelPage extends WebPage { public SimpleModelPage() { IModel message=Model.of("Initialwert"); message.setObject("Jetzt ist "+new Date()); add(new Label("message",message)); } }
Im Konstruktor der Seite erzeugen wir ein Modell mit einem String als Inhalt. Dabei gibt es mehr als eine Variante des Aufrufs. Die im Quelltext benutzte Variante ist eine Kurzform für new Model("Initialwert"). Die Kurzform erspart einiges an Schreibarbeit, sodass man gut beraten ist, nur diese Variante zu benutzen. In der nächsten Zeile werden die Daten im Modell überschrieben und ein Label erstellt, das zur Anzeige das Modell als Parameter übergeben bekommt. Damit die Seite angezeigt werden kann, müssen wir jetzt noch ein passendes Markup anlegen. Dazu legen wir im Ressourcenpfad (src/main/resources) ein Verzeichnis de/wicketpraxis/web/thema/models an. Das Verzeichnis entspricht der Struktur, wie Java Pakete im Verzeichnisbaum abbildet. Tipp
Um ein Verzeichnis im Ressourcenpfad eines Projekts anzulegen, kann man unter Eclipse auch einfach ein Package mit demselben Namen wie das Package der Klasse erzeugen. Eclipse erzeugt dann wie für den Quelltext der Klasse auch das entsprechende Verzeichnis. Das geht zum einen schneller, und zum anderen kann man auch in den Ressourcen wie in den Quelltexten über die Hierarchieansicht navigieren.
In dem Fall lautet der Name des Pakets de.wicketpraxis.web.thema.models. In diesem Verzeichnis/Paket legen wir nun eine Datei an. Die Markup-Datei einer Klasse muss sich immer im selben Pfad oder Paket wie die dazugehörige Komponentenklasse befinden und denselben Namen tragen. Nur die Endung lautet „.html“.
57
5 Modelle Listing 5.5 SimpleModelPage.html
Simple Model Page
Das wird ersetzt.
Wichtig ist, dass der Wert in wicket:id derselbe ist wie der erste Parameter der LabelKomponente, damit Wicket weiß, welche Komponente er darstellen soll. Der erste Parameter einer Komponente ist meist die ID der Komponente, hier kurz Wicket-ID. Jetzt müssen wir noch in unserer WebApplication-Klasse den Rückgabewert von getHomePage() anpassen, damit wir beim Start der Anwendung auf unserer Seite landen, und schon sehen wir folgendes Ergebnis (natürlich mit einem anderen Datum): Jetzt ist Wed Mar 25 20:57:11 CET 2009
Wenn wir jetzt im Browser die Seite neu laden, wird immer wieder der Konstruktor aufgerufen und eine neue Seite mit einem neuen Datum erstellt. Dadurch ändert sich natürlich das Ergebnis.
5.2.1
Modelle verändern
Wenn man die Daten eines Modells verändern möchte, kann man setObject() des Modells aufrufen oder über die Komponentenmethode setDefaultModelObject() die Daten des Modells der Komponente aktualisieren. Jede Komponente hat ein Modell. Entweder wird das konkrete Modell als Parameter beim Erzeugen der Komponente übergeben oder ein leeres Modell benutzt. Das Modell einer Komponente kann mit setDefaultModel neu gesetzt werden. Wenn man das Modell direkt verändert (also die Methode setObject des Modells benutzt), dann kann Wicket unter Umständen nicht feststellen, ob sich Daten geändert haben. In diesem Fall wird Wicket keine neue Version einer Seite erstellen. Was bedeutet das konkret? Wenn der Nutzer auf einer Seite etwas ändert, dann erzeugt Wicket für den Fall, dass sich Daten geändert haben, eine neue Version der Seite. Wenn der Nutzer dann über den Browser auf eine ältere Seite zurück navigiert, kann Wicket auf diese Seite in diesem älteren Zustand zurückgreifen. Der Nutzer sieht nicht nur alte Daten, sondern die Interaktion, die der Nutzer dann durchführt, basiert auch auf diesen Daten. Wenn Wicket nun keine neue Version einer Seite anlegt, dann navigiert der Nutzer auf die Seite im ursprünglichen Zustand. Auch wenn man dieses Verhalten deaktivieren kann, eröffnet es doch interessante Anwendungsmöglichkeiten. Wenn man nicht verhindern kann, dass man die Daten eines Models direkt ändern muss, kann man Wicket trotzdem mitteilen, dass sich der Zustand eines Modells geändert hat. Dazu ruft man für die Komponente, die sich dadurch verändert, die Methode modelChanging() vor der Veränderung und modelChanged() nach der Veränderung auf. Diese Funk-
58
5.2 Einfache Modelle tionen werden auch in setDefaultModelObject() aufgerufen. Das folgende Beispiel soll den Unterschied veranschaulichen. Listing 5.6 ModelChangePage.java package de.wicketpraxis.web.thema.models; ... public class ModelChangePage extends WebPage { public ModelChangePage() { final IModel message = Model.of(0); add(new Label("message", message)); add(new Link("changeModel",message) { @Override public void onClick() { setModelObject(getModelObject()+1); } }); add(new Link("changeModelDirect",message) { @Override public void onClick() { getModel().setObject(getModel().getObject()+1); } }); add(new Link("doNothing") { @Override public void onClick() { } }); } }
Es wird wieder ein Modell angelegt, das durch ein Label zur Darstellung gebracht wird. Gleichzeitig wird ein Link angelegt, bei dem nach einem Klick der Wert des Modells, das zusätzlich an den Link gebunden ist, aktualisiert wird. In diesem Fall erhöhen wir den Wert um 1. Der zweite Link führt dieselbe Aktion aus, ohne Wicket davon in Kenntnis zu setzen. Der dritte Link führt keinerlei Aktionen aus. Die Seite muss trotzdem neu geladen werden, sodass der aktuelle Inhalt des Modells durch das Label dargestellt wird. Das Markup für diese Seite sieht ähnlich einfach aus wie für die Seite im Beispiel davor und muss im passenden Verzeichnis abgelegt werden. Listing 5.7 ModelChangePage.html
Model Change Page
59
5 Modelle Aktueller Wert: Das wird ersetzt.
ändern
direkt ändern
nicht ändern
Wie zu erwarten, ändern wir den Rückgabewert der getHomePage-Methode auf diese Seite und starten die Anwendung neu. Je nachdem, welchen Link wir anklicken, wird der Wert jeweils um eins erhöht. Wenn wir auf den Link „direkt ändern“ klicken, dann ändert sich ebenfalls der Wert. Wenn wir dann im Browser auf die Seite davor springen, sehen wir nicht eine Seite mit dem vorletzten Wert, sondern die Seite, die als Letztes versioniert werden konnte. Zusammenfassung Jetzt sind wir bereits in der Lage, Informationen anzuzeigen und durch Aktionen zu verändern. Diese einfache Möglichkeit ist nicht besonders elegant, veranschaulicht aber sehr schön das Prinzip.
5.3
Modell-Hilfsklassen Wicket bietet für bestimmte Anwendungen bereits vordefinierte Hilfsklassen, um aus z.B. aus einer Liste ein Modell zu erzeugen. Das folgende Beispiel dient nur zur Darstellung der verschiedenen Klassen und Funktionsaufrufe. Unter den auskommentierten Funktionsaufrufen findet sich der gleichwertige kürzere Aufruf. Listing 5.8 ModelTypesPage.java package de.wicketpraxis.web.thema.models; ... public class ModelTypesPage extends WebPage { public ModelTypesPage() { List liste = Arrays.asList("Das", "ist", "ne", "Liste"); Map map = new HashMap(); IModel collectionModel = \ new CollectionModel(liste); IModel listModel = new ListModel(liste); IModel setModel = new SetModel(map.keySet());
60
// //
IModel mapModel = new MapModel(map); IModel mapModel = Model.ofMap(map);
// //
IModel
7.2.4
Das wicket:message-Tag
Keine Komponente, aber eine Markup-Funktionalität stellt das wicket:message-Tag bereit. Dieses Tag führt dazu, dass Inhalte der Seite durch Texte aus Property-Dateien ersetzt werden können. Dabei stellt dieses Tag dieselben Funktionen wie das StringResourceModel (siehe Abschnitt 5.6.3) bereit, ohne dass man eine Zeile Code dafür schreiben muss. Die Klasse für die Seite sieht daher sehr einfach aus. Listing 7.43 WicketMessageTagPage.java package de.wicketpraxis.web.thema.komponenten.basis.content; import org.apache.wicket.markup.html.WebPage; public class WicketMessageTagPage extends WebPage { public WicketMessageTagPage() { } public String getHello() { return "I am a Wicket Message Page"; } } Listing 7.44 WicketMessageTagPage.html ...
from properties
from properties
from \ properties
...
Man kann wicket:message als eigenständiges Tag benutzen. Dann wird der Inhalt des Tags und im Deployment-Modus auch das Tag selbst mit dem Inhalt des passenden Eintrags aus der Property-Datei ersetzt. Wenn man wicket:message als Attribut innerhalb eines Tags benutzt, dann gibt der Teil vor dem Doppelpunkt den zu ersetzenden Attributnamen an.
131
7 Basiskomponenten Listing 7.45 WicketMessageTagPage.properties message.text=This is some text message.style=color:red; message2.text=This is some other text message2.style=color:yellow; message.textMitProperty=Text WicketMessageTagPage.getHello(): \ ${hello}
Die zweite Property-Datei legen wir im XML-Format an. Wicket unterstützt beide Versionen. Listing 7.46 WicketMessageTagPage_de.xml
Deutsch]]> color:blue color:green
Um in der XML-Variante der Property-Datei ein eingebettetes HTML-Tag schreiben zu können, muss der ganze Eintrag in einer CDATA-Definition stehen. Da die normale Property-Datei immer die ISO-8859-1-Kodierung benutzt, ist die Benutzung von Sonderzeichen und Umlauten sehr erschwert. Das Eurozeichen müsste in dem Fall als \u20ac umschrieben werden. Die XML-Variante benutzt, wenn nicht anders definiert, die UTF-8Kodierung. In Abbildung 7.6 können wir sehen, dass die HTML-Tags aus den PropertyDateien ungefiltert dargestellt wurden.
Abbildung 7.6 Das wicket:message-Tag
7.2.5
Image
Einfache Grafiken werden in Wicket mit der Image-Komponente eingebunden. Dabei unterscheidet sich die Image-Komponente in Bezug auf die Anpassbarkeit an Spracheinstellung, Style und Variation nicht von anderen Komponenten. 7.2.5.1 Grafiken direkt Einbinden Das nachfolgende Beispiel zeigt unterschiedliche Möglichkeiten, wie man Grafiken einbinden kann. Gerade in Zusammenhang mit der Auswahl der passenden Grafik in Abhängigkeit zur Lokalisation und des eingestellten Stils (Session.getStyle()) gibt es ein paar Besonderheiten zu beachten.
132
7.2 Inhaltselemente Listing 7.47 SimpleImagePage.java package de.wicketpraxis.web.thema.komponenten.basis.content; ... public class SimpleImagePage extends WebPage { public SimpleImagePage() { add(new Label("locale",Model.of(getLocale()))); add(new Label("style",getStyle())); // test1.gif add(new Image("image1","test1.gif")); // test2.gif, test2_de_DE.gif add(new Image("image2","test2.gif")); add(new Image("image2ResourceRef",new ResourceReference( \ SimpleImagePage.class,"test2.gif",getLocale(),getStyle()))); add(new Image("image2ResourceRefOhne",new ResourceReference( \ SimpleImagePage.class,"test2.gif"))); add(new Image("image2ResourceRefMit",new ResourceReference( \ SimpleImagePage.class,"test2.gif",null,null))); // test3.gif, test3_rot.gif add(new Image("image3","test3.gif")); add(new Image("image3rot","test3.gif") { @Override public String getVariation() { return "rot"; } }); // test4.gif nicht vorhanden add(new Image("image4","test4.gif")); } } Listing 7.48 SimpleImagePage.html ...
Locale: Locale, Style: Style
133
7 Basiskomponenten
...
Zusätzlich benötigen wir noch folgende Grafikdateien, die sich im selben Verzeichnis wie die Markup-Datei befinden: test1.gif test2.gif und test2_de_DE.gif test3.gif und test3_rot.gif Die referenzierte Grafik test4.gif legen wir nicht an. Die Grafiken sollten sich unterscheiden, damit man das Ergebnis besser beurteilen kann.
Abbildung 7.7 Darstellung der eingebundenen Grafiken
In der ersten Reihe von Abbildung 7.7 sehen wir zweimal das Bild test1.gif. Das erste haben wir als Komponente eingebunden und das zweite mit dem wicket:link-Tag. Bei der Komponente image2ResourceRef wird die Grafik über eine ResourceReference adressiert. Die auf den ersten Blick recht unterschiedlichen Möglichkeiten führen im Hintergrund zu ein und demselben Resultat. Mit dem String wird eine ResourceReference erzeugt, die als Basisklasse die in der Hierarchie der Image-Komponente übergeordnete Komponentenklasse benutzt. Das wicket:link-Tag erzeugt automatisch eine Komponente, die den Wert aus einem „src“- oder „href“-Attribut benutzt und mit der Klasse der Komponente eine ResourceReference erzeugt. Es gibt allerdings einen wichtigen Unterschied: Wenn man eine ResourceReference mit einer Klasse als Referenz für den Pfad und einem String als Referenz für den Dateinamen benutzt, dann werden die Werte aus Session.getLocale() und Session.getStyle() nicht mit übernommen. Deshalb unterscheiden sich image2ResourceRefOhne und image2ResourceRefMit von den anderen Grafiken in der Reihe. In den ersten drei Fällen konnte Wicket die richtige (test2_de_DE.gif) für die Anzeige benutzen. Die Komponente image3rot zeigt, dass auch der Rückgabewert getVariation()Einfluss auf das Ergebnis hat. Interessant wird es bei image4. Da Wicket die Grafik nicht finden kann, erscheint im Attribut „src“ der Wert „.../test4_de_DE.gif“. Entgegen der Erwartung erscheint nur dann der längstmögliche Pfad, wenn Wicket eine entsprechende Datei gefunden hat oder Wicket keine passende Datei finden konnte. In jedem anderen Fall wird das Attribut so
134
7.2 Inhaltselemente gesetzt, dass es auf eine vorhandene Datei passt. Man sollte sich also wundern, wenn Wicket einen Pfad zu einer Datei benutzt, die man so nicht angelegt hat. Dann findet Wicket vermutlich die eigentliche Datei nicht. 7.2.5.2 Grafiken zentral verwalten Dass Grafiken wesentlicher Bestandteil einer Komponente sein können, deckt nur einen Teil der Anforderungen an Webanwendungen ab. Oft möchte man alle Grafiken an einem zentralen Ort bündeln. Eine Möglichkeit bestünde darin, eine andere Referenzklasse für eine ResourceReference zu benutzen. Um zu vermeiden, dass sich die Aufrufe in der ganzen Anwendung verteilen, sollte man die Image-Komponente durch die Referenzklasse erzeugen lassen. Für das folgende Beispiel erstellen wir alle Klassen im selben Paket. Listing 7.49 Images.java package de.wicketpraxis.web.thema.komponenten.basis.content; ... public enum Images { TEST1("test1.gif"),TEST2("test2.gif"),TEST3("test3.gif"); String _name; private Images(String name) { _name=name; } public Image newImage(String id) { Session session = Session.get(); return new Image(id,new ResourceReference( \ getClass(),_name,session.getLocale(),session.getStyle())); } } Listing 7.50 ImagesAsEnumPage.java package de.wicketpraxis.web.thema.komponenten.basis.content; ... public class ImageAsEnumPage extends WebPage { public ImageAsEnumPage() { add(TEST1.newImage("i1")); add(TEST2.newImage("i2")); add(TEST3.newImage("i3")); } } Listing 7.51 ImageAsEnumPage.html ...
...
135
7 Basiskomponenten
Abbildung 7.8 Zentral verwaltete Grafiken
In diesem Beispiel kann man erkennen, wie man die Angabe von Basisklassen und Pfadangaben vermeidet (Abbildung 7.8). Außerdem ist diese Variante erfreulich kompakt. Sprechende Namen (also bessere als in unserem Beispiel) sorgen zusätzlich für Übersichtlichkeit. 7.2.5.3 Dynamisch generierte Grafiken Wicket kann nicht nur statische Grafiken anzeigen. Wicket bietet auch alle Möglichkeiten, Grafiken dynamisch zu erzeugen. In diesem einfachen Beispiel greifen wir auf eine Komponente zurück, die einen grafischen Button erzeugt (Abbildung 7.9). Abbildung 7.9 Dynamisch erzeugte Grafiken
Listing 7.52 SimpleDynamicImages.java package de.wicketpraxis.web.thema.komponenten.basis.content; ... public class SimpleDynamicImages extends WebPage { public SimpleDynamicImages() { add(new Image("ok",new DefaultButtonImageResource("Ok"))); add(new Image("hallo",new \ DefaultButtonImageResource(122,16,"Hallo"))); DefaultButtonImageResource res = \ new DefaultButtonImageResource("Wicket"); res.setWidth(140); res.setArcHeight(20); res.setArcWidth(20); res.setColor(new Color(10,128,250)); res.setTextColor(new Color(0,0,0)); res.setFont(new Font("Helvetica", Font.BOLD, 32)); add(new Image("wicket",res)); } } Listing 7.53 SimpleDynamicImages.html ...
...
Auch wenn der praktische Wert dieser Klasse gering sein mag, kann man sich bei Eigenentwicklungen an der Implementierung von DefaultButtonImageResource orientieren.
136
7.3 Links
7.3
Links Eine Webanwendung ohne Links ist schwer vorstellbar. Welche Link-Klassen es gibt und wie man sie einsetzt, zeigen die folgenden Beispiele. Hinweis
Die Basisklasse aller Links ist nicht die Klasse Link, sondern die Klasse AbstractLink. Möchte man eine Liste von Link-Komponenten verwalten oder soll der Rückgabewert einer Funktion eine Link-Komponente sein, kann es notwendig werden, AbstractLink als Rückgabetyp zu verwenden.
7.3.1
Von A nach B
Um den Nutzer von Seite A auf Seite B zu lenken, gibt es mehr als eine Möglichkeit. Dabei gibt es eventuell kein Unterschied im Resultat, aber in der Art und Weise, wie dieses Ziel erreicht wird. Listing 7.54 SimpleLinkPage.java package de.wicketpraxis.web.thema.komponenten.basis.links; ... public class SimpleLinkPage extends WebPage { private Label _message; public SimpleLinkPage() { Model messageModel = Model.of("Jetzt ist es "+new Date()); _message = new Label("message",messageModel); add(_message); add(new BookmarkablePageLink( \ "bookmarkLink",SimpleLinkPage.class,new PageParameters("a=1"))); add(new Link("noBookmarkLink") { @Override public void onClick() { setResponsePage(SimpleLinkPage.class,new PageParameters("b=2")); } }); add(new Link("link") { @Override public void onClick() { _message.setDefaultModelObject("Link geklickt ("+new Date()+")"); } }); add(new Link("linkModel",messageModel) { @Override public void onClick() { setModelObject("Link mit Model geklickt ("+new Date()+")"); } }); } }
137
7 Basiskomponenten Der erste Link ist ein BookmarkablePageLink. Der Link-Typ erwartet als Parameter eine Klasse vom Typ Page. Außerdem ist es möglich, eine Parameterliste zu übergeben, die an den Link angehängt wird. Wicket erzeugt daraus einen Link, den sich der Nutzer als Lesezeichen abspeichern könnte: http://server/de.wicketpraxis--webapp/?wicket:bookmarkablePage= :de.wicketpraxis.web.thema.komponenten.basis.links.SimpleLinkPage&a=1
Ein Klick auf den Link springt dann zu dieser Seite. Der zweite Link springt ebenfalls eine Seite über die Klasse der Seite und einer optionalen Parameterliste an. Da diese Information aber erst zur Verfügung steht, wenn der Nutzer bereits auf den Link geklickt hat und die onClick()-Methode aufgerufen wurde, unterscheidet sich der Link wesentlich: http://server/de.wicketpraxis--webapp/? wicket:interface=:1:noBookmarkLink::ILinkListener::
Wenn der Nutzer auf den Link klickt, dann landet er auf der Seite mit folgender URL: http://server/de.wicketpraxis--webapp/?wicket:bookmarkablePage= :de.wicketpraxis.web.thema.komponenten.basis.links.SimpleLinkPage&b=2
Auch wenn das Ergebnis für den Nutzer an sich gleich bleibt, gibt es Unterschiede z.B. im Bezug auf die Suchmaschinentauglichkeit dieser Navigationsform. Im ersten Link ist keine sitzungsabhängige (Session) Information kodiert, sodass dieser Seitenaufruf unabhängig von vorangegangenen Aktionen erlaubt ist. Der Link ist also nicht nur als Lesezeichen verwendbar, sondern kann auch durch Suchmaschinen verarbeitet werden. Wie man Wicket-Anwendungen für Suchmaschinen optimiert, behandelt Abschnitt 11.6. Der Link mit der ID „link“ demonstriert, dass durch einen Klick auf einen Link nicht automatisch ein Seitenwechsel durchgeführt wird. Es wird einfach eine Aktion ausgeführt und die Seite neu geladen. Der Link mit der ID „linkModel“ unterscheidet sich vom Link „link“ dadurch, dass er dasselbe Modell wie das Label „message“ benutzt und daher den Modellwert direkt und damit typsicher setzen kann. Listing 7.55 SimpleLinkPage.html ...
Bookmarkable Link
No Bookmarkable Link
Link
...
7.3.2
Ajax und Links
Wicket bietet im Zusammenhang mit Ajax einen sehr komfortablen Weg, Ajax in einer Seite zu benutzen, ohne dass man Nutzer, die JavaScript deaktiviert haben, daran hindert, die Webanwendung benutzen zu können.
138
7.3 Links Listing 7.56 AjaxFallbackLinkPage.java package de.wicketpraxis.web.thema.komponenten.basis.links; ... public class AjaxFallbackLinkPage extends WebPage { private Label _message; public AjaxFallbackLinkPage() { _message = new Label("message","Jetzt ist es "+new Date()); _message.setOutputMarkupId(true); add(_message); add(new AjaxFallbackLink("ajax") { @Override public void onClick(AjaxRequestTarget target) { _message.setDefaultModelObject("Link geklickt ("+new Date()+")"); if (target!=null) { _message.setDefaultModelObject("Ajax Link geklickt ("+ new Date()+")"); target.addComponent(_message); } } }); add(new IndicatingAjaxFallbackLink("ajax2") { @Override public void onClick(AjaxRequestTarget target) { timeConsumingTask(); _message.setDefaultModelObject("Link geklickt ("+new Date()+")"); if (target!=null) { _message.setDefaultModelObject("Ajax Link geklickt ("+ new Date()+")"); target.addComponent(_message); } } }); } private void timeConsumingTask() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }
Die Klasse AjaxFallbackLink unterscheidet sich von der Link-Klasse darin, dass der Methode onClick() ein AjaxRequestTarget übergeben wird. Wenn der Parameter nicht gesetzt wurde, dann wurde der Link nicht per Ajax aufgerufen. Man muss also prüfen, ob der Link per Ajax aufgerufen wurde, und kann nur dann die Komponenten zur Aktualisierung vorschlagen.
139
7 Basiskomponenten Die Klasse IndicatingAjaxFallbackLink zeigt nach dem Klick ein Symbol an, das dem Nutzer darstellen soll, dass die Antwort noch auf sich warten lässt. Sobald die Antwort vom Server geliefert wurde, wird das Symbol entfernt. Damit das in diesem Beispiel funktioniert, wartet die Methode timeConsumingTask() einfach nur eine halbe Sekunde, bevor es dann weitergeht. Listing 7.57 AjaxFallbackLinkPage.html ...
AjaxFallbackLink
AjaxFallbackLink mit Indikator
...
Für den Fall, dass man Links benutzen möchte, die nur den Ajax-Aufruf tätigen, gibt es die Klassen AjaxLink und IndicatingAjaxLink. In der onClick()-Methode ist der Parameter AjaxRequestTarget dann immer gesetzt und muss daher nicht mehr geprüft werden.
7.3.3
Link-Tricks
Normalerweise erwartet ein Link ein entsprechendes Link-Tag im Markup. Wenn man die Komponente aber an ein anderes Tag bindet, wird der Link in einen JavaScript-Aufruf umgewandelt. Listing 7.58 LinkTrickPage.java package de.wicketpraxis.web.thema.komponenten.basis.links; ... public class LinkTrickPage extends WebPage { public LinkTrickPage() { Model messageModel = Model.of(""); add(new Label("message",messageModel)); add(new Link("span",messageModel) { @Override public void onClick() { setModelObject("Trotzdem geklickt"); } }); } } Listing 7.59 LinkTrickPage.html ...
Der Link versteckt sich im Text.
...
140
7.3 Links Wenn man die Seite aufruft, sieht man im Quelltext, wie Wicket den zweiten Link umgewandelt hat: ...
Der Link versteckt sich im Text.
...
Der Nachteil dieser Methode liegt darin, dass sich der Mauszeiger nicht ändert und der Nutzer nicht sieht, dass er auf den Text klicken kann. Es ist ja auch kein Link.
7.3.4
Externe Links
Es ist jederzeit möglich, im Markup einen Link einzubinden, ohne dass Wicket diesen Link anpassen würde. Man könnte z.B. auf diese Weise einen Link zu einer anderen Webseite einbinden. Doch ich empfehle, auch in diesem Fall eine passende Komponente zu benutzen, auch wenn das für das Ergebnis keinen Unterschied macht. Die ExternalLinkKlasse schreibt den Wert des zweiten Parameters direkt in das Attribut href. Listing 7.60 ExternalLinkPage.java package de.wicketpraxis.web.thema.komponenten.basis.links; ... public class ExternalLinkPage extends WebPage { public ExternalLinkPage() { add(new ExternalLink("external","http://www.wicket-praxis.de")); add(new ExternalLink("mail","mailto:[email protected]")); } } Listing 7.61 ExternalLinkPage.html ...
wicket-praxis.de
[email protected]
...
7.3.5
Popups
Um mit Wicket ein Popup zu öffnen, muss man den JavaScript-Aufruf nicht selbst erstellen. Ob ein Link in einem Popup geöffnet werden soll (Abbildung 7.10), reduziert sich auf das Setzen der notwendigen Einstellungen.
141
7 Basiskomponenten
Abbildung 7.10 Verschiedene Popup-Fenster
Listing 7.62 PopupLinkPage.java package de.wicketpraxis.web.thema.komponenten.basis.links; ... public class PopupLinkPage extends WebPage { public PopupLinkPage() { PopupSettings popupSettings = new \ PopupSettings(PopupSettings.RESIZABLE|PopupSettings.TOOL_BAR); popupSettings.setHeight(300); popupSettings.setWidth(300); popupSettings.setWindowName("Popup"); BookmarkablePageLink popupLink = \ new BookmarkablePageLink("popup",PopupPage.class); popupLink.setPopupSettings(popupSettings); add(popupLink); Link popupLink2 = new Link("popup2") { @Override public void onClick() { setResponsePage(PopupPage.class); } }; popupLink2.setPopupSettings(popupSettings); add(popupLink2); BookmarkablePageLink newWindowLink = \ new BookmarkablePageLink("newWindow",PopupPage.class); add(newWindowLink); } } Listing 7.63 PopupLinkPage.html ...
Popup
Popup2
newWindow
...
Wie man an diesem Beispiel sieht, kann man ein Popup-Fenster sowohl mit einem BookmarkablePageLink als auch mit einem normalen Link öffnen. Man hat dabei Zugriff auf
142
7.3 Links alle Einstellungen, die man auch per JavaScript ansprechen kann, nur ist es so natürlich viel einfacher. Der letzte Link zeigt, dass die Zielseite eine ganz normale Wicket-Seite ist, die über einen Link auch einfach in einem neuen Fenster (target="_blank") geöffnet werden kann (Abbildung 7.10). Auf der Seite, die dann als Popup geöffnet wird, binden wir einen Link ein, der dieses Popup wieder schließt. Listing 7.64 PopupPage.java package de.wicketpraxis.web.thema.komponenten.basis.links; ... public class PopupPage extends WebPage { public PopupPage() { add(new PopupCloseLink("close")); } } Listing 7.65 PopupPage.html ...
Schliessen
Popup
...
7.3.6
ResourceLink
Wenn man dem Nutzer die Möglichkeit geben möchte, dass er sich Informationen z.B. als PDF herunterladen kann, kann man auf zwei Komponenten zurückgreifen. Listing 7.66 ResourceLinksPage.java package de.wicketpraxis.web.thema.komponenten.basis.links; ... public class ResourceLinksPage extends WebPage { public ResourceLinksPage() throws URISyntaxException { add(new ResourceLink("resource", \ new ResourceReference(ResourceLinksPage.class,"images/test.gif"))); URL resourceURI = getClass().getResource( \ "/"+ getClass().getPackage().getName().replace('.', File.separatorChar)+ "/images/test.gif"); add(new DownloadLink("file", \ new File(resourceURI.toURI()),"test.gif")); } }
143
7 Basiskomponenten Listing 7.67 ResourceLinkPage.html ...
Resource Link
Download Link
...
Der ResourceLink setzt das Attribut „href“ des Links einfach mit der URL einer Ressource, die z.B. auch als Image eingebunden werden könnte. Ein Klick auf den Link zeigt dann die Ressource an. Der DownloadLink übermittelt die Datei in derselben Anfrage, in welcher der Klick ausgewertet wird. Dadurch ist die PageMap blockiert, was zur Folge hat, dass z.B. kein weiterer Download auf der Seite gestartet werden kann, solange der andere Download noch nicht fertig ist. Außerdem benötigt der DownloadLink eine Datei, die man evtl. erst erzeugen müsste. Daher empfiehlt es sich, immer einen ResourceLink zu benutzen, da diese einfacher zu benutzen sind und die Zugriffe die Anwendungen nicht blockieren. Außerdem sind sie flexibler in der Anwendung, da man Ressourcen auch zur Laufzeit erzeugen kann (siehe Abschnitt 11.7).
7.3.7
Formularlinks
Die zwei Linkklassen SubmitLink und AjaxSubmitLink werden in Abschnitt 9.4 erklärt, da sie sinnvollerweise in Formularen benutzt werden.
7.4
Behavior Wenn sich zwei Komponenten nur dadurch unterscheiden, dass ein Attribut im HTML-Tag einen anderen Wert hat, kann man das auf verschiedenen Wegen lösen. Entweder erzeugt man eine Variante der Markup-Datei oder überschreibt die Methode onComponentTag(). Spätestens wenn man dieses Attribut bei einer ganzen Reihe von Komponenten anpassen müsste, steht der Aufwand in keinem Verhältnis zum Nutzen. In Wicket kann eine Komponente durch ein (oder mehrere) Behavior verändert werden. Dazu muss ein Behavior mehrere Methoden implementieren, die während des Verarbeitungsprozesses aufgerufen werden. So ist es möglich, vor und nach der Komponente oder beim Darstellen des HTML-Tags einzugreifen.
7.4.1
Darf es etwas JavaScript sein?
Im ersten Beispiel machen wir nur von der Möglichkeit Gebrauch, dem HTML-Tag der Komponente ein weiteres Attribut hinzuzufügen:
144
7.4 Behavior Listing 7.68 SimpleBehaviorPage.java package de.wicketpraxis.web.thema.komponenten.behaviors; ... public class SimpleBehaviorPage extends WebPage { public SimpleBehaviorPage() { add(new Label("message","Text"). \ add(new OnMouseUpInnerHtmlBehavior("neuer Text"))); } static class OnMouseUpInnerHtmlBehavior extends AbstractBehavior { String _content; public OnMouseUpInnerHtmlBehavior(String content) { _content=content; } @Override public void onComponentTag(Component component, ComponentTag tag) { tag.put("onmouseup", "this.innerHTML = '"+_content+"'"); } } }
Dem Label wird unser OnMouseUpInnerHtmlBehavior hinzugefügt. Wenn die Komponente dargestellt wird, wird in unserem Behavior unter anderem die Methode onComponentTag() aufgerufen. In der Methode setzen wir das onmouseup-Attribut auf eine Zeile JavaScript-Code, der den Inhalt des Labels mit einem neuen Text versieht. Beim Klicken und Loslassen der Maustaste (onmouseup) wird dann die JavaScript-Funktion ausgeführt und der Text ersetzt. Listing 7.69 SimpleBehaviorPage.html ...
... Listing 7.70 Ergebnis.html ...
Text
...
7.4.2
Attribute anpassen
Oft muss man, wie in der Einleitung bereits geschildert, einfach nur ein Attribut im HTMLTag der Komponente anpassen. Welche vorgefertigten Möglichkeiten es gibt, demonstriert dieses Beispiel:
145
7 Basiskomponenten Listing 7.71 AttributeModifierPage.java package de.wicketpraxis.web.thema.komponenten.behaviors; ... public class AttributeModifierPage extends WebPage { public AttributeModifierPage() { add(new WebMarkupContainer("div1").add(new AttributeModifier( \ "style",true,Model.of("border:2px solid red;")))); add(new WebMarkupContainer("div2").add(new AttributeAppender( \ "style",true,Model.of("border-left:2px solid red"),";"))); add(new WebMarkupContainer("div3").add(new SimpleAttributeModifier( \ "style","border-right:2px solid red;"))); } }
Um zu veranschaulichen, wo der Unterschied der drei Varianten liegt, betrachten wir das Markup der Seite: Listing 7.72 AttributeModifierPage.html
SimpleBehavior Page
Das ist der erste Block
Das ist der zweite Block
Das ist der dritte Block
Die Klasse AttributeModifier ersetzt den Wert des angegebenen Attributs durch den neuen Wert. Die Klasse AttributeAppender kann an ein bereits vorhandenes Attribut eigene Daten anhängen. Dazu muss man ein Trennzeichen definieren, das zwischen dem alten und dem neuen Wert eingefügt wird. Wenn man z.B. das Attribut „style“ anpasst, ist das Trennzeichen ein „;“ beim Attribut „class“ ist es ein einfaches Leerzeichen.
Abbildung 7.11 Komponenten mit angepassten Attributen
Die Klasse SimpleAttributeModifier macht es sich einfach und ersetzt den Wert eines Attributs durch einen neuen. Die Klasse SimpleAttributeModifier verzichtet darauf, den Wert aus einem Modell zu ermitteln. Daher macht diese Klasse dann Sinn, wenn man schon im Vorfeld weiß, welche Anpassung durchzuführen ist. In Abbildung 7.11, aber auch im Quelltext der Seite kann man erkennen, was verändert wurde:
146
7.4 Behavior Listing 7.73 Ergebnis.html ...
Das ist der erste Block
Das ist der zweite Block
Das ist der dritte Block
...
7.4.3
Attribute erweitern
Wenn man mehr als ein Behavior für eine Komponente benutzt, dann kann es passieren, dass das gleiche Attribut mehrfach verändert wird. Damit alle Veränderungen in das Endergebnis einfließen, sollte man in solchen Fällen die Klasse AttributeAppender benutzen. Dabei werden nicht nur die bestehenden Werte aus dem Markup berücksichtigt, sondern es wird ebenso auf die letzte Änderung zurückgegriffen. Somit kann man unabhängige Behavior-Klassen entwickeln, sodass Kollisionen ausgeschlossen werden können. Listing 7.74 AttributeAppenderPage.java package de.wicketpraxis.web.thema.komponenten.behaviors; ... public class AttributeAppenderPage extends WebPage { public AttributeAppenderPage() { WebMarkupContainer div = new WebMarkupContainer("div"); div.add(new AttributeAppender("style",true,Model.of( \ "border-left:2px solid red"),";")); div.add(new AttributeAppender("style",true,Model.of( \ "border-right:2px solid green"),";")); div.add(new AttributeAppender("style",true,Model.of( \ "border-top:2px solid yellow"),";")); div.add(new AttributeAppender("style",true,Model.of( \ "border-bottom:2px solid blue"),";")); add(div); } }
Vier AttributeAppender modifizieren nacheinander das Attribut style und hängen jeweils für eine Seite eine CSS-Definition für den Rahmen an. Listing 7.75 AttributeAppenderPage.html ...
147
7 Basiskomponenten Das ist der Block
...
Im Ergebnis sehen wir, dass jede der CSS-Definitionen korrekt eingebunden wurde. Listing 7.76 Ergebnis.html ...
Das ist der Block
...
7.4.4
Ajax und Formulare
Gerade im Zusammenhang mit Formularen stehen Funktionen bereit, die den Umgang mit Formularen vereinfachen und die Anwenderfreundlichkeit (Usability) verbessern können (siehe Abschnitt 9.13). Um dem nicht vorwegzugreifen, möchten wir an dieser Stelle nur noch eine Klasse betrachten, die eine Komponente nach einer festen Zeitspanne per Ajax aktualisiert. Wenn man die Seite aufruft, wird ohne eigenes Zutun die Uhrzeit aktualisiert. Listing 7.77 AjaxUpdatingPage.java package de.wicketpraxis.web.thema.komponenten.behaviors; ... public class AjaxUpdatingPage extends WebPage { public AjaxUpdatingPage() { LoadableDetachableModel uhrModel= \ new LoadableDetachableModel() { @Override protected String load() { return "Mit dem Zeitzeichen ist es genau "+new Date(); } }; Label uhr=new Label("uhr",uhrModel); uhr.setOutputMarkupId(true); uhr.add(new AjaxSelfUpdatingTimerBehavior(Duration.ONE_SECOND)); add(uhr); } } Listing 7.78 AjaxUpdatingPage.html ...
...
148
8 8 Listen und Tabellen Listen kommen in Webanwendungen häufiger vor, als man denkt. Dabei geht es nicht nur um Ergebnislisten, sondern um alles, was mehr als einmal vorhanden ist und sich z.B. durch den Inhalt, aber nicht durch die Struktur unterscheidet. Das können Menüeinträge, Prozessschritte oder eine Liste von Eingabefeldern in Formularen sein. Bisher waren alle Komponenten vorher bekannt. Für jede Komponente gab es die passende ID und die Stelle, wo sie im Markup referenziert wurde. Wenn man eine Liste von Werten oder Elementen darstellen möchte, dann funktioniert dieser Ansatz nicht, da die Anzahl der Elemente meist unbekannt ist. Auf den folgenden Seiten zeige ich unterschiedliche Ansätze, wie wir diese Aufgabenstellung mit Bordmitteln lösen können. Im ersten Abschnitt geht es nur darum, alle Elemente einer Liste darzustellen. Im zweiten Teil zeigen wir einen veränderlichen Ausschnitt einer Liste an.
8.1
Darstellung von Listen Von den unterschiedlichen Möglichkeiten, die Wicket für die Darstellung von Listen bietet, wird man in der Praxis nur wenige, diese aber um so intensiver einsetzen. Dabei hängt es oft von der zu lösenden Aufgabe ab, welcher Komponente der Vorzug zu gewähren ist.
8.1.1
RepeatingView
Die einfachste Komponente für die Listendarstellung ist die RepeatingView. Dieser Komponente fügt man die darzustellenden Kindelemente mit einer durch die Komponente erzeugten ID hinzu. Listing 8.1 RepeatingViewPage.java package de.wicketpraxis.web.thema.komponenten.basis.repeater; ... public class RepeatingViewPage extends WebPage
149
8 Listen und Tabellen { public RepeatingViewPage() { RepeatingView repeatingView = new RepeatingView("list"); for (int i=0;i