michael MOSMANN

WICKET
PROFESSIONELLE WEB-2.0ANWENDUNGEN ENTWICKELN

PRAXISBUCH

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: michael@mosmann.de

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

2 .

4 Teilprojekt Persistenz .....................5 1........................................................4 Sicher.2.......3 Teilprojekte Datenbankkonfiguration...............................................................................................2.................3..........................................24 2...................................................................2................................................. 1 Warum Wicket? ... 15 Nomenklatur der Teilprojekte .................1...................................1 Java.......1.....7 Grundlagen einer Webanwendung .................................................................. Maven und Eclipse ..............................................................................................................................................................................22 2........................1 Einleitung ......................................................................................3..6 Komplett.......3 2 2..............................24 2.......................2...5 1.......................................................................................................1 2..................................1..........6 Vorbereitung und Installation....11 1..................15 Aufsetzen der Teilprojekte .............................6 1........2.........................................................................1 Einfach................................................................................4 1.....................................................................7 1..........................................................................2 Verzeichnis und Paketstruktur.............................................................7 1...................................2 Versionskontrolle mit Subversion .................8 1.................................................................................. Konsistent.............................................................16 2...........................................2.........................................................................2 Wiederverwendbarkeit ......3 VII ..............12 Aufsetzen der Teilprojekte .....................................................................................1 Anwendungsschichten...............................3 Unit-Tests ....6 Teilprojekt Webapp .....................2 1......................................................2 2..........................................................1...................2...XIII 1 1.........5 Effizient und skalierbar ..........................................1 Projektbasis ParentPom ..................................................................20 2...27 1...Inhalt Vorwort...................................5 Teilprojekt Applikationsschicht.........................................1.........1......................... Offensichtlich .........................................3......8 1.................................6 1...................................1....7 Eine gute Wahl ...........................................2...........3 Sauber getrennt....4 1.........................................................26 Erstellen von Eclipse-Projektdateien................................................................................3 1..............................................................2 Teilprojekt Base ..................20 2.......................................................2..16 2.........7 Teilprojekt ParentPom – Abschluss........................................................

................................4 Datenbankzugriff – Konfiguration ...... Markup .3....................................... 46 Die Wicket-Architektur .......................4 Spring-Konfiguration ..................................................................... 49 Wicket und das HTTP-Protokoll...................3 4..........................1 5.3..2..............................................................................................................2 Datenbankzugriff – Hilfsklassen ............................................................................................................................................................................................ 40 Anwendungsschicht ............................................................ 50 4..............................1 Modelle verändern ....4 3.... 30 3.......... 33 3....................................................3............. 53 4.5.............. 35 3.............................................................1 Datenbankzugriff – Allgemeine Schnittstellendefinition ........................................ 31 3.......................................................................................4 VIII ... 50 4.................... 42 3.............. 61 5.....................................................................................................................................2 Teilprojekt dbconfig-test ................. 44 3.................................................................................3..5 PageStore ....................... 52 Komponenten........................2............2............ 49 4................................2....................................................................................................3................................ 51 4...... 49 Struktur ........................................................................5....4............ 50 4.............1 Komponenten ....................................2...................3 PageMap ........................................................ 52 4........................................................................4 Page.....................5..................................................................................2............ 29 Datenbankkonfiguration...3 Markup ...............1 Teilprojekt dbconfig...........................................................................................................................6 Component ....................................... 55 Konverter ...................................... 53 4.................Inhalt 3 3.............1 DetachableModel – Dynamische Modelldaten....................4 Schemagenerierung mit Hibernate .............................................. 37 3.....................................................................4 5 5....................................................5 4 4.............................1 3............... 60 Modelle und Serialisierung ......................................................1 Hilfsklasse für Maven-Projekte...............................................................2 4...............................................2 Session .........................................................................3................................................................................................... 41 Präsentationsschicht..................................................... 33 3.................................................................................................................................6 Schema-Update ......................................................................................3 Servlet-Konfiguration............................. 30 3.............. 58 Modell-Hilfsklassen..................................5 Persistenz-Tests........................................................ 32 Persistenz .............................1 Komponentenphasen ..........................................2 Modelle ............ 53 4....................................................................................................................2....................................2.........................................................................3 5............................................ 29 Konfiguration mit Spring ..................2 Wicket Web Application ......3 3...2 Mit Leben füllen ..5.......................... 55 Einfache Modelle ..........................................5............................ 46 3..........................................................................................1 4.......... 41 3........................................................................ 38 3.................................. Modelle....................................................................2 Nebenläufigkeit – Threads ....................3....... 31 3...................2.................................................. 41 3...3......... 57 5...4..................................................................................4.... 53 Modelle ...................... 34 3....................... 61 3.............................2 5.............3 Datenbankzugriff – User ...........5 Start der Anwendung..............4.............................................................................................1 WebApplication ............................................. 50 4...................................................................2..............................................3 Teilprojekt dbconfig-schema-update........................................................................ 51 Request-Behandlung ....................... 51 4...........2.................

...........2 Empfehlung zur Anwendung............................2 IX ..........................................5...........2.4 Komponentenpfad ..............3 6....................................................................................................1 Ajax-Events...78 Komponenten..................................................2..........131 5....................................127 7......................84 6...............................................................1................................................................................................................ 81 Basisklasse Component.....................................1.......1......................................................6............................... 109 Gruppierende Komponenten .................................................................................................1....................................................5.............................................120 7..................4.................................119 7...........................................3 Page..3 Fragment .. Locale und Variation..2 Die Klasse PropertyModel..........................................................................117 7.............4..........84 6.............................2.........5.......................................................5 5................126 Inhaltselemente .........................................Inhalt 5............................................................................5......................103 6.......................................81 6..........................6................1 Komponentenbaum .......................................1....................................3 CompoundPropertyModel ......................1 wicket:enclosure.................................................125 7.....2............5 7 7..5 ComponentBorder .............................................................1.....1 7............................76 5...............1...............3 Automatische Event-Behandlung ..............................................6..............4 6.........109 7.........83 6..........85 6......................................81 6........1 6........84 6.......................3 XML.....2 Panel............................................76 5..........101 Ajax.........................................1 Seiten..........................................................3 Automatische Kaskadierung von Modellen................................85 6....4 Border..................100 6...........................................1 Markup-Variationen ..........109 7..........85 Grundlagen der Vererbung..........................................................................................................................................................................................69 5.............................4.............................1..........................................................................................................................66 Komplexe Modellklassen.................................................................................5.......................................................94 6...........................................................................................................127 7..........2 Vererbung für Fortgeschrittene...................................................................2......................................................................................62 5.......6 Feedback .............65 5.4 Das wicket:message-Tag ............................1 Eine Seite mit eigenen Komponenten....2 6......................4..........................................................................................................................................................................................99 6......................................................3 StringResourceModel ....91 Style....................74 Ausgelagerte Informationen........................ Session und Application .......69 5..........104 6...................................................94 Sichtbarkeit ...2 Kaskadierung von Modellen.........1...1.....2....................................................................................................................................................................................................................1.....................................................2 Einfache Event-Behandlung ...........4 Datenbankzugriffsmodelle........2 Darstellungsphasen................................................................................................................4......................129 7.1 Zugriff auf Bean-Properties...........................102 6..................3....................................130 7.......................................................1 Einfacher Zugriff auf Ressourcen.......................5......................................1 Label und MultiLineLabel..................................................................................................5 Modelle ......................................2 ResourceModel.........................1.............................................................105 Basiskomponenten...2 Lokaler Konverter ..............................6 WebMarkupContainer ......................72 5..................................................................................................................6 6 6..................................................................76 5.....

........................4..................................3.....................3 DataGridView ...............................................1.................1 Select.......2 Automatische Typermittlung..4 POST und GET ....... 171 Formulare absenden . 152 8..................................3...........................4..... 140 7......1.1 RepeatingView..................................3 Submit per Ajax .............4..................................................................5 Image...............3 7.................... 170 Basisklasse für alle Beispiele .1 Von A nach B.............................3 Attribute erweitern .............. 178 9.... 147 7....1 9..........................................Inhalt 7............................................................ 148 Listen und Tabellen .............................. 169 Feedback ........................................................................................................ 156 8................. 137 7.. 138 7...............................1 8.......................................................................................................................................... 176 9................................................. 141 7.......................................................... 161 8..........................................................9 X ................................. 137 7.......4......................2 9 9...................................................................................................................................................3 ListView............................................................. 149 8.........................................................2 9.................................................................................................................................................. 181 CheckBox .............2 Ajax und Links.........................................................................5 DefaultDataTable .....................2............................................................................ 149 Darstellung von Listen............................1 Typangabe.............1.....2...................................................................... 143 7. 169 Voraussetzungen ....................6 9.........................................................................2 RefreshingView................................................................................................ 156 8......................... 159 8................................................... 172 9......................3...... 150 8....................................................................................................2..................................4.............................................. 132 Links ..........................3.......................................................................5 ColumnListView ........................4........................................................3................ 153 8........................................... 186 9............................................................3.....................................2.............................................4 Externe Links ................4 9...................9...........................8 9................................................. 154 DataProvider ................................3............ 141 7.............................................. 185 Auswahlfelder............................7 9......... 179 Label ....................1 Absenden mit Submit-Button ..................................... 158 8.....................................3 9.....................5........................................................................................................................4 Ajax und Formulare ... 182 RadioButton....................... 174 9.......................................5.....................................................................................................................................6 ResourceLink ........1 Darf es etwas JavaScript sein? ................................................................1 DataView ..............................................................................................................................2 Button-Komponente ............................................................................4.......4 PropertyListView ....................................................................................................................................................................................................... 144 7..................2 GridView............................. 175 Textfelder........4 DataTable ...................................................................................................................................................................................................... 145 7...............2 Attribute anpassen ................2..........................5 9........................... 172 9.................................................................................4 8 8.......................... 162 Formulare ............................................................ 149 8................................................................................ 144 7...... 144 Behavior..................................................................................2.......................................... 186 7..........................................1...4..5 Popups.......................................7 Formularlinks .....................................................................................................................................................1........ 173 9......................................3 Link-Tricks........

......17 10 10.........................................................................1..............................................................................................244 Eigene Basiskomponenten ........................228 Marker an Komponenten................................225 10......................................................205 9........209 9..5 Feedback per CSS .......1............3 E-Mail ..207 9..................................................................................................199 9.........................................................................220 Verschachtelte Formulare ...............................................................................11......................................................................205 9........192 9.............................194 Gültigkeitsprüfung ................................................195 9..................11...............................226 10.............15.......................................233 Wicket in der Praxis......................................10......................227 10.12..................1 Eine eigene Session-Klasse .........................................................................1 11...................231 Elemente ausblenden........13.......11.....197 9.......................10 9...........................207 9.....4 OnChangeBehavior ......203 Ajax..............................................215 9......................................................................1 Feedback zum Formular ......................................................................................................................2 10....225 10.......2 AjaxFormValidatingBehavior .....................13............................................................3 Feedback als Rahmen .........................................214 9.............................15.................235 Navigation...................................................................199 FormValidator...................16 9.222 Sessions und Security .....3 11.............................3 11 11.............................5 AutoCompleteTextField ...............................................................250 9.............................................................15................................................5 Eigene Validatoren ...................1 10...............................................................................................................................................10...............................1...................196 9.....................................................9..................................................................................201 9.....................13.................................................212 Erweitertes Feedback .................................................. 235 Die Integration von Spring.........................................2 11.............................................................11......2 Feedback für die Komponente........1...........................................................................................................................15........237 CSS einbinden..................2 Minimum und Maximum ..................................................................................................................................4 XI ......................................................................................................1............13..................................................12.........2 MultiFileUpload ....................................................214 9...................................2 DropDownChoice................................................................218 Generierte Formulare ....................................................................................................................................................................................13.......................................192 9.........................................................................................217 9..216 9...................................226 10......11 9............................................................................................................3 Strategie..........12 9................................................................................................2 Geschützte Seiten ........1 AjaxFormSubmitBehavior.........................1 FileUpload.....................................190 Dateien hochladen.........................................................11....Inhalt 9....14 9..............................................197 9.....................................................188 9.......................9........................................1 Passwortprüfung......................3 ListMultipleChoice..................................15...................................................................3 AjaxComponentUpdatingBehavior ...................................2 Eigene Prüfung ...........210 AjaxEditableLabel ...........1 StringValidator ............................................. 225 Einfache Variante..............................................................4 Feedback als Indikator............................4 WebApplication...........13 9..............................................................................................................................................................201 9...5 Seiten........................15 9............................4 URL............................................

.............................9...... 287 12........................................ 284 11....................................2 Automatisch generierte Thumbnails........ 285 Fehlersuche...... 278 11..2 13 Register.............2 Komponente ist bereits vorhanden .............................................................5 Tracking mit Google Analytics ................................................................................Inhalt 11................................................9 12 12.. 280 Links auf Seiten und Ressourcen ....................................................................................................................................................................... 295 XII ......1 Pfad für BookmarkablePages .............. 287 12.............................................................................................................................................................. 284 11.................. 276 11........................................................................................................ 282 Optimierungen ........................................6..................................................................................7....................................................................................9........................................................... 288 Anfang oder Ende?.................................... 274 11.................. 258 11....................... 287 12................................................................... 254 11............................. 271 Ressourcen...7... 258 11...............1 Dynamisch erzeugte Grafiken ............................6 11................6.8 11.6............9......................1 Applikation.............................. 268 11......................................................................................................................4 Servlet-Filter .....5.........................................................7...............................................................................2 SessionTimeoutPage ...7.............................................6...........6........1 AjaxFallbackConfirmLink .................................. 262 11..... 274 11....... 284 11....................................................................................................7 11................ 288 Unit-Tests ........1...............................................................3 Download durch Formular .......5............................................................................................................. 293 11.. 277 11...........................................................................................2 Konverter ............................. 256 Suchmaschinenoptimierung ..............................................................9.....................................................................................................................1 Komponenten fehlen ............................................................................................ 264 11..............................................3 Ajax funktioniert nicht ..3 Debug....... 253 11......................................................1.......................................................1 12.............................................5 Komponententausch.................4 Ressource .....1...................................................................................................................................................................................2 Wizard.............5 RSS-Feed .....3 SEO-Links.................................. 284 11.4 Shared Resources ................... 287 Häufige Fehlerquellen.....................................7...

dass man bei neuen Features nicht darüber nachdenkt. wie man sie realisiert. Thinwire um nur einige zu nennen. Im Laufe der Jahre kamen so unterschiedliche Programmiersprachen und Betriebssysteme zusammen. Ich hatte mir bis dahin so einiges angesehen: GWT. an und startete mit ersten Anwendungen. ob man sie realisiert. sondern im ersten Moment nur. die im Internet zur Verfügung gestellt wurden. die damals als Servlets und mit JavaServer Pages realisiert wurden. Mangels guter Alternativen entstanden so im Laufe der Jahre einige selbst entwickelte Anwendungsframeworks. Die Anwendungen waren klein und der Nutzerkreis beschränkt. sind diese Anforderungen mit Wicket keine Herausforderung und das Entwickeln mit Wicket macht einfach nur Spaß. Wusste man bei den alten Anwendungen genau. Der Einstieg in die objektorientierte Programmierung kam mit C++. Webanwendungen zu schreiben ist so einfach geworden. Im Juli 1999 fing ich bei Dr. beseitigten das grundlegende Problem aber nicht. Die ersten Versuche. Im Gegensatz zu diesen überzeugte mich Wicket von Anfang an. die z. Anwendungen mit Benutzeroberflächen zu schreiben. Rails. Doch der Spaß sollte nicht lange auf sich warten lassen. Die Suche nach Alternativen ging weiter und so landete ich im März 2008 auf der Webseite von Wicket.B. welche Problemstellungen „Bauchschmerzen“ verursachten. In diesem Sinne: Let’s have some fun. Die Arbeit kommt vor den Vergnügen. Diese linderten zwar die Probleme etwas. Nicht viel später entstanden die ersten Webanwendungen. Klein und Co. Das ist nun mehr als ein Jahr her und für mich hat sich durch Wicket einiges grundlegend verändert. Die Anwendungen wurden komplexer und dieses Entwicklungsmodell stieß zunehmend an seine Grenzen. Das waren Java-Applets.Vorwort Ich beschäftige mich seit meiner frühen Jugend mit der Softwareentwicklung. Angefangen hat das auf einem ZX-Spektrum-Klon in Basic. Kreditberechnungen ermöglichten. XIII . habe ich auf einem Amiga unternommen.

Ohne Stephan Lamprecht wäre dieses Buch nie entstanden. Vermutlich hätte ich sonst bereits das Handtuch geworfen. Michael Mosmann. Daher ist es unmöglich. die in dieser Auflistung nicht vorkommen. der einen Blick auf Auszüge dieses Buches werfen durfte und mir bestätigte. den einen oder anderen zu vergessen. nicht aus Versehen. die immer sehr viel geduldiger und ruhiger war als ich. Ich danke natürlich meiner Frau. die mich direkt und indirekt beim Schreiben dieses Buches unterstützt haben. sondern auch als erfahrender Autor immer wieder mit einem „Das ist ganz normal“ meine Seele gestreichelt hat. aber mindestens das Gefühl haben dürfen. Ich danke dem Carl Hanser Verlag und da ganz besonders Frau Metzger. was ich da zusammengeschrieben habe. 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. August 2009 XIV . Die Reihenfolge sollte auf keinen Fall als Wertung missverstanden werden ☺. dass sie mir während dieser Zeit das Leben leichter gemacht haben. dass das nicht ganz unverständlich ist. weil sie mich immer wieder angespornt und mir den Rücken frei gehalten hat. weil er nicht nur den Kontakt zum Verlag hergestellt hat. Und zum Schluss möchte ich noch allen danken. Dank gebührt auch Dirk Diebel.Vorwort Danksagung Es gab eine Menge Menschen.

Dabei werden alle Aspekte beleuchtet. Viele Wege führen nach Rom. und im Januar 2007 wurde dann das erste Release als Apache-Projekt veröffentlicht. Und so habe ich am Anfang auch das eine oder andere Mal eine falsche Abbiegung genommen. Quelltexte werden ohne besondere Überschrift als Codeblock dargestellt: public void javacode() Bei der Auszeichnung mit Überschrift wird ein Dateiname angegeben: 1 .1 1 Einleitung Ich bin im Frühjahr 2008 auf Wicket aufmerksam geworden. Entwickler. wird nach der Lektüre dieses Buches sehr schnell Webanwendungen entwickeln können. aber nicht jeder ist kurz oder schnell. aber teilweise einen gehörigen Umweg darstellte. die bereits Wicket einsetzen. Verzeichnisse und Dateinamen werden innerhalb des normalen Textes als code dargestellt. Die Idee zu diesem Buch entstand. die Wicket bietet. zurechtfindet. vermisste aber oft den Praxisbezug der Beispiele und Lösungen. nachdem ich ein paar Monate mit Wicket gearbeitet hatte und die Begeisterung für das Framework immer stärker zunahm. damit man sich auf den gut ausgebauten Straßen. Doch da hatte Wicket bereits eine längere Entwicklung hinter sich. Wer sollte das Buch lesen? Wer Webanwendungen entwickelt oder entwickeln möchte und dabei auf Java als Programmiersprache setzt. Konventionen In diesem Buch gelten folgende Konventionen: Programmcode. die sich zwar nicht als Sackgasse entpuppte. die für das Erstellen einer Webanwendung wichtig und notwendig sind. Dieses Buch soll als Straßenkarte dienen. Ich hatte zu dem Zeitpunkt bereits einiges über Wicket gelesen. profitieren von den praxisnahen Beispielen und bewährten Lösungen. 2006 wurde Wicket ein offizielles Apache-Projekt. denn es wird bereits seit 2005 entwickelt. Seit April 2008 setze ich Wicket mit viel Freude in Projekten ein.

} } 2 . Allerdings beginnen Kommandos immer mit einem Dollarzeichen.2 BeispielBean. ist es ebenso wahrscheinlich. Wenn eine Klasse als Bean benutzt wird. die in der Konsole eingeben werden müssen.. und beginnt auf der neuen Zeile mit zwei Leerzeichen.1 Einleitung Listing 1.und Markup-Dateien wird dieselbe Formatierung wie für Java-Quelltexte benutzt. als dass er auf eine Zeile passen \ würde. erscheinen an der entsprechenden Stelle drei Punkte: . public String getTitel() { return _titel. Property.und get-Methode.1 Beispiel... dass nur leichte Anpassungen an bereits bestehenden Quelltexten vorgenommen wurden.java public class BeispielBean { String _titel. Dann ergibt sich die Bedeutung aus dem Text: $ kommando Java Alle Felder einer Klasse starten mit einem Unterstrich. Es gelten daher dieselben Regeln zum Umbruch wie bei Quelltexten. wird im Laufe des Buches das eine oder andere gekürzt.. Wenn durch die begrenzte Breite des Buches Text umbrochen werden muss und das an dieser Stelle nicht möglich ist. \ die ebenfalls ignoriert werden müssen. Nur wenn das „\“-Zeichen an letzter Stelle steht..java public void andererCode() Für HTML-Quelltexte. Kommandos. wird das unwillkürliche Zeilenende durch ein Leerzeichen und „\“ angegeben. Die nächste Zeile wird dann um zwei Leerzeichen gegenüber dem Zeilenanfang eingerückt: Text=Der Text ist zu lang. Listing 1. sie kommen innerhalb des Textes vor. das stand hier schon doch das ist neu . dann erfolgt der Zugriff auf das Feld durch eine passende set. Ein \-Zeichen mittendrin ist in Ordnung.. } public void setTitel(String titel) { _titel=titel. Damit man erkennen kann. sind von Natur aus nie mehrzeilig. ist ein unwillkürlicher Zeilenumbruch gemeint. es sei denn. Wenn solche Kürzungen vorgenommen wurden. Diese werden der besseren Übersichtlichkeit halber entsprechend hervorgehoben: . ob gekürzt wurde. Da sich viele Dinge immer wiederholen.

Online Da die Entwicklung von Wicket immer weiter geht.. bleiben trotzdem noch einige Frameworks übrig.de/blog/ fortlaufend Erfahrungen und Tipps aus der praktischen Arbeit. getTitel(). da man die meisten Anforderungen mit einer ganzen Reihe von Technologien umsetzen könnte. genügt eine E-Mail an: michael@mosmann.3 übertragen werden. Dabei kann einer Komponente eine HTML-Datei zugeordnet werden. Die Beispiele können begrenzt auch auf Wicket 1. Diese Datei wird im folgenden Markup genannt..1...io..wicket-praxis. Dabei werden alle Felder mit den entsprechenden Typangaben aufgeführt und exemplarisch die Methodennamen für die ersten Felder angegeben. in der alle nötigen Strukturen für die Darstellung enthalten sind. Markup Für die Darstellung von Komponenten nutzt Wicket einen Template-Mechanismus. } Importanweisungen werden nur aufgeführt. Version Das Buch bezieht sich auf die Wicket-Version 1. Wenn Sie mit mir in Kontakt treten wollen.1 Warum Wicket? Im Folgenden wird diese ausführliche Schreibweise gekürzt.). die um die Gunst der Programmierer wetteifern. die mit dem Erscheinen dieses Buches in einer finalen Version verfügbar sein wird. import java. 3 .B. Wicket noch in einer alten Version einzusetzen. veröffentliche ich auf der Seite http://www. Die Auswahl fällt schwer. 1.4 migrieren.de. Wenn man dann die zu verwendende Programmiersprache auf Java eingrenzt.Serializable.1 Warum Wicket? Für das Entwickeln von Webanwendungen stehen eine ganze Reihe von Frameworks und Technologien bereit.4. sollte man spätestens jetzt den Schritt wagen und auf Wicket in der Version 1. 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.setTitel().java (gekürzt) public class BeispielBean { String _titel. Die Kürzung wird durch . Listing 1.3 BeispielBean. gekennzeichnet. Wenn man nicht durch andere Abhängigkeiten gezwungen ist.

Wicket-Anwendungen können über Unit-Tests getestet werden. habe ich an eine Übersicht auf der Webseite des Wicket-Projektes 1 angelehnt. Es gibt nur eine Stelle. Die Lernkurve für die ersten Gehversuche ist sehr gering. Anhand dieser Anwendung kann man einige der folgenden Punkte recht schnell nachvollziehen: Alles kann in Java realisiert werden.org/quickstart. 1.html 4 . die man sich über die „Quickstart“-Funktion 2 generieren lassen kann. ein Testprojekt aufzusetzen.html http://wicket. an der ein Fehler auftreten kann: im Code.org/introduction. Nachdem ich auf diese Weise sehr schnell einen ersten Eindruck gewinnen konnte. Das Grundprinzip erinnert an Swing und ist leicht zu verstehen. Es müssen keine Konfigurationsdateien erstellt werden. Wicket ist einfach nur Java. bei dem man auch bei Komponenten auf Vererbung zurückgreifen kann. Wicket benötigt keine speziellen Vorbereitungen oder Vorarbeiten.1.2 Wiederverwendbarkeit Wicket ist zwar nicht das einzige Framework. Nach einer kurzen Einarbeitungsphase und einigen kleinen Beispielanwendungen war ich mir sicher. Offensichtlich Ich empfehle als Einstieg eine kleine Beispielanwendung. 1.1. Dabei hat Wicket zunächst mit recht einfachen Mitteln mein Interesse geweckt: Es gibt eine sehr einfache und schnelle Möglichkeit. bei dem das sehr einfach umzusetzen ist. Wicket-Anwendungen können einfach für Suchmaschinen optimiert werden. Dieses kann sofort gestartet werden und zeigt ein minimales Grundgerüst einer WicketAnwendung.apache. dass ich Wicket für meine zukünftigen Webprojekte einsetzen werde.apache. Die Kriterien. Mit den sehr ausführlichen Fehlermeldungen kann der Fehler sehr schnell eingegrenzt und behoben werden. Die bestehende Anwendung kann einfach verändert werden. Hervorragende Unterstützung bei der Fehlersuche durch ausführliche Informationen im Entwicklungsmodus. habe ich mich eingehender mit Wicket beschäftigt. beschränke mich aber hier auf die für mich besonders wichtigen Aspekte.1 Einfach.1 Einleitung Aus der umfangreichen Liste der Möglichkeiten habe ich Wicket ausgewählt. dass alles in Java ausgedrückt werden muss. Es ist für mich einer der größten Vorteile. 1 2 http://wicket. die für Wicket sprechen. was zu einem sehr schnellen Erfolgserlebnis führt. doch das einzige. Konsistent.

die zur Komponente gehören und für die Darstellung wichtig sind) keine Code-Bestandteile zu finden sind. Da Wicket keine besondere Notation erfordert. Während der Arbeit mit Wicket offenbart dieses Konzept seine Vorteile: Im Markup befinden sich nur Referenzen auf Komponenten. Alle notwendigen Daten und Informationen können in eine Java-Bibliothek ausgelagert und dann in verschiedensten Projekten eingesetzt werden. wie man sie aus anderen Bereichen der Anwendungsentwicklung kennt. kann man sie innerhalb derselben Anwendung beliebig oft nutzen. 1. Die Komponentenarchitektur unterstützt diesen Aspekt zusätzlich.org/wiki/Cross-site_scripting 5 .1.und andere Attacken. 3 4 http://en.B. da man oft fast alle allgemeinen Komponenten wiederverwenden kann. Programmcode entsteht auf diese Weise nur an einer Stelle und in einer Sprache.1 Warum Wicket? Das ermöglicht sehr leistungsfähige und gleichzeitig effektive Komponenten.wikipedia. in Mailinglisten thematisiert wird. Im Zweifelsfall muss nur die versehentlich gelöschte Bezeichnung der Komponente wiederhergestellt werden. 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. Da Komponenten ihre Funktionalität vollständig in sich kapseln. 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.B. aber kein Code oder spezielle Logik. So entsteht auch hier eine Bibliothek von Lösungen.wikipedia. wundert sich. sodass komplexe Anwendungen sehr schnell realisiert werden können.org/wiki/Code_injection http://en. gehen bei der Bearbeitung keine Informationen verloren. dass im Markup (HTML-Schnipsel.1. Dazu muss die Bibliothek nur in das Projekt eingebunden werden. Wicket-Komponenten können auch anwendungsunabhängig entwickelt werden. XSS 4. Dieser Punkt irritiert so stark.1.4 Sicher Je nach verwendetem Framework sind Webanwendungen mehr oder weniger anfällig für Code-Injection 3-. Auf diese Weise profitiert man sogar in allen nachfolgenden Wicket-Projekten von der eigenen Entwicklungsarbeit. sodass die Gestaltung der Vorlagen z. dass er immer wieder z. So wird die Arbeitsteilung zwischen Entwickler und Gestalter gut unterstützt.3 Sauber getrennt Wer Wicket zum ersten Mal testet und vorher schon Erfahrungen mit JSP. 1. Die Markup-Dateien für die Komponenten können mit einfachen Textprogrammen oder mit HTML-Editoren bearbeitet werden. durch einen Webdesigner angepasst werden kann. JSF oder ähnlichen Ansätzen gemacht hat.

die mit anderen Webtechnologien umgesetzt werden können.terracotta. auf die Lastverteilung von Java-Anwendungen spezialisierte Produkte eingesetzt werden (z.1 Einleitung Wicket-Anwendungen sind sicher. Der Zustand der Anwendung wird serverseitig gespeichert. Der architekturelle Überbau hat dabei kaum Auswirkungen auf die Gesamtperformance.1. sodass eine Kodierung dieser Information in URL-Parameter nicht notwendig ist. Wicket gehört zu den leichtgewichtigen Lösungen. Da Wicket vollständig in Java realisiert wurde. eigene Anpassungen vorzunehmen. Man muss also nicht in Vorleistung gehen und sich ein eigenes Anwendungsgerüst schaffen. 1. Es ist allerdings derart einfach. Wie stark die Einschnitte in den verschiedenen Bereichen sind.1. Da Wicket-Anwendungen vollständig in Java realisiert werden. hängt von der eingesetzten Lösung ab.6 Komplett Webanwendungen können vollständig mit den in Wicket enthaltenen Komponenten umgesetzt werden. sodass der Einarbeitungsaufwand sehr viel geringer ist. 1. Das Einschleusen von Schadcode über manipulierte Parameter ist somit weitestgehend ausgeschlossen. stehen alle Sicherheitsaspekte der Java-Plattform zur Verfügung.B. können zudem zusätzlich zu den klassischen Lösungsansätzen für die Lastverteilung in Webanwendungen andere. eigene Komponenten zu entwickeln oder einfach fremde Komponenten zu benutzen. sollte Wicket also unbedingt ausprobieren. Terracotta 5). was sich gerade bei der Anpassung von bestehenden Anwendungen positiv bemerkbar macht. bei Weitem. Die Komplexität von mit Wicket realisierbaren Anwendungen übersteigt die Komplexität solcher Anwendungen. wie reduziert bisherige Lösungen und die damit realisierten Anwendungen waren.org/ 6 . 5 http://www.7 Eine gute Wahl Mit Wicket zu arbeiten macht Spaß. Die Komponentenarchitektur gewährleistet dennoch eine große Übersichtlichkeit. Möchte man den Zustand einer Anwendung durch URL-Parameter steuerbar gestalten.1. Wer Webanwendungen entwickelt. 1. In Wicket werden nur die Parameter der Nutzerinteraktion in der URL kodiert. dass man sich schnell an die erweiterten Möglichkeiten gewöhnt und rückblickend feststellen muss. muss man diese Möglichkeit explizit bereitstellen.5 Effizient und skalierbar Jede Abstraktion oder Zwischenschicht zieht ein anderes Laufzeitverhalten nach sich.

Ein Plugin für Maven kann installiert werden. Im Prinzip reichen Java und ein Texteditor. wenn man mit mehreren Entwicklern an einem Projekt arbeitet. Alle definierten Abhängigkeiten werden dabei in die Eclipse-Projekte übernommen. kommt man aber um ein etwas komplizierteres Setup nicht herum.org/ 8 http://www. Spätestens. Für die Installation lädt man das passende Archiv von der Apache-Maven-Seite 7 herunter und folgt den Installationsanweisungen auf der Seite. In diesem Buch und auch sonst bevorzuge ich Maven für das Build-Management. Maven und Eclipse Für Wicket benötigt man ein Java Development Kit (JDK) ab Version 5. Ein Versionsver6 7 http://java.sun. Unter Linux sollte man Java über das integrierte Paketmanagement installieren.com/javase/downloads/index. Dafür gibt es zwei Gründe: Eclipse ist kostenlos. weil Maven die Eclipse-Projekte ohne fremde Hilfe erzeugt. funktioniert das Backup-Prinzip nicht mehr.2.org/ 7 . Das ist aber nicht notwendig.2 Versionskontrolle mit Subversion Ein regelmäßiges Backup ist ein gutes Ruhekissen.2 Vorbereitung und Installation 1. Maven-Projekte können sehr einfach in Eclipse importiert werden. Eine Java Runtime Environment (JRE) reicht zum Entwickeln nicht aus.2.2 Vorbereitung und Installation Um Webanwendungen mit Wicket schreiben zu können. Unter Windows laden Sie die Installationsdateien einfach von der Javaseite 6 von Sun herunter und starten die Installation.1. haben sich im Laufe der Zeit unterschiedliche Ansätze und Lösungen für dieses Problem entwickelt. Apache Maven Damit man den Java-Compiler nicht immer von Hand in der Kommandozeile starten muss.jsp http://maven.apache.1 Java Java. 1. Auf diese Weise kann man jederzeit einen älteren Zustand wiederherstellen. bedarf es weniger Vorraussetzungen. Wenn man ernsthaft Webanwendungen entwickeln möchte.4.eclipse. Dafür bekommt man dann aber auch eine Menge an nützlichen Funktionen und Hilfsmitteln. Ich empfehle für dieses Buch Eclipse IDE for Java EE Developers ab Version 3. Eclipse Für das Buch empfehle ich als Entwicklungsumgebung Eclipse 8. 1.

1. Empfehlungen Für die Arbeit an einem Projekt empfiehlt es sich unter Windows. Man kann Dinge ausprobieren und jederzeit wieder auf einen älteren (nicht nur den letzten) Stand zurücksetzen. Man kann in der Entwicklung gefahrlos verzweigen und diesen Entwicklungszweig wieder mit dem Hauptzweig zusammenführen.com/ 12 http://de. VCS) wird unumgänglich.tigris. alle wesentlichen Aspekte der Entwicklungen von Webanwendungen abzubilden. da sich das Ablageformat teilweise unterscheidet und der ältere Client dann nicht mehr auf die Daten zugreifen kann. ist darauf zu achten.1 Anwendungsschichten Das Buch hat den Anspruch.B. sondern durch den Einsatz eines Dependency Injection 13-Frameworks. Subversion hat sich als Standard etabliert und kann über Plugins recht gut in Eclipse integriert werden. Da das Eclipse-Plugin eine eigene SubversionBibliothek mitbringt. kann auch auf SubversionHosting-Angebote 11 zurückgreifen. den Datenbankzugriff.org/wiki/3-Tier#Drei-Schichten-Architektur 13 http://de.org/ 11 http://www.svnhostingcomparison. dass die Subversion-Client-Version in beiden Fällen dieselbe ist. Das Zusammenfügen der verschiedenen Anwendungsschichten geschieht nicht mehr durch Programmcode.1 Einleitung waltungssystem (Version Control System.3 Grundlagen einer Webanwendung Wicket ist im Gegensatz zu Grails oder Rails kein Framework.3. Wer keinen eigenen Subversion-Server aufsetzen möchte. 1. wenn der neuere Client sie erst einmal in das neuere Format konvertiert hat. Da auch die Präsentationsschicht vollständig in Java realisiert wird. Unter Linux kann man alle Aufgaben über die Kommandozeile durchführen und sollte daher Subversion über die Paketverwaltung installieren. in einem Paket bündelt. bei der Wicket zum Einsatz kommt. Dazu gehört neben der Präsentationsschicht. sowohl das EclipsePlugin 9 als auch das Windows Explorer-Plugin TortoiseSVN 10 zu installieren. die Schicht der Anwendungslogik (Business Logic) und die der Datenhaltung (Persistenz) (siehe den Wikipedia-Eintrag zur Drei-Schichten-Architektur 12).org/ http://tortoisesvn.wikipedia.wikipedia.org/wiki/Dependency_Injection 10 8 . Aber auch für den einzelnen Entwickler bringt der Einsatz eines VCS erhebliche Vorteile. Wir müssen uns daher selbst um die notwendigen Bibliotheken bemühen.tigris. 9 http://subclipse. was uns aber vor keine großen Herausforderungen stellen wird. ist die Anbindung an die anderen Applikationsschichten besonders einfach. das alle Aspekte einer Webanwendung. z.

org/wiki/Objektrelationale_Abbildung 9 .1. Daher wurde für die Anbindung von relationalen Datenbanksystemen OR-Mapper 15 entwickelt. hat gegenüber den Speziallösungen aber den entscheidenden Vorteil. Objektorientierte Sprachen können mit Objekten und Beziehungen von Objekten untereinander arbeiten. Der Zugriff erfolgt in Tabellenform. Dabei wird meist ein Objekt auf eine Tabelle und die Eigenschaften des Objekts auf die Spalten einer Tabelle abgebildet.wikipedia. weil es einfach zu benutzen und am Markt etabliert ist. die miteinander verknüpft werden können. Im Prinzip sind damit alle Anforderungen an eine Datenbankschnittstelle erfüllt. jede Zeile steht für einen eigenständigen Datensatz. Relationale Datenbanksysteme bilden Daten in Tabellen ab.1. dass auf bewährte Technologien zurückgegriffen werden kann. Diese Methode ist bei der Abbildung von komplexen Objektbeziehungen natürlich limitiert. Diese Speziallösungen konnten sich bis heute aus verschiedenen Gründen nicht durchsetzen. In den Spalten einer Tabelle werden die verschiedenen Attribute eines Datensatzes abgelegt. die es sowohl für verschiedene Programmiersprachen als auch für unterschiedliche Datenbanksysteme gibt. Außerdem kann der Zugriff auf die Daten in Abhängigkeit von der Datenbank optimiert werden.org/wiki/Objektorientierte_Datenbank http://de. sodass die aufgezeigten Lösungsvorschläge auch auf andere Frameworks abgebildet werden können. 14 15 http://de. Eine objektorientierte Darstellung der Daten und deren Transformation in die Tabellenstruktur der Datenbank hat auf Seiten der Anwendungsentwicklung zu wesentlich schnelleren Ergebnissen geführt.1 Persistenz mit Hibernate Alle Anwendungen arbeiten mit Daten. Warum Hibernate? In diesem Buch und auch sonst empfehle ich Hibernate als Persistenz-Framework. Für die Datenbankanbindung in Java gibt es JDBC mit den entsprechenden datenbankabhängigen Treibern.3. Die Datenhaltung in Tabellen unterscheidet sich derart von den Möglichkeiten objektorientierter Datenhaltung. Die Wahl fiel auf Hibernate. Daten werden klassisch in Datenbanksystemen abgelegt.3 Grundlagen einer Webanwendung 1. dass die Modelle nicht automatisch aufeinander abbildbar sind. Natürlich kann man jedes andere Persistenz-Framework mit Wicket benutzen. Es zeichnet sich ähnlich wie Wicket durch eine gute Java-Integration aus. OR-Mapper stellen daher neben der Transformation der Daten meist grundlegende Datenbankoperationen zur Verfügung.wikipedia. die Objekte und deren Beziehung in einer Objektdatenbank 14 abbilden können. Die Anbindung an eigene Applikationen ist dennoch recht kompliziert und mit vielen Fallstricken versehen. Wicket bietet keine besondere Hibernate-Integration. Es gibt Speziallösungen. Diese OR-Mapper übernehmen die Transformation der unterschiedlichen Modelle ineinander.

Warum Spring? Das Spring-Framework wurde aus ähnlichen Gründen wie Hibernate ausgewählt.1. Die Informationen. hängt sehr stark von dem verwendeten Framework ab.1 Einleitung Funktionsweise Mit Hibernate kann man Klassen und deren Attribute auf Datenbanktabellen abbilden. Dazu muss jedes Objekt über Informationen seiner Umgebung verfügen. können sowohl in der Klasse selbst (per Annotation) als auch in gesonderten Konfigurationsdateien abgelegt werden.org/wiki/MVC 10 . und in der Präsentationsschicht werden diese Daten dann dargestellt. Dieses Vorgehen reduziert die Abhängigkeit der Klasse zur Umgebung und zu konkreten Umsetzungen der abhängigen Module.3. In diesen Dateien werden die Objekte mit Namen versehen und die Konfiguration der Attribute vorgenommen. Das Framework erzeugt über Konfigurationsdateien oder Metainformationen in den Klassen die Anwendungsmodule und löst die definierten Abhängigkeiten auf. Die Abhängigkeiten von Objekten können über Referenzen und deren Übergabe in Attribute vorgenommen werden. 1.3 Anwendungslogik und Präsentation mit Wicket Die Architektur von Wicket folgt dem Model-View-Controller 16-Ansatz (Modell-Präsentation-Steuerung). Beziehungen untereinander können ebenso abgebildet werden.wikipedia. Wie gut die Ergebnisse in diesem Buch dann übertragbar sind. 16 http://de. Spring ist somit einfach in jede Java-Anwendung integrierbar.2 Dependency Injection mit dem Spring-Framework In klassischen objektorientierten Anwendungen ist jedes Objekt selbst dafür verantwortlich.1.3. Eine Anwendung lädt über Funktionen des Frameworks eine Spring-Konfiguration und kann dann über den Namen auf die Objekte bekannten Typs zugreifen. Dependency Injection verlagert die Verantwortung für das Finden und Verwalten von Ressourcen und Abhängigkeiten aus dem Objekt in ein Framework. Das Modell beinhaltet die darzustellenden Daten. 1. Und ähnlich wie bei Hibernate kann auch dieser Aspekt durch ein anderes Framework abgebildet werden. der Controller übernimmt die Steuerung der Interaktionsmöglichkeiten. Der Zugriff auf die Daten kann über eine objektorientierte Alternative zu SQL (HQL) oder über eine Schnittstelle erfolgen. auf welche Tabelle und auf welchen Spalten die Objekte abgebildet werden. Spring benutzt für die Konfiguration XML-Dateien. Die Transformation in Objekte erfolgt transparent. Hibernate sorgt dabei für die Transformation der Daten und Abhängigkeiten in ein für die Datenbank verständliches Format. die Abhängigkeiten aufzulösen sowie die notwendigen Ressourcen zu erzeugen und zu verwalten.

Auf diese Weise können sogar Anpassungen an der laufenden Anwendung vorgenommen werden.B.xml). seine Softwareprojekte gut zu strukturieren.0 grundlegend erweitert und umfasst seither vor allem auch die Abhängigkeitsverwaltung. Ant 17 mit Ivy 18 oder Buildr 19.org/ivy/ 19 http://incubator. Alternativen zu Maven sind z. Ich lege daher für jede Anwendungsschicht mindestens ein eigenständiges Teilprojekt an.1.2. Maven ist dabei ein Build-Tool mit vielfältigen Möglichkeiten.org/buildr/ 11 . die Webanwendung direkt aus dem Projektverzeichnis heraus starten zu können. im Verzeichnis src/main/java nach Java-Dateien und kompiliert diese. die minimale Informationen zum Projekt enthält. die notwendigen JavaQuelltexte in standardisierten Verzeichnissen. wobei Maven diese Abhängigkeiten selbstständig auflöst und die referenzierten Bibliotheken automatisch aus einem Verzeichnis herunterlädt und lokal vorhält. In src/test/java befinden sich Unit-Tests. Wurde eine Bibliothek bereits einmal heruntergeladen. In der Projektdefinitionsdatei können auch die Abhängigkeiten zu den anderen Teilprojekten oder zu anderen Bibliotheken eingestellt werden. Über Plugins ist es außerdem möglich.B. Build-Tools sind hauptsächlich für das Erstellen der ausführbaren Programme aus vorhandenen Quelltexten zuständig.apache. dass man nie zu früh anfangen kann. In den jeweiligen resource-Verzeichnissen befinden sich alle anderen Dateien (Grafiken. Das Grundprinzip von Maven ist einfach.apache. Maven sucht dann für das Erstellen des Projekts z.3.apache. In einem Verzeichnis gibt es ein vorgeschriebenes Verzeichnislayout für ein Projekt: pom. Weitere Funktionalitäten können fast beliebig durch Plugins erweitert werden und reichen vom Erstellen von Dokumentationsseiten bis zum Deployment der Anwendung auf Produktivsystemen. greift Maven auf die in einem lokalen Verzeichnis abgelegte Version zurück. 1.2 Verzeichnis und Paketstruktur Es zeigt sich. Texte etc. Das Aufgabenspektrum hat sich spätestens seit Maven in der Version 1.org/ http://ant. 17 18 http://ant.xml src main java resources test java resources Dazu benötigt Maven neben einer sehr einfach gehaltenen Projektdefinitionsdatei (pom.). Das verkürzt den Entwicklungszyklus und verbessert die Übersichtlichkeit.3 Grundlagen einer Webanwendung 1.1 Projektverwaltung mit Maven Für das Erstellen der Teilprojekte und die Abhängigkeitsverwaltung setze ich Maven ein. die beim vollständigen Erstellen der Anwendung ausgeführt werden.3.

Was sich im ersten Moment etwas „verrückt“ anhört. Außerdem bietet ein gut formulierter Test einen hervorragenden Sicherheitsfallschirm für den Fall.B. dokumentieren. dass es genauso stabil läuft wie vor der letzten Anpassung.bzw. die auf diese Funktionen aufbaut. Wenn dann in so einem Test ein Fehler auftritt und der Test somit fehlschlägt.B. 1. nennt sich „Test Driven Development“. die ein einfaches Wicket-Anwendungsgerüst erstellt. Komponententests) unterscheiden sich vom normalen Programmcode nur dadurch.2. die dieses Teilprojekt eingebunden haben. Doch Fehler in Anwendungen sind ärgerlich. ist sehr viel aufwendiger. 12 . Wenn man mit Maven ein Projekt kompiliert. Denselben Fehler innerhalb einer laufenden Anwendung zu isolieren. Eine Methode. die Anzahl der Fehler mit jedem Entwicklungsschritt nachhaltig einzudämmen. „testIrgendetwas“ heißen. selbst durchführt. Dabei geht es im Prinzip darum. desto mehr Zeit benötigt man. was die Anpassung bewirkt. So kann sichergestellt werden. Unit-Tests (Modul. dass die Namen der Klassen meist mit „Test“ anfangen und die Methoden der Klasse z. für jede Zeile Programm wieder ein Programm zu schreiben. das man z. Der Wichtigste für mich: Das Projekt benutzt angepasste Einstellungen.2 Besser als Quickstart Wicket bietet auf der eigenen Webseite eine „Quickstart“-Funktion.3. was sich neben der besseren Dokumentation des eigenen Projekts positiv auf die Fehlersuche auswirkt.B. stellt sich nach kurzer Zeit als unglaublich entlastendes Instrument dar. das sofort lauffähig ist. die so nicht nötig sind und vom Standard abweichen. Fehler in der eigenen Anwendung kann man aber vermeiden. Denn je komplexer die Anwendungen werden.3. kann das ganze Projekt nicht gebaut werden und wird daher auch nicht für andere Projekte. Dann kann man z. Für einen ersten Test sollte man sich mit „Quickstart“ ein Testprojekt erstellen lassen. Allerdings empfehle ich. das die Funktionsfähigkeit und das Ergebnis überprüft. wenn man alle Anpassungen. immer noch funktioniert.3 Unit-Tests Irren ist menschlich. kann man sich ziemlich sicher sein. dass auch die Anwendung. kann man die Suche nach der Ursache sehr stark einschränken. Wenn das Teilprojekt mit den automatisch ablaufenden Tests erstellt werden konnte. Wenn in einem Test Fehler auftreten. Dafür gibt es verschiedene Gründe. die man im Laufe des Entwicklungsprozesses vorgenommen hat.1 Einleitung 1. Wenn die Tests nach einer solchen Anpassung immer noch funktionieren. Außerdem ist es hilfreich. kann man fast davon ausgehen. zur Verfügung gestellt. Optimierungen vornimmt oder die Funktionalität erweitert. um selbst triviale Fehler einzugrenzen. dieses Projekt für eigene Entwicklungen nicht weiter zu verwenden. dann werden die nach dieser Nomenklatur erstellten Klassen und Funktionen ausgeführt. dass alle Abhängigkeiten in einem fehlerfreien Zustand sind.

3.3. was mit anderen Frameworks realisierbar ist.1. Zusammenfassung Interessanterweise unterscheidet sich unsere Auswahl nur unwesentlich von den Bibliotheken. Das macht Wicket in diesem Bereich einmalig. die auch Grails benutzt. dass Nebeneffekte durch bereits bestehende Daten ausgeschlossen werden können. bewahren wir so die Flexibilität. ein anderes Persistence-Framework einzusetzen. weil wir ebenso wie das Grails-Projekt auf etablierte Standards setzen. Allerdings ist zu beachten. Auch wenn die Testbarkeit sicher mit Einschränkungen behaftet ist.1 Persistenz-Tests Auch mit Hibernate können Unit-Tests durchgeführt werden.2 User-Interface-Tests Mit Wicket können Teile von Wicket-Anwendungen und -Komponenten getestet werden. 1. Somit kann man alle Datenbankoperationen (z.3 Grundlagen einer Webanwendung 1. Das bedeutet.B.3. Dabei kann eine temporäre Datenbank benutzt werden. die nur zur Laufzeit des Tests vorhanden ist und keinerlei Daten enthält. Löschen) ohne die Gefährdung von Produktivsystemen testen. sicherlich auch. 13 . dass diese Aufwände nur einmal in einem Projekt anfallen und damit selten eine große Rolle spielen. Auch wenn der Aufwand für das Aufsetzen eines Projektes ungleich höher ist. z.3.B. geht die Testabdeckung und die Einfachheit beim Erstellen eigener Test weit über das hinaus. die es uns erlauben würde.

1 Einleitung 14 .

Ich persönlich finde es akzeptabel. artifactId: Diese ID gibt dem Projekt seinen Namen. sollten sich durch leichte Modifikationen auf andere Projekte anwenden lassen. Im zweiten Schritt hauchen wir dem Ganzen Leben ein. Die Strukturen. auf Leerzeichen in den Verzeichnisnamen zu verzichten. In den folgenden Abschnitten erstellen wir zuerst das notwendige Grundgerüst mit allen Abhängigkeiten zu externen Bibliotheken und eigenen Teilprojekten. in der die Artefakte zusammengefasst werden. die im Folgenden erstellt werden. version: Version des Projekts.1 anfangen. sonst kann es zu unnötigen Komplikationen kommen. in dem künftig alle Teilprojekte und sonstige Daten beheimatet sind. wenn aus unterschiedlichen Gruppen Artefakte mit derselben Versionsnummer und artifactId benutzt werden. Es empfiehlt sich. Die GruppenID hat eher organisatorische Gründe und ist leider ungeeignet. die in einem Projekt angegeben werden müssen: groupId: Bezeichnet eine Gruppe.1 Nomenklatur der Teilprojekte Maven benutzt für das Auflösen von Abhängigkeiten drei Informationen. in diesem Buch bei Version 1. Namenskollisionen zu vermeiden.2 2 Aufsetzen der Teilprojekte Wir schreiben endlich unsere erste eigene Wicket-Anwendung. sodass es zu Dateinamenskollisionen kommen kann. Diese ID findet sich später auch im Dateinamen der erzeugten Bibliothek und im Projektnamen in Eclipse wieder.0 anzufangen. Als Erstes sollte man sich für dieses Projekt ein Verzeichnis anlegen. 2. Eine gute Versionierung sollte eigentlich bei 0. Allerdings wird bis zur Fertigstellung 15 . Im Dateinamen der fertigen Bibliothek kommt dieser Bezeichner nicht vor.

0-SNAPSHOT lauten (das ist die Grundeinstellung von Maven). Das Verzeichnis. Für dieses Beispiel benutze ich als groupId den Wert „de. dass beim Auflösen dieser Abhängigkeiten regelmäßig geprüft wird. Dieses Versionierungsschema empfiehlt sich aus zwei Gründen.2 Aufsetzen der Teilprojekte Beim Erstellen der Teilprojekte arbeiten wir uns von der Basis an aufwärts. Zum einen wird auf diese Weise das Teilprojekt als „in Arbeit“ gekennzeichnet und sorgt zum anderen bei Maven dafür. hänge ich ein „projekt. in dem alle Daten dieses Teilprojektes enthalten sind. Wenn man aber im Team entwickelt und ein Continuous-Build-System 1 zum Einsatz kommt. hat ebenso diesen Namen. ob eine Version mit einem neueren Zeitstempel existiert.wicketpraxis–-parentPom an. um einen Namen für dieses Teilprojekt ergänzt. Der Verzeichnisname für dieses Teilprojekt entspricht dem Wert in artifactId.theasolutions.com/tutorials/scrum_agile.wicketpraxis“. weil in diesem Teilprojekt übergreifende Einstellungen vorgenommen werden können.2. am Ende ein vollständiges Projekt mit allen notwendigen Teilprojekten zu erhalten. gefolgt von „--“. Da die groupId aus oben genannten Gründen nicht zur Strukturierung von Projekten herangezogen werden kann. 1 http://www. Wenn man allein an einem Projekt entwickelt. wird sich da kein Unterschied bemerkbar machen. Der Name parentPom wurde gewählt.2 Aufsetzen der Teilprojekte die Versionsbezeichnung 1. die das Funktionieren und das Zusammenspiel der verschiedenen Schichten veranschaulicht. Ein Teilprojekt hat demzufolge als artifactId den Wert „de. Im zweiten Schritt wird für jede Schicht eine beispielhafte Funktionalität realisiert. handhabe ich die Bezeichnung der Projekte wie folgt: In groupId schreibe ich den Domainnamen des Projekts in umgekehrter Reihenfolge. in dem alle teilprojektübergreifenden Einstellungen und Abhängigkeiten abgelegt werden.1 Projektbasis ParentPom Als erstes benötigen wir ein Maven-Projekt.<projektname>“ an. ist nur so gewährleistet. wobei die höchste Schicht die Präsentationsschicht ist. Wenn es ein Unterprojekt für eine existierende Domain ist. Dazu legen wir ein Projekt unter de. 2. weil die Teilprojekte entsprechend konfiguriert wurden. in diesem Fall also Wicket. 2. dass man die aktuellste Version eines Teilprojektes benutzt. Ziel ist es.wicketpraxis--teilprojekt“. Der Wert in artifactId fängt mit dem Wert aus groupId an und wird. Außerdem kann zum Schluss die ganze Anwendung über dieses Projekt erstellt werden.jsp 16 .

wicketpraxis</groupId> <artifactId>de. Wenn alles richtig konfiguriert wurde und alle Tests erfolgreich abgeschlossen werden konnten..0.B. In dem generierten Projektverzeichnis befindet sich außerdem die Projektdatei pom.0 http://maven. [INFO] ---------------------------------------------------------------[INFO] BUILD SUCCESSFUL [INFO] ---------------------------------------------------------------.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.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.0.0.wicketpraxis \ -DartifactId=de. Dazu gehören z. Unter test/java werden die Quelltexte für die Unit-Tests abgelegt.8. die in dem Test auf die Ressourcen unter test/resources zurückgreifen können. erscheint am Ende die Ausgabe: .0-SNAPSHOT</version> <name>de. was in diesem Projekt sonst noch an Daten benutzt wird.w3. Programmcode und Daten aus dem test-Ordner werden nicht in der Bibliotheksdatei weitergegeben.apache.wicketpraxis--parentPom</artifactId> <packaging>jar</packaging> <version>1.apache.wicketpraxis--parentPom</name> <url>http://maven.wicketpraxis--parentPom Dabei legt Maven erfreulicher Weise gleich ein Verzeichnis passend zur artifactId an. Jetzt kann man mit $ mvn install auf der Kommandozeile das Projekt erstellen lassen.2.maven.org/POM/4.. In main/resources wird alles abgelegt.0" xmlns:xsi="http://www.apache.apache.xsd"> <modelVersion>4.. Bilder oder Konfigurationsdateien.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.archetypes \ -DgroupId=de.xml mit folgendem Inhalt: <project xmlns="http://maven.1</version> <scope>test</scope> </dependency> </dependencies> </project> 17 .org/POM/4. In diesem Verzeichnis finden sich folgende Verzeichnisse: src main java resources test java resources In main/java finden sich die Quelltexte der Anwendung.apache..org/maven-v4_0_0.0</modelVersion> <groupId>de.

finden sich dort unsere Werte für groupId und artifactId wieder.artifactId} (Wicket Praxis .0.\ Parent Pom)</name> <url>http://wicketpraxis. die für das Ausführen der Unit-Tests vorhanden sein muss..apache.wicketpraxis--parentPom</artifactId> <version>1.. Außerdem sind noch zwei Bereiche von besonderem Interesse.1 pom. Der zweite interessante Aspekt ist im Bereich dependencies zu finden.wicketpraxis</groupId> <artifactId>de.0</modelVersion> <groupId>de.xml <?xml version="1. was bedeutet. dass aus diesem Projekt eine normale Bibliothek in einer JAR-Datei erzeugt wird.2 Aufsetzen der Teilprojekte Wie man erkennen kann.0" encoding="UTF-8"?> <project . > <modelVersion>4.0-SNAPSHOT</version> <packaging>pom</packaging> <name>${pom. Jetzt passen wir die Projektbeschreibungsdatei wie folgt an: Listing 2.maven. Zusätzlich ist der Parameter scope aufgeführt.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org. Der Parameter „packaging“ ist auf dem Wert „jar“ gesetzt.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> 18 .de</url> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.apache. bei dem der Wert test darauf hinweist.groupId}--${pom. dass diese Bibliothek nur für die Unit-Tests benutzt wird. Man kann auch hier wieder die drei relevanten Parameter für die Adressierung dieser Abhängigkeit erkennen.5</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.maven. Dort wird eine Bibliothek als Abhängigkeit definiert.

2.8. ohne dass man Anwendungscode anpassen muss. Damit erspart man sich einerseits Schreibaufwand und kann andererseits bestimmte Informationen an einer Stelle bündeln (z.version}</version> </dependency> </dependencies> <properties> <junit. jederzeit das verwendete Framework austauschen zu können. Folgende Anpassung habe ich vorgenommen: An der Stelle. Außerdem gibt es noch von Apache das log4j-Logging-Framework. sonst können Projekte nicht gebaut werden.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>${slf4j. Mit dem Compiler-Plugin (maven-compiler-plugin) wird die Java-Version auf 1. Das Maven-Resource-Plugin (maven-resources-plugin) sorgt dafür. Außerdem bietet slf4j eine praktische Schnittstelle.2 Aufsetzen der Teilprojekte <version>${junit. Damit ist dieses Teilprojekt fertig gestellt. mithilfe des Maven-Source-Plugins (maven-source-plugin) auch die Quelltexte zum Projekt in ein Archiv packen zu lassen.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.groupId} werden durch die aktuell definierten Inhalte ersetzt. Es empfiehlt sich. Es muss mindestens eine Bibliothek für Unit-Tests definiert sein.. dass alle Ressourcendateien mit UTF-8-Kodierung eingebunden werden. Für dieses Projekt benutzen wir junit für die Unit-Tests.version> </properties> </project> Diese Projektdefinition wird nachher von allen anderen Teilprojekten referenziert und definiert somit die Grundeinstellung für alle Teilprojekte.version> <slf4j. um parametrisierte Log-Ausgaben generieren zu können.2</slf4j. Jetzt müssen wir dieses Projekt als Basisprojekt in alle weiteren Teilprojekte einbinden. 19 .B.4. Die Platzhalter wie z. habe ich die Darstellung aus Platzgründen gekürzt. Davor sollten wir es kurz mit mvn install erstellen.version>3. wo <project .version>1. ${pom. ohne dass man das Projekt geöffnet haben muss. Der Parameter packaging muss für dieses Teilprojekt auf pom gesetzt werden.B. Java bietet ein integriertes Logging-Framework. die Versionen für die Abhängigkeiten im Bereich properties).1</junit. Das slf4j-Projekt bietet die Möglichkeit. Außerdem wird die Kodierung für die Quellcodedateien auf UTF-8 gesetzt..5 gesetzt. Damit benötigt die Anwendung mindestens ein Java 5 oder Java 6.> steht. So kann man später in der Entwicklungsumgebung sehr einfach auf die passenden Quelltexte zugreifen. Die Angaben an dieser Stelle entsprechen den automatisch generierten.

> <modelVersion>4. 2.2.3 pom. muss es wie im Bereich parent referenziert werden. Wir passen die Projektdatei an und fügen die Abhängigkeiten für den Datenbanktreiber ein.0</modelVersion> <parent> <groupId>de. empfehle ich.0" encoding="UTF-8"?> <project . die keinem Projekt direkt zugerechnet werden können.groupId}--base</artifactId> <name>${pom.0-SNAPSHOT</version> </parent> <artifactId>${pom.artifactId} (Wicket Praxis .wicketpraxis--parentPom</artifactId> <version>1.wicketpraxis--base Die generierte Projektdatei wird vollständig ersetzt.wicketpraxis \ -DartifactId=de.xml <?xml version="1. ein Teilprojekt anzulegen. Listing 2.apache. > <modelVersion>4. Damit diese Funktionen sich gar nicht erst in den Untiefen eines Projekts verlieren. da wir viele Einstellungen bereits in dem übergeordneten Projekt getroffen haben und diese deshalb nicht noch einmal angegeben werden müssen.. Auch wenn der Aufwand im ersten Moment übertrieben wirkt. Listing 2. Daher wird die Bibliothek für diesen Datenbanktreiber eingebunden. 2.groupId}--${pom. in dem solche allgemeinen Funktionen dann eine passende Heimat haben..3.0. Dieses Teilprojekt erstellen wir wieder mit: $ mvn archetype:create \ -DarchetypeGroupId=org..xml <?xml version="1.2.3 Teilprojekte Datenbankkonfiguration Für die Datenbankanbindung werden drei Teilprojekte angelegt. wenn wir nachher diese Projekte einbinden und nutzen.2 Teilprojekt Base Im Laufe einer langjährigen Entwicklung sammeln sich immer mehr Funktionen.0</modelVersion> 20 .archetypes \ -DgroupId=de.wicketpraxis--dbconfig an. ist nur durch die Teilung eine saubere Abhängigkeitsstruktur erreichbar.1 Konfigurationsprojekt für die Produktivdatenbank Wir legen ein Teilprojekt mit der artifactId de..0" encoding="UTF-8"?> <project .2 pom.maven.\ Base)</name> </project> Damit die nötigen Informationen vom Basisprojekt übernommen werden können.2 Aufsetzen der Teilprojekte 2.wicketpraxis</groupId> <artifactId>de. Als Produktivdatenbank soll eine MySQL-Datenbank zum Einsatz kommen. sich aber einer ungemeinen Nützlichkeit im Projektalltag erfreuen.0.2. Das erklärt sich sehr viel besser.

.6</mysql.groupId}--dbconfig</artifactId> <name>${pom. Dieses Projekt dient nur dazu. > <modelVersion>4.wicketpraxis--dbconfigschema-update. Dabei nimmt Hibernate ebenfalls datenbankspezifische Optimierungen vor.0. Auf die automatische Schemagenerierung greifen wir bei der Testdatenbank ebenfalls zurück.version> </properties> </project> 2. andere Zugangsdaten für die Datenbank bereitzustellen.artifactId} (Wicket Praxis . </parent> <artifactId>${pom. Listing 2. sodass derselbe Datenbanktreiber benutzt wird.. Wir erstellen das Teilprojekt mit der artifactId de. aus der konfigurierten Datenbankzugriffschicht automatisch das Datenbankschema zu erzeugen.\ DB Config)</name> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql. Außerdem können wir so verhindern.4 pom..2.0</modelVersion> <parent> .xml <?xml version="1.groupId}--${pom. muss natürlich in beiden Fällen die noch zu erstellende Konfiguration angepasst werden. </parent> <artifactId>${pom. dass diese Zugangsdaten auf Produktivsystemen auftauchen. Wenn man nicht gerade über umfangreiches datenbankspezifisches Wissen verfügt.artifactId} (Wicket Praxis .version}</version> </dependency> </dependencies> </project> Wenn sich der Datenbanktreiber ändern sollte.2. dürfte das Know-how aus dem Hibernate-Projekt zu den besseren Ergebnissen führen.1..groupId}--dbconfig-schema-update</artifactId> <name>${pom.groupId}--${pom. Wir definieren eine Abhängigkeit zum Projekt mit der Produktdaten- bankkonfiguration.2 Aufsetzen der Teilprojekte <parent> ...groupId}</groupId> <artifactId>${pom.version>5.\ DB Config Schema Update)</name> <dependencies> <dependency> <groupId>${pom.version}</version> </dependency> </dependencies> <properties> <mysql. auf die man durchaus zurückgreifen sollte. 21 .2 Konfigurationsprojekt Schema-Update Hibernate bietet die Möglichkeit.groupId}--dbconfig</artifactId> <version>${pom. die mit anderen Rechten ausgestattet die entsprechenden Tabellen anlegen kann.0" encoding="UTF-8"?> <project .3.

für die Hibernate Unterstützung anbietet.0. das den Datenbankzugriff regelt..1</hsqldb.5 pom. Wir erstellen das Teilprojekt mit der artifactId de.und Konfigurationsaufwand.6 pom.0. legen wir nun das Teilprojekt (artifactId de.0" encoding="UTF-8"?> <project .xml <?xml version="1.\ DB Config Test)</name> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.0" encoding="UTF-8"?> <project .groupId}--persistence</artifactId> <name>${pom..\ Persistence)</name> <dependencies> <dependency> <groupId>${pom.xml <?xml version="1..groupId}--${pom.wicketpraxis– -dbconfig-test und passen die Projektdatei an.version>1.version> </properties> </project> Ich benutze die HSQL-Datenbank für die Testumgebung.4 Teilprojekt Persistenz Nachdem wir nun alle Datenbankkonfigurationsprojekte angelegt (aber noch nicht konfiguriert) haben.0</modelVersion> <parent> .groupId}--${pom.0..8.. Es können natürlich beliebige andere Datenbanken benutzt werden.. denn wir müssen die anderen Teilprojekte einbinden. </parent> <artifactId>${pom.groupId}--dbconfig-test</artifactId> <name>${pom. Es ist aber ebenso praktikabel. die ohne Installation auskommt und innerhalb einer beliebigen Java-Anwendung gestartet werden kann.0</modelVersion> <parent> . > <modelVersion>4.2.groupId}</groupId> 22 .2.. Diese Projektdatei wird etwas umfangreicher. 2. Damit erspart man sich Installations.artifactId} (Wicket Praxis . > <modelVersion>4. Listing 2. Listing 2.2 Aufsetzen der Teilprojekte 2. </parent> <artifactId>${pom.artifactId} (Wicket Praxis .wicketpraxis-persistence) an.3 Konfigurationsprojekt für die Testdatenbank Für den Datenbanktest wird auf eine Datenbank zurückgegriffen.version}</version> </dependency> </dependencies> <properties> <hsqldb..3. auch während der Entwicklung auf diese Datenbank statt auf eine lokale MySQL-Installation zurückzugreifen.

6</springframework.version> <hibernate.hibernate</groupId> <artifactId>hibernate</artifactId> <version>${hibernate.groupId}</groupId> <artifactId>${pom.6.1. Dabei werden die Konfigurationen für die Datenbanktests und die Schemaaktualisierung nur für die Unit-Tests eingebunden (<scope>test</scope>).version}</version> </dependency> <!-.version}</version> </dependency> <dependency> <groupId>org.version}</version> </dependency> <dependency> <groupId>org. Die Schemaaktualisierung wird wie ein Unit-Test gestartet.\ annotations.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${springframework.version>3.version>3.groupId}--dbconfig</artifactId> <version>${pom.version>2.version> <hibernate-annotations.version> </properties> </project> Im ersten Abschnitt der Abhängigkeitsverwaltung (dependencies) werden die Datenbankkonfigurationsprojekte eingebunden.groupId}--dbconfig-test</artifactId> <version>${pom.groupId}</groupId> <artifactId>${pom. 23 .groupId}</groupId> <artifactId>${pom.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${springframework.version}</version> <scope>test</scope> </dependency> <!-.5.groupId}--base</artifactId> <version>${pom.version}</version> <scope>test</scope> </dependency> <!-.ga</hibernate.2 Aufsetzen der Teilprojekte <artifactId>${pom.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>${hibernate-annotations.springframework</groupId> <artifactId>spring</artifactId> <version>${springframework.Base --> <dependency> <groupId>${pom.Hibernate --> <dependency> <groupId>org.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>${pom.groupId}--dbconfig-schema-update</artifactId> <version>${pom.2.version}</version> </dependency> </dependencies> <properties> <springframework.GA</hibernate.version}</version> </dependency> <dependency> <groupId>${pom.Spring --> <dependency> <groupId>org.2. sodass dann auf diese Konfiguration zurückgegriffen werden kann.3.

groupId}</groupId> <artifactId>${pom.0" encoding="UTF-8"?> <project .org/ http://www.groupId}--persistence</artifactId> <version>${pom. werden diese meist in der Anwendungsschicht realisiert.groupId}--${pom.App)</name> <dependencies> <dependency> <groupId>${pom.2.artifactId} (Wicket Praxis .6 Teilprojekt Webapp Das vorletzte Teilprojekt (artifactId de. Im vierten Abschnitt werden alle notwendigen Hibernate-Bibliotheken definiert. </parent> <artifactId>${pom.2 Aufsetzen der Teilprojekte Im zweiten Abschnitt binden wir unsere Basisbibliothek ein. Diese Abhängigkeit wird weitervererbt.7 pom. > <modelVersion>4.und Persistenzlogik führt in den meisten Fällen zu schwer wartbarem Code. Dann werden auch die notwendigen Abhängigkeiten an dieser Stelle hinzugefügt.xml <?xml version="1.mortbay. Tomcat 2 oder Jetty 3 deployt werden kann.wicketpraxis-webapp) ist die Präsentationsschicht.0</modelVersion> <parent> .5 Teilprojekt Applikationsschicht Die Businesslogik bekommt ein eigenes Projekt (artifactId de. die dieses Teilprojekt einbinden.groupId}--app</artifactId> <name>${pom. Außerdem werden Hilfsbibliotheken für die Unit-Tests eingebunden... 2 3 http://tomcat. Die starke Unterteilung hilft gerade in diesem Bereich.version}</version> </dependency> </dependencies> </project> Im Moment benötigt die Anwendungsschicht nur die Abhängigkeit zur Persistenzschicht. 2. Wenn im Laufe des Projekts Funktionen hinzukommen. 2.org/jetty/ 24 ..0. sodass wir in anderen Teilprojekten. Das Verschwimmen und Durchmischen von Anwendungs.. Dieses Projekt erstellt dann final auch das Anwendungsarchiv (war-Datei). das anschließend in einem Servlet-Container wie z. nicht noch einmal diese Abhängigkeit definieren müssen.2.wicketpraxis--app). Listing 2. Im dritten Abschnitt wird Spring als Dependency Injection-Framework eingebunden. die Grenzen der Anwendungsschichten einzuhalten.apache.B.

artifactId} (Wicket Praxis .groupId}--app</artifactId> <version>${pom.xml <?xml version="1.groupId}--webapp</artifactId> <packaging>war</packaging> <name>${pom.servlet for compile --> <dependency> <groupId>javax.version>1. > <modelVersion>4.version}</version> </dependency> <dependency> <groupId>org.groupId}--${pom.12.wicketpraxis--parentPom</artifactId> <version>1.apache.0</modelVersion> <parent> <groupId>de.version> </properties> </project> 25 .0" encoding="UTF-8"?> <project .wicket</groupId> <artifactId>wicket-extensions</artifactId> <version>${wicket.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2..jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> </dependencies> <properties> <wicket.4</version> <scope>provided</scope> </dependency> <dependency> <artifactId>jsp-api</artifactId> <groupId>javax.rc3</version> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>${pom.wicket --> <dependency> <groupId>org.4</wicket.0-SNAPSHOT</version> </parent> <artifactId>${pom.wicket</groupId> <artifactId>wicket-spring</artifactId> <version>${wicket.version}</version> </dependency> <!-.wicketpraxis</groupId> <artifactId>de.8 pom.Webapp)</name> <build> <plugins> <plugin> <groupId>org.apache.2 Aufsetzen der Teilprojekte Listing 2.0.version}</version> </dependency> <dependency> <groupId>org.wicket</groupId> <artifactId>wicket</artifactId> <version>${wicket..apache.version}</version> </dependency> <!-.servlet</groupId> <version>2.1.mortbay.groupId}</groupId> <artifactId>${pom.2.

Das Wicket-Framework wird hinzugefügt. Ebenso wird eine Integrationsbibliothek für Spring eingebunden./de... Jetzt wäre ein guter Zeitpunkt. <modules> <module>... 26 .2 Aufsetzen der Teilprojekte Folgende Anpassungen sind in die Projektdatei eingeflossen: Das Jetty-Plugin (maven-jetty-plugin) startet die Webanwendung direkt aus dem Projektverzeichnis. Auf diese Weise kann ich das vollständige Projekt und alle Teilprojekte auf einmal erstellen lassen. Es muss eine lauffähige Webanwendung erstellt werden../de. entfällt. Da die Webanwendung in einem Servlet-Container ausgeführt wird. Die Anwendungsschicht wird als Abhängigkeit eingebunden. Erst jetzt kann dieses Teilprojekt erfolgreich erstellt werden.wicketpraxis--webapp</module> </modules> </project> Die Teilprojekte müssen in einer definierten Reihenfolge als Module referenziert werden...xml erstellen. würde es sonst zu Kollisionen kommen.wicketpraxis--dbconfig-test</module> <module>.. Dadurch werden diese Bibliotheken nur zum Erstellen der Klassen und beim Durchführen der Tests eingebunden.wicketpraxis--dbconfig-schema-update</module> <module>. > .. An dieser Stelle ist es erst einmal nur notwendig.. Wer sich nicht sicher ist.wicketpraxis--base</module> <module>. In diesem Verzeichnis muss man dann eine leere Datei web. Die Besonderheit eines Maven-Projekts vom Typ Webarchiv (<packaging>war</packaging>) liegt in dem Ergebnis des Build-Prozesses. ein Verzeichnis src/main/webapp/ WEB-INF/ innerhalb des Projektverzeichnisses anzulegen.7 Teilprojekt ParentPom – Abschluss Die Projektdatei im Teilprojekt „parentPom“ ändern wir wie folgt: <project . in der alle Änderungen aus den Teilprojekten enthalten sind. Wenn man innerhalb von Wicket auf die Klassen ServletRequest und ServletResponse zugreifen muss. wird diese Funktion für jedes definierte Modul aufgerufen. die erstellten Dateien und Verzeichnisse in einer Versionsverwaltung abzuspeichern./de.wicketpraxis--dbconfig</module> <module>. 2. bindet man die Bibliotheken mit dem Attribut scope=provided ein. da der Servlet-Container eigene Versionen der Bibliotheken mitbringt. Der Zwischenschritt. In der Kombination mit mvn clean oder besser noch mvn clean install erhalte ich eine fast fertige Webanwendung.wicketpraxis--persistence</module> <module>. welche Dateien in einer Versionsverwaltung verwaltet werden sollten.2./de../de. Wenn ich in diesem Projekt jetzt ein mvn install aufrufe. die Webanwendung in einen lokalen ServletContainer zu deployen.wicketpraxis--app</module> <module>./de./de. Dafür sind allerdings noch ein paar Voraussetzungen zu erfüllen. kann alle temporären Dateien mit mvn clean löschen.

2. kann man aus dem fertigen Maven-Projekt eine entsprechende Eclipse-Projektdatei generieren lassen. kann es notwendig sein. Man kann diesen Befehl auch im parentPom-Projektverzeichnis ausführen. die Eclipse-Projektdateien noch einmal zu erstellen. Alle weiteren Arbeiten sollten in einer entsprechenden Entwicklungsumgebung stattfinden. Dazu sollte das Projekt in Eclipse aber geschlossen sein. da der Doppelpunkt eine Funktion hat. und die Projektdateien werden in jedem Teilprojekt erstellt. Dazu wechselt man in das entsprechende Projektverzeichnis und ruft Maven mit folgenden Parametern auf: $ mvn eclipse:eclipse –DdownloadSources=true Hinweis Unter Linux kann es notwendig sein. die man dann in Eclipse öffnet. Im Verzeichnis werden dann die Eclipse-Projektdateien erstellt. Um mit Eclipse auf die Maven-Projekte zuzugreifen. Wenn neue Abhängigkeiten hinzugekommen sind oder Einstellungen verändert wurden.3 Erstellen von Eclipse-Projektdateien Alle Projekte sind angelegt. 27 . den Aufruf in mvn eclipse\:eclipse zu ändern.3 Erstellen von Eclipse-Projektdateien 2.

2 Aufsetzen der Teilprojekte 28 .

2 beschrieben.B.3 3 Mit Leben füllen Nachdem wir nun die Grundstruktur der Anwendung erstellt und alle Teilprojekte mit allen Abhängigkeiten definiert haben. Dazu muss man die notwendigen Definitionen in der Konfigurationsdatei einbinden. 1 2 http://www. die über entsprechende XML-Tags benutzt werden können. In diesen Dateien werden Objekte (<bean>) definiert.x/reference/xsd-config. Die verschiedenen Objekte können dann über die ID verknüpft werden. Dazu erstellen wir in den verschiedenen Schichten die notwendigen Konfigurationsdateien und verknüpfen diese zu einer großen Anwendungskonfiguration.org/spring/docs/2. Die Konfiguration wird durch XML-Dateien beschrieben.org/documentation http://static.1. das Attribut des einen Objekts mit einem anderen Objekt belegt wird.html 29 . der diesem Thema gewidmet ist (XML-Schema based configuration 2). Das Spring-Framework (kurz: Spring) stellt neben dem reinen Dependency InjectionMechanismus außerdem noch Hilfsfunktionen bereit.1 Konfiguration mit Spring Wie in Abschnitt 1. Für ein besseres Verständnis sollte man sich unbedingt die Dokumentation 1 des Frameworks ansehen. indem einem Objekt eine ID zugewiesen wird und die Attribute des Objekts mit definierten Werten belegt werden.3. In der Dokumentation des Frameworks gibt es einen Abschnitt. 3.0.springsource. greifen wir für die Konfiguration der Anwendung auf das Spring-Framework zurück.springframework. füllen wir das Ganze mit Inhalt. indem z.

DriverManagerDataSource"> <property name="driverClassName" value="com.mysql. Dazu werden die notwendigen Tabellen erzeugt und datenbankabhängige Optimierungen vorgenommen.String" value-type="java.2 Datenbankkonfiguration In allen Datenbankkonfigurationsteilprojekten fügen wir passende Konfigurationsdateien unterhalb des Pfades src/main/resources/de/wicketpraxis/db/config/ hinzu.org/schema/util" xsi:schemaLocation="http://www.connection.jdbc.datasource.springframework.lang. Listing 3.Properties" \ key-type="java.org/schema/util http://www. das Passwort.springframework.3 Mit Leben füllen 3.springframework. Die Anpassungen veranlassen Hibernate. die nur für die Unit-Tests benutzt und nach dem Beenden der Tests gelöscht wird.0" encoding="UTF-8"?> <beans xmlns="http://www.1 Teilprojekt dbconfig Wir erstellen in dem oben erwähnten Pfad die Konfiguration für die Produktivdatenbank.Driver"/> <property name="url" value="jdbc:mysql://localhost/wicketpraxis"/> <property name="username" value="wicketpraxis"/> <property name="password" value="wicketpraxis"/> </bean> <util:map id="hibernateProperties" map-class="java. Die dritte Datenbank entspricht der ersten Datenbank. das Datenbankschema an die Struktur unserer Persistenzschicht anzupassen. Dafür sind neben der Datenbanktreiberklasse das Login.statement_cache. Diese Einstellung wird auch benutzt. der Servername sowie die JDBCURL anzugeben.xsd http://www.hibernate.springframework.xml <?xml version="1.1 dbconfig.springframework.org/schema/util/spring-util-2.0.org/schema/beans/spring-beans.2.pool_size" value="10"></entry> <entry key="hibernate.xsd"> <bean id="dataSource" \ class="org.String"> <entry key="hibernate.springframework.MySQL5InnoDBDialect"></entry> <entry key="hibernate. 30 .util.dialect.w3.springframework.size" value="10"></entry> </util:map> </beans> Es werden zwei Objekte definiert: Das Objekt datasource konfiguriert den JDBC-Datenbankzugriff.org/schema/beans http://www. da in beiden Fällen derselbe Datenbanktreiber und dieselbe Einstellung benutzt wird. Die zweite Datenbank ist eine InMemory-Datenbank. 3.jdbc.lang.dialect" \ value="org. allerdings mit veränderten Einstellungen.org/schema/beans" xmlns:xsi="http://www. wenn die Anwendung lokal ausgeführt wird.org/2001/XMLSchema-instance" xmlns:util="http://www. In unserem Beispielprojekt benutzen wir drei verschiedene Datenbankkonfigurationen: Die erste Datenbank ist eine Datenbank auf dem Entwicklungsrechner oder die Produktivdatenbank.

Durch das Setzen des Attributs hibernate.lang.datasource. So findet jeder Test die Datenbank immer im selben Zustand vor.Properties" \ key-type="java.util.xml an.dialect kann Hibernate auf einen Tabellentyp zurückgreifen.2 Teilprojekt dbconfig-test Wie eben für das Teilprojekt dbconfig erstellen wir im oben erwähnten Pfad. der Transaktionen unterstützt. wenn der Reflection-Optimizer deaktiviert ist. Das Debuggen der Anwendung gestaltet sich einfacher. Wir legen die Datei dbconfig-schema-update.2 dbconfig-test.xml orientiert.2. Listing 3.auto" value="create-drop"></entry> <entry key="hibernate.bytecode.3..jdbc.DriverManagerDataSource"> <property name="driverClassName" value="org. Hilfreich ist außerdem. 3. wenn wir jetzt eine Konfiguration erstellen. mit der wir das Erzeugen des Datenbank-Schemas erzwingen können.dialect" \ value="org.HSQLDialect"></entry> <entry key="hibernate. Wem die umfangreiche Ausgabe zu viel ist.0" encoding="UTF-8"?> <beans .hibernate. kann die formatierte Ausgabe (format_sql=false) auch abschalten. die von Hibernate ausgewertet werden.xml angelehnten Inhalt.connection. > <bean id="dataSource" \ class="org.lang.3 Teilprojekt dbconfig-schema-update Wie wir in der letzten Konfiguration gesehen haben.2 Datenbankkonfiguration Im Objekt hibernateProperties werden Attribute gesetzt. aber im Teilprojekt dbconfig-test die Datei dbconfig-test.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:mem:test"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <util:map id="hibernateProperties" map-class="java.pool_size" value="10"></entry> <entry key="hibernate.use_reflection_optimizer" \ value="false"></entry> <entry key="hibernate.statement_cache. 31 .String"> <entry key="hibernate.dialect. 3.hsqldb.2. Außerdem wird Hibernate so konfiguriert.String" value-type="java.format_sql" value="true"></entry> </util:map> </beans> Damit wird für die Unit-Tests eine InMemory-HSQL-Datenbank konfiguriert. die sich ebenfalls an dbconfig. dass die resultierenden SQL-Abfragen ausgegeben werden.xml <?xml version="1. dass vor Beginn des Tests das Datenbankschema automatisch erzeugt und nach dem Test wieder automatisch gelöscht wird.size" value="10"></entry> <entry key="hibernate.xml mit einem an dbconfig. Das machen wir uns zunutze.springframework..show_sql" value="true"></entry> <entry key="hibernate. kann Hibernate das Schema einer Datenbank automatisch erzeugen.hbm2ddl.

4 Schemagenerierung mit Hibernate Folgende Werte sind für das Attribut hibernate.3 Mit Leben füllen Listing 3.. sondern nur angepasst.auto möglich: Wert validate update create create-drop Bedeutung prüft die Gültigkeit der Datenbanktabellen erstellt und aktualisiert die Datenbanktabellen erstellt die Datenbanktabellen initial erstellt die Datenbanktabelle beim Start und löscht sie am Ende Für die Testdatenbank benutzen wir daher am besten create-drop.springframework.3 dbconfig-schema-update.String" value-type="java.. dass das Produktivsystem dadurch in einen unbrauchbaren Zustand versetzt wird und die Anwendung nicht mehr funktioniert.String"> <entry key="hibernate.util. In solchen Fällen sollte man die Datenstruktur nachträglich von Hand anpassen.hibernate. Für das SchemaUpdate auf die lokale Entwicklungsdatenbank benutzen wir update.jdbc. auf die Angabe vollständig zu verzichten. Für den normalen Zugriff würde sich die Verwendung von validate anbieten.Driver"/> <property name="url" value="jdbc:mysql://localhost/wicketpraxis"/> <property name="username" value="wicketpraxis_root"/> <property name="password" value="wicketpraxis_root"/> </bean> <util:map id="hibernateProperties" map-class="java.dialect. weil Datenbanken ihrerseits die gewünschten Typen 32 .jdbc.lang.auto" value="update"></entry> <entry key="hibernate.MySQL5InnoDBDialect"></entry> <entry key="hibernate. > <bean id="dataSource" \ class="org.0" encoding="UTF-8"?> <beans .Properties" \ key-type="java.auto=update). wenn man Attribute (und damit den resultierenden Spaltennamen der Tabelle) umbenannt hat.xml <?xml version="1.pool_size" value="10"></entry> </util:map> </beans> Wie bei dbconfig-test. Die alte Spalte wird jedoch nicht gelöscht.dialect" \ value="org.connection. Allerdings werden die alten Daten dabei nicht gelöscht (hibernate.DriverManagerDataSource"> <property name="driverClassName" value="com. Dann wird eine neue Spalte erzeugt.lang. auch das Schema einer Produktivdatenbank automatisch anpassen zu lassen. Daher empfehle ich.hbm2ddl.hbm2dll. Es empfiehlt sich aber. Das Risiko besteht dann allerdings darin. Es ist auf diese Weise natürlich ohne Weiteres möglich.datasource.mysql. 3.2. Dieses Vorgehen kann immer dann zu Problemen führen.xml wird ein Abgleich zwischen geforderter und vorhandener Datenbankstruktur durchgeführt. das Ändern des Schemas der Produktivdatenbank immer von Hand oder durch die Unterstützung von darauf spezialisierten Anwendungen durchzuführen.hbm2ddl.

java package de.persistence.io. . Damit passt das von Hibernate erwartete Ergebnis nicht zu dem von der Datenbank zurückgemeldeten. public { void void void interface DaoInterface<K extends Serializable. DAO) muss ebenfalls ein entsprechendes Interface implementieren: Listing 3. Die Datenbankzugriffsklasse (Data Access Object.3 Persistenz Nachdem wir die Datenbankkonfigurationen erstellt haben.1 Datenbankzugriff – Allgemeine Schnittstellendefinition Alle Datenbankobjekte (Do).persistence. public List<T> findAll(int offset. diese speichern. können wir uns mit dem Datenbankzugriff beschäftigen. 3.3.. T getNew(). update(T o). ohne die genaue Klasse des Objektes zu kennen.3. 3. die durch die Persistenzschicht in Tabellen abgelegt werden. erstellen wir ein paar Hilfsklassen. } Dadurch ist es möglich. public interface DoInterface<K extends Serializable> extends Serializable { public K getId(). die allen abgeleiteten Klassen häufig benutzte Funktionen zur Verfügung stellt. } Wenn dieses Interface vollständig implementiert ist. müssen folgendes Interface implementieren: Listing 3. was dann einen Fehler in der Anwendung hervorruft.3 Persistenz selbst noch einmal anpassen.4 DoInterface.wicketpraxis. import java.java package de.. kann man über diese Schnittstelle neue Datenbankobjekte erstellen. public int countAll().int max). verändern. T get(K id).Serializable. also Objekte.wicketpraxis. Damit wir das Rad nicht jedes Mal neu erfinden. über eine ID suchen und letztendlich auch löschen. Außerdem können alle Objekte in einer Liste dargestellt werden. 33 . die eindeutige Identifikation für ein Datenbankobjekt zu ermitteln.T extends DoInterface<K>> delete(T o). save(T o).5 DaoInterface. Dazu wechseln wir in das entsprechende Teilprojekt persistence.

T> { private static final Logger _logger = \ LoggerFactory. protected AbstractDao(Class<T> domainClass) { _domainClass=domainClass. } protected Class<T> getDomainClass() { return _domainClass. Außerdem können wir an dieser Stelle die Integration mit Hibernate über Spring realisieren.6 AbstractDao.. der über die eben definierten Schnittstellen möglich ist.class). } public Session getSession() { return _sessionFactory.save(object).hibernate.T extends DoInterface<K>> implements DaoInterface<K. Listing 3. import org.transaction.3.springframework. Es bietet sich an.annotation. import org. } 34 . } public SessionFactory getSessionFactory() { return _sessionFactory. diese Funktionen in eine abstrakte Klasse auszulagern. } @Transactional public void delete(T object) { getSession().2 Datenbankzugriff – Hilfsklassen Der Datenbankzugriff. public abstract class AbstractDao<K extends Serializable. muss nicht für jede Klasse neu implementiert werden.java package de.wicketpraxis.persistence.. private Class<T> _domainClass. } @Transactional public void save(T object) { getSession().Transactional. sodass in der abgeleiteten Klasse nur noch sehr wenige Anpassungen notwendig sind.*.delete(object).createCriteria(_domainClass). } public void setSessionFactory(SessionFactory sessionFactory) { _sessionFactory = sessionFactory.3 Mit Leben füllen 3. } protected Criteria getCriteria() { return getSession(). private SessionFactory _sessionFactory.getCurrentSession(). .getLogger(AbstractDao. von der dann die konkreten Klassen abgeleitet werden.

return (List<T>) criteria.setFirstResult(offset).list(). if (offset!=0) criteria. darauf hinzuweisen. dass vor dem Aufruf eine Transaktion gestartet und am Ende der Methode geschlossen wird. konnten alle Funktionen aus der Interface-Definition umgesetzt werden. } public T getNew() { try { return _domainClass.error("newInstance failed". } public int countAll() { Criteria criteria = getSession().createCriteria(_domainClass).int max) { Criteria criteria = getSession(). da Annotationen nicht vererbt werden.3. id). dass die Annotation @Transactional für die Transaktionssteuerung benutzt wird. } catch (InstantiationException e) { _logger.e). 3.3 Persistenz @Transactional public void update(T object) { getSession().e). 35 . return (Integer) criteria.error("newInstance failed". } catch (IllegalAccessException e) { _logger. if (max!=0) criteria.createCriteria(_domainClass).uniqueResult().update(object). Wichtig ist es.setMaxResults(max). criteria.setProjection(Projections. } } Wie man sieht. } return null.newInstance().3. } public T get(K id) { return (T) getSession(). Spring kümmert sich bei einer so markierten Methode darum. Hinweis In abgeleiteten Klassen muss die Annotation @Transactional neu gesetzt werden. } public List<T> findAll(int offset.3 Datenbankzugriff – User Für unsere Beispielanwendung greifen wir auf eine sehr einfach gehaltene Nutzerdatenbank zurück.get(_domainClass.rowCount()).

beans. import javax. Diese Klasse leitet von AbstractDao ab und erbt somit alle grundlegenden Datenbankfunktionen. Der Zugriff auf die Nutzer erfolgt dann über eine eigene Klasse. @Id @GeneratedValue(strategy=GenerationType. Listing 3. .persistence.7 User. } public void setId(Integer id) { _id=id. 36 . import org.*.persistence. } public void setName(String name) { _name = name.name="email") public String getEMail() { return _eMail.. . } } Die Klasse ist jetzt noch sehr übersichtlich. } @Column(nullable=false) public String getName() { return _name.dao. Wer bereits weitere Attribute identifiziert hat.unique=true. kann diese einfach hinzufügen.hibernate.. @Entity @Table(name="Users") public class User implements DoInterface<Integer> { Integer _id. String _name.java package de. String _eMail.8 UserDao.*. die einen Datensatz anhand der E-Mail ermittelt und als Ergebnis zurückgibt.3 Mit Leben füllen Listing 3. Wir fügen in diesem Fall noch eine Methode hinzu. User> { public static final String BEAN_ID="userDao"..wicketpraxis.criterion. } public void setEMail(String mail) { _eMail = mail.AUTO) public Integer getId() { return _id..persistence. public class UserDao extends AbstractDao<Integer.wicketpraxis.java package de. } @Column(nullable=false.

add(Property. Jetzt müssen wir die Persistenzschicht noch richtig konfigurieren.0. } public User getByEMail(String email) { return (User) getCriteria().AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="annotatedClasses"> <list> <value>de.4 Datenbankzugriff – Konfiguration Wir legen eine Konfigurationsdatei im Verzeichnis src/main/resources/de/wicketpraxis/persistence/ an.org/schema/tx" xsi:schemaLocation="http://www.org/schema/tx http://www.springframework.xsd"> <bean id="sessionFactory" class="org.orm.0" encoding="UTF-8"?> <beans xmlns="http://www.forName("EMail").org/schema/beans/spring-beans.xml <?xml version="1. \ annotation.persistence.springframework. Listing 3. die für das Erzeugen einer HibernateSession zuständig ist.orm.wicketpraxis. \ HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager" \ proxy-target-class="true"/> <bean id="userDao" class="de.hibernate3.w3.3.org/schema/beans" xmlns:xsi="http://www.class).springframework.User</value> </list> </property> <property name="hibernateProperties" \ ref="hibernateProperties"></property> </bean> <bean id="transactionManager" \ class="org.springframework. An diese Session Factory.9 bean-config.wicketpraxis.dao.xsd http://www.springframework.uniqueResult().springframework. Die Anweisung <tx:annotation-driven> veranlasst Spring dazu.3. 3.3 Persistenz public UserDao() { super(User.hibernate3.persistence. } } Damit haben wir alle Klassen zusammen.org/schema/beans http://www.org/2001/XMLSchema-instance" xmlns:tx="http://www. die Annotationen in den Zugriffsklassen auszuwerten und für die saubere Transaktionsbehandlung Proxy-Klassen für die 37 . wird ein Transaktionsmanager angebunden.springframework.springframework.org/schema/tx/spring-tx-2.UserDao"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> </beans> Die von Spring bereitgestellte Session Factory (sessionFactory) wertet die Annotationen in der User-Klasse aus.beans. die für den Datenbankzugriff verantwortlich sind.eq(email)) .

xml"/> <import resource="bean-config.xml"/> <import resource="bean-config. kann man mit Hibernate relativ einfach Unit-Tests mit einer temporären Datenbank durchführen.xml <?xml version="1.xml jetzt mit der Datenbankkonfiguration für die Testdatenbank.. Als Basisklasse für alle weiteren Tests leiten wir eine eigene Klasse von einer Unit-Test-Hilfsklasse des Spring-Frameworks ab.test. aber noch keine Datenbankschnittstelle angebunden. Für die Tests legen wir eine entsprechende Konfiguration an.11 persistence-test.xml <?xml version="1. import org.> <import resource="classpath:/de/wicketpraxis/db/config/dbconfig.> <import resource="classpath:/de/wicketpraxis/db/ \ config/dbconfig-test.12 AbstractTest. damit diese nur für die Tests benutzt wird und nicht im fertigen Projektarchiv auftaucht. Listing 3. Wir haben jetzt die Persistenzschicht konfiguriert. Das erzeugte Zwischenobjekt ruft die Funktionen der eigentlichen Klasse auf und führt dabei vorher und nachher die notwendigen Transaktionsfunktionen aus.0" encoding="UTF-8"?> <beans .3.springframework. Diese Konfigurationsdatei legen wir aber im Verzeichnis src/test/resources/de/ wicketpraxis/persistence/ ab.xml verwenden. 3. ein spezielles Interface pro Klasse zu implementieren. Das Attribut proxy-target-class=true zwingt Spring dabei. Dazu erstellen wir eine weitere Konfigurationsdatei im selben Verzeichnis..10 persistence. auf die Verwendung von dynamischen Proxies zu verzichten und Proxies mithilfe des cglib Frameworks bereitzustellen..3 Mit Leben füllen Zugriffsklassen bereitzustellen.java package de.0" encoding="UTF-8"?> <beans .wicketpraxis.5 Persistenz-Tests Wie bereits erwähnt.persistence.xml"/> </beans> In dieser Datei greifen wir auf die Datenbankkonfiguration für die Produktivdatenbank zu und referenzieren die Konfiguration der Persistenzschicht. Listing 3. Wir erweitern die Klasse ein wenig und leiten alle konkreten Testklassen von dieser Klasse ab: Listing 3. In den Unit-Tests müssen wir daher die Konfigurationsdatei persistence-test. Dadurch ist es nicht mehr notwendig..xml"/> </beans> Wir benutzen dieselbe bean-config. \ 38 .

requiredType).getEMail()). dass wir bisher nicht allzu viele Funktionen realisiert haben.Assert. Das liegt sicher auch daran.. String email = "klaus@test. Assert.BEAN_ID. } } 39 .nutzer.UserDao.assertNotNull("User".assertEquals("Email".class).BEAN_ID.assertNotNull("User". nutzer=userDao.get(nutzer. . User nutzer=new User(). Assert.nutzer.nutzer).values()).3.3 Persistenz AbstractTransactionalDataSourceSpringContextTests.xml" }.user). nutzer.persistence.get(1).13 TestUserDao.. import junit.nutzer).UserDao. if (beansOfType. public class TestUserDao extends AbstractTest { public void testKeinNutzer() { UserDao userDao = getBean(UserDao. public abstract class AbstractTest \ extends AbstractTransactionalDataSourceSpringContextTests { @Override protected String[] getConfigLocations() { return new String[] \ { "classpath:/de/wicketpraxis/persistence/persistence-test.save(nutzer).getBean(name. } return null.getEMail()).dao.size()==1) { return (T) new ArrayList(beansOfType.email.getByEMail(email).email.wicketpraxis. Assert. } protected <T> T getBean(Class<T> requiredType) { Map beansOfType = getApplicationContext(). Listing 3.getId()).java package de. } protected <T> T getBean(String name. userDao. nutzer=userDao. User user = userDao.setEMail(email).class). Assert. \ getBeansOfType(requiredType).assertEquals("Email".framework.de". nutzer.Class<T> requiredType) { return (T) getApplicationContext().setName("Klaus"). } } Der Unit-Test für die User-Klasse gestaltet sich recht übersichtlich.get(0). } public void testEinNutzer() { UserDao userDao = getBean(UserDao. Assert.assertNull("Kein Nutzer".

wicketpraxis.slf4j.15 SchemaUpdate. Die Unit-Tests werden dabei jedes Mal ausgeführt.class).xml"/> </beans> Diese Datei unterscheidet sich von persistence-test. kann man z. Listing 3. Gerade wer wenig Erfahrungen in Bezug auf Datenbanken hat. Wir benutzen für den Start von Hibernate mit der entsprechenden Konfiguration dasselbe Verfahren wie bei den Persistenz-Tests. Um die Funktion auszuführen. Diesen Test kann man mit mvn test ausführen. ob die Unit-Tests tatsächlich ausgeführt werden.getLogger(SchemaUpdate. legen wir erst eine eigene Konfigurationsdatei im Verzeichnis src/test/resources/de/wicketpraxis/persistence/ an. import org.3 Mit Leben füllen Der erste Test prüft.xml <?xml version="1. Der Test sollte dann fehlschlagen. ob der Nutzer angelegt. Listing 3. 40 . sollte im Klassenname das Wort „Test“ nicht vorkommen.xml"/> <import resource="bean-config. Als einfacher Test. public class SchemaUpdate extends AbstractTest { private static final Logger _logger = LoggerFactory.java package de. Das sollte nicht der Fall sein.xml nur an der Stelle.xml einfließen lassen. den Namen in setName auf einen anderen Wert ändern.slf4j. Das Erzeugen und Ändern von Tabellen und Spalten sollte aber nicht jedem Datenbanknutzer erlaubt sein. Je nach Einstellung führt Hibernate dann die entsprechenden Anpassungen durch. ob ein Nutzer mit der ID=1 bereits in der Datenbank ist. Daher empfiehlt es sich. kann man dieses Teilprojekt mit mvn install erstellen lassen.Logger. die SchemaUpdate-Funktion nicht automatisch auszuführen. import org. Diese Einstellungen haben wir in die dbconfig-schema-update.persistence.> <import resource="classpath:/de/wicketpraxis/db/ \ config/dbconfig-schema-update.3. an der wir die Datenbankkonfiguration auswählen.B. weil bei jedem Test die Datenbank wieder gelöscht wird. auf diese Funktion von Hibernate zurückzugreifen. Wenn keine Fehler aufgetreten sind. und dafür einen gesonderten Datenbanknutzer mit erweiterten Rechten zu nehmen. Im zweiten Test wird geprüft.0" encoding="UTF-8"?> <beans . ob an der in der Datenbank vorhandenen Tabellenstruktur Anpassungen notwendig sind. gelesen und anhand der E-Mail gefunden werden kann.LoggerFactory. Damit dieser „Test“ aber nur bei Bedarf ausgeführt wird. 3.6 Schema-Update Hibernate prüft je nach Einstellung vor dem ersten Datenbankzugriff. ist meist besser damit beraten.14 persistence-schema-update...

reicht es aus. } } Die Testfunktion hat dann eigentlich keine Funktion. Da wir in dieser Schicht im Moment keine Funktionen implementieren. Mehr ist an dieser Stelle nicht notwendig.error("Schema Update"). Als letztes Teilprojekt vervollständigen wir die Präsentationsschicht. also auf eine Kopie 41 .xml"/> </beans> In dieser Datei wird die Konfiguration aus der Persistenzschicht eingebunden.. 3.0" encoding="UTF-8"?> <beans . Listing 3.4 Anwendungsschicht Wir wechseln in das Teilprojekt app. } public void testSchemaUpdate() { _logger. Im Vorfeld wird Hibernate mit der referenzierten Datenbankkonfiguration gestartet und das Schema angepasst. Um den Test und damit das Schema-Update zu starten. 3.16 app.xml <?xml version="1.1 Hilfsklasse für Maven-Projekte Wicket lädt im Entwicklungsmodus die Markup-Dateien für die Komponenten bei jeder Veränderung neu.. Wir haben durch Datenbanktests die Funktion getestet. rufen wir Maven wie folgt auf: $mvn –Dtest=de/wicketpraxis/persistence/SchemaUpdate test Das Teilprojekt ist jetzt soweit fertig.5 Präsentationsschicht Wir haben jetzt alle untergeordneten Anwendungsschichten erstellt. 3. Dabei wird aber auf Dateien in target zugegriffen. wenn wir eine Konfigurationsdatei im Verzeichnis src/main/resources/de/wicketpraxis/app/ erstellen.> <import resource="classpath:/de/wicketpraxis/ \ persistence/persistence.3.xml" }.5.4 Anwendungsschicht @Override protected String[] getConfigLocations() { return new String[]{ \ "classpath:/de/wicketpraxis/ \ persistence/persistence-schema-update. wir können per Schema-Update die Datenbanktabellen anlegen lassen und mit der Standardkonfiguration aus der Anwendung heraus dann darauf zugreifen.

import org. wird sie an dieser Stelle etwas komplexer. die dann in der Applikationsklasse als Startseite definiert werden kann.pages. Da unsere kleine Anwendung aber z.ResourceStreamLocator. bereits über eine Datenbankanbindung verfügt.exists()) && (f. Natürlich müssen wir daher auf Funktionen zurückgreifen.. Listing 3.B.apache.locator. } } 3. return super.java package de.java package de.17 MavenDevResourceStreamLocator..2 Wicket Web Application Die kleinste Wicket-Anwendung besteht aus mindestens einer Seite und einer von WebApplication abgeleiteten Klasse. import org. Dieses Grundgerüst kann dann als Basis für alle folgenden Beispiele benutzt werden. public class MavenDevResourceStreamLocator extends ResourceStreamLocator { String _prefix="src/main/resources/".3 Mit Leben füllen von src/main/resources.wicketpraxis. path).xml) vornehmen.util. die Anwendung in diesem Aspekt anzupassen. . zuerst in dem in Maven-Projekten definierten Standard-Ressourcenpfad (src/main/resources). @Override public IResourceStream locate(Class<?> clazz.wicket. . Damit im richtigen Verzeichnis gesucht wird.resource. public class Start extends WebPage 42 .5. if (located != null) return located.18 Start. Zuerst legen wir eine Seite an. } return null. } private IResourceStream getFileSysResourceStream(String path) { File f=new File(_prefix+path). wie eine Webanwendung mit Wicket funktioniert. müsste man Anpassungen in der Projektbeschreibung (pom..wicket.web. Leider funktioniert diese Methode nicht zuverlässig. if ((f.util. Die folgende Klasse sucht die Ressourcen.wicketpraxis. die bisher nicht erklärt wurden..apache. die aber bereits einen guten Eindruck vermitteln können.*. Listing 3.isFile())) { return new FileResourceStream(f). Glücklicherweise bietet Wicket die Möglichkeit. die Wicket benötigt.locate(clazz.resource. String path) { IResourceStream located=getFileSysResourceStream(path).wicket. sodass ich nach Alternativen gesucht habe.resource.util.

3. Dabei wird über einen optional zu definierenden Namen auf die Spring-Konfiguration zurückgegriffen und nach einer Spring-Bean vom Typ UserDao und der optionalen ID mit dem Wert aus dem Attribut name gesucht. Das Objekt userModel sorgt dafür. Das Objekt userList ist für die Anzeige zuständig und fügt für jeden Eintrag der Liste ein Label mit dem Nutzernamen hinzu. } }. dass bei Bedarf die Liste der ersten 10 Nutzer aus der Datenbank geladen werden.html <html> <head> <title>WicketPraxis</title> </head> <body> <h1>WicketPraxis</h1> <table> <thead> <tr> <th>Name</th> </tr> </thead> <tbody> <tr wicket:id="userList"> <td><span wicket:id="name"></span></td> </tr> </tbody> </table> </body> </html> 43 .5 Präsentationsschicht { @SpringBean(name=UserDao. } } Die Annotation @SpringBean definiert. } }. ListView<User> userList=new ListView<User>("userList". Listing 3. add(userList). 10).getModelObject(). wie das Feld initialisiert werden soll.item.userModel) { @Override protected void populateItem(ListItem<User> item) { item. public Start() { LoadableDetachableModel<List<User>> userModel= new LoadableDetachableModel<List<User>>() { @Override protected List<User> load() { return _userDao.add(new Label("name".findAll(0.getName())). Jetzt benötigen wir noch eine passende Markup-Datei für die Seite und legen eine Datei im Verzeichnis src/main/resources/de/wicketpraxis/web/pages/ an.BEAN_ID) UserDao _userDao. Dieses Verhalten muss noch in der Applikation definiert werden.19 Start.

Wenn Wicket im Entwicklungsmodus ist. . } } @Override public Class<? extends Page> getHomePage() { return Start. wird der MavenDevResourceStreamLocator eingebunden und lädt während der Entwicklung zuverlässig alle veränderten Markup-Dateien neu.equalsIgnoreCase(getConfigurationType())) { getResourceSettings().5.xml im Verzeichnis src/main/webapp/WEBINF/. dass es beim Bereitstellen auf einem Unix-Server zu Problemen kommen kann. \ setResourceStreamLocator(new MavenDevResourceStreamLocator()).21 web. Listing 3. Start wird als die Startseite für die Anwendung definiert. addComponentInstantiationListener(new SpringComponentInjector(this)). Der Servlet-Container erwartet die web. In dieser Methode werden alle Einstellungen für diese Anwendung durchgeführt. müssen wir die Servlet-Konfiguration anpassen.init(). Listing 3. wenn der Verzeichnisname nicht richtig geschrieben wurde.java package de. Als Erstes wird der SpringComponentInjector eingebunden.web.und Kleinschreibung.wicketpraxis. Dabei wird die init-Methode nur einmal aufgerufen.3 Servlet-Konfiguration Damit die Anwendung auch gestartet wird.0" encoding="ISO-8859-1"?> <!--DOCTYPE web-app PUBLIC "-//Sun Microsystems. Das kann dazu führen.20 WicketPraxisApplication.3 Mit Leben füllen Die Attribute wicket:id referenzieren dabei die Komponenten in unserer Start-Klasse.und Verzeichnisnamen nicht zwischen Groß.xml <?xml version="1. der für die Auswertung der SpringBean-Annotation sorgt. Wir ändern unsere bisher leere web.. 3. Hinweis Windows unterscheidet bei Datei. public class WicketPraxisApplication extends WebApplication { @Override protected void init() { super. if (DEVELOPMENT..//DTD Web Application 2. Als Nächstes müssen wir unsere eigene Applikationsklasse erstellen.4//EN" 44 .class.xml aber im Verzeichnis WEB-INF. Inc. } } Beim Start der Webanwendung wird von dieser Klasse genau eine Instanz erzeugt.

Die Reihenfolge der Filter im Bereich filter-mapping ist daher 45 .com/xml/ns/j2ee http://java.webapp</filter-name> <filter-class>org.support. Im WicketFilter geben wir nun unsere eigene Wicket-Anwendung an und setzen den Startmodus auf deployment. Das Öffnen und Schließen der Session für den Datenbankzugriff über Hibernate übernimmt der OpenSessionInViewFilter. Der ContextLoaderListener stellt einen aus der Konfiguration ermittelten ApplicationContext bereit und sorgt beim Beenden des Servers für das Herunterfahren des Context.webapp</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> An erster Stelle wird eine Spring-Konfiguration im selben Verzeichnis angegeben (/WEBINF/applicationContext.wicketpraxis.WicketPraxisApplication </param-value> </init-param> <init-param> <param-name>configuration</param-name> <param-value>deployment</param-value> </init-param> </filter> <filter> <filter-name>de.ContextLoaderListener </listener-class> </listener> <filter> <filter-name>de.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.context.web.springframework.apache.protocol.hibernate.osv</filter-name> <filter-class> org.wicketpraxis.hibernate3.sun. Damit wird das Anwendungsarchiv standardmäßig mit diesem Modus erstellt.hibernate.osv</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>de.wicket.xml).web.springframework.4"> <display-name>wicketpraxis.com/j2ee/dtds/web-app_2_4.w3.sun.com/xml/ns/j2ee/web-app_2_4.OpenSessionInViewFilter </filter-class> </filter> <filter-mapping> <filter-name>de.5 Präsentationsschicht "http://java.http. Auf diese Konfiguration wird sowohl von Wicket als auch durch den OpenSessionInViewFilter zugegriffen.sun.xsd" version="2.dtd"--> <web-app xmlns="http://java.com</display-name> <description> WicketPraxis Webapp </description> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.3.wicketpraxis.xml</param-value> </context-param> <listener> <listener-class> org.orm.WicketFilter </filter-class> <init-param> <param-name>applicationClassName</param-name> <param-value>de.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.wicketpraxis.wicketpraxis.

Damit ist sichergestellt. 3.161::INFO: Started SelectChannelConnector@0.. das Unit-Tests für die Präsentationsschicht durchgeführt werden müssten. über das ParentPom-Projekt erstellt (mvn install) werden. Dabei könnten wir zwar direkt unsere app. Für den Fall.xml"/> </beans> Die Anwendung ist damit vollständig konfiguriert und kann jetzt gestartet werden. *** *** ^^^^^^^^^^^ *** *** Do NOT deploy to your live server(s) without changing this. 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.0. Listing 3.4 in development mode ******************************************************************** *** WARNING: Wicket is running in DEVELOPMENT mode.4 Spring-Konfiguration Wir erstellen jetzt im selben Verzeichnis die referenzierte Konfigurationsdatei für den ApplicationContext. dass die Datenbankverbindung geöffnet ist...0:8080 [INFO] Started Jetty Server 46 .23 webapp.xml einbinden. wenn die Anfrage durch Wicket behandelt wird.5.. *** *** See Application#getConfigurationType() for more information.> <import resource="classpath:/de/wicketpraxis/web/webapp.xml <?xml version="1. Danach erstellen wir diese Datei im Verzeichnis src/main/resources/de/wicketpraxis/web/.5 Start der Anwendung Wir haben jetzt alles zusammen. > <import resource="classpath:/de/wicketpraxis/app/app. wenn noch nicht geschehen.xml"/> </beans> In diesem binden wir die noch zu erstellende Datei webapp. Die Anfrage durchläuft den erstgenannten Filter vor dem nächsten.. Dann wechseln wir in das Webapp-Projekt und starten die Webanwendung mit: $ mvn jetty:run –Dwicket.3 Mit Leben füllen wichtig. wäre es sehr schwierig (wenn nicht unmöglich). INFO: [WicketPraxisApplication] Started Wicket version 1. Alle Teilprojekte sollten.0" encoding="UTF-8"?> <beans .5.0. 3.configuration=development Der eingebettete Webserver sollte starten und den erfolgreichen Start mit folgender Meldung quittieren: .xml <?xml version="1.0" encoding="UTF-8"?> <beans . Listing 3. Ich emp- fehle aber einen Umweg.xml aus dem Ressourcenpfad ein. an diese Datei heranzukommen. *** ******************************************************************** 2009-01-13 19:18:17..22 applicationContext.

und jede der notwendigen Schichten ist erstellt und eingebunden.3. die im Development-Modus angezeigt werden.1 Startseite mit Daten aus der Datenbank Wenn man wie ich schon einen Eintrag in die Datenbank getätigt hat. Abbildung 3. Alle erweiterten Informationen. Dort erscheint eine Fehlermeldung mit einem Link. Hinweis Wicket unterscheidet zwischen den Modi Deployment und Development. werden durch den Modus Deployment deaktiviert. der auf unsere Webanwendung verweist. Damit ist die Anwendung funktionsfähig. weil das die Fehlersuche erheblich vereinfacht. sieht man in der Ergebnistabelle auch schon einen Eintrag.5 Präsentationsschicht Damit wurde der Server erfolgreich gestartet.1 sehen. Nach einem Klick sollte man ein Ergebnis wie in Abbildung 3. Man öffnet nun mit dem Browser die Seite http://localhost:8080/. Für die Entwicklung sollte der Development-Modus gewählt werden. 47 .

3 Mit Leben füllen 48 .

Über dieses Protokoll wird eine Anfrage an den Server geschickt und die Antwort an den Browser übermittelt. 4. sodass man als Entwickler nur selten damit konfrontiert wird. sodass sich ein Gesamtbild ergibt.4 4 Die Wicket-Architektur Nachdem wir nun unsere erste kleine Webanwendung erstellt haben. Das Protokoll dient dem Transport der Daten und ist selbst zustandslos.2 Struktur Wicket besitzt verschiedene Elemente. dass eine Anfrage an den Server in keiner Beziehung zu einer vorangegangenen Antwort stehen muss. die in den verschiedenen Webframeworks unterschiedlich gelöst werden. um mit dem Browser zu kommunizieren. bevor wir uns in den nächsten Kapiteln mit den einzelnen Komponenten beschäftigen. Ich werde jedes im Folgenden etwas eingehender erläutern. möchte ich etwas genauer auf die Architektur von Wicket eingehen. dass es sich immer noch um eine Webanwendung handelt. die in einer Webanwendung zusammenspielen. 49 .1 Wicket und das HTTP-Protokoll Eine Webanwendung benutzt das HTTP-Protokoll. 4. Das bedeutet. Dennoch unterstützt Wicket den Entwickler mit einer Reihe von Schnittstellen. Diese Zustandslosigkeit des Protokolls verursacht eine Reihe von Problemen. was für das Verständnis von Wicket hilfreich ist. Wicket orientiert sich dabei stark an einer Desktop-Anwendung. die weiterhin den Zugriff auf das Transportprotokoll ermöglichen oder davon abstrahiert bestimmte Funktionen zur Verfügung stellt.

denn dieser Vorgang geschieht automatisch und vollkommen transparent. da Wicket alle Informationen.2. Neben der zuletzt besuchten Version einer Seite finden sich in der PageMap auch ältere Versionen der Seite wieder. überträgt Wicket in dieser Phase nur die vom Nutzer durchgeführte Aktion. Von dieser Klasse wird auf dem Server nur eine Instanz pro Anwendung erzeugt. Man könnte allerdings auch seinen eigenen SessionStore implementieren. 4. die für eine Nutzerinteraktion notwendig sind. In dieser PageMap sind die Seiten (Page) abgelegt. 4.2. Wenn ein Nutzer mehr als ein Browserfenster geöffnet hat (z. sodass an dieser Stelle alle Einstellungen vorgenommen werden können. Man kann sich eine Page auch als Browserfenster vorstellen.servlet-Paket gespeichert. Während einige andere Frameworks versuchen. die der Nutzer aufgerufen hat. den Zustand einer Anwendung in URL-Parametern abzulegen. Normalerweise werden die Daten in einer HttpSession aus dem javax. mit einem Popup).1 WebApplication Wie wir bereits gesehen haben. Außerdem gibt es verschiedene Methoden. der auf die Anwendung zugreift bekommt eine Session zugewiesen. weil Wicket den Zustand der Anwendung zu diesem Zeitpunkt wiederherstellen kann. in dieser Session speichert.3 PageMap Jede Session hat mindestens eine PageMap. Die Darstellung einer Seite liefert als Ergebnis die HTML-Daten. die für die Anwendung gelten sollen. Die Seite ist damit die oberste Komponente im Komponentenbaum. Das ist notwendig. 4. muss man sich als Entwickler nicht mit solchen Details beschäftigen. dass jederzeit die korrekten Funktionen aufgerufen werden.2.4 Die Wicket-Architektur 4. die man mit eigenen Implementierungen überladen und so die Anwendung an die eigenen Bedürfnisse anpassen kann. Die Daten einer Session werden in einem SessionStore gespeichert. legt Wicket mehr als eine PageMap an.2 Session Jeder Nutzer. die sich von anderen Wicket-Komponenten nur dadurch unterscheidet. die der Browser dann darstellt. Auch wenn diese Vorgehensweise dafür sorgt. Die init-Methode wird beim Start nur einmal ausgeführt.4 Page Eine Seite (Page) ist eine Komponente. 50 . Dadurch kann der Nutzer im Browser auf die letzten Seiten zurückspringen. müssen wir für unsere eigene Anwendung eine von WebApplication abgeleitete Klasse erstellen.B.2. ohne dass es zu Fehlern kommt. dass alle anderen Komponenten immer zu einer Seite gehören.

4. Alle anderen Seiten werden im PageStore abgelegt.und HttpResponse-Klassen. PROCESS_EVENTS: Die Event-Behandlung durch den RequestCycleProcessor wird gestartet.2. wobei die Behandlung der verschiedenen Phasen an einen RequestCycleProcessor übergeben werden. RESOLVE_TARGET: Ermittelt das Ziel dieser Abfrage (RequestTarget) durch den RequestCycleProcessor. DONE: Die Abarbeitung ist abgeschlossen. DETACH_REQUEST: Alle temporären Daten werden gelöscht. Diese werden durch einen RequestCycle abgearbeitet. der nächste Request kann verarbeitet werden. Danach wird die Seite in der PageMap abgelegt. indem für jede Komponen- te die Methode detach() aufgerufen wird. damit sie im PageStore abgelegt werden kann. 4.3 Request-Behandlung 4. wenn man später auf diese Seite zugreifen möchte. Der RequestCycleProcessor stellt dabei folgende Funktionen bereit: resolve(RequestCycle. Auf diese Weise hält sich der Speicherverbrauch in Grenzen. RequestParameters): Die URL und die URL-Parameter werden dekodiert und das RequestTarget ermittelt.5 PageStore Wicket hält die aktuellste Seite der PageMap im Speicher vor. 51 . RESPOND: Der Response wird durch den RequestCycleProcessor erstellt. kann Wicket diese nicht im PageStore finden und gibt eine Fehlermeldung aus. Im Standardfall wird dazu die Seite serialisiert und in einem DiskPageStore. Zusätzlich wird die Seite serialisiert. kann Wicket die ganze Seite nicht serialisieren und somit nicht in den PageStore schreiben. Das kann zu Problemen führen. Im RequestCycle werden folgende Phasen durchlaufen: PREPARE_REQUEST: Startet die Request-Verarbeitung. Alle Wicket-Komponenten sind von dieser Klasse abgeleitet. 4.2. ohne an Funktionalität einzubüßen.3 Request-Behandlung Wicket kapselt die bei einer Webanwendung beteiligten HttpRequest. Bei Bedarf wird die Seite wieder aus dem PageStore geladen. Wenn zu einem späteren Zeitpunkt auf diese Seite zugegriffen wird. die nicht serialisiert werden können.6 Component Eine Komponente ist die Basiseinheit einer Wicket-Anwendung. Hinweis Wenn an dieser Stelle eine Komponente noch Referenzen auf Objekte hat. also auf der Festplatte abgelegt.

die onBeforeRender-Methode aufgerufen.1 Request-Behandlung Die durch den Request ausgelösten Aktionen werden durchgeführt.3. respond(RequestCycle): Nachdem die Events verarbeitet wurden. um auf diesen Zustand geeignet zu reagieren. ob die Anfrage eine Seite oder ein Bild zurückliefern soll.2 Nebenläufigkeit – Threads Pro Request wird ein Thread ausgeführt. IlinkListener). was Einfluss auf diesen und den nächsten Schritt hat.B. onRender: wird aufgerufen.1 Komponentenphasen Jede Komponente hat neben dem Lebenszyklus. oder ob das Ergebnis für eine Ajax-Anfrage aufbereitet werden muss. respond(RuntimeException. der mit dem Erstellen der Komponente über einen Konstruktor beginnt und mit dem Bereinigen durch den Garbage Collector endet.1). Dabei sind die wesentlichen Phasen: Request-Behandlung: Die durch den Request beschriebene Aktion wird durchgeführt (Abschnitt 4. eine andere. Wenn die Komponente gefunden wurde. Bei Formularen werden die durch den Request übergebenen Werte der Formularkomponenten verarbeitet. damit die Session nur soviel Platz wie nötig belegt.B. RequestCycle): Wenn ein Fehler aufgetreten ist. onBeforeRender: Wenn die Komponente sichtbar ist.1. dass die temporären Daten gelöscht werden können. dass irgendwann onClick() aufgerufen wird. sodass man sich normalerweise keine Gedanken um dieses Thema machen muss – mit einer wichtigen Ausnahme: die Session.3. Dabei werden zuerst die Komponenten gesucht. im Fall eines Links dazu führt. Da ein Nut- 52 . wird diese Methode aufgerufen. Dabei kann z.4 Die Wicket-Architektur processEvents(RequestCycle): Wenn das RequestTarget ermittelt wurde. der nicht abgefangen wurde.1. bevor die Komponente dargestellt wird. Im Entwicklungsmodus wird z. die Sichtbarkeit manipuliert werden.3. auch wenn die Komponente unsichtbar ist.B. 4. die dann z.B. Was dabei an den Browser zurückgeschickt wird. hängt natürlich davon ab. auch einen Request-Zyklus. mit mehr Informationen versehene Fehlerseite dargestellt. 4. wird die Aktion ausgelöst. 4. Dabei werden dann Events wie der Klick auf einen Link oder das Abschicken eines Formulars ausgewertet (IRequestListener z. wenn die Komponente dargestellt wird. werden die Events verarbeitet. dann wird. bei der eine Aktion ausgelöst wurde.3. wird das Ergebnis gerendert und an den Browser zurückgeschickt. onAfterRender: wird immer aufgerufen. onDetach: wird danach aufgerufen und sorgt dafür.

1 Komponenten Die kleinste Einheit einer Wicket-Anwendung ist die Komponente.4 Komponenten. in src/main/resources im selben Unterverzeichnis wie die Klasse abgelegt werden muss und die Endung html besitzt. Das bedeutet.wicketpraxis. 4. Das Model stellt die Daten bereit. Daher muss man selbst sicherstellen. Wenn Wicket über die Modelländerungen informiert wird. Die alte Version mit alten Daten wird im PageStore abgelegt. Es werden immer alle Komponenten mit den zugehörigen Daten dargestellt. zieht Wicket diese Informationen heran. In diesem Modell informiert der Controller die View darüber. View und Controller. Modelle. die durch den Controller verändert werden können und die durch eine View dargestellt werden. Markup zer einer Session zugewiesen ist. Markup Wicket ist ein MVC-Framework. die von einer Wicket-Basisklasse geerbt hat. Da bei Webanwendungen immer (es sei denn.4. dass eine Klasse StartPage im Package de. und einer dazugehörigen Markup-Datei. kann es passieren. Eine Komponente übernimmt dabei die Funktion eines Controllers. dass der Zugriff auf die Session gleichzeitig erfolgt.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. 53 . 4. wobei das Framework dafür sorgt. dass der Zugriff synchronisiert erfolgt. Modelle.2 Modelle Wicket orientiert sich dabei an dem Programmiermodell einer Desktop-Anwendung. um für diesen neuen Zustand eine neue Version der Seite anzulegen. die denselben Namen besitzt.4 Komponenten. entfällt das Benachrichtigen der View über die Veränderung. MVC steht dabei für Model. ob sich etwas geändert hat und die Komponente neu gezeichnet werden muss. Ajax kommt zum Einsatz) die ganze Seite dargestellt werden muss. dass die Aktion des Nutzers der richtigen Komponente zugeordnet werden kann und die Komplexität dieser Verarbeitung vollständig kapselt.html benötigt. 4.4.4.3 Markup Eine Wicket-Komponente besteht aus einer Java-Klasse.4. 4.

.

wird bei einem Aufruf der detach()-Methode der Komponente auch die detach()-Methode des Modells aufgerufen. bei Formularen benötigt wird. muss man erst klären. definiert nur drei Methoden: setObject(T): setzt die Daten. welche die Daten in Text und Text in Daten umwandeln können. Die meisten Komponenten greifen auf ein Modell zu.5 5 Modelle Es ist sicher ungewöhnlich. 55 .B. eigene Konverter für unbekannte Datentypen oder die bestehenden Konverter für einen oder mehrere Datentypen bereitzustellen. Dass ich es trotzdem tue.1 Konverter Die Daten. die nicht auf ein Modell zugreifen. um etwas darzustellen. Das bedeutet. Das IModel<T>-Interface. dass Modelle beliebige Daten enthalten können. setObject(T): gibt die Daten zurück. detach(): Wenn ein Modell einer Komponente zugeordnet wurde. liefert Wicket für die wichtigsten Datentypen Konverter mit. um die Modelldaten direkt oder indirekt darzustellen. Auch wenn Wicket für die wichtigsten Datentypen passende Konverter mitbringt. woher dieses Etwas kommt. die ein Modell zurückliefern kann. 5. Andererseits ist die häufigste Frage in Bezug auf Wicket: Wie stelle ich etwas dar? Und bevor man diese Frage beantworten kann. Damit diese Daten sinnvoll dargestellt werden können. Dazu greifen die Komponenten über sehr einfache Methoden auf die Daten zu. das alle Modelle implementieren müssen. dass man bei einem MVC-Framework mit der Erklärung der Modelle anfängt. können beliebigen Typs sein. Die einfachste Komponente Label konvertiert die Daten des Modells in einen String und stellt diesen dann dar. Dazu überschreibt man am besten die newConverterLocator()-Methode der WebApplication-Klasse. wie es z. hat zwei Gründe: Einerseits gibt es kaum Komponenten. kann es notwendig sein.

Da wir nicht alle Konverter neu definieren möchten..class. sollte unsere ConverterLocator-Klasse auf die Konverter der Standardklasse zurückgreifen.. Die Methode convertToString ist für die Umwandlung zur Darstellung verantwortlich. Locale locale).IConverter> _customMap=new HashMap<Class<?>. . dass mit Locale auch die gewünschte Spracheinstellung übergeben wird.java package org.3 IConverter.newConverterLocator()). String convertToString(Object value. Interessant ist hierbei.java package de.5 Modelle Listing 5.java .converter. Die Methode convertToObject konvertiert im besten Fall einen String in den gewünschten Typ. if (ret==null) ret=_fallback..apache. public interface IConverter extends IClusterable { Object convertToObject(String value. } ..get(type).1 WicketPraxisApplication. @Override protected IConverterLocator newConverterLocator() { return new CustomConverterLocator(super.. } public IConverter getConverter(Class<?> type) { IConverter ret=_customMap. } } Das IConverter-Interface ist ähnlich einfach wie das IModel-Interface. Listing 5.2 CustomConverterLocator. { _customMap..web. Listing 5. \ IConverter>().convert.util. . } 56 . sondern bei fast allen Typen auf die bewährten Konverter zurückgreifen wollen. } public CustomConverterLocator(IConverterLocator fallback) { _fallback=fallback.wicket. Map<Class<?>.put(Some. new SomeClassConverter()). return ret..wicketpraxis.getConverter(type). public class CustomConverterLocator implements IconverterLocator { IConverterLocator _fallback.. Locale locale).

2 Einfache Modelle 5. 57 . Nur die Endung lautet „. sodass man gut beraten ist.web. Die im Quelltext benutzte Variante ist eine Kurzform für new Model<String>("Initialwert"). kann man unter Eclipse auch einfach ein Package mit demselben Namen wie das Package der Klasse erzeugen. wie sich das alles zusammenfügt. message.wicketpraxis.java package de.models. Listing 5. liegt in der Natur der Sache. Die Markup-Datei einer Klasse muss sich immer im selben Pfad oder Paket wie die dazugehörige Komponentenklasse befinden und denselben Namen tragen. Dazu legen wir im Ressourcenpfad (src/main/resources) ein Verzeichnis de/wicketpraxis/web/thema/models an. public class SimpleModelPage extends WebPage { public SimpleModelPage() { IModel<String> message=Model.html“. die von WebPage abgeleitet wird). wie Java Pakete im Verzeichnisbaum abbildet. Dabei gibt es mehr als eine Variante des Aufrufs. und zum anderen kann man auch in den Ressourcen wie in den Quelltexten über die Hierarchieansicht navigieren. Dazu erstellen wir eine Seite (das bedeutet: eine neue Klasse. müssen wir jetzt noch ein passendes Markup anlegen. In diesem Verzeichnis/Paket legen wir nun eine Datei an.of("Initialwert"). In dem Fall lautet der Name des Pakets de.web.4 SimpleModelPage. Das Verzeichnis entspricht der Struktur. Das geht zum einen schneller..thema.2 Einfache Modelle Genug der Theorie. Damit die Seite angezeigt werden kann. ..thema.message)).setObject("Jetzt ist "+new Date()). In diesem ersten Beispiel möchten wir die aktuelle Uhrzeit auf der Seite anzeigen. Die Kurzform erspart einiges an Schreibarbeit. add(new Label("message". In der nächsten Zeile werden die Daten im Modell überschrieben und ein Label erstellt. nur diese Variante zu benutzen. Eclipse erzeugt dann wie für den Quelltext der Klasse auch das entsprechende Verzeichnis.models. Tipp Um ein Verzeichnis im Ressourcenpfad eines Projekts anzulegen.wicketpraxis.5. } } Im Konstruktor der Seite erzeugen wir ein Modell mit einem String als Inhalt. Schauen wir uns an einem ersten Beispiel an. Dass dabei noch nicht alles bekannt ist. das zur Anzeige das Modell als Parameter übergeben bekommt.

dann kann Wicket unter Umständen nicht feststellen. sondern die Interaktion.5 SimpleModelPage.html <html> <head> <title>Simple Model Page</title> </head> <body> <span wicket:id="message">Das wird ersetzt. Dadurch ändert sich natürlich das Ergebnis. Jede Komponente hat ein Modell. Wenn man das Modell direkt verändert (also die Methode setObject des Modells benutzt). ob sich Daten geändert haben. Das Modell einer Komponente kann mit setDefaultModel neu gesetzt werden. Wenn der Nutzer dann über den Browser auf eine ältere Seite zurück navigiert. Wenn man nicht verhindern kann. Was bedeutet das konkret? Wenn der Nutzer auf einer Seite etwas ändert.</span> </body> </html> Wichtig ist.5 Modelle Listing 5. Dazu ruft man für die Komponente. 5. kann man Wicket trotzdem mitteilen. wird immer wieder der Konstruktor aufgerufen und eine neue Seite mit einem neuen Datum erstellt. dass der Wert in wicket:id derselbe ist wie der erste Parameter der LabelKomponente. Jetzt müssen wir noch in unserer WebApplication-Klasse den Rückgabewert von getHomePage() anpassen. dann erzeugt Wicket für den Fall. die der Nutzer dann durchführt. dass man die Daten eines Models direkt ändern muss. 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. Auch wenn man dieses Verhalten deaktivieren kann. die sich dadurch verändert. Diese Funk- 58 . basiert auch auf diesen Daten. Der Nutzer sieht nicht nur alte Daten. In diesem Fall wird Wicket keine neue Version einer Seite erstellen. damit Wicket weiß. Wenn Wicket nun keine neue Version einer Seite anlegt. dann navigiert der Nutzer auf die Seite im ursprünglichen Zustand. eröffnet es doch interessante Anwendungsmöglichkeiten. kann man setObject() des Modells aufrufen oder über die Komponentenmethode setDefaultModelObject() die Daten des Modells der Komponente aktualisieren. Der erste Parameter einer Komponente ist meist die ID der Komponente. hier kurz Wicket-ID. dass sich der Zustand eines Modells geändert hat. damit wir beim Start der Anwendung auf unserer Seite landen. Entweder wird das konkrete Modell als Parameter beim Erzeugen der Komponente übergeben oder ein leeres Modell benutzt. kann Wicket auf diese Seite in diesem älteren Zustand zurückgreifen. dass sich Daten geändert haben. welche Komponente er darstellen soll. eine neue Version der Seite.1 Modelle verändern Wenn man die Daten eines Modells verändern möchte. die Methode modelChanging() vor der Veränderung und modelChanged() nach der Veränderung auf.2.

bei dem nach einem Klick der Wert des Modells. add(new Label("message". sodass der aktuelle Inhalt des Modells durch das Label dargestellt wird..wicketpraxis. } }).models. Die Seite muss trotzdem neu geladen werden. } }).6 ModelChangePage.message) { @Override public void onClick() { getModel(). add(new Link<Integer>("changeModel".html <html> <head> <title>Model Change Page</title> </head> <body> 59 .message) { @Override public void onClick() { setModelObject(getModelObject()+1). add(new Link<Integer>("changeModelDirect".java package de. message)). das durch ein Label zur Darstellung gebracht wird.2 Einfache Modelle tionen werden auch in setDefaultModelObject() aufgerufen.7 ModelChangePage. Listing 5. aktualisiert wird. } } Es wird wieder ein Modell angelegt. Gleichzeitig wird ein Link angelegt. Das folgende Beispiel soll den Unterschied veranschaulichen. 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. public class ModelChangePage extends WebPage { public ModelChangePage() { final IModel<Integer> message = Model.web. add(new Link("doNothing") { @Override public void onClick() { } }).. Der zweite Link führt dieselbe Aktion aus. In diesem Fall erhöhen wir den Wert um 1. . das zusätzlich an den Link gebunden ist.getObject()+1).of(0).thema.setObject(getModel().5. Der dritte Link führt keinerlei Aktionen aus. Listing 5. ohne Wicket davon in Kenntnis zu setzen.

Map<String. sehen wir nicht eine Seite mit dem vorletzten Wert.</span><br> <a wicket:id="changeModel">ändern</a><br> <a wicket:id="changeModelDirect">direkt ändern</a><br> <a wicket:id="doNothing">nicht ändern</a> </body> </html> Wie zu erwarten. public class ModelTypesPage extends WebPage { public ModelTypesPage() { List<String> liste = Arrays. Informationen anzuzeigen und durch Aktionen zu verändern.B. dann ändert sich ebenfalls der Wert. IModel<List<? extends String>> wildcardListModel = new WildcardListModel<String>(liste). IModel<Set<String>> setModel = new SetModel<String>(map.of(liste). Wenn wir auf den Link „direkt ändern“ klicken. IModel<List<String>> listModel = new ListModel<String>(liste).. Wenn wir dann im Browser auf die Seite davor springen. .3 Modell-Hilfsklassen Wicket bietet für bestimmte Anwendungen bereits vordefinierte Hilfsklassen.models.web. "ist". sondern die Seite.ofMap(map). welchen Link wir anklicken. Das folgende Beispiel dient nur zur Darstellung der verschiedenen Klassen und Funktionsaufrufe. "Liste").. um aus z. Diese einfache Möglichkeit ist nicht besonders elegant. String> map = new HashMap<String.thema. String>(map).keySet()). IModel<Collection<? extends String>> wildcardCollectionModel = new WildcardCollectionModel<String>(liste).8 ModelTypesPage. Listing 5.wicketpraxis.java package de. String>(). "ne". veranschaulicht aber sehr schön das Prinzip. wird der Wert jeweils um eins erhöht. Unter den auskommentierten Funktionsaufrufen findet sich der gleichwertige kürzere Aufruf.asList("Das". ändern wir den Rückgabewert der getHomePage-Methode auf diese Seite und starten die Anwendung neu. String>> mapModel = Model.of((Collection<String>)liste). IModel<Map<String. die als Letztes versioniert werden konnte. 5. IModel<Collection<? extends String>> wildcardCollectionModel = \ Model. String>> mapModel = new MapModel<String.5 Modelle Aktueller Wert: <span wicket:id="message">Das wird ersetzt. IModel<Collection<String>> collectionModel = \ new CollectionModel<String>(liste). // // 60 . IModel<List<? extends String>> wildcardListModel = Model. aus einer Liste ein Modell zu erzeugen. Zusammenfassung Jetzt sind wir bereits in der Lage. // // // // IModel<Map<String. Je nachdem.

4 Modelle und Serialisierung // // } } IModel<Set<? extends String>> wildcardSetModel = new WildcardSetModel<String>(map. gibt es bereits Modellklassen. ohne dass der Nutzer durch eine Aktion die Anwendung dazu veranlassen muss oder der Nutzer die ganze Seite über den Browser neu lädt. Wenn aber die Daten aus der serialisierten Version der Seite wieder hergestellt werden. muss Wicket alle Komponenten der Seite serialisieren. IModel<Set<? extends String>> wildcardSetModel = \ Model. Damit man sich aber nicht selbst die Mühe machen muss. 5. Ein Label soll immer die aktuelle Uhrzeit anzeigen.keySet()). sodass alle Aktionen. Listing 5. die auf der Seite ausgeführt werden.of(map. die für diesen Fall geschaffen wurden. und Wicket kennt trotzdem den Zustand der Seite und kann die durch den Nutzer ausgeübten Aktionen korrekt zuordnen. die immer wieder aktualisiert werden müssen. die Daten eines persistierten Objekts darstellen soll. Bei bestimmten Daten ist das aber nicht möglich oder nicht gewünscht.keySet()).1 DetachableModel – Dynamische Modelldaten Als Beispiel wählen wir eine Abwandlung der vorherigen Beispiele. Damit Wicket die Seite serialisieren kann. die also nur für die Anzeige benötigt werden. Da Komponenten auf Modelle und deren Daten zugreifen.. 5.9 DetachedModelPage. der bei Bedarf die Daten ermittelt und beim Aufruf der detach()-Methode wieder vergisst. weil die Daten in der Datenbank geändert wurden.4. sich einen Mechanismus auszudenken. . dass die Darstellung nicht mehr korrekt ist. Für alle Daten. Der Nutzer kann dann im Browser zurück navigieren.wicketpraxis. müssen diese Modelle und Daten ebenfalls serialisiert werden.web. auf diesen Zustand zurückgreifen können.thema..5. kann auf die detach()-Methode zurückgegriffen werden. Wenn Wicket z. kann es passieren.java package de.models. kann sich der Zustand des Objekts ändern.B. Wicket serialisiert außerdem die Seite und legt diese in einem PageStore ab. public class DetachedModelPage extends WebPage { public DetachedModelPage() { IModel<String> message = new LoadableDetachableModel<String>() { @Override protected String load() { 61 .4 Modelle und Serialisierung Wicket merkt sich den Zustand der letzten Seite in der Session.

message)). zurückgesetzt. Wenn dann die Methode detach() aufgerufen wird. Dabei wird die Funktion nur einmal aufgerufen. add(new Link("doNothing") { @Override public void onClick() { // hier wir keine Aktion ausgeführt } }). das mit transient markiert ist und somit nicht serialisiert wird.html <html> <head> <title>DetachedModel Page</title> </head> <body> <span wicket:id="message">Das wird ersetzt. durch einen Klick auf den Link). Dabei prüft das Modell beim Aufruf der Funktion getObject() der IModel-Schnittstelle. ob die Daten bereits über load() erzeugt wurden. Listing 5.B.2 Kaskadierung von Modellen Stellen wir uns folgendes Szenario vor: Eine Funktion liefert eine Liste von Ergebnissen. Wenn das nächste Mal die Seite durch Wicket dargestellt wird (z. Die Daten werden innerhalb der Klasse in einem Feld gespeichert. Damit dieser Prozess funktioniert. wird für das Modell von der Klasse LoadableDetachableModel abgeleitet. dass für die beiden Darstellungen zweimal dieselbe Funktion aufgerufen wird.</span><br> <a wicket:id="doNothing">nicht ändern</a> </body> </html> 5. Auf der Seite sollen aber nur die ersten fünf Ergebnisse angezeigt werden. dass die Daten bereits geladen wurden.10 DetachedModelPage. werden dieser Wert und die Information. Dabei muss man die Methode load() implementieren. add(new Label("message". muss das Modell an eine Komponente gebunden sein. Außerdem soll aber auch die Anzahl aller Ergebnisse dargestellt werden. ermitteln wir zuerst nur die Ergebnisliste. Damit wir vermeiden. } }. } } Wie man sieht. die detach()-Methode für alle relevanten Modelle aufzurufen. 62 .5 Modelle return "Jetzt ist " + new Date(). Die Komponente ist dafür verantwortlich. In einem weiteren Modell greifen wir dann auf das Modell mit der Ergebnisliste zu und können dabei die entsprechenden Daten extrahieren. Die Modelldaten werden nicht gespeichert. da beim Zugriff durch ein zweites Modell die Daten bereits geladen wurden. wird die Methode load() wieder aufgerufen und erzeugt einen String mit dem aktuellen Datum.4.

. public class DetachedDetachedModelPage extends WebPage { public DetachedDetachedModelPage() { final IModel<Date> dateModel= new LoadableDetachableModel<Date>() { @Override protected Date load() { return new Date(). wenn der Link mit der ID „doNothing“ das zweite Mal angeklickt wird. dass beim nächsten Seitenaufruf die Anzeige veraltete Ergebnisse präsentiert.11 DetachedDetachedModelPage. Im folgenden Beispiel kann das Problem demonstriert werden: Listing 5. was dazu führen kann. } Das dateModel-Modell liefert immer das aktuelle Datum zurück. add(new Link("doNothing") { @Override public void onClick(){} }).models. add(new Link("detach") { @Override public void onClick() { dateModel.4 Modelle und Serialisierung Jetzt ist allerdings das erste Modell nicht mehr mit einer Komponente verbunden und wird somit auch nicht per detach() zurückgesetzt. 63 .5. wird dateModel zurückgesetzt und das Datum für die Darstellung neu erzeugt.web.detach().java package de. message)). . Das message-Modell greift auf dateModel zurück und bekommt das Datum. Wenn die Seite fertig dargestellt wurde. wird für message die detach()-Methode aufgerufen. } }). Das dateModel bleibt davon unberührt.wicketpraxis..thema. Damit gibt es keine Veränderung auf der Seite. add(new Label("message". } }. Daraus wird ein String erstellt und an das Label ausgeliefert. Erst wenn man den „detach“-Link anklickt. Die Daten können also unter Umständen noch geladen sein.getObject(). } }. IModel<String> message = new LoadableDetachableModel<String>() { @Override protected String load() { return "Jetzt ist " + dateModel.

} }. add(new Link("doNothing") { @Override public void onClick() { } }). wenn für das Modell die Methode detach() aufgerufen wurde.html <html> <head> <title>DetachedDetachedModel Page</title> </head> <body> <span wicket:id="message">Das wird ersetzt..getObject(). beim Aufruf von detach() die detach()-Methode des ersten Modells aufgerufen werden.13 CascadingDetachedModelPage.B.models.12 DetachedDetachedModelPage.java package de. public class CascadingDetachedModelPage extends WebPage { public CascadingDetachedModelPage() { final IModel<Date> dateModel= new LoadableDetachableModel<Date>() { @Override protected Date load() { return new Date().detach(). IModel<String> message = new LoadableDetachableModel<String>() { @Override protected String load() { return "Jetzt ist " + dateModel. dass die aktuellen Daten angezeigt werden. } }.</span><br> <a wicket:id="doNothing">nicht ändern</a><br> <a wicket:id="detach">detach() aufrufen</a> </body> </html> Dass der Nutzer dafür sorgen soll. sollte deshalb innerhalb eines Modells. Dabei wird onDetach() nur aufgerufen. wenn das Modell noch nicht zurückgesetzt wurde. } } Die Klasse LoadableDetachableModel ruft die Methode onDetach() auf. das an eine Komponente gebunden ist. Sollte aus irgendeinem Grund (z. add(new Label("message". Listing 5. . } @Override protected void onDetach() { dateModel. Um dieses Problem zu umgehen.web.wicketpraxis. weil 64 .5 Modelle Listing 5.thema. ist keine gute Idee.. message)).

65 .model.15 CascadingLoadableDetachableModel.4. M ret=load(result).</span><br> <a wicket:id="doNothing">nicht ändern</a><br> </body> </html> 5. public abstract class CascadingLoadableDetachableModel<M. return ret. } Dabei passiert Folgendes: Wenn für das Modell die getObject()-Methode aufgerufen wird. } @Override protected void onDetach() { _parent. Listing 5..5.getObject(). dann wird onDetach() nicht mehr aufgerufen. Diese Daten werden an die Methode load(P) übergeben. ruft die Basisklasse bei Bedarf die load()-Methode auf. Als Lösung bietet sich eine sehr einfache Klasse an. _parent=parent. Diese Klasse ist nicht Teil von Wicket.3 Automatische Kaskadierung von Modellen Die detach()-Methode immer selbst weiterzureichen.java package de.html <html> <head> <title>CascadingDetachedModel Page</title> </head> <body> <span wicket:id="message">Das wird ersetzt. } @Override final protected M load() { P result=_parent. Die Anzeige ändert sich jetzt bei jeder Interaktion.web.. aber so nützlich. Diese holt sich aus dem referenzierten Modell die Daten.14 CascadingDetachedModelPage. die diese Funktion bereitstellt. dass man sie in allen weiteren Projekten gut gebrauchen kann. public CascadingLoadableDetachableModel(IModel<? extends P> parent) { super(). Es empfiehlt sich also. Listing 5.4 Modelle und Serialisierung das Modell von zwei Komponenten referenziert wird) die detach()-Methode ein zweites Mal aufgerufen werden.P> extends LoadableDetachableModel<M> { private IModel<? extends P> _parent. diese Klasse in eine eigene Bibliothek auszulagern. ist aufwendig und fehleranfällig.detach().wicketpraxis. } protected abstract M load(P p). .

} } 5.java package de. Neben technologischen Aspekten (z.models. . add(new Label("message". warum Datenbankobjekte nicht serialisiert werden dürfen. Damit wir diese Aufgabe nicht immer wieder von Neuem lösen.16 CascadingDetachedModelModelPage.web. beschränkt sich die Klasse auf die Funktionen.thema.B. Listing 5.4. Außerdem wird onDetach() entsprechend überschrieben.5 Modelle Diese Methode muss anstelle der load()-Methode implementiert werden.. IModel<String> message = new \ CascadingLoadableDetachableModel<String. der Verwendung von Proxy-Klassen für persistierte Objekte) ist ein Aspekt besonders wichtig: Der Nutzer sollte die aktuellen Daten angezeigt bekommen. add(new Link("doNothing") { @Override public void onClick() { } }). implementieren wir eine Modellklasse.wicketpraxis..4 Datenbankzugriffsmodelle Es gibt verschiedene Gründe. } }.Date>(dateModel) { @Override protected String load(Date p) { return "Jetzt ist " + p. } }. 66 . Das Ergebnis der Methode dient dann als Rückgabewert für die load()-Methode. message)). die für das Funktionieren des Modells notwendig sind. die diese Aufgabe für alle Datenzugriffsklassen übernehmen kann. Damit holt die Klasse automatisch die notwendigen Daten aus dem angegebenen Modell und setzt es nach Aufforderung entsprechend zurück. Da wir bereits eine sehr mächtige Datenzugriffsklasse haben. public class CascadingDetachedModelModelPage extends WebPage { public CascadingDetachedModelModelPage() { IModel<Date> dateModel= new LoadableDetachableModel<Date>() { @Override protected Date load() { return new Date().

T extends DoInterface<K>> implements IModel<T> { DaoInterface<K. _attached=true.T object) { this(dao). } return _object. _id=null. _object=object..model. private K _id. public DaoModel(DaoInterface<K. } public DaoModel(DaoInterface<K. private transient T _object. T> dao) { _dao=dao. _id=_object. } public DaoModel(DaoInterface<K. } protected T load(K id) { if (id==null) return _dao. } } public void detach() { _object=null.getNew(). public class DaoModel<K extends Serializable.5. _attached=true. .. T> dao.getId(). if (_object!=null) { _id=_object.web. _object=object. return _dao. T> dao.getId().K id) { this(dao). } } 67 .java package de.4 Modelle und Serialisierung Listing 5. _attached=false. } public void setObject(T object) { _attached=false.17 DaoModel. _attached=true. _id=id.get(id). } public T getObject() { if (!_attached) { _object=load(_id). private transient boolean _attached=false. T> _dao.wicketpraxis.

dass noch kein Objekt in der Datenbank existiert. public class SimpleDaoModelPage extends WebPage { @SpringBean UserDao _userDao. Die Klasse kennt zwei Zustände in Bezug auf das Datenbankobjekt: 1.thema. add(new Label("name".5 Modelle Das Prinzip dieser Modellklasse ist einfach. User>(model) { @Override protected String load(User p) { if (p!=null) return p. Wird das Formular dann abgeschickt. DaoModel<Integer.getByEMail("test@wicket-praxis.18 SimpleDaoModelPage.web. sich das Objekt zu merken.de"). Dann wird immer ein neues Objekt der entsprechenden Klasse erzeugt. Daher hat es eine ID und kann anhand dieser immer wieder aus der Datenbank geholt werden..java package de. Das Objekt wurde bereits in der Datenbank abgelegt. 2. public SimpleDaoModelPage() { User user = _userDao.models. werden die vorhandenen Objektinformationen im Formular angezeigt. User> model= new DaoModel<Integer. gibt es drei Konstruktoren. .getId() : \ null). Während der erste für den Fall gewählt wird. IModel<String> nameModel = new CascadingLoadableDetachableModel<String. Wie man erkennen kann. wird dieses Objekt als Modellobjekt benutzt. weil es noch nicht in der Datenbank abgelegt wurde. nameModel)). } }. return null. wird das Objekt aus der Datenbank gelesen oder neu erzeugt.wicketpraxis. Doch dazu später mehr. speichern oder aktualisieren). Listing 5.user!=null ? user. da die Eingabedaten durch das Formular bereitgestellt werden.User>(_userDao. Das hat folgende Bewandtnis: Wenn in einem Formular (was wir später in Beispielen sehen werden) das Modell mit einem Objekt benutzt wird. Das Objekt hat noch keine ID.getName(). und man kann mit dem nun so veränderten Objekt arbeiten (es z. Danach verhalten sich beide Varianten wieder gleich. unterscheiden sich die anderen beiden in einem nicht unwesentlichen Detail: Wird der zweite Konstruktor mit einem bereits aus der Datenbank geladenen Objekt aufgerufen. Es ist also nicht nötig.B. bedarf aber trotzdem einiger Erklärungen. bis die detach()-Methode aufgerufen wird. add(new Link("doNothing") { @Override public void onClick() { 68 .. Dann werden die Werte aus dem Formular gesetzt.

thema. reichen die bisher behandelten Modellklassen aus.. 5. wird diese Änderung beim erneuten Laden der Seite angezeigt (z. } } Damit das Beispiel Sinn macht.5 Komplexe Modellklassen Wenn es nur darum geht. Darauf aufbauend holt sich das nächste Modell den Datensatz und gibt.. wenn man ein Datenbankobjekt mit vielen Attributen hat und dieses anzeigen möchte.19 SimpleDaoModelPage.20 SubBean. Doch zuerst benötigen wir eine Klasse mit einigen Attributen.html <html> <head> <title>SimpleDaoModel Page</title> </head> <body> <span wicket:id="name">Das wird ersetzt.5 Komplexe Modellklassen } }).. Daten aus Modellen direkt anzuzeigen. 5. wenn vorhanden.java (gekürzt) package de.5. Dann wird der Nutzer aus der Datenbank ausgelesen. public class SubBean implements Serializable { Date _datum. Diese Daten werden dann durch das Label zur Anzeige gebracht. setDatum(). durch einen Klick auf den Link). 69 .getDatum(). den Namen zurück. reicht die Funktionalität der Modellschnittstelle nicht aus. Und für jedes Attribut eine eigene Klasse zu schreiben.5. Die ID unserer Klasse wird als Parameter übergeben.models.1 Zugriff auf Bean-Properties Um den Zugriff auf die verschiedenen Attribute einer Klasse zu ermöglichen.web. zeige ich an dieser Stelle den ersten offensichtlichen Weg. Listing 5.B.wicketpraxis. ist aufwendig und nicht besonders effektiv. Listing 5. Das sollte für das Verständnis nützlich sein. Allerdings kann es schon sehr aufwendig werden.</span><br> <a wicket:id="doNothing">nicht ändern</a> </body> </html> Ändert man in der Datenbank jetzt den Namen. um am Ende die elegante und schlanke Lösung zu zeigen. . sollte man in der Datenbank einen Eintrag mit entsprechender E-Mail anlegen. Damit man das Grundprinzip besser verstehen und einordnen kann..

_selector=selector.. public DummyBeanPropertyModel(DummyBean bean.thema.models. public class DummyBeanPropertyModel<T extends Serializable> implements IModel<T> { public enum PropertySelector { NAME.. Wir benutzen diese Klasse als Attribut einer weiteren Klasse.wicketpraxis.21 DummyBean.web. import java. public void setObject(T object) { switch (_selector) { 70 .java (gekürzt) package de.web. Listing 5.java package de. } } Jetzt erstellen wir eine Modellklasse.PropertySelector selector) { _bean=bean.5 Modelle @Override public String toString() { return "Datum: " + _datum. }.getAlter(). Alter: "+_alter+" ("+_sub+")". } } Die Klasse hat nur ein Attribut und ist serialisierbar.. int _alter.getName(). Listing 5.setName(). } public T { switch { case case } return } getObject() (_selector) NAME:return (T) _bean. null. ALTER.io. public class DummyBean implements Serializable { String _name.wicketpraxis. getName(). @Override public String toString() { return "Name: "+_name+".22 DummyBeanPropertyModel. PropertySelector _selector.Serializable. ALTER: return (T) new Integer(_bean.getAlter()). die den Zugriff auf ein Attribut der Klasse gewährleistet.models. . DummyBean _bean.. SubBean _sub..thema.

add(new Label("alter". Listing 5. case ALTER: Integer alter=(Integer) object.setObject("Klaus").java package de.und der getObject()-Methode zur Unterscheidung herangezogen wird.models. wo dieses Vorgehen sinnvoll ist. _bean. nameModel. public class HandmadePropertyModelPage extends WebPage { public HandmadePropertyModelPage() { DummyBean bean=new DummyBean().24 HandmadePropertyModelPage.intValue()).nameModel)). DummyBeanPropertyModel<String> nameModel= new DummyBeanPropertyModel<String>(bean. Außerdem kann man diese Modellklasse nur für Objekte der DummyBean-Klasse benutzen.PropertySelector.web.NAME). 71 .PropertySelector.5.thema. Für jedes neue Attribut muss man dann mehrere Zeilen Code schreiben. reicht für die meisten Anwendungsfälle ein sehr viel einfacheres Modell vollkommen aus.setObject(28). add(new Label("name". } } public void detach() {} } Die Auswahl des Attributs erfolgt über den Parameter selector. der dann in der setObject(). break.wicketpraxis.23 HandmadePropertyModelPage.ALTER). alterModel.5 Komplexe Modellklassen case NAME: _bean.alterModel)).setAlter(alter. } } Nach dem Erzeugen der Modellklassen werden Name und Alter über die Modellfunktionen gesetzt und später dann zur Anzeige gebracht.setName((String) object). DummyBeanPropertyModel<Integer> alterModel= new DummyBeanPropertyModel<Integer>(bean. . Listing 5. break..html <html> <head> <title>HandmadePropertyModel Page</title> </head> <body> Name: <span wicket:id="name">Name</span><br> Alter: <span wicket:id="alter">Alter</span><br> </body> </html> Während es durchaus Situationen geben kann..

PropertyModel<Date> datumModel= \ new PropertyModel<Date>(bean.wicketpraxis. das Attribut Sub der Klasse DummyBean mit diesem neuen Objekt gesetzt und zum Schluss das Attribut Datum der Klasse SubBean mit dem übergebenen Wert gesetzt. dass die über die IModel-Schnittstelle gesetzten Daten auch in der DummyBean-Instanz gesetzt werden. Im einfachsten Fall gibt man den Attributnamen des Objektes an.java package de. add(new Label("name"."alter"). nameModel.alterModel)).2 Die Klasse PropertyModel Die Klasse PropertyModel ermöglicht den Zugriff auf ein Attribut einer Klasse über eine Property-Expression. datumModel."name"). add(new Label("toString".datum"). add(new Label("datum". Listing 5. Als Kontrolle.html <html> <head> <title>Property Model Page</title> </head> <body> Name: <span wicket:id="name">Name</span><br> Alter: <span wicket:id="alter">Alter</span><br> Datum: <span wicket:id="datum">datum</span><br> toString: <span wicket:id="toString"></span><br> </body> </html> 72 . .Datum angesprochen werden. PropertyModel<Integer> alterModel= \ new PropertyModel<Integer>(bean.setObject(new Date()).setObject("Klaus").models. Alter und Sub.5 Modelle 5. Als Nächstes definieren wir drei Modelle. Anschließend werden die Modellfunktionen aufgerufen und entsprechende Werte gesetzt. add(new Label("alter".toString())). Dabei wird beim Aufruf von setObject() beim Modell datumModel eine neue Instanz der Klasse SubBean erzeugt..thema. wobei jeweils die Attribute Name. PropertyModel<String> nameModel= \ new PropertyModel<String>(bean. public class PropertyModelPage extends WebPage { public PropertyModelPage() { DummyBean bean=new DummyBean(). wird das Ergebnis von toString() angezeigt.setObject(28). Dieses Verhalten funktioniert nur.nameModel))..web. Listing 5.26 PropertyModelPage.5."sub.datumModel)). alterModel. } } Als Erstes erzeugen wir eine Instanz von DummyBean.bean. wenn der vom PropertyModel ermittelte Typ einen Konstruktor ohne Parameter besitzt.25 PropertyModelPage.

5.5 Komplexe Modellklassen
Listing 5.27 Ergebnis Name: Klaus Alter: 28 Datum: 28.02.09 toString: Name: Klaus, Alter: 28 (Datum: Sat Feb 28 15:20:40 CET 2009)

Die PropertyModel-Klasse unterstützt verschiedene Möglichkeiten, wie auf Attribute und Felder einer Klasse zugegriffen werden kann. 5.5.2.1 Zugriff auf Attribute Die einfachste Möglichkeit, auf Attribute einer Klasse zuzugreifen, ist die Angabe des Attributnamens. Dabei kann man durch die Aneinanderreihung von Attributnamen auf den Objektbaum zugreifen, wie wir das bei datumModel gesehen haben. Die Attributnamen werden durch einen Punkt getrennt:
new PropertyModel<String>(bean,"person.anschrift.strasse“);

Wenn in diesem Beispiel das Attribut anschrift aber leer ist, gibt es trotzdem keine Fehlermeldung. Es wird dann kein Wert zurückgeliefert. 5.5.2.2 Zugriff auf Listen und Arrays Wenn ein Attribut vom Typ Liste oder Array ist, kann über einen Index auf ein Element zugegriffen werden:
new PropertyModel<String>(bean,"person.anschrift.2.strasse"); new PropertyModel<String>(bean,"person.anschrift[2].strasse");

Im Gegensatz zum Array, bei dem es im schreibenden Zugriff (setObject()) dazu kommen kann, dass der Index außerhalb der Grenzen liegt, wird bei vorhandener Liste ein Eintrag an angegebener Stelle angelegt. Die erste Zeile gibt die Standardschreibweise an, beide Schreibweisen sind aber in diesem Fall erlaubt. Ich persönlich halte die zweite Schreibweise für nachvollziehbarer. 5.5.2.3 Zugriff auf indizierte Properties Der Zugriff auf indizierte Properties ähnelt dem Zugriff auf Listen. Die Klasse TestBean definiert ein indiziertes Attribut „Names“.
Listing 5.28 TestBean.java public static class TestBean { Map<Integer,String> _names=new HashMap<Integer, String>(); public void setNames(int idx, String name) { _names.put(idx, name); } public String getNames(int idx) { return _names.get(idx); } }

73

5 Modelle Dann kann mit folgender Zeile auf das Attribut zugegriffen werden:
new PropertyModel<String>(bean,"names.4");

Im Unterschied zu Listen und Feldern wird nur die Angabe ohne eckige Klammern unterstützt. 5.5.2.4 Felder Wenn kein passendes Attribut mit dem Namen gefunden wurde, wird versucht, ein Feld der Klasse mit entsprechendem Namen zu finden. Ich würde aber von der Verwendung dieser Methode abraten. 5.5.2.5 Maps Wenn das Attribut vom Typ Map ist, dann kann man auf die Elemente der Map über die Angabe eines Keys zugreifen:
new PropertyModel<String>(bean,"colormap[red].rgb"); new PropertyModel<String>(bean,"colormap.red.rgb");

Auch hier sind wieder beide Schreibweisen möglich. Möglichkeiten und Risiken So interessant die Möglichkeiten der PropertyModel-Klasse auch sind, birgt die Verwendung doch gewisse Risiken. Das größte Risiko besteht darin, dass bestimmte Fehler erst zur Laufzeit auftreten, weil die Informationen ja per Reflektion ermittelt werden. Diesem Problem kann man mit einer hohen Testabdeckung begegnen. Das hilft auch in den Fällen, wo sich Attributnamen geändert haben.

5.5.3

CompoundPropertyModel

Das CompoundPropertyModel ist eine Modellklasse, die den Aufwand im Umgang mit Objektattributen stark vereinfacht. Dazu implementiert die Klasse das IcomponentInheritedModel-Interface. Wenn eine Komponente ein Modell benutzt, das dieses Interface implementiert, dann rufen alle Kindkomponenten ohne eigenes Modell die Methode wrapOnInheritance mit sich selbst als Parameter auf. CompoundPropertyModel erzeugt bei diesem Aufruf eine eigene PropertyModel-Instanz, welche die Komponenten-ID als Property Expression benutzt.
Listing 5.29 CompoundPropertyModelPage.java package de.wicketpraxis.web.thema.models; ... import org.apache.wicket.model.*; public class CompoundPropertyModelPage extends WebPage { public CompoundPropertyModelPage() {

74

5.5 Komplexe Modellklassen
DummyBean bean=new DummyBean(); bean.setSub(new SubBean()); bean.setName("Achim"); bean.getSub().setDatum(new Date()); CompoundPropertyModel<DummyBean> model= \ new CompoundPropertyModel<DummyBean>(bean); setDefaultModel(model); add(new Label("name")); add(new Label("sub.datum")); add(new Label("toString",bean.toString())); } }

Da hier die Wicket-ID der Komponente gleichzeitig als Property Expression genutzt wird, muss man bei einer Anpassung des Attributnamens sowohl im Code als auch im Markup Anpassungen vornehmen.
Listing 5.30 CompoundPropertyModelPage.html <html> <head> <title>Compound Property Model Page</title> </head> <body> Name: <span wicket:id="name"></span><br> Datum: <span wicket:id="sub.datum"></span><br> toString: <span wicket:id="toString">nr</span><br> </body> </html>

Da die Klasse PropertyModel ebenso wie die Klasse CompoundPropertyModel auch Modelle als Parameter akzeptiert, ergeben sich daraus sehr interessante Anwendungen.
Listing 5.31 DaoModelPage.java package de.wicketpraxis.web.thema.models; ... public class DaoModelPage extends WebPage { @SpringBean UserDao _userDao; public DaoModelPage() { User user = _userDao.getByEMail("test@wicket-praxis.de"); DaoModel<Integer, User> model= \ new DaoModel<Integer, User>(_userDao,user); setDefaultModel(new CompoundPropertyModel<User>(model)); add(new Label("eMail")); add(new Label("name")); } }

In diesem Beispiel wird zuerst ein Objekt aus der Datenbank geladen, das für das Datenobjektmodell benutzt wird. Da dann das Modell der Seite auf das CompoundPropertyModel gesetzt wird, kann der Zugriff auf die Attribute des Datenbankeintrags über die ID der Label-Komponenten erfolgen.

75

5 Modelle

5.6

Ausgelagerte Informationen
Fehlermeldungen und Textrückmeldungen im Programmcode anpassen zu müssen, ist mühsam und fehleranfällig. Wenn dann auch noch eine mehrsprachige Anwendung gefordert wird, kann es schnell kompliziert werden. Wicket bietet an verschiedenen Stellen die Möglichkeit, Mehrsprachigkeit zu unterstützen.

5.6.1

Einfacher Zugriff auf Ressourcen

Jede Komponente kann über die Methode getString() auf die Ressourcen zugreifen. Im folgenden Beispiel erstellen wir eine neue Seite, eine Markup-Datei und im selben Verzeichnis wie die Markup-Datei eine Property-Datei.
Listing 5.32 SimpleResourcePage.java package de.wicketpraxis.web.thema.models; ... public class SimpleResourcePage extends WebPage { public SimpleResourcePage() { add(new Label("text",getString("text", Model.of(12), \ "Nicht gefunden"))); } } Listing 5.33 SimpleResourcePage.html <html> <head> <title>Simple Resource Page</title> </head> <body> <span wicket:id="text">Text</span><br> </body> </html>

Die Property-Datei hat folgenden Inhalt:
Listing 5.34 SimpleResourcePage.properties text=Das ist ein Text und das Model ist eine Zahl: ${} \ (als Fließkommazahl ${doubleValue}).

Allerdings liefert die Methode getString() einen String und kein Modell zurück, was bedeutet, dass die Anzeige des Labels sich auch dann nicht verändert, wenn das beim getString-Aufruf übergebene Modell verändert wird.

5.6.2

ResourceModel

Die ResourceModel-Klasse bietet eine sehr einfache Möglichkeit, Texte in Property-Dateien auslagern zu können. Der Zugriff ist sehr einfach und mehrsprachenfähig. Dadurch entstehen sehr übersichtliche Anwendungen, die ohne Programmieraufwand weitere Spra-

76

5.6 Ausgelagerte Informationen chen unterstützen können. Die Funktionsweise können wir an einem einfachen Beispiel testen.
Listing 5.35 ResourceModelPage.java package de.wicketpraxis.web.thema.models; ... public class ResourceModelPage extends WebPage { public ResourceModelPage() { add(new Label("text1",new ResourceModel("text1"))); add(new Label("text2",new ResourceModel("text2","Nicht gefunden"))); add(new Label("text3",new ResourceModel("text3"))); } } Listing 5.36 ResourceModelPage.html <html> <head> <title>Resource Model Page</title> </head> <body> <span wicket:id="text1">Text1</span><br> <span wicket:id="text2">Text1</span><br> <span wicket:id="text3">Text1</span><br> </body> </html>

Dazu legen wir noch mindestens zwei Property-Dateien in src/main/resource und dem passenden Verzeichnis an. Der Dateiname entspricht dem Klassennamen mit einem angehängten .properties.
Listing 5.37 ResourceModelPage.properties text1=This is text for 'text1'. text3=This is some text for 'text3'.

Die erste, die sprachunabhängige Property-Datei sollte alle notwendigen Definitionen enthalten, da auf diese Datei immer dann zurückgegriffen wird, wenn in den anderen Dateien kein entsprechender Wert gefunden werden konnte. Die zweite Datei im selben Verzeichnis trägt den Namen ResourceModelPage_de_DE.properties.
Listing 5.38 ResourceModelPage_de_DE.properties text3=Das ist die deutsche Version von 'text3'.

Wenn man die Seite mit einem Browser aufruft, der als bevorzugte Sprache Deutsch eingestellt hat, wird man folgendes Ergebnis sehen:
This is text for 'text1'. Nicht gefunden Das ist die deutsche Version von 'text3'.

Ändert man nun die Spracheinstellung, bekommt man folgende Anzeige:
This is text for 'text1'. Nicht gefunden This is some text for 'text3'.

77

5 Modelle In diesem Beispiel haben wir in keiner Property-Datei etwas für den Eintrag text2 definiert. Wenn ein ResourceModel den gewünschten Eintrag nicht findet (hier text2), wird entweder auf den zweiten Parameter zurückgegriffen oder ein entsprechender Fehler erzeugt. Man muss also sicherstellen, dass dieser Eintrag mindestens einmal definiert wurde. Auf diese Weise haben wir bereits eine kleine mehrsprachige Anwendung geschrieben.

5.6.3

StringResourceModel

Oft reicht es nicht aus, nur unterschiedliche Texte darzustellen, wenn die Sprache gewechselt wird. Informationen, die erst zur Laufzeit entstehen, müssen entsprechend eingebunden werden. Eine sehr umfangreiche Funktionspalette bietet die Klasse StringResourceModel. Dabei spielen folgende Parameter eine wichtige Rolle:
resourceKey: Dieser Parameter definiert wie im ResourceModel den Namen des Ein-

trags in der Property-Datei. Allerdings besteht hier die Möglichkeit, Platzhalter zu definieren, die durch Attribute des Modells (model) ersetzt werden können. component: Die Komponente, auf die sich das Modell bezieht. Aus dieser Information ergibt sich der Name für die Resource-Datei. model: Das Modell ist optional und kann mit Platzhalter sowohl im resourceKey als auch in den Einträgen in der Property-Datei angesprochen werden. parameters: Optionale Parameter, die über eine Positionierung in den Einträgen angesprochen werden kann. defaultValue: Wenn der Eintrag nicht gefunden werden konnte, wird dieser Wert benutzt. Eine Vorstellung von den Möglichkeiten bekommt man am besten anhand eines Beispiels.
Listing 5.39 StringResourceModelPage.java package de.wicketpraxis.web.thema.models; ... public class StringResourceModelPage extends WebPage { public StringResourceModelPage() { add(new Label("text",new ResourceModel("test.text"))); DummyBean bean=new DummyBean(); bean.setName("textA"); add(new Label("textA", new StringResourceModel("test.${name}",Model.of(bean)))); add(new Label("textB", new StringResourceModel("test.textB",Model.of(bean)))); Object[] parameter= { new Date(), Model.of(1.23456789) }; add(new Label("textC", new StringResourceModel("test.textC",Model.of(bean),parameter))); } }

78

5.6 Ausgelagerte Informationen Als Erstes benutzen wir ein normales ResourceModel, das auf dieselbe Properties-Datei wie die StringResourceModel-Klassen zugreift. Für „textA“ erzeugen wir eine Instanz der Klasse DummyBean, wobei wir name mit „textA“ initialisieren. Dieses Objekt benutzen wir als Modell für das SpringResourceModel. Dadurch kann der resourceKey auf test.textA aufgelöst werden.
Listing 5.40 StringResourceModelPage.html <html> <head> <title>Simple Resource Model Page</title> </head> <body> <span wicket:id="text">Das wird ersetzt.</span><br> <span wicket:id="textA">Das wird auch ersetzt.</span><br> <span wicket:id="textB">Das wird auch...</span><br> <span wicket:id="textC">Das wird auch...</span><br> </body> </html> Listing 5.41 StringResourceModelPage.properties test.text=Das ist der erste Text test.textA=Das ist der zweite Text (Name=${name}) test.textB=Das ist der dritte Text (Name=${name}) test.textC=Das ist der vierte Text (Name=${name}) und Parameter 0={0,date} \ oder 0={0,time} und 1={1,number,###.##} order 1={1,number,###0.00##}

Wenn wir diese Seite im Browser öffnen, hat Wicket die Platzhalter durch die Werte ersetzt, und man bekommt folgende Anzeige:
Das Das Das Das und ist der erste Text ist der zweite Text (Name=textA) ist der dritte Text (Name=textA) ist der vierte Text (Name=textA) und Parameter 0=28.03.2009 oder 0=22:24:51 1=1,23 order 1=1,2346

In der letzten Zeile sieht man die Möglichkeiten dieses Modells. In den Texten kann auf Attribute des Modellobjektes (${name}) zurückgegriffen werden. Auf das Objekt selbst kann man mit ${} zugreifen, wobei dann die toString()-Methode aufgerufen wird. Auf die Einträge des parameter-Arrays wird über einen Index zugegriffen. Dabei kommt die Klasse MessageFormat (java.text) zum Einsatz. Zusammenfassung Modelle sind der Grundstein jeder Wicket-Anwendung. Das breite Spektrum an vorhandenen Klassen und die einfache Erweiterbarkeit ermöglichen kompakte, funktionale Anwendungsmodule.

79

6
6 Komponenten
In den bisherigen Beispielen kamen schon einige Komponenten zum Einsatz. Auf den folgenden Seiten werden wir die verschiedenen Komponenten, die bereits in Wicket enthalten sind, eingehender betrachten.

6.1

Basisklasse Component
In Wicket wird jede Komponente von einer Basisklasse Component abgeleitet. Diese Basisklasse verfügt bereits über sehr vielfältige Funktionen, die eine ausführliche Betrachtung notwendig machen.

6.1.1

Komponentenbaum

Eine Komponente hat immer eine ID. Diese ID muss beim Erzeugen der Komponente definiert werden. Die ID kann nicht mehr verändert werden. Sie muss auf derselben Hierarchiestufe eindeutig sein und darf daher nicht mehr als einmal vorkommen. Wenn eine Komponente zu einer anderen hinzugefügt wurde, kann sie die übergeordnete Komponente durch den Aufruf von getParent() ermitteln. Gleichzeitig wird eine Liste aller Kindkomponenten verwaltet, sodass man auf diese Weise alle Komponenten einer Seite ermitteln kann.
Listing 6.1 RandomNumberPage.java package de.wicketpraxis.web.thema.komponenten.tree; ... public class RandomNumberPage extends WebPage { private static final List<Integer> NUMBERS = \ Arrays.asList(1,2,3,4,5,6,7,8,9); public RandomNumberPage() { List<Integer> list = getNumbers();

81

6 Komponenten
for (int i=0;i<9;i++) { add(new Label("l"+i,Model.of(list.get(i)))); } add(new Link("link") { @Override public void onClick() { VisitLabels visitor = new VisitLabels(); RandomNumberPage.this.visitChildren(visitor); } }); add(new BookmarkablePageLink<RandomNumberPage>( \ "reset",RandomNumberPage.class)); } // 1..9 in zufälliger Reihenfolge private List<Integer> getNumbers() { ... } static class VisitLabels implements IVisitor<Component> { public Object component(Component component) { if (component instanceof Label) { Object data=component.getDefaultModelObject(); if (data instanceof Integer) { int val=(Integer) data; if ((val % 3)==0) component.setVisible(!component.isVisible()); } } return IVisitor.CONTINUE_TRAVERSAL; }; } }

Wenn die Seite das erste Mal aufgerufen wird, erzeugt die Funktion getNumbers() eine Liste der Zahlen 1 bis 9 in zufälliger Reigenfolge. Dann werden 9 Labels erzeugt, die jeweils eine Zahl aus dieser Liste anzeigen. Beim Klicken auf den Link mit der ID „link“ werden über den Visitor alle Elemente der Seite durchsucht. Wenn der Visitor ein Label findet, bei dem der Wert des hinterlegten Modells ein Integer und außerdem durch 3 teilbar ist, dann wird die Sichtbarkeit der Komponente umgedreht. Der Link mit der ID „reset“ ruft die Seite noch einmal auf, und die Zahlen werden daher neu verteilt.
Listing 6.2 RandomNumberPage.html <html> <head> <title>RandomNumber Page</title> <style> td { border:1px solid #aaa; background-color:#ccc; font-size:30px; } </style> </head>

82

6.1 Basisklasse Component
<body> <table> <tr> <td><span wicket:id="l0"></span></td> <td><span wicket:id="l1"></span></td> <td><span wicket:id="l2"></span></td> </tr> <tr> <td><span wicket:id="l3"></span></td> <td><span wicket:id="l4"></span></td> <td><span wicket:id="l5"></span></td> </tr> <tr> <td><span wicket:id="l6"></span></td> <td><span wicket:id="l7"></span></td> <td><span wicket:id="l8"></span></td> </tr> </table> <a wicket:id="link">An/Aus</a> <a wicket:id="reset">Neu</a> </body> </html>

Am Anfang werden die 9 Kästchen mit je einer Zahl wie in Abbildung 6.1 angezeigt. Nach einem Klick auf den Link „An/Aus“ werden die durch 3 teilbaren Zahlen ausgeblendet.

Abbildung 6.1 Zufallszahlen ein- und ausgeblendet

6.1.2

Darstellungsphasen

Für jede Komponente wird vor der Darstellung die Methode onBeforeRender() ausgeführt. In dieser Methode kann die Sichtbarkeit verändert werden, Komponenten erzeugen Kindelemente, Daten werden für die Darstellung aufbereitet.
Hinweis

Wenn eine Komponente unsichtbar ist, kann es sein, dass diese Methode nicht aufgerufen wird. Wenn das aus einem Grund notwendig sein sollte, kann man die Methode callOnBeforeRenderIfNotVisible() überschreiben, die dann true zurückliefern sollte.

Wenn für alle Komponenten diese Methode ausgeführt wurde, dann wird die Methode render() aufgerufen, welche die notwendigen Grundlagen schafft und danach die Methode onRender() ausführt. In diesem Schritt werden die Modelle und Daten in eine Ergebnisseite überführt. Wenn die Komponente nicht sichtbar ist, dann wird diese Methode nicht ausgeführt. Nachdem dieser Schritt abgeschlossen wurde, wird noch für alle Komponenten onAfterRender() ausgeführt. Im Abschluss wird über den Aufruf von detach() alles Temporäre

aus den Komponenten entfernt. Während dieses Prozesses wird onDetach() aufgerufen.

83

auf der die Komponente eingebunden ist.get() liefert die aktuelle Instanz der Anwendung. Das sorgt dafür. getApplication() – die Applikation (.1.“.getSession()).getApplication()). Aus einem „<“ wird so ein „&lt. dass Daten aus dem Modell nicht zu Veränderungen auf der Seite führen. sollte man diese Funktionen benutzen: getDefaultModelObject() setDefaultModelObject() Für die Umwandlung in einen String (z.. Dabei werden die Komponenten-IDs aneinandergehängt und mit „:“ abgetrennt (z.4 Komponentenpfad Für jede Komponente auf einer Seite lässt sich über die Methoden getPath() und getRelativePath() der Hierarchiepfad ermitteln.B. die der Browser als HTML-Code werten könnte. überschreibt man eine der folgenden Methoden: onBeforeRender() onRender() onAfterRender() onDetach() 6..getLocale()).6 Komponenten Wenn man in einer der verschiedenen Komponentenphasen eigene Verarbeitungsroutinen einbinden möchte.B. Application. getSession(). Session und Application Die Klasse Component stellt häufig benötigte Funktionen bereit: getPage() – die Seite. ersetzt werden müssen. welche die Darstellung der Seite beeinflussen. ungewandelt werden.3 Page. 6. Dabei werden die eingestellten Konverter für die Umwandlung benutzt. Die Zugriffsmethoden lauten: setDefaultModel() getDefaultModel() Um Änderungen an Modelldaten vorzunehmen. getSession() – die Session (entspricht getPage().. getSession().5 Modelle Jede Komponente kann ein Modell haben. ob im Ergebnis Zeichen.1. „liste:box:link“). getDefaultModelObjectAsString() 84 . 6.. Ebenso wird geprüft.1.get() ermitteln. Die aktuelle Session kann man auch über die statische Methode Session. für die Darstellung auf der Seite) wird die folgende Methode benutzt. da alle Zeichen. getLocale() – das eingestellte Gebietsschema (.

java package de.1 Eine Seite mit eigenen Komponenten Im folgenden Beispiel erstellen wir eigene Komponenten und leiten von diesen ab. 6.1. die mit einer Markup-Datei assoziiert werden müssen. Dabei wird für die Komponente in der aktuellen Session eine FeedbackMessage vom passenden Typ angelegt.2.. sondern auch das Aussehen einer Komponente durch Vererbung beeinflusst werden kann. Ein Beispiel soll das veranschaulichen. . Diese Nachrichten kann man über eine Komponente auf der Seite anzeigen lassen. public class BasePanel extends Panel { public BasePanel(String id) { super(id). was somit eine Rückmeldung an den Nutzer ermöglicht (siehe Abschnitt 9. warn() und error().2 Grundlagen der Vererbung Komponenten sind alle direkt oder indirekt von der Klasse Component abgeleitet. 6.wicketpraxis. auch in der Markup-Datei der abgeleiteten Klasse definiert werden.3 BasePanel.extension.6 Feedback Jede Komponente hat die Methoden info().1. add(new Label("message". } protected String getMessage() { return "Base". Listing 6. Allerdings kann Wicket auf Markup-Ebene auch auf einfache Vererbungsfunktionen zurückgreifen. ist die Ableitung einer eigenen Klasse von der Klasse Panel. eine eigene Komponente zu erstellen. durch das Anlegen einer eigenen Markup-Datei die Darstellung der Komponente zu beeinflussen.1 Einfache Vererbung Die einfachste Möglichkeit.. Zum Schluss erstellen wir eine Seite.2 Grundlagen der Vererbung 6. 6. die von der Basisklasse benutzt werden. hat man die Möglichkeit.thema. sodass nicht nur die Funktionen.2.vererbung. Dabei unterscheidet man zwischen Komponenten mit und ohne assoziierte Markup-Datei.komponenten.web. Wenn Komponenten von anderen Komponenten abgeleitet werden.6. die alle Komponenten einbindet. } } 85 . Dabei müssen alle Komponenten.2).getMessage())). die als Parameter einen String erwarten.

} @Override protected String getMessage() { return "Extends ("+super.java package de. kann man durch die Verwendung von <wicket:head> entsprechende Angaben im Kopf platzieren.8). 6.5 ExtendedOnlyPanel.vererbung. Dennoch kann es hilfreich sein. Alles.wicketpraxis. Komponenten.getMessage()+")". sondern nur noch das Fragment einer Seite.4 BasePanel. Listing 6.html <html> <head> <title>Nicht sichtbar</title> <wicket:head> <!-. ihrerseits von Component abgeleitet. was sich außerhalb dieser Tags befindet.komponenten.6 Komponenten Da die Klasse Panel. public class ExtendedOnlyPanel extends BasePanel { public ExtendedOnlyPanel(String id) { super(id). die im Kopf der Seite (<head>) abgelegt werden sollten. Als Alternative kann man solche Informationen auch programmatisch hinzufügen (siehe Abschnitt 7. keine eigene Markup-Datei mitbringt. wenn man dadurch eine bessere Vorschau der Ergebnisseite im Editor ermöglichen kann. eine vollständige HTML-Seite zu definieren. dass hier nicht mehr eine vollständige HTML-Seite definiert werden muss. padding:4px. Dabei achtet Wicket darauf.1.2 Vererbung ohne eigenes Markup Von unserer ersten eigenen Komponente leiten wir eine weitere ab. Wenn eine Komponente Informationen bereitstellen muss. wird nicht mit dargestellt. dass diese Information nur einmal dargestellt wird. müssen im Markup den darzustellenden Teil mit <wicket:panel> umschließen.extension.1. } } 86 .thema. die von Panel ableiten.BasePanel Header --> </wicket:head> </head> <body> <wicket:panel> <div style="background-color:#ffeeee.1.2."> <span wicket:id="message">Message</span> <wicket:child/> </div> </wicket:panel> </body> </html> Der Unterschied zu den bisher erstellten Markup-Dateien liegt darin. Listing 6. müssen wir ein eigenes Markup erstellen.web.

1. background-color:#eeeeff. dass auch das eigene Markup angepasst werden muss. public class OverrideWithMarkupPanel extends BasePanel { public OverrideWithMarkupPanel (String id) { super(id).komponenten.1).2.4 Markup-Vererbung Bisher haben wir gesehen. dass Komponenten eigene Markup-Dateien benutzen können.html <wicket:head> <!-.6.java package de. Dabei kann das Aussehen der Komponente angepasst werden.3 Vererbung mit eigenem Markup Wir leiten ein weiteres Mal eine Klasse ab. Wir überschreiben allerdings keine Methode. wenn man an der Basisklasse keine Veränderungen vornehmen kann. Einen ähnlichen Effekt kann man erzielen. oder nur die Funktion der Komponente verändern.2 Grundlagen der Vererbung In diesem Fall verzichten wir auf das Anlegen einer eigenen Markup-Datei. Unsere Anpassung bezieht sich nur auf den Rückgabewert von getMessage(). 6.web. 6.wicketpraxis. Die eigene Klasse ist zu diesem Zeitpunkt noch nicht vollständig initialisiert. die vorhandene Markup-Definitionen der Basiskomponente überschreiben können. Hinweis Die Methode getMessage() wird im Konstruktor der Basisklasse aufgerufen. Man sollte sich diese Möglichkeit für den Fall merken. dass bei einer Veränderung der Basisklasse Elemente gelöscht oder hinzugefügt werden könnten.1. sondern definieren ein neues eigenes Markup.3."> Override (<i><span wicket:id="message">Message</span></i>) </div> </div> </wicket:panel> In dem Markup müssen nun alle eigenen oder durch die Basisklasse hinzugefügten Elemente referenziert werden. Listing 6. Wicket greift dann auf das Markup der Basisklasse zurück.thema.7 OverrideWithMarkupPanel. \ padding:4px.OverrideWithMarkup Header --> </wicket:head> <wicket:panel> <div style="border:1px solid blue. sodass das Markup der Basiskomponente 87 . Man sollte beachten."> <div style="font-weight: bold.2.extension.6 OverrideWithMarkupPanel. was zur Folge hat. indem man von der getVariation()-Methode Gebrauch macht (siehe Abschnitt 6.vererbung. } } Listing 6.

ExtendedWithMarkup Header --> </wicket:head> <wicket:extend> <div style="background-color:#ffcccc.WebPage.web. weil diese ID schon benutzt wurde. Wenn nun dieselbe ID benutzt wird.wicketpraxis. erstellen wir eine Seite. gibt es daher eine Kollision.basic. Listing 6.web. } } Hinweis Wenn in einer abgeleiteten Komponente Elemente hinzugefügt werden.markup. erstellen wir eine neue Datei."Extends With Markup")).html <wicket:head> <!-. dass die Markup-Definition der Basisklasse durch die der abgeleiteten Klasse ergänzt wird.wicket. ob das Markup der Basisklasse überschrieben oder erweitert werden soll.komponenten.wicketpraxis.extension. Listing 6.2.8 ExtendedWithMarkupPanel. dass Wicket das Markup der Basisklasse nicht überschreibt. dann befinden sich diese Elemente auf derselben Hierarchie wie die Elemente der Basisklasse.html.komponenten. das wir bereits in Markup der Klasse BasePanel definiert haben.thema.apache.vererbung. Listing 6. Da wir in diesem Fall das Markup erweitern wollen.6 Komponenten übernommen wird. padding:4px. auf der wir diese Komponenten einbauen. Wicket nimmt nun dieses Fragment und fügt es in das BasisTemplate ein. 88 .Label.thema. Das Markup gibt nun an. import org.9 ExtendedWithMarkupPanel.vererbung. import org.java package de."> <span wicket:id="message2">Extended</span> <wicket:child/> </div> </wicket:extend> Das Tag <wicket:extend> ist dafür verantwortlich. Dazu leiten wir wiederum eine Klasse ab.wicket.10 SimpleExtensionPage.apache.extension.html. public class ExtendedWithMarkupPanel extends BasePanel { public ExtendedWithMarkupPanel(String id) { super(id). In der letzten Variante betrachten wir die Möglichkeit. Der Platzhalter wird dabei durch das Tag <wicket:child/> definiert.5 Ergebnis Damit wir die verschiedenen Varianten und damit die verschiedenen Darstellungen beurteilen können.1. add(new Label("message2". 6.markup.html package de. Wir haben in dieser Komponente ein weiteres Label hinzugefügt.

2 Grundlagen der Vererbung public class SimpleExtensionPage extends WebPage { public SimpleExtensionPage() { add(new BasePanel("base")).2 Darstellung der abgeleiteten Komponenten Im Quelltext der Ergebnisseite (Abbildung 6.html <html> <head> <title>Simple Extension Page</title> <!-.6.12 Ergebnis.BasePanel Header --> <!-. add(new OverrideWithMarkupPanel("override")).html <html> <head> <title>Simple Extension Page</title> </head> <body> <wicket:container wicket:id="base"></wicket:container><br> <wicket:container wicket:id="extended"></wicket:container><br> <wicket:container wicket:id="override"></wicket:container><br> <wicket:container wicket:id="withMarkup"></wicket:container><br> </body> </html> Bisher haben wir Elemente direkt an ein HTML-Tag gebunden."> <span wicket:id="message">Base</span> <wicket:child/> </div> </wicket:panel></wicket:container><br> <wicket:container wicket:id="extended"><wicket:panel> 89 . wie die Seite entstanden ist. Abbildung 6. } } Listing 6. Listing 6.11 SimpleExtensionPage. wird das umschließende Tag von Wicket nicht dargestellt. padding:4px.ExtendedWithMarkup Header --> </head> <body> <wicket:container wicket:id="base"><wicket:panel> <div style="background-color:#ffeeee.2) können wir gut erkennen. Wenn Komponenten mit <wicket:container> eingebunden werden. add(new ExtendedOnlyPanel("extended")).OverrideWithMarkup Header --> <!-. add(new ExtendedWithMarkupPanel("withMarkup")).

background-color:#eeeeff."> <span wicket:id="message">Extends (Base)</span> <wicket:child/> </div> </wicket:panel></wicket:container><br> <wicket:container wicket:id="override"><wicket:panel> <div style="border:1px solid blue.OverrideWithMarkup Header --> <!-.BasePanel Header --> <!-."> <span>Base</span> </div><br> <div style="background-color:#ffeeee.13 Deployment-Ergebnis. Im Fall der Komponente mit der ID withMarkup wird dann innerhalb von wicket:child das Markup der abgeleiteten Komponente eingebunden."> <span wicket:id="message2">Extends With Markup</span> <wicket:child/> </div> </wicket:extend></wicket:child> </div> </wicket:panel></wicket:container><br> </body> </html> An diesem Beispiel kann man gut nachvollziehen."> <span>Extends (Base)</span> </div><br> <div style="border:1px solid blue. der für den Kopf bestimmt ist."> <div style="font-weight: bold.html <html> <head> <title>Simple Extension Page</title> <!-. eingebettet wird."> Override (<i><span>Base</span></i>) </div> </div><br> <div style="background-color:#ffeeee. padding:4px. wie das Markup der Komponente ohne den Anteil.6 Komponenten <div style="background-color:#ffeeee. Man kann ebenso gut erkennen. Dabei kann man gut erkennen. padding:4px. Ich habe zur besseren Übersicht die Wicket-Tags hervorgehoben. Listing 6.ExtendedWithMarkup Header --> </head> <body> <div style="background-color:#ffeeee. background-color:#eeeeff."> Override (<i><span wicket:id="message">Base</span></i>) </div> </div> </wicket:panel></wicket:container><br> <wicket:container wicket:id="withMarkup"><wicket:panel> <div style="background-color:#ffeeee. dass das äußere wicket:container-Tag mit der ID der Komponente das Ergebnis-Markup der Komponente einschließt. \ padding:4px. padding:4px. padding:4px. padding:4px. padding:4px."> <div style="font-weight: bold. \ padding:4px. wie Wicket die Ergebnisseite erzeugt hat."> <span wicket:id="message">Base</span> <wicket:child><wicket:extend> <div style="background-color:#ffcccc. Im Deployment-Modus werden die Wicket-eigenen Tags nicht mehr dargestellt."> 90 .

Listing 6.java package de.markup.basic.thema.extension3.vererbung. die von dieser Klasse ableiten.Label. Wie wir diese Konstruktion anwenden.2 Vererbung für Fortgeschrittene Wenn Komponenten von abstrakten Klassen ableiten. public abstract class AbstractBasePanel extends Panel { public AbstractBasePanel(String id) { super(id). import org.2.14 BasePanelLabel.15 AbstractBasePanel. wird die ID der Komponente unveränderlich auf „message“ gesetzt. protected abstract Panel getChild(String id). können die ID nicht verändern.wicketpraxis. Auf diese Weise kann man sicherstellen. Alle Klassen.wicketpraxis.komponenten.wicket. import org.java package de. kommen wir an dieser Stelle ohne eine MarkupDatei aus.extension3.apache. wird im weiteren Verlauf sichtbar.web.html."> <span>Extends With Markup</span> </div> </div><br> </body> </html> 6. add(getChild("child")).panel.2 Grundlagen der Vererbung <span>Base</span> <div style="background-color:#ffcccc. dass auch jede abgeleitete Klasse dieselbe ID benutzt.wicket. } 91 . weil wir eine Konstante als ID benutzen.apache.html. add(getMessage()).web. public BasePanelLabel(String message) { super(MESSAGE_ID. kann Wicket trotzdem auf das Markup der abstrakten Klasse zugreifen. padding:4px. public class BasePanelLabel extends Label { public static final String MESSAGE_ID="message". bei der die ID der Komponente nicht verändert werden kann.vererbung.markup.komponenten. Welche Möglichkeiten sich daraus ergeben. zeigt das nachfolgende Beispiel.6.Panel. Listing 6. Wir erstellen als Nächstes eine abstrakte Klasse. Als Erstes erstellen wir eine Klasse.message). } protected abstract BasePanelLabel getMessage(). Da unsere Klasse von Label abgeleitet ist. } } Wie man sieht.thema. die wir dann auf unserer Seite benutzen werden.

web.komponenten. erstellen wir eine Klasse.web. Listing 6.Panel. Die erste Methode muss eine Instanz der Klasse BasePanelLabel zurückliefern.extension3.Panel.wicketpraxis.panel. Listing 6. Da die ID in der Klasse festgelegt wurde.18 BoxPanel. Die Methode getChild liefert eine Instanz der Klasse Panel zurück.6 Komponenten Die Klasse definiert zwei abstrakte Methoden.vererbung.extension3."> Box </div> </wicket:panel> Jetzt erstellen wir noch eine Komponente. eine zweite Instanz hinzuzufügen.16 AbstractBasePanel.markup. public class BoxPanel extends Panel { public BoxPanel(String id) { super(id). height:100px.html <wicket:panel> <span wicket:id="message">Message</span><br> <div style="border:1px dotted black.wicketpraxis.wicket.17 BoxPanel. Listing 6. } } Listing 6. background-color:white. padding: 4px.thema. Welcher Variante der Vorzug gegeben werden sollte. margin:4px. import org.19 HasBoxPanel. weil die ID innerhalb der Komponente bereits benutzt wird.vererbung.thema.komponenten."> <wicket:container wicket:id="child"></wicket:container> </div> </wicket:panel> Damit wir den Unterschied im Ergebnis sehen können.java package de. Die ID wird erst durch den Aufruf festgelegt.html. hängt sehr stark vom Anwendungsfall ab.panel. die von AbstractBasePanel ableitet und die zwei geforderten Methoden implementiert.html <wicket:panel> <div style="width:100px. public class HasBoxPanel extends AbstractBasePanel { public HasBoxPanel(String id) 92 . import org.apache. die einfach nur eine Box darstellt. die von jeder abgeleiteten Klasse überschrieben werden müssen. das dann in den abgeleiteten Klassen nicht überschrieben oder erweitert wird. border:1px solid black.markup. ist es unmöglich.apache. Das Markup wird damit fast komplizierter als die Klassendefinition.html. Beide Varianten erreichen in etwa dasselbe Ergebnis. Für diese Klasse definieren wir ein Markup.wicket.java package de.

wicketpraxis. add(new AbstractBasePanel("inline") { @Override protected BasePanelLabel getMessage() { return new BasePanelLabel("Ich hab keine Box").thema.komponenten. wie man von einer Komponente wie BasePanelLabel ableiten kann. die diese Komponenten bereits jetzt bieten. die von Panel abgeleitet ist.2 Grundlagen der Vererbung { super(id). } @Override protected BasePanelLabel getMessage() { return new HalloLabel().web.html <html> <head> <title>Extended Extension Page</title> 93 .java package de.6. wollen wir es an dieser Stelle so kompliziert machen. Listing 6.21 ExtendedExtensionPage.. } @Override protected Panel getChild(String id) { return new BoxPanel(id).vererbung. } } } Auch wenn dasselbe Ergebnis mit sehr viel weniger Aufwand erreichbar wäre.. public class ExtendedExtensionPage extends WebPage { public ExtendedExtensionPage() { add(new HasBoxPanel("hasBox")).20 ExtendedExtensionPage. } static class HasBoxLabel extends BasePanelLabel { public HasBoxLabel() { super("Hab ne Box"). könnte aber natürlich jede beliebige Komponente zurückliefern. Die Methode getChild liefert eine Instanz der Klasse BoxPanel zurück.extension3. } @Override protected Panel getChild(String id) { return new EmptyPanel(id). In der Klasse HasBoxLabel kann man sehen. } }). . Wir erstellen eine Seite und kombinieren die verschiedenen Möglichkeiten. } } Listing 6.

94 .3. Das Ergebnis sehen wir in Abbildung 6.22 SimplePanel... Locale und Variation In mehrsprachige Anwendungen werden oft nicht nur die Texte in der gewählten Landessprache dargestellt. 6. wenn man für eine Komponente verschiedene Markup-Dateien bereitstellen könnte. Wicket bietet über diese Anforderungen hinaus auch die Möglichkeit. mehrere Variationen einer Komponente zu erstellen.wicketpraxis. Dabei bewirkt nicht nur der Rückgabewert der Funktion.java package de.variations. ohne dass Anpassungen am Programmcode der Komponente notwendig werden.komponenten."></div> </body> </html> In diesem Beispiel habe ich auf die Verwendung von wicket:container verzichtet. wäre das ein zu großer Aufwand.web. Locale _locale.thema. sondern oft auch Anpassungen am Layout und der Gestaltung vorgenommen.3.6 Komponenten </head> <body> <div wicket:id="hasBox" style="background-color:#ffeeee.3 Style. Abbildung 6. . Listing 6.3 Zwei abgeleitete Komponenten 6. Die div-Tags werden in dem Fall auch nicht im Deployment-Modus gelöscht. Vielmehr ergibt sich der Dateiname aus der Spracheinstellung und dem Rückgabewert der Methode getStyle() der aktuellen Session-Instanz. public class SimplePanel extends Panel { String _variation. ohne dass sich dabei die Funktion verändert."></div> <div wicket:id="inline" style="background-color:#ccccff. dass eine andere Markup-Datei zu Einsatz kommt. Durch das Überschreiben der Methode getVariation() können wir eine andere Markup-Datei für die Darstellung heranziehen. wenn man dieses Problem über Vererbung lösen würde. Wesentlich einfacher wäre es.1 Markup-Variationen Wenn eine Komponente unterschiedlich dargestellt werden soll.

Dann legen wir die folgenden Markup-Dateien an.24 SimplePanel_v1.html <wicket:panel> <div style="background-color:#eee.25 SimplePanel_v1_rot.6. Die Dateinamen sind in jedem Fall relevant und sollten auch in Bezug auf Groß. } } In dieser Komponente überschreiben die Methoden getLocale() und getVariation(). padding:10px. } public SimplePanel(String id. Locale und Variation public SimplePanel(String id) { super(id).und Kleinschreibung genau wie angegeben angelegt werden.3 Style. Listing 6. return this. _variation=variation. color:white."> Variante 1 </div> </wicket:panel> Listing 6.String variation) { this(id). } public SimplePanel setLocale(Locale locale) { _locale = locale.html <wicket:panel> <div style="background-color:#00e. } @Override public Locale getLocale() { if (_locale!=null) return _locale. \ color:white"> Variante 1 in Rot </div> </wicket:panel> 95 . margin:4px.html <wicket:panel> <div style="background-color:#f00. \ margin:4px. } @Override public String getVariation() { return _variation. Außerdem muss darauf geachtet werden. border:1px solid black. \ padding:10px.getLocale().23 SimplePanel. im selben Verzeichnis wie die Komponentenklasse angelegt werden. return super. dass die Markup-Dateien innerhalb des Resource-Verzeichnisses im selben Paket bzw."> Standard </div> </wicket:panel> Listing 6. margin:4px.

27 SimplePanel_v2_rot_de_CH.26 SimplePanel_v2_de. add(new SimplePanel("withVariation". 96 . sodass wir nicht darauf achten müssen.komponenten.6 Komponenten Listing 6.java package de. . unterscheiden sich diese verschiedenen Markup-Dateien nur unwesentlich. margin:4px. public class StyleLocaleVariationPage extends WebPage { public StyleLocaleVariationPage(PageParameters pageParameters) { String changeStyle = pageParameters. Hinweis Es ist natürlich möglich. add(new BookmarkablePageLink<StyleLocaleVariationPage>( \ "rot". auch in Abhängigkeit vom benutzten Markup (also dem Rückgabewert von getVariation) Kindkomponenten wegzulassen.html <wicket:panel> <div style="background-color:#ff0.thema. Außerdem benutzten wir an dieser Stelle keine Kindkomponenten.28 StyleLocaleVariationPage. dass alle Komponenten in der richtigen Hierarchie referenziert wurden. padding:10px.length()>0)) { getSession(). \ new PageParameters("style=rot"))). add(new SimplePanel("withCustomLocale".">+</span><br> Variante 2 für die Schweiz in Rot </div> </wicket:panel> Wie man erkennen kann."CH"))).variations."v1")). So vermeidet man Fehler.wicketpraxis. } else { getSession()."v2") ..B.setStyle(changeStyle).getString("style").setLocale(new Locale("de". Da die Reihenfolge innerhalb derselben Hierarchiestufe irrelevant ist.web. Allerdings empfehle ich. in einem besonderen Fall diese Kindkomponenten vergessen oder fälschlicherweise hinzugefügt hat. dass die Komponentenstruktur nicht zu dem verwendeten Markup passt. könnte man auf diese Weise recht einfach die Anordnung von Kindkomponenten verändern. die darauf zurückzuführen sind. margin:4px. if ((changeStyle!=null) && (changeStyle.html <wicket:panel> <div style="background-color:#f00.StyleLocaleVariationPage. } add(new SimplePanel("pure")). in einem solchen Fall eher die Sichtbarkeit von Komponenten anzupassen."> Variante 2 </div> </wicket:panel> Listing 6.. \ color:white"> <span style="font-size: 30px. weil man z.class.setStyle(null). Listing 6. padding:10px.

Wenn die Seite aufgerufen wird.StyleLocaleVariationPage. Wenn man auf den Link „in Rot“ klickt.class)).3 Style. Es werden zwei Links eingebunden. Danach wird die Komponente mit dem Attribut Variation=„v2“ gesetzt und das Attribut Locale auf „de“ und „CH“ gesetzt. erhält man ein Ergebnis wie in Abbildung 6."> <wicket:container wicket:id="pure"></wicket:container> <wicket:container wicket:id="withVariation"></wicket:container> <wicket:container wicket:id="withCustomLocale"></wicket:container> <br> <a wicket:id="rot">in Rot</a> <a wicket:id="normal">Normal</a> </body> </html> Die Seite erfüllt folgende Funktionen: Die Parameter des Seitenaufrufs werden als Parameter im Konstruktor übergeben.6.4 Normale Darstellung der Komponenten Abbildung 6.4.5 Alternative Darstellung 97 . Im zweiten Fall wird das Attribut Variation mit „v1“ gesetzt.29 StyleLocaleVariationPage. Abhängig vom Inhalt des Parameters „style“ wird das Attribut Style in der Session gesetzt oder gelöscht. Abbildung 6. } } Listing 6. welche die Seite einmal mit dem Parameter „style=rot“ und das andere Mal ohne den Parameter aufrufen. Locale und Variation add(new BookmarkablePageLink<StyleLocaleVariationPage>( \ "normal". dann wechselt die Darstellung wie in Abbildung 6. Zuerst wird die Komponente ohne Anpassungen erstellt.html <html> <head> <title>StyleLocalVariation Page</title> </head> <body style="width:300px.5.

html Klassenname_variation_style. Wenn man für eine abgeleitete Klasse kein Markup angelegt hat.und Property-Dateien. Klassenname_variation_style_language_COUNTRY. Klassenname.package_variation_style_language_COUNTRY.B. sondern auch innerhalb des Pakets. in dem sich die Klasse befindet..html Klassenname_language.klasse...html Klassenname_variation_style_language.xml de. 98 .wicketpraxis.xml . dann versucht Wicket.xml Basisklasse_variation_style_language_COUNTRY.klasse..properties Basisklasse.xml .wicketpraxis.. verkürzt sich die Suche entsprechend. wird aus „de..properties Klassenname_variation_style_language_COUNTRY.html Klassenname. Dabei wird nicht nur die Property-Datei der Klasse und aller Basisklassen durchsucht.wicketpraxis.properties Basisklasse_variation_style_language_COUNTRY.und Property-Dateien Die drei Attribute haben auch Auswirkungen auf die Auflösung von Resource.xml .properties Klassenname.package_variation_style_language_COUNTRY. Dabei ergibt sich der Verzeichnisname aus dem Paketnamen der Klasse (z. Letztere werden nach einem stark erweiterten Muster aufgelöst. Der Wert aus getVariation() und getStyle() gilt dann ebenso. Dabei wird nach demselben Muster vorgegangen.6 Komponenten Wicket sucht zu jeder Komponente die passende Markup-Datei. Resource.properties de. dann aller übergeordneten Pakete sowie zum Schluss noch in der Hierarchie der Applikationsklasse und aller Basisklassen der Applikationsklasse. wird zusätzlich das Markup der Basisklasse ebenfalls anhand dieses Musters ermittelt. Wenn man auf Vererbung auch im Markup zurückgreift.html Wenn Variation und Style nicht gesetzt sind.pages“ „de/wicketpraxis/pages“) und der Dateiname für das Markup nach folgendem Suchmuster: Klassenname_variation_style_language_COUNTRY. ein Markup mit der Namen der Basisklasse zu finden. Basisklasse.html Klassenname_language_COUNTRY.

Dann hat der Aufruf von setVisible() nicht den gewünschten Effekt. Gibt die Methode isVisibilityAllowed() false zurück.package_variation_style_language_COUNTRY. dass die Definitionen in den Properties. WicketPraxisApplication_variation_style_language_COUNTRY.wicketpraxis. Das wirkt sich besonders auf den Umgang mit Zeichen in anderen Sprachen aus..6. sodass sich die Sichtbarkeit der Komponenten aus allen drei Informationen ergibt. Application_variation_style_language_COUNTRY.xml Das muss man sich dann so vorstellen. die Definitionen weiter unten überschreiben.B.. bestimmte Werte applikationsweit zu setzen und diese im Einzelfall zu überschreiben... package_variation_style_language_COUNTRY. wenn man sich folgenden Anwendungsfall vorstellt: 99 .properties Application. kann über die Methode determineVisibility() ermittelt werden.4 Sichtbarkeit Wenn Komponenten unsichtbar sind. Ist isRenderAllowed()=false. nicht auf die Zeichenkodierung ISO-8859-1 festgelegt sind. de. dass es diese Komponente überhaupt gibt und auf der Seite benutzt wird.properties .properties . dann gibt es in der Ergebnisseite nichts.. Hinweis Wicket kann dabei bereits Property-Dateien im XML-Format verarbeiten. ist die Komponente unsichtbar. wird die Komponente nicht dargestellt. Für die nach ISO-8859-1 kodierten PropertyDateien mussten diese bisher aufwendig umgewandelt werden. 6. Ob eine Komponente sichtbar ist. Der Rückgabewert von isRenderAllowed() kann durch Sicherheitseinstellungen beeinflusst werden. Dieser Wert kann durch Setzen oder Überschreiben der Methode verändert werden. wird die Komponente ebenfalls nicht dargestellt.. Diese Methode kann nicht überschrieben werden. Die Methode isVisibilityAllowed() kann nicht überschrieben werden.4 Sichtbarkeit de. Ob eine Komponente sichtbar ist. Das hat folgenden Hintergrund: Die Methode isVisible() kann von einer Komponente überladen werden.properties . Der Rückgabewert kann über setVisibilityAllowed() verändert werden. die in dieser Liste ganz oben stehen. Was auf den ersten Blick verwirrend klingt. wird in zwei Stufen ermittelt: Ist isVisible()=false. wird sinnvoll und nachvollziehbar. So ist es möglich.properties Application. die z. was darauf hindeuten könnte.

.und ausgeblendet werden. add(new Label("hide".31 EnclosurePage.30 EnclosurePage.1 wicket:enclosure Mit dem wicket:enclosure-Tag kann man Markup-Bereiche im Einzugsbereich der Komponente in Abhängigkeit zur Sichtbarkeit der Komponente ausblenden. kann die Komponente durch Sicherheitseinstellungen für z..komponenten.setVisible(false)). add(new Label("hide2". muss diese über das Attribut child adressiert werden.java package de.thema.web."> <span wicket:id="hide"></span> </div> </wicket:enclosure> <wicket:enclosure child="hide2"> <div style="border:2px solid green. Befindet sich mehr als eine Komponente innerhalb des Tags. muss dazu innerhalb des wicket:enclosure-Tags liegen."wieder unsichtbar"). } } Listing 6. Wenn der Nutzer aber nicht die entsprechenden Rechte hat. 6. Listing 6.setVisible(false)).B.visibility. unangemeldete Benutzer ausgeblendet werden (siehe Abschnitt 10."> <span wicket:id="view"></span> </div> </wicket:enclosure> <wicket:enclosure> <div style="border:1px solid black."Hallo")). add(new Label("view2". die für die Sichtbarkeit herangezogen wird. kann die Anwendung die Komponente unabhängig vom Nutzer ausblenden.wicketpraxis. . Soll der Nutzer gar keinen Zugriff auf die Komponente erhalten. Dazu wird die Wicket-ID der 100 . Dafür wird entweder setVisible() aufgerufen oder die Methode isVisible() überschrieben."Das zweite Mal")).html <html> <head> <title>Enclosure Page</title> </head> <body> <wicket:enclosure> <div style="border:1px dotted black.6 Komponenten Die Komponente kann durch den Nutzer ein."> <span wicket:id="view2"></span><br> <span wicket:id="hide2"></span> </div> </wicket:enclosure> </body> </html> Die Komponente. public class EnclosurePage extends WebPage { public EnclosurePage() { add(new Label("view"."unsichtbar").4.3).

Die Implementierung folgt dann diesem Muster: Listing 6. } . dass man die Methode isVisible() überschreibt und so die Sichtbarkeit der Komponente steuert.. Befindet sich eine Komponente mit der ID „kind“ innerhalb einer anderen Komponente mit der ID „vater“. lautet der Wert für das Attribut „vater:kind“. Um dieses Verhalten abzuändern..onBeforeRender(). } @Override protected void onBeforeRender() { super. Allerdings ist dabei zu beachten. Ich bevorzuge die zweite Variante.6. dass diese Methode mehrmals aufgerufen werden kann. sollte man zwischen zwei Alternativen wählen: Das Ergebnis wird gepuffert und innerhalb der zu überschreibenden Methode onDetach() gelöscht. Wenn das Ermitteln der Sichtbarkeit aufwendig ist. Innerhalb der Methode erfolgt dann der Aufruf von setVisible().4 Sichtbarkeit Komponente angegeben.. In diesem Fall sollte eine Elternkomponente aktualisiert werden. setVisible(isVisibleCalledOnlyOneTime()). protected boolean isVisibleCalledOnlyOneTime() { . 101 . die das wicket:closure-Tag enthält. wenn eine Komponente unsichtbar ist.32 EineKomponente. 6. } @Override protected boolean callOnBeforeRenderIfNotVisible() { return true.4. dass die Methode onBeforeRender() nicht aufgerufen wird. wirkt sich das nicht auf den durch wicket:enclosure definierten Block aus..2 Empfehlung zur Anwendung Es ist naheliegend. Die Methode onBeforeRender() wird überschrieben. Hinweis Wenn die Sichtbarkeit der Komponente per AJAX geändert wird.. Allerdings ist dabei zu beachten. muss man die Methode callOnBeforeRenderIfNotVisible() überschreiben.java ..

Teile der Webseite per Ajax zu aktualisieren. Wir fügen in diesem Beispiel allerdings nur eins der beiden Label zur Komponentenliste des AjaxRequestTarget hinzu.wicketpraxis. add(_labelNoRefresh). ruft Wicket die onClick()-Methode mit dem AjaxRequestTarget als Parameter auf.addComponent(_labelRefresh). Wicket kümmert sich dann um die Aktualisierung der Darstellung im Browser.")...setDefaultModelObject("neuer Text"). } }).33 SimpleAjaxPage. In Wicket gibt es vielfältige Möglichkeiten. Damit Wicket weiß. add(_labelRefresh). sondern dass dadurch die Geschwindigkeit und die Interaktivität einer Webanwendung wesentlich gesteigert werden konnte.setDefaultModelObject(". public class SimpleAjaxPage extends WebPage { private Label _labelRefresh.komponenten. muss jede Komponente..web..6 Komponenten 6. _labelRefresh."). _labelNoRefresh. Listing 6. 102 . sondern nur die Reaktionszeit auf die Nutzeraktion. dass der Nutzer die Technik dahinter so spannend fand. sondern nur noch ein Teil der Seite über das Internet übertragen werden muss. Alle Komponenten werden durch den Aufruf von target.5 Ajax Dass sich Ajax so durchsetzen konnte. Auch in Zeiten von breitbandigen Internetanschlüssen ist dieser Unterschied relevant. welche Komponenten neu gezeichnet werden sollen. Als Resultat wird nur das Label mit der ID „refresh“ aktualisiert. Dabei ändert sich aber nicht die Geschwindigkeit der Anwendung an sich.thema."Bin gleich weg. } } Es werden zwei Labels erstellt. target. public SimpleAjaxPage() { _labelRefresh = new Label("refresh". in eine Liste eingetragen werden. Wenn der Link angeklickt wird.ajax.auch hier neuer Text"). obwohl sich auch der Inhalt des anderen Labels verändert hat.setOutputMarkupId(true). da nicht mehr die vollständige Seite. . die aktualisiert werden muss. Außerdem konnten so neue Interaktionsformen entwickelt werden. private Label _labelNoRefresh. Innerhalb der Metho- de wird für jedes Label ein neuer Text für die Anzeige hinterlegt.java package de. lag nicht daran.addComponent() aktualisiert. add(new AjaxLink("link") { @Override public void onClick(AjaxRequestTarget target) { _labelRefresh. _labelNoRefresh = new Label("noRefresh"."Bin noch da.

die Wicket bereits mitbringt..thema.komponenten.komponenten.34 AjaxEventListenerInterface.getPage().visitChildren( AjaxEventListenerInterface. public interface AjaxEventListenerInterface { public void notifyAjaxEvent(AbstractAjaxEvent event).class. protected AbstractAjaxEvent(Component source. } Einer Komponente. Listing 6. dass innerhalb der Ergebnisseite auch ein HTML-Tag mit der passenden ID vorhanden ist. aber in anderen Komponenten liegen und man nur mit sehr viel Aufwand Zugriff auf diese Komponente erlangen kann. die man per Ajax aktualisieren möchte.6. kann man bei der Verwendung von Ajax die sonst notwendige starke Koppelung von Komponenten aufweichen. dass ein Event ausgelöst wurde. . _requestTarget=requestTarget. 6.thema. Dazu muss man Wicket mitteilen.. und ruft die Methode setOutputMarkupId(true) auf. muss Wicket dafür sorgen.5 Ajax Hinweis Für eine Komponente. wird durch den Aufruf von notifyAjaxEvent() mitgeteilt. public abstract class AbstractAjaxEvent { Component _source.1 Ajax-Events Durch eine geschickte Kombination von Funktionen.ajax.wicketpraxis.35 AbstractAjaxEvent. die sich durch einen AjaxRequest verändern sollen.wicketpraxis. new AjaxEventVisitor(this)). empfiehlt sich ein alternativer Ansatz.java package de. } public void fire() { _source.ajax. Wenn die Komponenten. Innerhalb einer Komponente ist diese Vorgehensweise ausreichend. Listing 6.5. AjaxRequestTarget _requestTarget. welche Komponente ein potentieller Kandidat ist.web. } public Component getSource() { return _source.java package de.web. } public void update(Component component) 103 .AjaxRequestTarget \ requestTarget) { _source=source. die das Interface implementiert.

die AjaxEventListenerInterface implementiert. Der Event wird dann über die fire()-Methode ausgelöst.addComponent(component).java package de. import java.36 SimpleAjaxEventPage. mit einem Visitor besucht..thema. } static class EventLabel extends Label implements \ AjaxEventListenerInterface { public EventLabel(String id. public class SimpleAjaxEventPage extends WebPage { public SimpleAjaxEventPage() { add(new EventLabel("message".ajax.fire().util. . Außerdem muss das AjaxRequestTarget übergeben werden.2 Einfache Event-Behandlung Angenommen. Dieser ruft dann die Methode notifyAjaxEvent() auf. wir möchten den Text eines Labels austauschen."Text")).notifyAjaxEvent(_event). } } } Die Klasse AbstractAjaxEvent nimmt als Parameter die Komponente entgegen. } protected static class AjaxEventVisitor implements IVisitor<Component> { AbstractAjaxEvent _event.komponenten. 6. protected AjaxEventVisitor(AbstractAjaxEvent event) { _event=event.web. label). Listing 6. } 104 . String label) { super(id. } public Object component(Component component) { ((AjaxEventListenerInterface) component). setOutputMarkupId(true).wicketpraxis.. Dabei wird jede Komponente der Seite. return IVisitor.target). die den Event auslöste.CONTINUE_TRAVERSAL. add(new AjaxLink("link") { @Override public void onClick(AjaxRequestTarget target) { new SomeEvent(this. wenn der Nutzer auf einen Link klickt.Date.6 Komponenten { _requestTarget.5. } }).

die das Interface implementiert.5.3 Automatische Event-Behandlung Ein häufiger Anwendungsfall besteht darin. wenn sich beide Komponenten wie in diesem Beispiel innerhalb einer Komponente befinden.java package de. 6. werden natürlich auch die eingebetteten Komponenten aktualisiert. event. die genau das macht: Listing 6. wäre ein zu großer Aufwand.wicketpraxis.html.37 AjaxEventListener. ob die Nutzeraktion nur Veränderungen innerhalb der übergeordneten Komponente (hier eine Seite) verursacht. Wie man gut sehen kann.ajax. In diesem Fall empfiehlt es sich. oder ob andere Komponenten betroffen wären. public class AjaxEventListener<T extends AbstractAjaxEvent> \ extends WebMarkupContainer implements AjaxEventListenerInterface { Class<? extends T> _eventType.apache. protected AjaxEventListener(String id. } } } static class SomeEvent extends AbstractAjaxEvent { protected SomeEvent(Component source. Wenn eine Komponente durch Ajax aktualisiert wird. Jede Komponente zu überschreiben. den die eine Komponente auslöst und auf den die andere Komponente reagiert.getSource().komponenten. Im Erfolgsfall wird der Text geändert und die Komponente über die update()-Methode der Event-Klasse der Komponentenliste von AjaxRequestTarget hinzugefügt.markup.getId()+ "("+new Date()+")"). prüft die Klasse innerhalb der Methode notifyAjaxEvent() ob der Typ des Events passt. AjaxRequestTarget \ requestTarget) { super(source. } } } Wir erstellen die Klasse EventLabel.web.wicket.thema. Natürlich ist der Aufwand kaum zu rechtfertigen.update(this). requestTarget). eine Gruppe von Komponenten zu aktualisieren. import org. Ausgelöst wird der Event durch den Aufruf in der onClick()-Methode des Links. Wenn ein Event ausgelöst wird. genau abzuwägen. Wir schreiben uns daher eine Komponente. Die Kommunikation erfolgt über den Event.5 Ajax public void notifyAjaxEvent(AbstractAjaxEvent event) { if (event instanceof SomeEvent) { setDefaultModelObject("Event ausgelöst durch "+ event. haben die zwei Komponenten (Link und Label) keine Beziehung miteinander.6.WebMarkupContainer.Class<? extends T> eventType) { 105 .

dass die Komponente nicht sichtbar ist.. _eventType=eventType. ob der Rückgabewert von onAjaxEvent() true ist. Wenn das der Fall ist. Die Klasse WebMarkupContainer besitzt kein eigenes Markup."Text").thema.java package de.web. } protected boolean onAjaxEvent(T event) { return true.wicketpraxis. setOutputMarkupPlaceholderTag(true). wird in der Methode notifyAjaxEvent() geprüft. dass Wicket den Inhalt des HTMLTags ersetzen kann. Wenn ein Event ausgelöst wurde. Listing 6. Diese prüft. dass Wicket für den Fall. Der Aufruf von setOutputMarkupPlaceholderTag() führt dazu. aber auf der Seite vorhanden ist. } } Die Komponente wird von WebMarkupContainer abgeleitet. Beim Aufruf von setOutputMarkupPlaceholderTag() wird gleichzeitig setOutputMarkupId() aufgerufen. kann aber Kindelemente enthalten. } } protected void ajaxEvent(T event) { if (onAjaxEvent(event)) event. public AjaxEventPage() { AjaxEventListener<Event> event = \ new AjaxEventListener<Event>("event".komponenten.38 AjaxEventPage.ajax. Damit ist gewährleistet. _someLabel = new Label("message". ob der Event-Typ passt. Man kann das Verhalten der Komponente beeinflussen. add(event). public class AjaxEventPage extends WebPage { private Label _someLabel.isInstance(event)) { ajaxEvent((T) event).class).update(this). 106 . Damit eignet sich die Klasse hervorragend für unsere Aufgabenstellung. ein HTML-Tag mit der notwendigen ID einfügt. das zwar nicht sichtbar. event. und schlägt in diesem Fall die Komponente zur Aktualisierung vor. } public void notifyAjaxEvent(AbstractAjaxEvent event) { if (_eventType.Event. . indem man entweder onAjaxEvent() überschreibt und den Rückgabewert ändert oder ajaxEvent() überschreibt und das Verhalten selbst definiert.6 Komponenten super(id). wird die Methode ajaxEvent() aufgerufen.add(_someLabel). sodass man sich diesen Aufruf sparen kann..

} } } In onClick() wird der Text des Labels direkt geändert. Durch das Auslösen von fire() wird die Komponente mit der ID „event“ und damit alle Kindkomponenten aktualisiert. 107 .target).setDefaultModelObject(event. add(event). } }..web. } static class Event extends AbstractAjaxEvent { protected Event(Component source."Text").Event. } }). return true.wicketpraxis. requestTarget). new Event(this.add(_message).fire(). die man dann beim Empfänger auswerten kann: Listing 6.thema. public AjaxEvent2Page() { AjaxEventListener<Event> event = \ new AjaxEventListener<Event>("event". _message = new Label("message". Listing 6. . AjaxRequestTarget requestTarget) { super(source.setDefaultModelObject("Link geklickt ("+ new Date()+")").komponenten. public class AjaxEvent2Page extends WebPage { private Label _message.ajax..40 AjaxEvent2Page.39 AjaxEventPage. event.html <html> <head> <title>AjaxEvent Page</title> </head> <body> <div wicket:id="event"> <span wicket:id="message"></span> </div> <a wicket:id="link">Klick mich</a> </body> </html> Durch das Ableiten von der Klasse AbstractAjaxEvent kann man dem Event weitere Informationen hinzufügen.class) { @Override protected boolean onAjaxEvent(Event event) { _message.getMessage()).6.5 Ajax add(new AjaxLink("link") { @Override public void onClick(AjaxRequestTarget target) { _someLabel.java package de.

Da Anwendungen im Laufe der Zeit komplexer werden. auch einen Rückkanal zuzulassen.target. Der vorgestellte Ansatz kann dabei als Anhaltspunkt dienen. requestTarget).6 Komponenten add(new AjaxLink("link") { @Override public void onClick(AjaxRequestTarget target) { new Event(this. Zusammenfassung Die aufgeführten Beispiele demonstrieren gut. AjaxRequestTarget requestTarget. } static class Event extends AbstractAjaxEvent { String _message. Der Text des Labels wird mit diesem Wert neu gesetzt. } public String getMessage() { return _message. protected Event(Component source."Link geklickt ("+new Date()+")"). das in der überladenen Methode onAjaxEvent() ausgelesen wird. wie flexibel Wicket im Umgang mit Ajax ist. _message=message. 108 . sollte man in den meisten Fällen von Anfang an mehr Aufwand in die Ajax-Behandlung stecken.B. oder z. \ String message) { super(source. Je nach Anwendung kann es sinnvoll sein.fire(). } } } In diesem Beispiel bekommt die Event-Klasse ein Attribut Message. } }). neben dem Ajax-Event-System ein allgemeines Nachrichtensystem zu benutzen.

noch nicht erschöpft. die eine ganze Anwendung bereitstellt.1 Seiten Die Klasse WebPage ist die oberste Komponente und eine Analogie zur Webseite. die von WebPage abgeleitet ist. wie wir später sehen werden. indem man bereits vorhandene innerhalb der eigenen Komponente benutzt und so aus vielen kleinen einfachen Elemente etwas Größeres und Komplexeres erschafft. ist es recht einfach. 7. Auch wenn man. die durch eine URL gekennzeichnet wird. Man benötigt in einer Wicket-Anwendung mindestens eine Klasse. andere Elemente einzubinden und zu gruppieren. 7. Diese Klasse stellt die Grundfunktionen bereit. dass innerhalb einer Komponente andere Komponenten eingebunden werden können. eine eigene Komponente zu erstellen. Das wären die Klassen WebPage und Panel. seine Anwendung auch über unterschiedliche Seiten zu strukturieren. sodass es unerheblich ist. ob man ein einfaches Panel oder eine Komponente benutzt. eine Anwendung mit nur einer Seite entwickeln könnte. Beide Komponenten sind von der Klasse MarkupContainer abgeleitet. 109 . die dafür verantwortlich sind.1 Gruppierende Komponenten Die beiden wichtigsten Komponenten sind uns bereits begegnet. empfiehlt es sich aber schon aus Gründen der Ordnung.1. Doch mit diesen beiden Komponenten sind die Möglichkeiten. die bereits in Wicket enthalten sind. Auf den folgenden Seiten erkläre ich die wichtigsten Komponenten. Die Komplexität der Komponenten wird dabei gut versteckt. Die einfachsten Komponenten entstehen.7 7 Basiskomponenten Wie wir bereits sehen konnten.

add(new Link("link") { @Override public void onClick() { setResponsePage(new SimplePage("direkt")).web. 110 .1. } public SimplePage(String message) { initPage("Constructor with Parameter: " + message + " (not bookmarkable)"). von denen man mindestens einen überschreiben muss. Damit ist die URL dieser Seite nicht für ein Lesezeichen geeignet. Eine Seite.1. der einen Parameter vom Typ PageParameter erwartet. Das kann durch das Betätigen eines Links sein oder indem die Seite die Startseite der Anwendung ist.1 SimplePage.wicketpraxis. SimplePage. message))..7 Basiskomponenten 7. Wie eine Instanz der Klasse WebPage von Wicket erzeugt wird. Der erste Konstruktor kommt zum Einsatz.. Neben dem Konstruktor ohne Parameter ist noch ein Konstruktor auffällig.class. die so erstellt wurde. wenn die Seite mit Seitenparametern aufgerufen wurde. Der dritte Konstruktor erwartet als Parameter einen einfachen String. } protected void initPage(String message) { add(new Label("message". public class SimplePage extends WebPage { public SimplePage() { initPage("Default Constructor"). In beiden Fällen kann der Nutzer ein Lesezeichen auf diese Seite setzen. add(new BookmarkablePageLink<SimplePage>("linkBookmarkableParameter".class)). } }). erschließt sich durch folgendes Beispiel: Listing 7.thema. Der Parameter kann natürlich durch jedes beliebige Objekt ersetzt werden. \ SimplePage.basis. Der zweite Konstruktor wird aufgerufen. wenn die Seite ohne irgendwelche Parameter aufgerufen wird. } public SimplePage(PageParameters pageParameters) { initPage("Constructor with PageParameter: " + pageParameters).pages.java package de. add(new BookmarkablePageLink<SimplePage>("linkBookmarkable". .komponenten. } } In der Klasse habe ich drei Konstruktoren definiert. kann nicht über eine fest definierte URL angesprochen werden.new PageParameters("p=1"))).1 WebPage und Bookmarkable Pages Die Klasse WebPage bietet verschiedene Konstruktoren.

6 behandelt. die den Zustand der Seite beeinflussen können. wenn der Nutzer auf den Link geklickt hat. welche die Funktionsweise veranschaulichen sollen. und beim Klick auf den dritten Link erscheint: Constructor with PageParameter: p = "[1]" Wenn der Konstruktor ohne Parameter weggelassen wird. bei dem Informationen. Wicket kann die URL. Diese URLs kommen aber nur zum Zuge. wenn die Seiten nicht über interne Parameter aufgerufen werden. frei definieren. 111 . Die einfachste Möglichkeit ist der Aufruf von mountBookmarkablePage(final String path. Der zweite und der dritte Link verweisen auf die Seite. \ final Class<T> bookmarkablePageClass) innerhalb der init()-Methode der WebApplication-Klasse. Listing 7. wird automatisch der Konstruktor mit dem Parameter vom Typ PageParameter aufgerufen.7.html <html> <head> <title>Simple Page</title> </head> <body> <span wicket:id="message"></span><br> <a wicket:id="link">direct to Page</a><br> <a wicket:id="linkBookmarkable">Page without PageParameter</a><br> <a wicket:id="linkBookmarkableParameter">Page with \ PageParameter</a><br> </body> </html> Beim initialen Aufruf wird folgender Text durch die Komponente mit der ID „message“ dargestellt: Default Constructor Ein Klick auf den ersten Link ändert den Text in: Constructor with Parameter: direkt (not bookmarkable) Beim zweiten Link wird dasselbe wie beim initialen Seitenaufruf angezeigt. innerhalb von Wicket über Parameter des Konstruktors weitergegeben werden. Ausführlicher wird das Thema in Abschnitt 11. Der erste Link setzt als neue Zielseite die Seite SimplePage mit entsprechendem Objekt als Parameter. Die URL muss man beim Applikationsstart definieren. unter der die Seiten erreicht werden können.1 Gruppierende Komponenten In der Methode initPage() werden drei verschiedenen Link-Typen definiert. Man sollte daher besser nur einen der ersten beiden Konstruktoren verwenden.2 SimplePage. Auf diese Weise wird einer der beiden anderen Konstruktoren aufgerufen. auf der die Klasse der Seite als Parameter benutzt wird. Das ist ein Beispiel für den Verweis auf eine Seite. Eine Anpassung ist jederzeit ohne Konsequenzen möglich.

7.de")). wenn sich daran etwas verändern sollte. Suchmaschinen und Browser gehen dann davon aus.1.komponenten.web. nach der die Zielseite aufgerufen werden soll. die als Parameter die URL und die Zeit in Sekunden erwartet.pages.1. auf externe Seiten zu verlinken.google.pages. Listing 7.wicketpraxis. man hat eine neue Version einer Anwendung erstellt. 7. können wir auch von WebPage ableiten.basis. public class RedirectExternalPage extends RedirectPage { public RedirectExternalPage() { super("http://www. welche die Weiterleitung durchführt.setRequestTarget( \ new RedirectRequestTarget("http://www. dass ich nur an einer Stelle die URL anpassen muss.1. Man könnte im Markup direkt einen Link definieren.web.2 RedirectPage Es gibt verschiedenen Möglichkeiten.thema. sendet Wicket eine Serverantwort mit dem HTTPStatuscode 302 (Temporary Redirect). Wenn die Seite aufgerufen wird. wenn das Ziel sich immer wieder ändert.thema. Dafür gibt es zwar keine vorgefertigte Klasse. Da die Anwendung anders funktioniert. In den Suchmaschinen wird aber immer noch die alte Adresse gefunden. Eine andere Möglichkeit ist der Aufruf einer Weiterleitungsseite.4 RedirectHttpTemporaryPage. der ohne Zuhilfenahme von Wicket funktioniert. Die einfachste Variante ist die RedirectPage... public class RedirectHttpTemporaryPage extends WebPage { public RedirectHttpTemporaryPage() { getRequestCycle()..7 Basiskomponenten 7. } } Da wir im Konstruktor die Weiterleitung vornehmen.1.basis.google. Diese Art der Weiterleitung eignet sich gut. } } Auf diese Weise kann ich innerhalb meiner Anwendung die konkrete Klasse als Linkziel benutzen und somit sicherstellen.java package de.com". hat sich vereinzelt auch die URL geändert. dass die Zielseite sich bei jedem Aufruf ändern könnte.wicketpraxis. .3 RedirectExternalPage.. aber der Aufwand für eine eigene Klasse ist sehr gering.komponenten.4 HTTP Permanent Redirect Angenommen. Listing 7.1.3 HTTP Temporary Redirect Neben der Weiterleitung per MetaTag kann auch einer Weiterleitung per HTTP erfolgen. .10).java package de. sodass Nutzer immer wieder auf den alten 112 .1.

_redirectUrl). aber die Suchmaschinen gehen davon aus. import javax.7.5 RedirectPermanentRequestTarget.RequestCycle.java package de.get() \ .substring(2).http.getResponse().pages. } public void respond(RequestCycle requestCycle) { WebResponse response = (WebResponse) requestCycle.getProcessor() \ . dass die Weiterleitung nur temporär ist. } redirect(response.wicketpraxis.reset().contains("://")) { redirect(response.basis. _redirectUrl).length() > 2) { location = location.startsWith("/")) { RequestContext rc = RequestContext. public class RedirectPermanentRequestTarget implements IRequestTarget { String _redirectUrl.getRelativePathPrefixToWicketHandler() + \ _redirectUrl). da Wicket für den Fall leider keine entsprechende Klasse mitbringt.isPortletRequest() && \ ((PortletRequestContext)rc).web..get(). } 113 . . } else { redirect(response./") && location. public RedirectPermanentRequestTarget(String url) { _redirectUrl=url.HttpServletResponse. } else { String location = RequestCycle.startsWith(". if (location. location).komponenten.. Leider ist das nicht ganz so einfach umzusetzen.getRequest() \ .thema.rewriteStaticRelativeUrl(_redirectUrl.servlet. if (rc.isEmbedded()) { redirect(response. Listing 7. } } else if (_redirectUrl. response. wohin die Reise gehen soll.substring(1)). Das Interface RequestTarget gibt.get() \ .1 Gruppierende Komponenten Seiten landen. if (_redirectUrl.getRequestCodingStrategy() \ . Dazu müssen wir eine eigene Implementierung von IRequestTarget erstellen. was also das Ziel dieses HttpRequests sein soll. wie der Name schon sagt. Statt eines HTTP-Statuscode 302 muss der Server nur mit dem Statuscode 301 (Moved Permanently) antworten. Dabei ist die Lösung recht einfach. Eine Weiterleitung lenkt zwar die Nutzer auf die neuen Seiten. Auskunft darüber.

mit den Anwendungen Schritt für Schritt auf Wicket umzuziehen. nur an einer Stelle gepflegt werden sollten.html..web.thema. an der in RedirectRequestTarget response.7 Basiskomponenten } private void redirect(WebResponse response. Die Lösung sollte nicht nur auf die alte Seite weiterleiten.1.java package de.basis. import org.apache.encodeURL(toUrl). \ setStatus(HttpServletResponse. setzen wir im HttpHeader den Parameter Location mit der Ziel-URL und setzen den Status der Serverantwort auf den gewünschten Wert.web. Listing 7. dass Verweise auf die alten Anwendungsteile eingebaut werden mussten. sondern auch später.setRequestTarget( \ new RedirectPermanentRequestTarget("http://www.7 MigrationPage. response.thema. Die Benutzung ist dann wieder recht einfach.wicketpraxis.de")). An der Stelle. Wie das erreicht werden kann. wenn dieser Teil in Wicket realisiert wird. Man muss die Funktionsweise dieser Klasse an dieser Stelle noch nicht verstehen. Da aber für eine komplette Umstellung der Aufwand viel zu hoch war. die ich natürlich auf Wicket migrieren wollte. public class MigrationPage extends WebPage 114 .redirect() aufgerufen wurde. } } 7..6 RedirectHttpPermanentPage.toString()).WebPage. Es war schnell klar.wicket. response. public class RedirectHttpPermanentPage extends WebPage { public RedirectHttpPermanentPage() { getRequestCycle(). entstanden bereits einige Anwendungen mit anderen Frameworks. } public void detach(RequestCycle requestCycle) {} } Als Vorlage diente die Klasse RedirectRequestTarget.SC_MOVED_PERMANENTLY). dass die Verweise. Wichtig ist nur.pages. dass der Statuscode entsprechend gesetzt wird. Davon sollte der Nutzer aber so wenig wie möglich mitbekommen.google.basis.komponenten.java package de. also Links. soll folgendes Beispiel demonstrieren. keine großen Anpassungen in den bereits implementierten Anwendungen nach sich ziehen.5 Migration mithilfe von Weiterleitungsseiten Bevor ich Webanwendungen mit Wicket entwickelt habe.markup.wicketpraxis. habe ich mich dafür entschieden.pages.komponenten. Listing 7.getHttpServletResponse().setHeader("Location". . Das bedeutete. String toUrl) { response.1. die entsprechend umgeändert wurde.

Wenn man E-Mails verschicken muss.6 URL der aktuellen Seite Immer wieder kommt es vor. Der Rückgabewert ist vom Typ AbstractLink.1. Die Einbindung in andere Seiten erfolgt über den Aufruf der statischen Methode getLink(). . new PageParameters("Nr="+nr)). add(new Label("currentUrlAbs".setRequestTarget( \ new RedirectRequestTarget( \ "/alteAnwendung?Nr="+pageParameters..getRequestTarget()).komponenten. add(new Label("currentUrl".currentUrl. wird dann im Konstruktor auf die alte Seite weitergeleitet.7)). \ new PageParameters("a=1.java package de.customUrl. In dem Beispiel benutze ich einen BookmarkablePageLink.1.getString("Nr"))). add(new BookmarkablePageLink<GetCurrentUrlPage>( \ "link".1 Gruppierende Komponenten { public MigrationPage(PageParameters pageParameters) { getRequestCycle(). } public static AbstractLink getLink(String id.8 GetCurrentUrlPage.. Listing 7. add(new Label("customUrl". Wenn die Seite geladen wird.int nr) { return new BookmarkablePageLink<MigrationPage>( \ id.toString()))). } } Die Seite selbst folgt dem Beispiel für die temporäre Weiterleitungsseite. Um dann in einer Komponente oder Seite einen Link auf diese Seite zu setzen.thema.web.toString())).class. public class GetCurrentUrlPage extends WebPage { public GetCurrentUrlPage(PageParameters pageParameters) { CharSequence currentUrl = \ urlFor(getRequestCycle().new PageParameters("c=3"))).class.class. sodass man jeden beliebigen Link benutzen kann.b=2")).getLink("link".GetCurrentUrlPage. die neben der ID für den Link außerdem die Parameter für die Seite entgegennimmt.toString())). in denen ein Registrierungslink enthalten ist. reicht dann folgender Funktionsaufruf: add(MigrationPage. 7. benötigt man die URL von einer anderen Seite. } } 115 .wicketpraxis. CharSequence customUrl = urlFor(GetCurrentUrlPage.basis. dass man die URL der aktuellen Seite benötigt.7.MigrationPage.toAbsolutePath(currentUrl. \ RequestUtils.pages. mit dem ich die Seite mit den entsprechenden Parametern aufrufe.

.basis...96:8080/de.. Dabei stellt die Klasse sicher.B. dass man auch einige JavaScript-Funktionen benutzt.2.GetCurrentUrlPage&a=1&b=2 Auf diese Weise ist es möglich. zum Einbinden von Grafiken (siehe Abschnitt 7.10 HeaderReferencesPage.web.wicketpraxis--webapp/ ?wicket:bookmarkablePage=:..GetCurrentUrlPage URL: ?wicket:bookmarkablePage=:.168.GetCurrentUrlPage einfach nur http://server/testUrlSeite stehen. <body> Aktuelle URL: <span wicket:id="currentUrl"></span><br> Aktuelle URL(absolut): <span wicket:id="currentUrlAbs"></span><br> URL <span wicket:id="customUrl"></span><br><br> <a wicket:id="link">Link mit Parametern</a><br> </body> .. 7.7 Basiskomponenten Listing 7. Wenn diese Seite z.thema. 7.komponenten. würde statt ?wicket: bookmarkablePage=:.1. Die Parameter der Seite werden dann an die URL angehängt. in der das Erscheinungsbild der Anwendung definiert wird.. JavaScript. . dass es eine zur konfigurierten Spracheinstellung. genügt ein einfacher Aufruf. Da es nicht unüblich ist..pages.7 ResourceReference Mit der Klasse ResourceReference kann man auf Ressourcen im Klassenpfad der Anwendung zugreifen. Listing 7. markierten Stellen wurde gekürzt): Aktuelle URL: ?wicket:bookmarkablePage=:. Die Datei wird dann innerhalb des Verzeichnisses. wie man die nötigen Verweise im Kopfbereich erscheinen lässt.5.getStyle()) und dem Wert aus Component.9 GetCurrentUrlPage. zeige ich im nächsten Beispiel.GetCurrentUrlPage Aktuelle URL(absolut): http://192. public class HeaderReferencesPage extends WebPage { public HeaderReferencesPage() 116 .1. des Pakets der Klasse gesucht.1.wicketpraxis.. Diese Klasse wird z. Der erste Parameter gibt die Referenzklasse an.B.8 JavaScript und CSS Eine Webanwendung ohne eine Stylesheet-Datei.. Um die Datei im Kopf einzubinden.1.java package de......getVariation() passende Ressource auswählt.html .und CSSDateien benutzt.. mit mountBookmarkablePage() an den Pfad /testUrlSeite gebunden wäre.1)..... Auf der Ergebnisseite wird Folgendes angezeigt (an den durch . die benötigten URLs zur Laufzeit zu ermitteln. ist kaum vorstellbar.2. dem Stil (Session.

panels. add(JavascriptPackageResource.basis. add(CSSPackageResource.class. wie man z. Innerhalb eines Panels kann man beliebige andere Komponenten verwenden und so die innere Komplexität von außen verstecken.renderString("<!-. add(new Label("message". empfiehlt sich dieses Vorgehen immer dann. Hinweis Als Basisverzeichnis für die JavaScript.web. Mit dem dritten Aufruf binden wir eine JavaScript-Datei ein. Nicht nur Seiten.7.1 Gruppierende Komponenten { add(CSSPackageResource.getLocale(). sondern jede Komponente kann auf diese Weise JavaScript-. } } In der ersten Zeile fügen wir eine CSS-Datei ein. dass dieselbe JavaScript-Datei nicht mehrfach eingebunden wird. Dabei sorgt Wicket dafür. Wenn man statt der aktuellen Klasse z. werden die Referenzen in Paketverzeichnis der Applikationsklasse gesucht.oder andere Referenzen einbinden.11 SimplePanel.getHeaderContribution( new ResourceReference( HeaderReferencesPage.2 Panel Die einfachste Komponente ist das Panel.css". einen Kommentar im Kopfbereich der Seite hinzufügt.java package de. } })).thema..mein Beitrag -->").String message) { super(id). .js")). wenn für die Anwendung eine globales CSS-Datei verwendet werden soll.getStyle()))). die Applikationsklasse benutzt.getHeaderContribution( HeaderReferencesPage. In der zweiten Zeile ergänzen wir eine sprachabhängige CSS-Datei.und CSS-Dateien wird das Paketverzeichnis der angegebenen Klasse verwendet.. und der vierte Block zeigt. } } 117 .getHeaderContribution( HeaderReferencesPage.komponenten. CSS.message)). "styles/standard. "styles/locale. Da sich diese Klasse viel seltener ändert als alle anderen Klassen und die Klasse eine vergleichsweise zentrale Rolle spielt.B.class. public class SimplePanel extends Panel { public SimplePanel(String id.B.class. "js/test.css")).wicketpraxis. add(new HeaderContributor(new IHeaderContributor() { public void renderHead(IHeaderResponse response) { response.1. 7. Listing 7.

html. add(new EmptyPanel("p4")).13 SimpleWithHeaderPanel.basis.12 SimplePanel. import org.7 Basiskomponenten Listing 7.panels. "styles/standard.getHeaderContribution( \ getClass()."Panel3")). add(CSSPackageResource. } div { margin:2px."Panel1")).komponenten. public class SimpleWithHeaderPanel extends SimplePanel { public SimpleWithHeaderPanel(String id.html <wicket:panel> <div style="background-color: #ffcccc. String message) { super(id. um das Ergebnis zu überprüfen. } Auf der folgenden Seite benutzen wir diese Komponenten.basis."> <span wicket:id="message">Message</span> </div> </wicket:panel> Um auch an dieser Stelle noch einmal zu demonstrieren.panels.wicketpraxis. dass jede Komponente Informationen zum Kopfbereich einer Seite hinzufügen kann.wicketpraxis.java package de..css span { color:red. Dazu legen wir das Verzeichnis styles an und erstellen eine CSS-Datei."Panel2")).css")).html <html> <head> <title>Simple Panel Page</title> 118 .14 standard.komponenten.thema. Listing 7. Listing 7.. } } Diese Komponente bekommt kein eigenes Markup. leiten wir von unserer Panel-Komponente eine weitere ab. add(new SimpleWithHeaderPanel("p2". message). Wir müssen aber die verwendete CSSDatei erstellen.web.16 SimplePanelPage.web.wicket. border:1px solid #cc8888. Listing 7.markup.CSSPackageResource. public class SimplePanelPage extends WebPage { public SimplePanelPage() { add(new SimplePanel("p1". .java package de.15 SimplePanelPage.apache. } } Listing 7. add(new SimpleWithHeaderPanel("p3".thema.

1 Gruppierende Komponenten </head> <body> <wicket:container <wicket:container <wicket:container <wicket:container </body> </html> wicket:id="p2"></wicket:container> wicket:id="p1"></wicket:container> wicket:id="p3"></wicket:container> wicket:id="p4"></wicket:container> Ich habe in diesem Beispiel absichtlich die Reihenfolge der Komponenten im Markup vertauscht.this).komponenten. add(new Fragment("p2".css" /> </head> <body> .basis.this).17 Ergebnis.1."fragment2".wicketpraxis.SimpleWithHeaderPanel/styles/standard. wenn man für jede von Panel abgeleitete Komponente eine eigene Markup-Datei anlegen müsste."fragment1".add( \ new Label("message"."p3"))). sondern kann die Klasse auch direkt verwenden."fragment1". kann es sich recht aufwendig gestalten. obwohl die Komponente mehrfach verwendet wurde.thema. Listing 7. da die Positionen der Komponenten erst im Markup definiert werden.3 Fragment Wenn man innerhalb einer Komponente viele kleine.this). dass der Verweis auf die CSS-Datei genau einmal im Kopfbereich aufgeführt wird.. wenn man diese wenigen Zeilen im Markup der Hauptkomponenten unterbringen könnte.html <html> <head> <title>Simple Panel Page</title> <link rel="stylesheet" type="text/css" \ href="resources/. Viel einfacher wäre es. Im Quelltext können wir erkennen. </body> </html> 7."p1"))).. add(new Fragment("p3".add( \ new Label("message". . Dabei muss man nicht notwendigerweise von Fragment ableiten...panels. dass das Markup der Klasse in einem anderen Markup eingebunden ist. einfache Komponenten benutzt. Die der Panel-Klasse sehr ähnliche Komponente unterscheidet sich dadurch."p2"))).java package de.7. Listing 7. public class SimpleFragmentPage extends WebPage { public SimpleFragmentPage() { add(new Fragment("p1". } } 119 .. Genau für diesen Anwendungsfall gibt es die Klasse Fragment.add( \ new Label("message"..18 SimpleFragmentPage.web.

in der das Markup für das Fragment enthalten ist."> <span wicket:id="message">Message</span> </div> </wicket:fragment> <wicket:fragment wicket:id="fragment2"> <div style="background-color: #ccffcc. 7. Das wicket:fragment-Tag mit der Fragment-ID umschließt das Markup.html . also eine Komponente mit assoziiertem Markup.1. In Abbildung 7. Fragmente bieten sich daher immer als Zwischenschritt in einem Refactoring-Prozess an. border:1px solid #88cc88. aus einem Fragment eine eigenständige Komponente zu erstellen. Listing 7.19 SimpleFragmentPage..."> <span wicket:id="message">Message</span> </div> </wicket:fragment> <wicket:container wicket:id="p1"></wicket:container> <wicket:container wicket:id="p2"></wicket:container> <wicket:container wicket:id="p3"></wicket:container> </body> . Abbildung 7. Dabei ergibt sich der Name der Markup-Dateien aus dem Klassennamen der umschließenden Klasse und dem Klassennamen der abgeleiteten Klassen.7 Basiskomponenten Der erste Parameter der Klasse ist die Wicket-ID. wenn man innerhalb einer Komponente bestimmte Funktionen herauslösen möchte. der zweite Parameter gibt die FragmentMarkup-ID an und der dritte Parameter einen MarkupContainer. dass beide Fragment-Markups benutzt werden. Es ist auf der anderen Seite allerdings sehr einfach. Das Markup fällt damit etwas komplizierter aus und ist erklärungsbedürftig.2) leiten wir zwei Klassen von der Border-Klasse ab. In diesem Fall ist das die Seite. <body> <wicket:fragment wicket:id="fragment1"> <div style="background-color: #ffcccc. In unserem Beispiel (Abbildung 7. border:1px solid #cc8888. Für ein Fragment muss dann keine eigene Markup-Datei erstellt werden.. Dazu kopiert man den Fragment-Block in eine eigenständige Markup-Datei und leitet die Klasse von einem Panel ab. 120 .1 kann man sehen.. Am Anfang der Datei kann man das Markup für die Fragmente erkennen. Die Position für das Fragment-Markup ist nicht festgelegt. das dann für die Fragmente benutzt wird.1 Fragmente Fragmente bieten sich immer dann an.4 Border Die Funktion der Border-Klasse erschließt sich schon aus ihrem Namen: Eine BorderKlasse kann um die enthaltenen Elemente einen Rahmen ziehen.

java package de.21 SimpleBorderPage...add(new Label("message".thema.web. border:1px solid #cc8888. add(blueBorder).2 Zwei unterschiedliche Border-Komponenten Listing 7.. ."in red border"))."in blue border")).html . blueBorder..7. BlueBorder blueBorder = new BlueBorder("blue"). public class SimpleBorderPage extends WebPage { public SimpleBorderPage() { RedBorder redBorder = new RedBorder("red").22 SimpleBorderPage$RedBorder.panels. } static class RedBorder extends Border { public RedBorder(String id) { super(id).add(new Label("message".html <wicket:border> <div style="background-color: #ffcccc. } } } Listing 7.html <wicket:border> <div style="background-color: #ccccff. } } static class BlueBorder extends Border { public BlueBorder(String id) { super(id).. add(redBorder). border:1px solid #8888cc. redBorder.wicketpraxis."> <i>before</i> <wicket:body/> <i>after</i> </div> </wicket:border> Listing 7. <body> <wicket:container wicket:id="red"> <span wicket:id="message"></span> </wicket:container> <wicket:container wicket:id="blue"> <span wicket:id="message"></span> </wicket:container> </body> .23 SimpleBorderPage$BlueBorder.basis. Listing 7."> [<wicket:body/>] </div> </wicket:border> 121 ..1 Gruppierende Komponenten Abbildung 7.komponenten.20 SimpleBorderPage.

"> <wicket:container wicket:id="border"> <span wicket:id="message"></span> </wicket:container> <br> <wicket:container wicket:id="border2"> <span wicket:id="message"></span> </wicket:container> </body> . complexBorder2. Das wicket:bodyTag gibt an.3).24 ComplexBorderPage.message))."in a border")). 7. ComplexBorder complexBorder2 = \ new ComplexBorder("border2". 122 ..html .setTransparentResolver(true)."again in a border?")).add(new Label("message".komponenten.. } } } Wir fügen der Seite zwei Border-Komponenten hinzu (Abbildung 7.panels.")..basis. .1 Border-Klasse mit Komponenten Eine Border-Komponente kann auch eigene Komponenten enthalten.thema. dass die verwendeten Komponenten-IDs nicht mit den IDs der Elemente in derselben Hierarchiestufe kollidieren.. Listing 7. add(new Label("border_message". add(complexBorder2). complexBorder."It's an other border. Dabei ist zu beachten.String message) { super(id). an welcher Stelle die Kindelemente der Border eingefügt werden sollen. "It's a border.4. add(new Label("message". public class ComplexBorderPage extends WebPage { public ComplexBorderPage() { ComplexBorder complexBorder = \ new ComplexBorder("border".1.web. Listing 7..25 ComplexBorderPage..wicketpraxis.7 Basiskomponenten Das Markup für eine Border wird von wicket:border umschlossen."). <body style="width: 300px. add(complexBorder).java package de. dass entgegen den Ausführungen in der Seitenklasse das zweite Label mit der ID message doch von der zweiten Instanz unserer Border-Klasse umschlossen wird. } static class ComplexBorder extends Border { public ComplexBorder(String id. Dabei fällt auf. Allerdings fügen wir das zweite Label mit der ID „message“ zur Seite und nicht unterhalb der zweiten Border-Komponente hinzu.

padding:4px. Es kann vorkommen.basis.4. Aber um sich die Fehlersuche zu vereinfachen.2 Sichtbarkeitsteuerung Eine Border-Komponente eignet sich ausgezeichnet. da die unsichtbare Border-Komponente automatisch alle Kindelemente unsichtbar macht. Da dieser Anwendungsfall relativ häufig vorkommt. public abstract class AbstractVisibleBorder<T> extends Border { public AbstractVisibleBorder(String id. \ border:1px dotted black.. IModel<T> model) { super(id."> [<wicket:body/>] </div> </div> </wicket:border> 7.web.wicketpraxis.thema. \ padding:4px.java package de.(IModel<T>) getDefaultModel()) { protected boolean isVisibleWith(T object) { 123 .26 ComplexBorderPage$ComplexBorder. sollte man auf eine saubere Hierarchie achten und auf solche „Tricks“ verzichten.html <wicket:border> <div style="background-color: #ccccff. In dem folgenden Beispiel ist die Sichtbarkeit von dem Wert eines Modells abhängig. Listing 7."> <span wicket:id="border_message"></span> <div style="background-color: white. um Komponenten ein.7. wird durch den Aufruf von setTransparentResolver(true) der Suchpfad entsprechend erweitert. Listing 7. return new AbstractVisibleBorder<T>(id. } public AbstractVisibleBorder<T> getInvers(String id) { final AbstractVisibleBorder<T> _this=this.3 Border-Komponente mit Kindelement Damit Wicket die Komponente trotzdem findet. border:1px solid #8888cc.1. dass so eine Konstruktion notwendig ist.27 AbstractVisibleBorder.. model). empfiehlt es sich.komponenten. dafür eine allgemeine Border-Komponente zu erstellen. margin:4px. an welcher Stelle das eingebettete Element verwendet wird.und auszublenden.panels. .1 Gruppierende Komponenten Abbildung 7. Im Markup für die Border-Komponente können wir erkennen.

} return false.onBeforeRender(). } protected abstract boolean isVisibleWith(T object). } }.java package de.add(new Label("message". AbstractVisibleBorder<String> invers = border.. }.thema.getString("border"))) { @Override protected boolean isVisibleWith(String object) { if ((object==null) || (object. die sich genau entgegengesetzt verhält. Das folgende Beispiel veranschaulicht die Funktion. }.28 AbstractVisibleBorder.basis.panels.29 VisibleBorderPage. setVisible(isVisibleWith(model. public class VisibleBorderPage extends WebPage { public VisibleBorderPage(PageParameters pageParameters) { AbstractVisibleBorder<String> border= \ new AbstractVisibleBorder<String>( \ "border". Die Methode callOnBeforeRenderIfNotVisible() wurde so überschrieben. 124 .htm <wicket:border><wicket:body/></wicket:border> Das Markup ist sehr kurz. . } Die Komponente prüft in der Methode onBeforeRender() die Sichtbarkeit über die zu überschreibende Methode isVisibleWith() mit den Daten des Modells als Parameter.wicketpraxis. denn es enthält keine zusätzlichen Informationen oder Komponenten. wenn die Komponente unsichtbar ist."in a border")). } @Override protected boolean callOnBeforeRenderIfNotVisible() { return true. } @Override protected void onBeforeRender() { super.komponenten.. border.Model.7 Basiskomponenten return !_this. Listing 7.getInvers("invers"). Die getInvers()Methode liefert eine Border zurück.isVisibleWith(object).web.equals("show"))) { return true. Listing 7.of(pageParameters. dass diese Prüfung auch durchgeführt wird.getObject())). add(border). IModel<T> model = (IModel<T>) getDefaultModel().

basis.31 ComponentBorderPage.. verstecken</a><br> <a wicket:id="hide">1.komponenten. add(invers). wird das erste Label angezeigt.setComponentBorder( \ new RedBorder())).thema. <body style="width: 300px. .. 2. public class ComponentBorderPage extends WebPage { public ComponentBorderPage() { add(new Label("l1".4) und keine wirkliche gruppierende Funktion. add(new BookmarkablePageLink<VisibleBorderPage>( \ "show". Wenn man auf den zweiten Link klickt. add(new Label("l2".new PageParameters("border=hide"))). für das Element eine Instanz der Klasse IComponentBorder zu setzen.panels.. dann verschwindet das erste Label. 2. und das zweite wird angezeigt. um ein Element einen Rahmen zu ziehen. Listing 7."in red border"). verstecken. kann sie selbst keine Komponenten einbinden.VisibleBorderPage.setComponentBorder( \ new BlueBorder()))."in blue border").7.html ."second in a border")). } static class RedBorder implements IComponentBorder { public void renderBefore(Component component) { 125 .30 VisibleBorderPage.. Da diese Klasse nicht von Component ableitet.5 ComponentBorder Die einfachste Möglichkeit."> <wicket:container wicket:id="border"> <span wicket:id="message"></span> </wicket:container> <br> <wicket:container wicket:id="invers"> <span wicket:id="message"></span> </wicket:container> <br> <a wicket:id="show">1.class.web. 7. dass man keine ID vergeben kann und muss..add(new Label("message". zeigen</a> </body> . zeigen.1 Gruppierende Komponenten invers. } } Listing 7.VisibleBorderPage.class)). Der Einsatz der ComponentBorder hat damit nur Einfluss auf die Darstellung der Komponente (siehe Abbildung 7. besteht darin.1. add(new BookmarkablePageLink<VisibleBorderPage>( \ "hide".wicketpraxis. Das bedeutet auch. Deshalb ist dieses Element auch nicht im Markup zu sehen.java package de.. Wenn die Seite geladen wird.

4 Komponenten mit gesetzter ComponentBorder Da eine Komponente nur einen Komponentenrahmen haben kann."> [<wicket:body/>] </div> </wicket:border> Abbildung 7. \ padding:4px.. border:1px solid #8888cc. sondern wird üblicherweise an ein HTML-Tag innerhalb der Komponente gebunden. <body style="width: 300px. Die Klasse benutzt kein eigenes Markup.html <wicket:border> <div style="background-color: #ccccff. Dass man diese Funktion verwenden kann.\">"). Listing 7.33 ComponentBorderPage$BlueBorder.getResponse().32 ComponentBorderPage."> <span wicket:id="l1"></span><br> <span wicket:id="l2"></span><br> </body> ..7 Basiskomponenten component. da die Klasse nicht von Component erbt. Listing 7. dass keine Elemente eingebettet werden können. die von der Struktur dem Markup für eine Border-Komponente gleicht.write( \ "<div style=\"border:1px solid red. macht es gerade im Hinblick auf eine schnelle und schlanke Möglichkeit. die Hierarchie der Elemente innerhalb einer Komponente zu strukturieren. Der Unterschied besteht darin.write("</div>"). } } static class BlueBorder extends MarkupComponentBorder { } } Die Klasse RedBorder implementiert die Schnittstelle direkt und schreibt den gewünschten HTML-Code direkt in die Ergebnisseite.6 WebMarkupContainer Die einfachste Art und Weise.1.getResponse(). ohne dass man das Markup der Komponente anpassen muss. eine Komponente mit einem Rahmen versehen zu können.. ist die Verwendung der Klasse WebMarkupContainer. interessant.html .. } public void renderAfter(Component component) { component. Diese Klasse ist mit einer Markup-Datei assoziiert. 7. Auf diese Weise kann man die Sichtbarkeit des Tags und der eventuell 126 . sind die Einsatzmöglichkeiten begrenzt. Die Klasse BlueBorder wird von MarkupComponentBorder abgeleitet.

7. gibt es keine Kollision mit der ID message. werden wir auf den nächsten Seiten behandeln. indem man über noch zu behandelnde Funktionen Attribute hinzufügt oder manipuliert.34 SimpleWebMarkupContainerPage. Wie man sehen kann. Es gibt aber auch hier eine Besonderheit zu beachten: Wicket sorgt dafür. alle Buchstaben. .."> <span wicket:id="message">Message</span> </div> <div wicket:id="p2" style="background-color: #ccffcc.web..wicketpraxis.2 Inhaltselemente vorhandenen Kindelemente beeinträchtigen.1 Label und MultiLineLabel Die Klasse Label ist die einfachste Komponente für die Darstellung von Textinformationen.. umgeschrieben werden. Listing 7. Wenn z. weil die Elemente jeweils Kinder der WebMarkupContainer-Instanzen sind. add(new WebMarkupContainer("p2"). Dabei haben wir bisher die einfachste Form der Textdarstellung kennengelernt.B. wie man Texte darstellt.java package de.html . Webseiten bestehen nicht nur aus Text. der darzustellende Text aus einem Datenbankeintrag stammt. dass über diesen Weg das Aussehen und das Verhalten 127 . \ border:1px solid #cc8888. bestünde sonst das Risiko.basis. die eine Bedeutung innerhalb der HTML-Seite haben könnten.add(new Label("message".2 Inhaltselemente In vielen Beispielen haben wir bereits gesehen. der durch einen Nutzer erzeugt werden konnte. <body> <div wicket:id="p1" style="background-color: #ffcccc."> <span wicket:id="message">Message</span> </div> </body> ..thema. Mann kann auch die Darstellung des Tags verändern.panels. In diesem Beispiel möchten wir uns aber auf die Verwendung als gruppierendes Element beschränken.add(new Label("message"."p1"))). die durch die Klasse dargestellt werden."p2"))).2. \ border:1px solid #88cc88. 7. Wie man Grafiken einbindet und welche Möglichkeiten Wicket dabei bietet. public class SimpleWebMarkupContainerPage extends WebPage { public SimpleWebMarkupContainerPage() { add(new WebMarkupContainer("p1")..7.35 SimpleWebMarkupContainerPage. dass für Inhalte.komponenten.. } } Listing 7.

text))./strong&gt. wicket:id="text"></span> <hr> wicket:id="textMitHtml"></span> <hr> wicket:id="texte"></span> <hr> wicket:id="texteMitHtml"></span> <hr> In diesem Beispiel ist der Quelltext zur Abbildung 7.html .7 Basiskomponenten der angezeigten Seite beeinflusst werden könnten.").br&gt. add(new Label("textMitHtml".of("Text und <strong>Html</strong><br>\n \ wird unterschiedlich dargestellt.<br/> wird \ unterschiedlich dargestellt.38 Ergebnis.komponenten. add(new Label("text". \ wird unterschiedlich dargestellt. Man kann sehr gut erkennen.</p><p>Zweiter Absatz.</p><p>Zweiter Absatz.html .of("Text und <strong>Html</strong>\n \ wird unterschiedlich dargestellt. . wie in den Fällen.texte). 128 ..&lt.</p></span> <hr> <span><p>Text und <strong>Html</strong><br/> wird unterschiedlich \ dargestellt.thema. <body> <span>Text und &lt.</p></span> <hr> </body> ./strong&gt.")..text)..strong&gt. } } Listing 7. add(new MultiLineLabel("texte". dass man dadurch die Anwendung nicht gefährdet.</span> <hr> <span><p>Text und &lt. Die Klasse MultiLineLabel wandelt dabei einen vorhandenen Zeilenumbruch in ein br-Tag und zwei aufeinanderfolgende Zeilenumbrüche in ein p-Tag um. <body> <span <span <span <span </body> . wo setEscapeModelStrings(false) nicht aufgerufen wurde.. Auch wenn dieses Verhalten abschaltbar ist. Listing 7. \ setEscapeModelStrings(false)). die „gefährlichen Elemente“ entschärft wurden.Html&lt. public class SimpleLabelPage extends WebPage { public SimpleLabelPage() { IModel<String> text=Model.Html&lt. Listing 7..setEscapeModelStrings(false)).5 interessant. IModel<String> texte=Model.</span> <hr> <span>Text und <strong>Html</strong><br> \ wird unterschiedlich dargestellt. add(new MultiLineLabel("texteMitHtml". sollte man sicherstellen..37 SimpleLabelPage.36 SimpleLabelPage.wicketpraxis..strong&gt.texte)).web..basis.\n\nZweiter Absatz...java package de.content.

public class ConverterWithLabelPage extends WebPage { public ConverterWithLabelPage() { IModel<Color> colorModel=Model.web.komponenten. } return super.128. add(new Label("custom". muss es für dieses Objekt eine Textdarstellung ermitteln.thema.7. der diese Umwandlung für den Objekttyp vornehmen kann. . } }).class) { return new ColorConverter().colorModel)).2.colorModel) { @Override public IConverter getConverter(Class<?> type) { if (type==Color. Für diese Umwandlung benutzt die Label-Klasse einen Konverter.getConverter(type). add(new Label("standard". Locale locale) { return null.of(new Color(255.5 Verschiedene LabelKomponenten 7.java package de..content.64)).39 ConverterWithLabelPage.. Listing 7.2 Inhaltselemente Abbildung 7. } static class ColorConverter implements IConverter { public Object convertToObject(String value.basis. 129 .2 Lokaler Konverter Wenn ein Label ein Objekt auf der Seite darstellen soll. Wenn man nicht auf den in der Anwendung definierten Konverter zurückgreifen möchte. erstellt man einfach einen eigenen.wicketpraxis.

Locale locale) { Color color=(Color) value. sondern mit der Dateierweiterung „xml“.getBlue()+")".thema. <body> <span wicket:id="standard"></span><br> <span wicket:id="custom"></span><br> </body> .komponenten.html . sollte diese Variante als schlanke Alternative im Hinterkopf behalten. dann muss man eigentlich nur eine Anpassung vornehmen. dass unser Konverter eine Instanz der Klasse Color erfolgreich in einen String umgewandelt hat. wie man Daten als XML ausgibt.wicketpraxis. return "("+color.. .. Listing 7."Klaus")). } } } Listing 7.128.40 ConverterWithLabelPage. die zweite Darstellung zeigt. 7.2.. 130 .7 Basiskomponenten } public String convertToString(Object value. } @Override public String getMarkupType() { return "xml".getRed()+".web. Man muss die Methode getMarkupType() überschreiben."+color.basis. Wir erhalten dann folgendes Ergebnis: java...41 XmlPage. public class XmlPage extends WebPage { public XmlPage() { add(new Label("name". Auch wenn es geeignetere Möglichkeiten gibt.3 XML Wenn man mit Wicket statt einer HTML-Seite XML ausgeben möchte.Color[r=255.64) Die erste Darstellung entspricht dem Aufruf der Methode toString() der Color-Klasse.java package de. } } Die Markup-Datei sucht Wicket jetzt allerdings nicht mehr mit der Endung „html“.content.g=128..getGreen()+".b=64] (255.awt."+ color.

.44 WicketMessageTagPage.html .2.xml <?xml version='1. } } Listing 7.3) bereit..text"> from properties </wicket:message> </span><br> <br> <wicket:message key="message. Wenn man wicket:message als Attribut innerhalb eines Tags benutzt.43 WicketMessageTagPage. aber eine Markup-Funktionalität stellt das wicket:message-Tag bereit.textMitProperty">from \ properties</wicket:message> </body> .basis.wicketpraxis.2 Inhaltselemente Listing 7.markup. <body> <span wicket:message="style:message. 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.web.apache.. ohne dass man eine Zeile Code dafür schreiben muss.org/"> <person> <name wicket:id="name"></name> </person> </someXml> 7.42 XmlPage. Die Klasse für die Seite sieht daher sehr einfach aus. Dieses Tag führt dazu.style"> <wicket:message key="message2.content.4 Das wicket:message-Tag Keine Komponente.komponenten. Dabei stellt dieses Tag dieselben Funktionen wie das StringResourceModel (siehe Abschnitt 5.style"> <wicket:message key="message. Listing 7. public class WicketMessageTagPage extends WebPage { public WicketMessageTagPage() { } public String getHello() { return "I am a Wicket Message Page". import org. dass Inhalte der Seite durch Texte aus Property-Dateien ersetzt werden können.text"> from properties </wicket:message> </span><br> <span wicket:message="style:message2.apache.7.wicket.java package de.6.thema.. 131 .html. dann gibt der Teil vor dem Doppelpunkt den zu ersetzenden Attributnamen an.WebPage. Man kann wicket:message als eigenständiges Tag benutzen.0' encoding='utf-8'?> <someXml xmlns:wicket="http://wicket.

6 Das wicket:message-Tag 7.xml <?xml version="1.properties message. wenn nicht anders definiert. Style und Variation nicht von anderen Komponenten.style">color:blue</entry> <entry key="message2.style=color:yellow. muss der ganze Eintrag in einer CDATA-Definition stehen. Dabei unterscheidet sich die Image-Komponente in Bezug auf die Anpassbarkeit an Spracheinstellung.2. die UTF-8Kodierung. Die XML-Variante benutzt.textMitProperty=Text WicketMessageTagPage.getStyle()) gibt es ein paar Besonderheiten zu beachten.5.6 können wir sehen.style">color:green</entry> </properties> Um in der XML-Variante der Property-Datei ein eingebettetes HTML-Tag schreiben zu können.text=This is some other text message2.com/dtd/properties.text"><![CDATA[Das ist der Text in \ <strong>Deutsch</strong>]]></entry> <entry key="message.46 WicketMessageTagPage_de. dass die HTML-Tags aus den PropertyDateien ungefiltert dargestellt wurden. Das Eurozeichen müsste in dem Fall als \u20ac umschrieben werden.1 Grafiken direkt Einbinden Das nachfolgende Beispiel zeigt unterschiedliche Möglichkeiten.getHello(): \ <strong>${hello}</strong> Die zweite Property-Datei legen wir im XML-Format an. Wicket unterstützt beide Versionen.2.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java. message.text=This is some text message. 7. Da die normale Property-Datei immer die ISO-8859-1-Kodierung benutzt.style=color:red.dtd"> <properties> <entry key="message. Gerade in Zusammenhang mit der Auswahl der passenden Grafik in Abhängigkeit zur Lokalisation und des eingestellten Stils (Session. In Abbildung 7.45 WicketMessageTagPage. wie man Grafiken einbinden kann.5 Image Einfache Grafiken werden in Wicket mit der Image-Komponente eingebunden.7 Basiskomponenten Listing 7. 132 . Abbildung 7. Listing 7. ist die Benutzung von Sonderzeichen und Umlauten sehr erschwert.sun. message2.

"test3."test2. Style: <span wicket:id="style">Style</span> <br> <img wicket:id="image1"> <wicket:link> <img src="test1.komponenten.getLocale()..gif")).of(getLocale())))."test2. add(new Image("image2ResourceRefMit".html .gif add(new Image("image1". } } Listing 7. // test2.new ResourceReference( \ SimpleImagePage.java package de. // test4."test2..content.gif")).gif")).Model. // test3. add(new Image("image2ResourceRef".class.gif"> </wicket:link> <br> <img wicket:id="image2"> <wicket:link> <img src="test2.gif add(new Image("image2".gif.class.gif")))."test1.new ResourceReference( \ SimpleImagePage. add(new Image("image3rot".new ResourceReference( \ SimpleImagePage.48 SimpleImagePage.gif."test3.basis..gif".web."test4.class. test3_rot.getStyle()))). public class SimpleImagePage extends WebPage { public SimpleImagePage() { add(new Label("locale".47 SimpleImagePage.null.wicketpraxis. } }). // test1.gif".gif") { @Override public String getVariation() { return "rot"."test2. <body> Locale: <span wicket:id="locale">Locale</span>. .2 Inhaltselemente Listing 7.null))).getStyle())).gif add(new Image("image3".gif")).thema. add(new Image("image2ResourceRefOhne"..7.gif"> </wicket:link> <br> <img wicket:id="image2ResourceRef"> <img wicket:id="image2ResourceRefOhne"> <img wicket:id="image2ResourceRefMit"> <br> <img wicket:id="image3"> <img wicket:id="image3rot"> 133 . add(new Label("style".gif nicht vorhanden add(new Image("image4". test2_de_DE.

/test4_de_DE. In jedem anderen Fall wird das Attribut so 134 .getLocale() und Session. Die Grafiken sollten sich unterscheiden. Die Komponente image3rot zeigt. wenn Wicket eine entsprechende Datei gefunden hat oder Wicket keine passende Datei finden konnte..oder „href“-Attribut benutzt und mit der Klasse der Komponente eine ResourceReference erzeugt. Da Wicket die Grafik nicht finden kann.7 sehen wir zweimal das Bild test1. Bei der Komponente image2ResourceRef wird die Grafik über eine ResourceReference adressiert. dass auch der Rückgabewert getVariation()Einfluss auf das Ergebnis hat. Zusätzlich benötigen wir noch folgende Grafikdateien...gif test3. dann werden die Werte aus Session. Mit dem String wird eine ResourceReference erzeugt.gif test2. Das erste haben wir als Komponente eingebunden und das zweite mit dem wicket:link-Tag. Deshalb unterscheiden sich image2ResourceRefOhne und image2ResourceRefMit von den anderen Grafiken in der Reihe. Die auf den ersten Blick recht unterschiedlichen Möglichkeiten führen im Hintergrund zu ein und demselben Resultat.7 Darstellung der eingebundenen Grafiken In der ersten Reihe von Abbildung 7. In den ersten drei Fällen konnte Wicket die richtige (test2_de_DE. Interessant wird es bei image4.7 Basiskomponenten <br> <img wicket:id="image4"> </body> .getStyle() nicht mit übernommen. die als Basisklasse die in der Hierarchie der Image-Komponente übergeordnete Komponentenklasse benutzt. damit man das Ergebnis besser beurteilen kann.gif) für die Anzeige benutzen.gif“. erscheint im Attribut „src“ der Wert „. 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.. Entgegen der Erwartung erscheint nur dann der längstmögliche Pfad. die sich im selben Verzeichnis wie die Markup-Datei befinden: test1.gif Die referenzierte Grafik test4. Das wicket:link-Tag erzeugt automatisch eine Komponente. Abbildung 7.gif und test2_de_DE.gif.gif legen wir nicht an.gif und test3_rot. die den Wert aus einem „src“.

session. Oft möchte man alle Grafiken an einem zentralen Ort bündeln.5.content.gif").. Man sollte sich also wundern. die man so nicht angelegt hat.basis.2 Grafiken zentral verwalten Dass Grafiken wesentlicher Bestandteil einer Komponente sein können. } } Listing 7..gif")..thema.gif"). sollte man die Image-Komponente durch die Referenzklasse erzeugen lassen. public class ImageAsEnumPage extends WebPage { public ImageAsEnumPage() { add(TEST1. String _name.newImage("i2"))._name.2 Inhaltselemente gesetzt. add(TEST3.TEST3("test3. public enum Images { TEST1("test1.. private Images(String name) { _name=name.komponenten.. <body> <img wicket:id="i1"><img wicket:id="i2"><img wicket:id="i3"> </body> .TEST2("test2.content.2.getLocale().get(). Eine Möglichkeit bestünde darin.java package de.50 ImagesAsEnumPage.komponenten. 7. dass es auf eine vorhandene Datei passt. } } Listing 7.thema.web. return new Image(id. Listing 7. Um zu vermeiden. dass sich die Aufrufe in der ganzen Anwendung verteilen.newImage("i1")).51 ImageAsEnumPage.new ResourceReference( \ getClass().wicketpraxis. wenn Wicket einen Pfad zu einer Datei benutzt.getStyle())).7. .web. } public Image newImage(String id) { Session session = Session. add(TEST2..java package de.newImage("i3")).49 Images. Dann findet Wicket vermutlich die eigentliche Datei nicht. . Für das folgende Beispiel erstellen wir alle Klassen im selben Paket.html .. 135 .basis. deckt nur einen Teil der Anforderungen an Webanwendungen ab.session.. eine andere Referenzklasse für eine ResourceReference zu benutzen.wicketpraxis.

res.. <body> <img wicket:id="ok"> <img wicket:id="hallo"> <img wicket:id="wicket"> </body> .wicketpraxis.komponenten.web. Grafiken dynamisch zu erzeugen. wie man die Angabe von Basisklassen und Pfadangaben vermeidet (Abbildung 7.BOLD. res.setFont(new Font("Helvetica".setArcWidth(20)."Hallo"))). Außerdem ist diese Variante erfreulich kompakt.16.5.res)).new DefaultButtonImageResource("Ok")))..html . DefaultButtonImageResource res = \ new DefaultButtonImageResource("Wicket").basis.setArcHeight(20).content.53 SimpleDynamicImages.new \ DefaultButtonImageResource(122. In diesem einfachen Beispiel greifen wir auf eine Komponente zurück. add(new Image("wicket". 32)).setColor(new Color(10. public class SimpleDynamicImages extends WebPage { public SimpleDynamicImages() { add(new Image("ok". 136 .. die einen grafischen Button erzeugt (Abbildung 7. res. res.2. add(new Image("hallo".setWidth(140).8).3 Dynamisch generierte Grafiken Wicket kann nicht nur statische Grafiken anzeigen. Font.9 Dynamisch erzeugte Grafiken Listing 7. Wicket bietet auch alle Möglichkeiten.128. Auch wenn der praktische Wert dieser Klasse gering sein mag. Sprechende Namen (also bessere als in unserem Beispiel) sorgen zusätzlich für Übersichtlichkeit.thema. res. kann man sich bei Eigenentwicklungen an der Implementierung von DefaultButtonImageResource orientieren.250)). res. } } Listing 7.7 Basiskomponenten Abbildung 7..9). 7.setTextColor(new Color(0.java package de.52 SimpleDynamicImages.0)).8 Zentral verwaltete Grafiken In diesem Beispiel kann man erkennen..0. . Abbildung 7..

} }). } }). zeigen die folgenden Beispiele. public class SimpleLinkPage extends WebPage { private Label _message. . add(_message). Welche Link-Klassen es gibt und wie man sie einsetzt..messageModel).links.new PageParameters("a=1"))). Möchte man eine Liste von Link-Komponenten verwalten oder soll der Rückgabewert einer Funktion eine Link-Komponente sein.54 SimpleLinkPage.class. Hinweis Die Basisklasse aller Links ist nicht die Klasse Link.basis.komponenten. } } 137 .wicketpraxis.3 Links Eine Webanwendung ohne Links ist schwer vorstellbar.1 Von A nach B Um den Nutzer von Seite A auf Seite B zu lenken.thema. sondern die Klasse AbstractLink. public SimpleLinkPage() { Model<String> messageModel = Model.class. wie dieses Ziel erreicht wird.SimpleLinkPage.web. } }).3 Links 7. Dabei gibt es eventuell kein Unterschied im Resultat.messageModel) { @Override public void onClick() { setModelObject("Link mit Model geklickt ("+new Date()+")"). add(new BookmarkablePageLink<AllLinkTypesPage>( \ "bookmarkLink". _message = new Label("message".new PageParameters("b=2")). AbstractLink als Rückgabetyp zu verwenden. add(new Link<String>("linkModel".3.of("Jetzt ist es "+new Date()). kann es notwendig werden. aber in der Art und Weise. add(new Link("noBookmarkLink") { @Override public void onClick() { setResponsePage(SimpleLinkPage.7.. 7. gibt es mehr als eine Möglichkeit.java package de. add(new Link<String>("link") { @Override public void onClick() { _message. Listing 7.setDefaultModelObject("Link geklickt ("+new Date()+")").

html .links.wicketpraxis. wenn der Nutzer bereits auf den Link geklickt hat und die onClick()-Methode aufgerufen wurde.komponenten.wicketpraxis--webapp/?wicket:bookmarkablePage= :de. unterscheidet sich der Link wesentlich: http://server/de.links.komponenten.B. sondern kann auch durch Suchmaschinen verarbeitet werden. <body> <span wicket:id="message"></span><br><br> <a wicket:id="bookmarkLink">Bookmarkable Link</a><br> <a wicket:id="noBookmarkLink">No Bookmarkable Link</a><br> <a wicket:id="link">Link</a><br> </body> . Da diese Information aber erst zur Verfügung steht. Der Link-Typ erwartet als Parameter eine Klasse vom Typ Page. Der Link mit der ID „linkModel“ unterscheidet sich vom Link „link“ dadurch. 138 .web. sodass dieser Seitenaufruf unabhängig von vorangegangenen Aktionen erlaubt ist. Im ersten Link ist keine sitzungsabhängige (Session) Information kodiert.55 SimpleLinkPage. gibt es Unterschiede z.3. die JavaScript deaktiviert haben.wicketpraxis--webapp/?wicket:bookmarkablePage= :de.7 Basiskomponenten Der erste Link ist ein BookmarkablePageLink.wicketpraxis--webapp/? wicket:interface=:1:noBookmarkLink::ILinkListener:: Wenn der Nutzer auf den Link klickt. 7. Außerdem ist es möglich. behandelt Abschnitt 11.. Der Link mit der ID „link“ demonstriert. Der Link ist also nicht nur als Lesezeichen verwendbar.SimpleLinkPage&b=2 Auch wenn das Ergebnis für den Nutzer an sich gleich bleibt. Ajax in einer Seite zu benutzen.. Es wird einfach eine Aktion ausgeführt und die Seite neu geladen. im Bezug auf die Suchmaschinentauglichkeit dieser Navigationsform. Der zweite Link springt ebenfalls eine Seite über die Klasse der Seite und einer optionalen Parameterliste an. ohne dass man Nutzer.. die an den Link angehängt wird.basis. die Webanwendung benutzen zu können. den sich der Nutzer als Lesezeichen abspeichern könnte: http://server/de. dass er dasselbe Modell wie das Label „message“ benutzt und daher den Modellwert direkt und damit typsicher setzen kann. Listing 7.thema..6. dass durch einen Klick auf einen Link nicht automatisch ein Seitenwechsel durchgeführt wird.basis. eine Parameterliste zu übergeben. Wicket erzeugt daraus einen Link. Wie man Wicket-Anwendungen für Suchmaschinen optimiert.web.SimpleLinkPage&a=1 Ein Klick auf den Link springt dann zu dieser Seite.wicketpraxis. dann landet er auf der Seite mit folgender URL: http://server/de. daran hindert.2 Ajax und Links Wicket bietet im Zusammenhang mit Ajax einen sehr komfortablen Weg.thema.

} catch (InterruptedException e) { e.printStackTrace().links. dann wurde der Link nicht per Ajax aufgerufen.addComponent(_message). public AjaxFallbackLinkPage() { _message = new Label("message". if (target!=null) { _message.setDefaultModelObject("Link geklickt ("+new Date()+")"). 139 .web.sleep(500). ob der Link per Ajax aufgerufen wurde. add(_message). . add(new IndicatingAjaxFallbackLink("ajax2") { @Override public void onClick(AjaxRequestTarget target) { timeConsumingTask().java package de.wicketpraxis. _message. public class AjaxFallbackLinkPage extends WebPage { private Label _message.setDefaultModelObject("Ajax Link geklickt ("+ new Date()+")").setDefaultModelObject("Ajax Link geklickt ("+ new Date()+")"). if (target!=null) { _message. und kann nur dann die Komponenten zur Aktualisierung vorschlagen..setOutputMarkupId(true).basis.addComponent(_message).setDefaultModelObject("Link geklickt ("+new Date()+")"). } } }). Man muss also prüfen. add(new AjaxFallbackLink("ajax") { @Override public void onClick(AjaxRequestTarget target) { _message. } } } Die Klasse AjaxFallbackLink unterscheidet sich von der Link-Klasse darin. target. dass der Methode onClick() ein AjaxRequestTarget übergeben wird.thema. Wenn der Parameter nicht gesetzt wurde.7. _message."Jetzt ist es "+new Date()). } } }). } private void timeConsumingTask() { try { Thread..komponenten.3 Links Listing 7. target.56 AjaxFallbackLinkPage.

<body> <span wicket:id="message"></span><br><br> <a wicket:id="ajax">AjaxFallbackLink</a><br> <a wicket:id="ajax2">AjaxFallbackLink mit Indikator</a><br> </body> . gibt es die Klassen AjaxLink und IndicatingAjaxLink.3.messageModel) { @Override public void onClick() { setModelObject("Trotzdem geklickt").58 LinkTrickPage. 7.3 Link-Tricks Normalerweise erwartet ein Link ein entsprechendes Link-Tag im Markup.messageModel))... das dem Nutzer darstellen soll... } } Listing 7. wird der Link in einen JavaScript-Aufruf umgewandelt.html . } }).java package de. Für den Fall.links. dass die Antwort noch auf sich warten lässt. bevor es dann weitergeht. 140 . <body> <span wicket:id="message"></span><br><br> Der <span wicket:id="span"><i>Link</i></span> versteckt sich im Text... dass man Links benutzen möchte. die nur den Ajax-Aufruf tätigen. . wartet die Methode timeConsumingTask() einfach nur eine halbe Sekunde.komponenten.basis. Damit das in diesem Beispiel funktioniert.html .59 LinkTrickPage. Listing 7.. Sobald die Antwort vom Server geliefert wurde. add(new Link<String>("span".of(""). wird das Symbol entfernt.. Wenn man die Komponente aber an ein anderes Tag bindet. add(new Label("message". In der onClick()-Methode ist der Parameter AjaxRequestTarget dann immer gesetzt und muss daher nicht mehr geprüft werden.web.57 AjaxFallbackLinkPage.. Listing 7. </body> ..7 Basiskomponenten Die Klasse IndicatingAjaxFallbackLink zeigt nach dem Klick ein Symbol an.thema. public class LinkTrickPage extends WebPage { public LinkTrickPage() { Model<String> messageModel = Model.wicketpraxis.

"mailto:michael@mosmann.3. sieht man im Quelltext.. wie Wicket den zweiten Link umgewandelt hat: .60 ExternalLinkPage. 141 .de</a><br> <a wicket:id="mail">michael@mosmann.} \ . im Markup einen Link einzubinden.href='?wicket:interface=:0:span::ILinkListener::'..B.. muss man den JavaScript-Aufruf nicht selbst erstellen.10).de")). ohne dass Wicket diesen Link anpassen würde. </body> .ownerDocument.de")).thema.basis. auch in diesem Fall eine passende Komponente zu benutzen.4 Externe Links Es ist jederzeit möglich.de</a><br> </body> .3...defaultView || \ this. Der Nachteil dieser Methode liegt darin. reduziert sich auf das Setzen der notwendigen Einstellungen.7..ownerDocument. add(new ExternalLink("mail".3 Links Wenn man die Seite aufruft. if (win == window) { \ window. Man könnte z. auch wenn das für das Ergebnis keinen Unterschied macht.location. . Es ist ja auch kein Link.return false"><i>Link</i></span> versteckt sich im Text. dass er auf den Text klicken kann.parentWindow. Doch ich empfehle. public class ExternalLinkPage extends WebPage { public ExternalLinkPage() { add(new ExternalLink("external".java package de.komponenten. auf diese Weise einen Link zu einer anderen Webseite einbinden. <body> <a wicket:id="external">wicket-praxis."http://www..html . dass sich der Mauszeiger nicht ändert und der Nutzer nicht sieht. 7.61 ExternalLinkPage.. Ob ein Link in einem Popup geöffnet werden soll (Abbildung 7. } } Listing 7. 7.web. <body> <span wicket:id="message"></span><br><br> Der <span wicket:id="span" \ onclick="var win = this. Die ExternalLinkKlasse schreibt den Wert des zweiten Parameters direkt in das Attribut href.wicketpraxis.. Listing 7.links..wicket-praxis.5 Popups Um mit Wicket ein Popup zu öffnen.

PopupPage.setWidth(300).. Wie man an diesem Beispiel sieht..setPopupSettings(popupSettings).10 Verschiedene Popup-Fenster Listing 7.7 Basiskomponenten Abbildung 7.thema.setPopupSettings(popupSettings)..komponenten. } }. add(newWindowLink). } } Listing 7. popupLink.TOOL_BAR).web.RESIZABLE|PopupSettings.basis.class). BookmarkablePageLink<PopupPage> popupLink = \ new BookmarkablePageLink<PopupPage>("popup". Man hat dabei Zugriff auf 142 ..class). Link popupLink2 = new Link("popup2") { @Override public void onClick() { setResponsePage(PopupPage. <body> <a wicket:id="popup">Popup</a><br> <a wicket:id="popup2">Popup2</a><br> <a wicket:id="newWindow" target="_blank">newWindow</a><br> </body> . add(popupLink).63 PopupLinkPage. popupSettings. popupSettings..setHeight(300).links.class).html .PopupPage. popupSettings. . popupLink2. public class PopupLinkPage extends WebPage { public PopupLinkPage() { PopupSettings popupSettings = new \ PopupSettings(PopupSettings. kann man ein Popup-Fenster sowohl mit einem BookmarkablePageLink als auch mit einem normalen Link öffnen.setWindowName("Popup").62 PopupLinkPage.wicketpraxis.java package de.. add(popupLink2). BookmarkablePageLink<PopupPage> newWindowLink = \ new BookmarkablePageLink<PopupPage>("newWindow".

. der dieses Popup wieder schließt.getResource( \ "/"+ getClass().komponenten.java package de.66 ResourceLinksPage. \ new File(resourceURI.web.. .3.65 PopupPage. 7.. Listing 7.6 ResourceLink Wenn man dem Nutzer die Möglichkeit geben möchte. \ new ResourceReference(ResourceLinksPage. Der letzte Link zeigt. } } Listing 7.links..10).wicketpraxis. add(new DownloadLink("file". als PDF herunterladen kann.class.replace('. kann man auf zwei Komponenten zurückgreifen.gif")). } } 143 . nur ist es so natürlich viel einfacher.B.separatorChar)+ "/images/test.gif"))). <body> <a wicket:id="close">Schliessen</a><br> <div style="width:280px.64 PopupPage.toURI()).getName(). public class ResourceLinksPage extends WebPage { public ResourceLinksPage() throws URISyntaxException { add(new ResourceLink("resource". Auf der Seite.gif"). public class PopupPage extends WebPage { public PopupPage() { add(new PopupCloseLink("close"))."test.3 Links alle Einstellungen. font-size:50px.java package de..wicketpraxis. \ background-color:#ffff88.getPackage().basis.thema. Listing 7. die dann als Popup geöffnet wird.. border:1px solid black.'."images/test.basis. URL resourceURI = getClass(). File..web. height:240px.thema. dass die Zielseite eine ganz normale Wicket-Seite ist."> Popup </div> </body> ..komponenten.links.7. dass er sich Informationen z.html . die über einen Link auch einfach in einem neuen Fenster (target="_blank") geöffnet werden kann (Abbildung 7.. binden wir einen Link ein. die man auch per JavaScript ansprechen kann.

4. was zur Folge hat. In Wicket kann eine Komponente durch ein (oder mehrere) Behavior verändert werden. vor und nach der Komponente oder beim Darstellen des HTML-Tags einzugreifen. dass ein Attribut im HTML-Tag einen anderen Wert hat. 7. steht der Aufwand in keinem Verhältnis zum Nutzen. Spätestens wenn man dieses Attribut bei einer ganzen Reihe von Komponenten anpassen müsste. die z.. Dadurch ist die PageMap blockiert. Außerdem sind sie flexibler in der Anwendung. erst erzeugen müsste.4 erklärt. Entweder erzeugt man eine Variante der Markup-Datei oder überschreibt die Methode onComponentTag().7 Formularlinks Die zwei Linkklassen SubmitLink und AjaxSubmitLink werden in Abschnitt 9.B. die während des Verarbeitungsprozesses aufgerufen werden.3. Dazu muss ein Behavior mehrere Methoden implementieren..html . So ist es möglich. 7.7 Basiskomponenten Listing 7.67 ResourceLinkPage.1 Darf es etwas JavaScript sein? Im ersten Beispiel machen wir nur von der Möglichkeit Gebrauch.4 Behavior Wenn sich zwei Komponenten nur dadurch unterscheiden. Der DownloadLink übermittelt die Datei in derselben Anfrage. die man evtl. kein weiterer Download auf der Seite gestartet werden kann. Daher empfiehlt es sich. <body> <a wicket:id="resource">Resource Link</a><br> <a wicket:id="file">Download Link</a><br> </body> .. auch als Image eingebunden werden könnte. Ein Klick auf den Link zeigt dann die Ressource an..7). 7. da man Ressourcen auch zur Laufzeit erzeugen kann (siehe Abschnitt 11. da diese einfacher zu benutzen sind und die Zugriffe die Anwendungen nicht blockieren. dem HTML-Tag der Komponente ein weiteres Attribut hinzuzufügen: 144 . immer einen ResourceLink zu benutzen. Der ResourceLink setzt das Attribut „href“ des Links einfach mit der URL einer Ressource. in welcher der Klick ausgewertet wird. kann man das auf verschiedenen Wegen lösen. da sie sinnvollerweise in Formularen benutzt werden. solange der andere Download noch nicht fertig ist. dass z.B. Außerdem benötigt der DownloadLink eine Datei.

Listing 7. "this.html . Wenn die Komponente dargestellt wird.wicketpraxis. <body> <span wicket:id="message"></span> </body> .70 Ergebnis. <body> <span wicket:id="message" \ onmouseup="this.put("onmouseup".4 Behavior Listing 7. 7.68 SimpleBehaviorPage... wird in unserem Behavior unter anderem die Methode onComponentTag() aufgerufen.innerHTML = '"+_content+"'"). } } } Dem Label wird unser OnMouseUpInnerHtmlBehavior hinzugefügt.html . demonstriert dieses Beispiel: 145 . der den Inhalt des Labels mit einem neuen Text versieht. \ add(new OnMouseUpInnerHtmlBehavior("neuer Text"))). public class SimpleBehaviorPage extends WebPage { public SimpleBehaviorPage() { add(new Label("message".7.innerHTML = 'neuer Text'">Text</span> </body> ..4.java package de. Listing 7. } @Override public void onComponentTag(Component component. .. einfach nur ein Attribut im HTMLTag der Komponente anpassen. } static class OnMouseUpInnerHtmlBehavior extends AbstractBehavior { String _content...behaviors. wie in der Einleitung bereits geschildert.. ComponentTag tag) { tag.69 SimpleBehaviorPage. public OnMouseUpInnerHtmlBehavior(String content) { _content=content. In der Methode setzen wir das onmouseup-Attribut auf eine Zeile JavaScript-Code. Welche vorgefertigten Möglichkeiten es gibt..2 Attribute anpassen Oft muss man..web."Text").thema..komponenten. Beim Klicken und Loslassen der Maustaste (onmouseup) wird dann die JavaScript-Funktion ausgeführt und der Text ersetzt.

behaviors. betrachten wir das Markup der Seite: Listing 7. public class AttributeModifierPage extends WebPage { public AttributeModifierPage() { add(new WebMarkupContainer("div1").komponenten. border:2px solid black"> Das ist der dritte Block </div> </body> </html> Die Klasse AttributeModifier ersetzt den Wert des angegebenen Attributs durch den neuen Wert.add(new SimpleAttributeModifier( \ "style". Dazu muss man ein Trennzeichen definieren.add(new AttributeModifier( \ "style". Wenn man z.of("border-left:2px solid red").thema. das zwischen dem alten und dem neuen Wert eingefügt wird.“ beim Attribut „class“ ist es ein einfaches Leerzeichen..html <html> <head> <title>SimpleBehavior Page</title> <style>div { margin:4px."border-right:2px solid red.7 Basiskomponenten Listing 7. add(new WebMarkupContainer("div2"). Die Klasse SimpleAttributeModifier verzichtet darauf.72 AttributeModifierPage.B. border:1px dotted black"> Das ist der erste Block </div> <div wicket:id="div2" style="color:red.true. } } Um zu veranschaulichen."))). }</style> </head> <body> <div wicket:id="div1" style="color:red. Daher macht diese Klasse dann Sinn. add(new WebMarkupContainer("div3").11 Komponenten mit angepassten Attributen Die Klasse SimpleAttributeModifier macht es sich einfach und ersetzt den Wert eines Attributs durch einen neuen. aber auch im Quelltext der Seite kann man erkennen. Abbildung 7.".wicketpraxis. . In Abbildung 7.add(new AttributeAppender( \ "style".of("border:2px solid red.71 AttributeModifierPage. was verändert wurde: 146 . das Attribut „style“ anpasst.")))).Model. Die Klasse AttributeAppender kann an ein bereits vorhandenes Attribut eigene Daten anhängen. wenn man schon im Vorfeld weiß.java package de. ist das Trennzeichen ein „. welche Anpassung durchzuführen ist.Model. wo der Unterschied der drei Varianten liegt. border:1px dotted black"> Das ist der zweite Block </div> <div wicket:id="div3" style="color:red.11.web. den Wert aus einem Modell zu ermitteln..true."))).

add(new AttributeAppender("style".Model..of( \ "border-top:2px solid yellow"). sodass Kollisionen ausgeschlossen werden können..")).true..true.of( \ "border-left:2px solid red").wicketpraxis.3 Attribute erweitern Wenn man mehr als ein Behavior für eine Komponente benutzt..Model.73 Ergebnis. div.". border:1px dotted black. dass das gleiche Attribut mehrfach verändert wird.4 Behavior Listing 7. . Listing 7. add(div).of( \ "border-right:2px solid green").")).. <body> <div wicket:id="div"> 147 .komponenten.")).of( \ "border-bottom:2px solid blue"). div.thema..add(new AttributeAppender("style". div. div.add(new AttributeAppender("style".add(new AttributeAppender("style".html ."> Das ist der dritte Block </div> .7.java package de. public class AttributeAppenderPage extends WebPage { public AttributeAppenderPage() { WebMarkupContainer div = new WebMarkupContainer("div"). Listing 7. Somit kann man unabhängige Behavior-Klassen entwickeln.".".behaviors.74 AttributeAppenderPage. Dabei werden nicht nur die bestehenden Werte aus dem Markup berücksichtigt.75 AttributeAppenderPage.html . 7..web.". sondern es wird ebenso auf die letzte Änderung zurückgegriffen.")).4.. sollte man in solchen Fällen die Klasse AttributeAppender benutzen.true. <div wicket:id="div1" style="border:2px solid red.border-left:2px solid \ red"> Das ist der zweite Block </div> <div wicket:id="div3" style="border-right:2px solid red. } } Vier AttributeAppender modifizieren nacheinander das Attribut style und hängen jeweils für eine Seite eine CSS-Definition für den Rahmen an."> Das ist der erste Block </div> <div wicket:id="div2" \ style="color:red. Damit alle Veränderungen in das Endergebnis einfließen. dann kann es passieren.true.Model.Model.

html . 148 . add(uhr).wicketpraxis."></span> </body> . 7. die den Umgang mit Formularen vereinfachen und die Anwenderfreundlichkeit (Usability) verbessern können (siehe Abschnitt 9.. möchten wir an dieser Stelle nur noch eine Klasse betrachten. uhr.html .border-bottom:2px solid blue"> Das ist der Block </div> .setOutputMarkupId(true).add(new AjaxSelfUpdatingTimerBehavior(Duration. <div wicket:id="div" style="border-left:2px solid red.76 Ergebnis. Label uhr=new Label("uhr".komponenten. die eine Komponente nach einer festen Zeitspanne per Ajax aktualisiert...78 AjaxUpdatingPage.border-top:2px solid yellow.. Listing 7..behaviors.border-right:2px \ solid green...77 AjaxUpdatingPage. Wenn man die Seite aufruft. public class AjaxUpdatingPage extends WebPage { public AjaxUpdatingPage() { LoadableDetachableModel<String> uhrModel= \ new LoadableDetachableModel<String>() { @Override protected String load() { return "Mit dem Zeitzeichen ist es genau "+new Date(). Um dem nicht vorwegzugreifen.thema. Listing 7. .4.. <body> <span wicket:id="uhr" style="font-size:20px. wird ohne eigenes Zutun die Uhrzeit aktualisiert. } } Listing 7. uhr.ONE_SECOND)).web. } }. Im Ergebnis sehen wir..uhrModel).13).... dass jede der CSS-Definitionen korrekt eingebunden wurde.4 Ajax und Formulare Gerade im Zusammenhang mit Formularen stehen Funktionen bereit.7 Basiskomponenten Das ist der Block </div> </body> .java package de.

Für jede Komponente gab es die passende ID und die Stelle. Das können Menüeinträge. Dabei geht es nicht nur um Ergebnislisten. Dieser Komponente fügt man die darzustellenden Kindelemente mit einer durch die Komponente erzeugten ID hinzu. Listing 8.1 RepeatingViewPage. was mehr als einmal vorhanden ist und sich z.web.komponenten. diese aber um so intensiver einsetzen.. da die Anzahl der Elemente meist unbekannt ist. Wenn man eine Liste von Werten oder Elementen darstellen möchte.repeater. aber nicht durch die Struktur unterscheidet.wicketpraxis. Prozessschritte oder eine Liste von Eingabefeldern in Formularen sein. wie wir diese Aufgabenstellung mit Bordmitteln lösen können. dann funktioniert dieser Ansatz nicht. Im ersten Abschnitt geht es nur darum. Dabei hängt es oft von der zu lösenden Aufgabe ab. public class RepeatingViewPage extends WebPage 149 . die Wicket für die Darstellung von Listen bietet. sondern um alles. Im zweiten Teil zeigen wir einen veränderlichen Ausschnitt einer Liste an. welcher Komponente der Vorzug zu gewähren ist.java package de.thema.1 RepeatingView Die einfachste Komponente für die Listendarstellung ist die RepeatingView. durch den Inhalt.1. wo sie im Markup referenziert wurde.8 8 Listen und Tabellen Listen kommen in Webanwendungen häufiger vor. wird man in der Praxis nur wenige.B.1 Darstellung von Listen Von den unterschiedlichen Möglichkeiten. .. als man denkt. Bisher waren alle Komponenten vorher bekannt. alle Elemente einer Liste darzustellen. Auf den folgenden Seiten zeige ich unterschiedliche Ansätze.basis. 8. 8.

Die RefreshingView-Komponente erzeugt daher die Kindkomponenten bei jeder Darstellung neu. wenn die Komponente erzeugt wird. Vielmehr wird anstelle der RepeatingView das Kindelement mit dem Markup der RepeatingView dargestellt.html .komponenten. Daher ändert sich die Anzeige nicht.web.2 RefreshingView Die Elemente der RepeatingView werden einmal erzeugt..3 Ergebnis. Listing 8.basis.. } } Die dynamisch erzeugte ID für die Kindkomponente wird im Markup nicht referenziert. Im Quelltext der Ergebnisseite können wir alle 10 Kindelemente erkennen."Textteile").4 RefreshingViewPage.. } add(repeatingView)..1.2 RepeatingViewPage.thema. Veränderungen in den Ursprungsdaten führen dann natürlich zu Veränderungen in der Darstellung. <body> <ul> <li wicket:id="list"></li> </ul> </body> ....wicketpraxis.newChildId(). public RefreshingViewPage() { 150 .asList("Das". Listing 8. public class RefreshingViewPage extends WebPage { List<String> _texte = Arrays.i<10. "Zeile "+i))."sind".i++) { repeatingView.add(new Label(repeatingView. for (int i=0.repeater.. 8. .java package de. wenn zugrunde liegende Daten geändert wurden. <body> <ul> <li wicket:id="list">Zeile 0</li><li wicket:id="list">Zeile 1</li> <li wicket:id="list">Zeile 2</li><li wicket:id="list">Zeile 3</li> <li wicket:id="list">Zeile 4</li><li wicket:id="list">Zeile 5</li> <li wicket:id="list">Zeile 6</li><li wicket:id="list">Zeile 7</li> <li wicket:id="list">Zeile 8</li><li wicket:id="list">Zeile 9</li> </ul> </body> . Listing 8...8 Listen und Tabellen { public RepeatingViewPage() { RepeatingView repeatingView = new RepeatingView("list").html .

. add(new Link("link") { @Override public void onClick() { _texte=Arrays.5 RefreshingViewPage. } })..item.1 Darstellung von Listen add(new RefreshingView<String>("list") { @Override protected Iterator<IModel<String>> getItemModels() { return new ModelIteratorAdapter<String>(_texte. da die RepeatingView-Komponente eine Hilfskomponente (Item) einfügt.html . } @Override protected void populateItem(Item<String> item) { item."Text"). } }). Listing 8.8."ist". dass man mehr als eine Komponente hinzufügen kann."ein".. an die wir die eigenen Komponenten anbinden.asList("Das".html ...add(new Label("label". Listing 8.of(object). und in der Methode populateItem() werden die Kindkomponenten erzeugt. Ein Klick auf den Link tauscht die Daten für die Darstellung aus.getModelObject())).. <body> <table> <tr wicket:id="list"> <td> <span wicket:id="label">Das</span> 151 ."anderer". } }. <body> <table> <tr wicket:id="list"> <td> <span wicket:id="label"></span> </td> </tr> </table> <a wicket:id="link">Textliste ändern</a> </body> .iterator()) { @Override protected IModel<String> model(String object) { return Model. } } In der Methode getItemModels() wird die Liste der Elemente erzeugt. Das bedeutet gleichzeitig. Im Gegensatz zur RepeatingView muss hier die Kindkomponente im Markup referenziert werden.6 Ergebnis.

"Textliste")) { @Override protected void populateItem(ListItem<String> item) { String text = item.substring(1))). 152 . } }).java package de.thema.3 ListView Die ListView-Komponente unterscheidet sich von der RepeatingView vor allem dadurch. sodass wir über item. \ Arrays.substring(0.8 Listen und Tabellen </td> </tr><tr wicket:id="list"> <td> <span wicket:id="label">sind</span> </td> </tr><tr wicket:id="list"> <td> <span wicket:id="label">Textteile</span> </td> </tr> </table> <a wicket:id="link" \ href="?wicket:interface=:1:link::ILinkListener::"> Textliste ändern</a> </body> . public class ListViewPage extends WebPage { public ListViewPage() { add(new ListView<String>("list".. .add(new Label("part2". dann werden die neuen Texte angezeigt. Man kann den Eintrag der Liste auch als Modell an die Kindkomponenten weiterreichen. Die Komponente erwartet als zweiten Parameter ein Objekt vom Typ IModel<List> oder List.asList("Das".getModelObject() direkt auf das Element der Liste zugreifen können..repeater.text. item. } } Für jedes Element der Liste wird die Methode populateItem() mit einem neuen ListItem aufgerufen. wobei ein Objekt vom Typ List intern in ein IModel<List> umgewandelt wird.. item.wicketpraxis. Listing 8.web.komponenten. In diesem Beispiel schneiden wir von jedem Element den ersten Buchstaben ab und erzeugen mit diesem ersten Buchstaben und dem Rest jeweils eine Komponente.getModelObject(). Klickt man dann auf den Link.add(new Label("part1".text. indem man nur item.7 ListViewPage.1. "eine". 8..getModel() aufruft und diesen Wert als Aufrufparameter benutzt. dass man die Daten schon als Liste übergibt. Auf diese Weise kapselt die Komponente den Zugriff auf die darunter- liegende Liste. 1))).basis. "ist".

8. String name.10 Kunde.io.thema.wicketpraxis. Dadurch kann die Angabe eines expliziten Modells entfallen.4 PropertyListView Die Komponente PropertyListView ist von ListView abgeleitet und ersetzt das Modell.1 Darstellung von Listen Listing 8. public Kunde(String vorname. durch ein CompoundPropertyModel. Vorname und Alter besitzt. } Wir fügen in unserem Beispiel zwei Elemente vom Typ Kunde in eine Liste und kapseln diese Liste in einem Modell. _name = name. import java.1. 153 ..getName(). Für dieses und folgende Beispiele erstellen wir eine einfache Klasse Kunde. } getVorname(). wenn die Komponenten-ID gleichzeitig ein vorhandenes Attribut des Listenelements adressiert. Listing 8. <body> <table> <tr wicket:id="list"> <td> <strong><span wicket:id="part1"></span></strong> \ <span wicket:id="part2"></span> </td> </tr> </table> </body> </html> Da wir den ersten Buchstaben in Fettdruck dargestellt haben. erhalten wir folgendes Ergebnis. _geburtsjahr=geburtsjahr.java package de.. int _geburtsjahr. Listing 8..web..9 Ergebnis Das ist eine Textliste 8.8 ListViewPage.int geburtsjahr) { _vorname = vorname. welche die Attribute Name.html .komponenten.basis. String _vorname.Serializable.getGeburtsjahr(). public class Kunde implements Serializable { String _name. das an das ListItem gebunden ist.repeater.

getModel()..basis.12 PropertyListViewPage.java package de. Listing 8. ist also recht einfach.1967)).add(new Label("vorname")).repeater.basis.C> extends ListView<R> { IModel<? extends List<? extends C>> _columns.. . erzeuge ich pro Eintrag zwei Komponenten. Dabei entspricht das Resultat dieser Konstellation dem zweiten Aufruf. add(new PropertyListView<Kunde>("list". Dazu leiten wir eine eigene Komponente von ListView ab und ergänzen diese entsprechend. wie man einfach eine Tabelle darstellt.. Das folgende Beispiel soll veranschaulichen.. 8."Müller".repeater. Model.wicketpraxis.web."name"))). \ new PropertyModel<String>(item. Listing 8.wicketpraxis.thema. kann der Einsatz einer PropertyListView-Komponente Schreibarbeit reduzieren.8 Listen und Tabellen Listing 8.. .html . Kurz: Solange man mit einfachen Zugriffen auf Attribute eines Listenelements auskommt.komponenten.13 ColumnListView.1973).asList(new Kunde("Klaus". } })..11 PropertyListViewPage.of(liste)) { @Override protected void populateItem(ListItem<Kunde> item) { item. } } Um den Unterschied deutlich zu machen. indem man zwei Listen kombiniert..web.thema. 154 . wo ein explizites PropertyModel benutzt wird.. Das erste Label bekommt kein Modell als Parameter.komponenten. \ new Kunde("Hans".java package de.add(new Label("name". public class PropertyListViewPage extends WebPage { public PropertyListViewPage() { List<Kunde> liste = Arrays. public abstract class ColumnListView<R. <body> <table> <tr wicket:id="list"> <td> <span wicket:id="vorname"></span> </td> <td> <span wicket:id="name"></span> </td> </tr> </table> </body> . item.1."Meier".5 ColumnListView Elemente einer Liste darzustellen.

müsste man sicherstellen.2."B". Würde man eine Komponente in der Zeile hinzufügen. die wir dann in der überschriebenen populateItem-Methode benutzen..thema.8.populateItem(row.column). } }). rows). \ ListItem<C> column).of(Arrays.String>("rows". In dieser Methode rufen wir dann die zu überschreibende Methode populateItem auf. dass dieser Aufruf nur einmal pro Zeile erfolgt._columns) { @Override protected void populateItem(ListItem<C> column) { ColumnListView. \ String columnId.repeater. die jeweils den Wert der Zeile und den Wert der Spalte anzeigen. die aber in dem Fall beide ListItem-Komponenten als Parameter übergeben bekommt.basis. } @Override protected void populateItem(final ListItem<R> row) { row.java package de. \ ListItem<String> column) { 155 . \ IModel<? extends List<? extends R>> rows. Listing 8."C")).add(new ListView<C>(_columnId. } Wir benutzen als Parameter neben der Komponenten-ID und dem Modell für die ListView-Komponente zwei weitere Parameter. public class ColumnListViewPage extends WebPage { public ColumnListViewPage() { IModel<List<? extends Integer>> rows=Model.web. IModel<List<? extends String>> columns= \ Model.asList(1. dass man Komponenten nur zum ListItem der Spaltenliste hinzufügt.3)).1 Darstellung von Listen String _columnId. In dem Fall fügen wir zwei Label hinzu. public ColumnListView(String id.rows. .columns) { @Override protected void populateItem(ListItem<Integer> row. IModel<? extends List<? extends C>> columns) { super(id.14 ColumnListViewPage.wicketpraxis. _columnId=columnId. Wichtig ist dabei. Wir fügen für jede Zeile eine eigene ListView-Kompo- nente ein und überschreiben auch hier die populateItem-Methode. \ "columns".asList("A". } protected abstract void populateItem(ListItem<R> row.komponenten.of(Arrays. da die Methode für jede Spalte in jeder Zeile aufgerufen wird. Die Anwendung ist dann wieder recht einfach: Ich habe zwei Listen und möchte für jede Zeile und Spalte ein Ergebnis anzeigen.. Diese Komponentenklasse bietet also noch reichlich Potential für Verbesserungen. add(new ColumnListView<Integer. _columns=columns.this.

einen Ausschnitt der Gesamtliste abzufragen und die Länge der Liste zu ermitteln.. zum anderen kann die Menge unter Umständen eine Herausforderung darstellen.row.repeater. public class DataViewPage extends WebPage { public DataViewPage() { IDataProvider<String> data=new IDataProvider<String>() { public Iterator<? extends String> iterator(int first. <body> <table> <tr wicket:id="rows"> <td wicket:id="columns"> <span wicket:id="row"></span><span wicket:id="column"></span> </td> </tr> </table> </body> . Zum einen leidet die Übersichtlichkeit. die es einer Komponente ermöglicht.column.i<count.15 ColumnListViewPage. Die Methode model() muss überschrieben werden und ein Modell für das Listenelement zurückgeben. Dabei ist zu erkennen.add(new Label("column". Das IDataProvider-Interface definiert eine Abstraktion. wie man aus einfachen Komponenten ohne großen Aufwand wesentlich komplexere Darstellungsmöglichkeiten schaffen kann.add(new Label("row".html . Listing 8.2.2 DataProvider Selten möchte man alle Daten auf einmal darstellen. } }). .8 Listen und Tabellen column. Anstelle einer Liste wird allerdings ein DataProvider benutzt.getModel())). int count) { List<String> tempList=new ArrayList<String>(). dass die Liste für die Zeilen und die Spalten an das passende HTML-Tag gebunden werden. for (int i=0. 8..16 DataViewPage.basis.web.. Dieses Beispiel veranschaulicht sehr eindrucksvoll.java package de. 8.1 DataView Die DataView-Komponente ähnelt der ListView-Komponente.getModel())).... } } Listing 8. Wir erzeugen bei unserer Implementierung der IDataProvider-Schnittstelle die Daten generisch und legen die Anzahl der Einträge auf die willkürliche Größe von 124 fest.i++) 156 .thema.komponenten.wicketpraxis. column.

. Listing 8. Wenn man die nächsten 10 Einträge darstellen möchte.of(object).. add(new Link("link") { @Override public void onClick() { int page = dataView.. } public void detach() { /* hier nicht nötig */ } }. 157 . add(dataView). } }).10) { @Override protected void populateItem(Item<String> item) { item.iterator(). } }.item.17 DataViewPage..getModel())).html . <body> <table> <tr wicket:id="list"> <td> <span wicket:id="label"></span> </td> </tr> </table> <a wicket:id="link">Nächste Seite</a> </body> . } public int size() { return 124. final DataView<String> dataView = \ new DataView<String>("list".2 DataProvider { tempList. if (page<dataView. Die Komponente zeigt daraufhin die nächsten 10 Datensätze an.data. } public IModel<String> model(String object) { return Model.8. In unserem Beispiel erhöht ein Klick auf den Link die Seitenzahl um 1.getCurrentPage()+1.getPageCount()) dataView. } return tempList.setCurrentPage(page).add("Position "+(i+first)). kann man die Seitenzahl der Komponente erhöhen. } } Die DataView-Komponente stellt in dem Beispiel die ersten 10 Einträge dar.add(new Label("label".

repeater.2. sodass 9 Elemente dargestellt werden können.data) { @Override protected void populateItem(Item<String> item) { item. } return tempList.18 GridViewPage."Leer")). } public void detach() { /* hier nicht nötig */ } }.add("Position "+(i+first)).add(new Label("label".basis. und die nächsten 9 Elemente werden dargestellt. Wie im vorherigen Beispiel erhöht der Klick auf den Link den Seitenindex.thema.2 GridView Wie einfach man dieselben Daten in einer anderen Art und Weise darstellen kann.getModelObject())).i<count.of(object).8 Listen und Tabellen 8. Die Anzahl der Spalten und der Zeilen setzen wir auf 3.java package de. zeigt die GridView-Komponente (Abbildung 8. } public int size() { return 25. for (int i=0. add(gridView). int count) { List<String> tempList=new ArrayList<String>(). Dabei werden die Daten in einer Tabelle dargestellt. final GridView<String> gridView = new GridView<String>("list".web. . } }. } @Override protected void populateEmptyItem(Item<String> item) { item.item. } public IModel<String> model(String object) { return Model.add(new Label("label".wicketpraxis. gridView.i++) { tempList.1).setRows(3). Listing 8.setColumns(3). gridView. add(new Link("link") { @Override public void onClick() { 158 ..komponenten..iterator(). public class GridViewPage extends WebPage { public GridViewPage() { IDataProvider<String> data=new IDataProvider<String>() { public Iterator<? extends String> iterator(int first.

kann man populateEmptyItem überschreiben. Wenn für die Darstellung einer Spalte kein Datenelement zur Verfügung steht.wicketpraxis.3 DataGridView Bisher mussten wir die Anzeige der Daten immer innerhalb einer populateItem-Methode implementieren.data.java package de..html .B.markup. Doch anders als bei unseren Komponente besteht die Liste von Spalten nicht aus Daten.2). } }). sondern aus Hilfsklassen. Listing 8. import org.getCurrentPage()+1.grid. die in die Darstellung einfließen sollen (Abbildung 8.getPageCount()) gridView. Der Ansatz ist für die tabellarische Darstellung von Datensätzen nicht besonders gut geeignet. Listing 8..8.setCurrentPage(page). um z. welche die Komponenten für die Darstellung der Spalten erzeugen.2 DataProvider int page = gridView..web.basis.repeater. Ähnlich unserer ColumnListView-Komponente benutzt die DataGridView-Komponente eine Liste von Spalten.*. ist fest mit „cols“ definiert und kann nicht geändert werden. bei der die GridView-Komponente auf eine RepeatingView zurückgreift..apache. public class DataGridViewPage extends WebPage { public DataGridViewPage() { 159 .komponenten.thema...extensions. .repeater. eine alternative Darstellung zu ermöglichen.20 DataGridViewPage.2. } } Die ID der Spaltenkomponente.html.19 GridViewPage. <body> <table> <tr wicket:id="list"> <td wicket:id="cols"> <span wicket:id="label"></span> </td> </tr> </table> <a wicket:id="link">Nächste Seite</a> </body> .1 GridView 8. if (page<gridView.wicket. Abbildung 8.

Die Komponenten-ID für die Spalten und den Spalteninhalt ist mit „cells“ und „cell“ ebenfalls fest innerhalb der Komponente verankert.B. Wir erstellen außerdem eine eigene Implementierung.i<6.html .setRowsPerPage(3). } ListDataProvider<Kunde> data=new ListDataProvider<Kunde>(liste).of(kunde.getCurrentPage()+1. die ein Label mit dem Geburtsjahr des Kundendatensatzes erstellt.. <body> <table> <thead> <tr> <th>Vorname</th> <th>Name</th> <th>Geburtsjahr</th> </tr> </thead> <tbody> <tr wicket:id="list"> 160 .i++) { liste. cells. \ Model. IModel<Kunde> rowModel) { Kunde kunde = rowModel. die z. for (int i=1.add(new ICellPopulator<Kunde>() { public void populateItem(Item<ICellPopulator<Kunde>> cellItem. add(new Link("link") { @Override public void onClick() { int page = datagrid. Listing 8. } }). cells. } public void detach() {} }). add(datagrid). die alle das Interface ICellPopulator implementieren.add(new Kunde("Albrecht". cellItem. List<Kunde> liste = new ArrayList<Kunde>(). cells.add(new PropertyPopulator("Name")).add(new PropertyPopulator("Vorname")).21 DataGridViewPage.getGeburtsjahr()))).data).".getPageCount()) datagrid."von Klausewitz der "+i+". final DataGridView<Kunde> datagrid = \ new DataGridView<Kunde>("list".add(new Label(componentId. Auch hier gibt es vorgefertigte Implementierungen.setCurrentPage(page).cells. if (page<datagrid. ein Attribut des Elements referenzieren..8 Listen und Tabellen List<ICellPopulator<Kunde>> cells=new \ ArrayList<ICellPopulator<Kunde>>(). datagrid.getObject(). \ String componentId. } } Daher erzeugen wir als Erstes eine Liste von Spaltendefinitionen. \ 1381+i*69)).

wicketpraxis.i<7. public class DataTablePage extends WebPage { public DataTablePage() { List<Kunde> liste=new ArrayList<Kunde>(). IColumn[] columns= { new PropertyColumn<Kunde>(Model.wicket. Der Unterschied besteht darin.komponenten..22 DataTablePage.web.*. new PropertyColumn<Kunde>(Model.repeater. }.2 DataGridView Es fällt auf."Klapper der "+i+".1381+i*69)).2.table. Außerdem ist es sehr aufwendig.2 DataProvider <td wicket:id="cells"> <wicket:container wicket:id="cell"></wicket:container> </td> </tr> </tbody> </table> <a wicket:id="link">Nächste Seite</a> </body> .i++) { liste."geburtsjahr"). new PropertyColumn<Kunde>(Model.markup.repeater..8."vorname").html. for (int i=1.apache. Genau dafür setzen wir die nächste Komponente ein. sodass ohne großen Aufwand eine paginierbare Datensatzansicht erzeugt werden kann (Abbildung 8.thema. Listing 8.. Außerdem gibt es bereits eine vorgefertigte Komponente für die Seitennavigation. 8..of("Name"). dass die Überschriften über den Spalten nicht automatisch mit den Spaltendefinitionen korrespondieren. .4 DataTable Wie bereits im letzten Beispiel verwendet die DataTable-Komponente eine Spaltendefinition.basis.of("J").of("Vorname"). \ 161 .java package de. dass diese Definition auch für die Darstellung der Spaltenüberschriften benutzt wird. Abbildung 8. import org. } IDataProvider<Kunde> data=new ListDataProvider<Kunde>(liste).3).add(new Kunde("Hansi".extensions. wenn man aus diesem Beispiel eine vollwertige Anzeige mit Seitennavigation machen wollte. DataTable<Kunde> dataTable = new DataTable<Kunde>("list"."."name").data.

addTopToolbar(new NavigationToolbar(dataTable)).2. <body> <table wicket:id="list"></table> </body> . ähnlich gelagerte Problem in Grenzen. } Das CriteriaFilterInterface implementiert genau eine Methode. um z. import org.3 DataTable 8. 8. Da die meisten Komponenten und Klassen wiederverwendbar sind.Criteria. hält sich der Aufwand für jedes weitere. Abbildung 8.8 Listen und Tabellen columns.. } } Listing 8.23 DataTablePage.24 CriteriaFilterInterface. add(dataTable). public interface CriteriaFilterInterface { public void applyFilter(Criteria criteria). Damit aus den Teilen ein Ganzes wird.2.5 DefaultDataTable Anhand der DefaultDataTable-Komponente möchte ich zeigen. welche die benötigten Funktionen unabhängig von der persistenten Klasse bereitstellt.persistence.java package de.B.wicketpraxis. müssen wir allerdings noch etwas Vorarbeit leisten. 162 .1 Ergänzungen der Datenbankschicht Zuerst müssen wir unsere Datenbankschicht um ein paar Funktionen ergänzen.5. Datensätze aus einer Datenbank anzeigen. Auf dieses Weise kann man dann sehr schnell komplexe Anwendungen erstellen.html .. wie man die verschiedenen Teile zusammenfügt.hibernate.3). Dazu schreiben wir uns eine abstrakte Hilfsklasse.. Diese wird sowohl bei der Erzeugung der Abfrage für die Liste als auch beim Ermitteln der Größe des Gesamtergebnisses aufgerufen..data. sortieren und löschen zu können. dataTable. Listing 8.

for (CriteriaFilterInterface filter: getFilter()) filter. } } Die Klasse AbstractDaoList liefert immer einen Ausschnitt einer Liste von Objekten zurück. ret=(Integer) criteria. T> _dao.criterion.. public class AbstractDaoList<K extends Serializable.addOrder(order). } protected List<CriteriaFilterInterface> getFilter() { return new ArrayList<CriteriaFilterInterface>(). import org. Um die Größe der Ergebnisliste zu ermitteln. Criteria criteria = _dao.8. T> dao) { _dao=dao. if (max!=null) criteria.2 DataProvider Listing 8.Integer max) { List<R> ret=null. return ret.hibernate. .java package de.persistence. Nachdem wir diese Klasse fertiggestellt haben.setMaxResults(max). werden dieselben Filter angewendet.rowCount()). dass die Größe der Liste korrekt ermittelt wird.setFirstResult(offset). Auf diese Weise kann gewährleistet werden. die alle Elemente der Tabelle zurückliefert.R> { AbstractDao<K.applyFilter(criteria).setProjection(Projections.getCriteria(). } public int getSize() { int ret=0. import org. criteria. for (CriteriaFilterInterface filter: getFilter()) \ filter.Criteria.uniqueResult(). } public List<R> getList(Integer offset. die sortiert und gefiltert werden kann. return ret.getCriteria().25 AbstractDaoList..hibernate.applyFilter(criteria).T extends \ DoInterface<K>.*. } protected List<Order> getOrder() { return new ArrayList<Order>(). for (Order order : getOrder()) criteria. müssen wir unsere Datenzugriffsklasse etwas ergänzen. 163 .list(). Wir leiten eine eigene Klasse ab. Criteria criteria = _dao.wicketpraxis. ret=criteria. aber auf die Sortierung verzichtet. if (offset!=null) criteria. protected AbstractDaoList(AbstractDao<K.

Damit die Liste 164 . public class UserDao extends AbstractDao<Integer.. } public User getByEMail(String email) { return (User) getCriteria().dao.criterion. import org. import org.2.persistence.eq(email)).wicketpraxis. implementieren wir einen Adapter. User.8 Listen und Tabellen Listing 8. Der Einfachheit halber reduzieren wir die Sortiermöglichkeit auf ein Attribut.hibernate. } public static class All extends AbstractDaoList<Integer. protected All(UserDao userDao) { super(userDao).uniqueResult().java package de.boolean asc) { _sort=column.26 UserDao.5.hibernate. \ add(Property. boolean _asc. } return ret.. } } } In diesem Fall verzichten wir auf den Einsatz von Filtern. _asc=asc. User> { public static final String BEAN_ID="userDao". der zwischen der Datenbank und den Wicket-Komponenten vermittelt. sondern ermöglichen nur das Sortieren des Ergebnisses. 8.class). } public All getAll() { return new All(this).desc(_sort)).forName("EMail"). if (_sort!=null) { ret. public UserDao() { super(User. } @Override protected List<Order> getOrder() { ArrayList<Order> ret = new ArrayList<Order>().*. . User> { String _sort.add(_asc ? Order.Criteria.asc(_sort) : Order.2 DataProvider Damit die Komponenten mit den Daten umgehen können. } public void setOrder(String column.

web. public class UserList implements ISortableDataProvider<User> { UserDao _userDao.komponenten.repeater. } public Iterator<? extends User> iterator(int first. transient All _all.SingleSortState. true).java package de.wicket..table.ASCENDING: _all. import org.*.User>(_userDao. } } } } _attached=true.object). implementieren wir nicht IDataProvider.basis.data.sort.markup.thema.setOrder(s. import org. sondern IsortableDataProvider.setOrder(null.setOrder(s."EMail"}.extensions. } public IModel<User> model(User object) { return new DaoModel<Integer. } public int size() { 165 .repeater.extensions. public UserList(UserDao userDao) { _userDao=userDao. String[] properties={"id". Listing 8.repeater.markup.27 UserList.wicket. } protected All load() { if (!_attached) { _all=_userDao. boolean _attached=false. false). break.8.apache.iterator().getPropertySortOrder(s). . if (_sortState!=null) { _all.markup.html. int count) { return load(). import org.html. count).getList(first..html. case ISortState. ISortState _sortState=new SingleSortState().util. return _all.extensions.wicket.data. break.getAll(). true). switch (propertySortOrder) { case ISortState.DESCENDING: _all.apache.apache.wicketpraxis.repeater. for (String s : properties) { int propertySortOrder = _sortState.2 DataProvider auch sortiert werden kann.*.

ist das Zusammenfügen der Teile gewohnt einfach. \ "EMail". public class DefaultDataTablePage extends WebPage { @SpringBean UserDao _userDao.wicketpraxis. import org.markup. Was in diesem Beispiel noch auf die UserDao-Klasse zugeschnitten ist. Listing 8.of("")) { public void populateItem(Item<ICellPopulator<User>> cellItem.repeater.table.add(new PropertyColumn<User>(Model. an die Datenbankschicht weitergereicht. Daher wird das Feld _all beim Aufruf von detach() auch zurückgesetzt."deleteFragment".data.3 Zusammenfügen Nachdem alle notwendigen Hilfsklassen geschrieben sind. String componentId.extensions.web. List<IColumn<User>> columns=new ArrayList<IColumn<User>>().grid.html. } public void detach() { _all=null..apache. } public void setSortState(ISortState state) { _sortState=state.thema.2. 166 . darauf hinzuweisen. public DefaultDataTablePage() { UserList data=new UserList(_userDao). _attached=false."id"."EMail")).5. wird sie entsprechend geladen.repeater.html.basis. die durch die Tabellenkomponente gesetzt wurde.28 DefaultDataTablePage.repeater.8 Listen und Tabellen return load().komponenten.*.. detach().data."Id")).*. In der load()-Methode wird dann die Sortierung. .java package de. columns. IModel<User> rowModel) { Fragment fragment = new Fragment(componentId.of("EMail").this). import org.add(new AbstractColumn<User>(Model. Wenn über eine der Methoden auf diese Klasse zugegriffen werden muss.apache. } public ISortState getSortState() { return _sortState.of("Id"). columns.markup.add(new PropertyColumn<User>(Model.wicket. } } Es ist wichtig.wicket. kann man natürlich auch wesentlich allgemeiner umsetzen. 8. dass die Klasse All nicht serialisiert werden darf.extensions.getSize(). columns. \ DefaultDataTablePage.

} . width:400px.wicket_orderDown { border-bottom: 2px solid red.columns. die Anzeige der Spalten und für die Anzeige bei leerer Tabelle hinzugefügt. habe ich in diesem Beispiel ein paar CSS-Definitionen zum Markup hinzugefügt. } } Wir erstellen drei Spalten.wicket_orderUp { border-top: 2px solid red.liste tbody tr. } </style> </head> <body> <wicket:fragment wicket:id="deleteFragment"> <a wicket:id="link">löschen</a> </wicket:fragment> <table wicket:id="list" class="liste"> </table> </body> </html> In Abbildung 8. } table.29 DefaultDataTable.data.2 DataProvider fragment.liste tbody td { border:1px solid #a0a0a0.8. wobei die letzte Spalte einen Link enthält. Abbildung 8. } }).html <html> <head> <title>DefaultDataTable Page</title> <style> table.rowModel) { @Override public void onClick() { User user= getModelObject(). DefaultDataTable<User> table = new DefaultDataTable<User>("list". dass durch einen Klick auf die Kopfzeile der Spalte das Ergebnis sortiert werden kann. _userDao.delete(user). } }).add(fragment). der den Datensatz löscht. add(table). Listing 8. Damit die Struktur der Komponente etwas besser sichtbar wird.4 sehen wir dann das Ergebnis unserer Mühen. Die DefaultDataTable-Komponente hat bereits die Kindkomponente für die Seitennavigation. } table.add(new Link<User>("link".4 DefaultDataTable mit Daten aus der Datenbank 167 . cellItem. } .even td { background-color:#f0f0f0.3). Zu beachten ist.liste { border-collapse: collapse.

8 Listen und Tabellen Zusammenfassung Während einfache Listen am besten mit der ListView-Komponente dargestellt werden können. Wenn man die Klassen für den Zugriff auf die Daten aus einer Persistenzschicht etwas allgemeiner implementiert und die DataTable-Komponente noch um ein paar Hilfsfunktionen ergänzt. Wicket bietet dabei ausreichend Flexibilität. empfiehlt sich für Datensätze aller Art eine Variante der DataTable-Komponente. um die Lösung den eigenen Bedürfnissen entsprechend anpassen zu können. kann man sehr schnell komplexe Datenbankanwendungen schreiben. 168 .

der die Datei form. Listing 9. } li. list-style-type: none. Im folgenden Kapitel werden wir uns mit den unterschiedlichen Formularkomponenten befassen.class. "form. wenn wir durch ein wenig CSS die Darstellung von Formularen und Komponenten etwas verändern.komponenten.wicketpraxis.feedbackPanelINFO { border:1px dashed green.feedbackPanel { padding-left:0px.css in die Seite einbindet.getHeaderContribution( \ Forms. Dazu binden wir eine CSS-Datei in jede Beispielseite ein.. .forms. wie man Formulareingaben prüft und dem Nutzer ein hilfreiches Feedback gibt. wie man mithilfe von Ajax die Nutzbarkeit eines Formulars weiter verbessern kann. public class Forms { public static HeaderContributor getCss() { return CSSPackageResource. allerdings in src/main/resources. } } Der Aufruf der Methode getCSS() liefert einen HeaderContributor.2 form.9 Formulare Formulare sind ein wesentlicher Bestandteil einer Webanwendung. 9. Listing 9.1 Voraussetzungen Für die folgenden Beispiele ist es hilfreich. Außerdem werden wir uns damit beschäftigen.feedbackPanelWARNING { 169 .css ul.web. } li.1 Forms.thema. Ich werde anhand von Beispielen demonstrieren.java package de. background-color:#ddffdd.css"). Diese Datei müssen wir im selben Paket wie die FormsKlasse anlegen..

Listing 9.html <html> <head> <title>FeedbackPanel Page</title> </head> <body> <div wicket:id="feedback"></div> 170 .feedbackPanelERROR { border:1px solid red. sodass auf der Ergebnisseite darauf zugegriffen werden kann. Dabei werden die Nachrichten in der Session abgelegt.thema. background-color:#ffffcc.ERROR input { border:1px solid red. background-color:#ffcccc. public class FeedbackPanelPage extends WebPage { public FeedbackPanelPage() { add(Forms. } div. list-style-type: none. verwenden wir im folgenden Beispiel drei Links.2 Feedback Für Rückmeldungen an den Nutzer steht in Wicket die FeedbackPanel-Komponente zur Verfügung. add(new Link("link") { @Override public void onClick() { info("Geklickt").basics. } }). Um zu veranschaulichen. } div.web.ERROR { border:1px solid red.. add(new Link("linkFehler") { @Override public void onClick() { error("Irren ist menschlich.. list-style-type: none. dass das FeedbackPanel unabhängig von Formularen funktioniert und dass man mehr als nur Fehlermeldungen anzeigen kann.4 FeedbackPanelPage.").forms. font-weight:bold. add(new FeedbackPanel("feedback")). } 9. add(new Link("linkWarnung") { @Override public void onClick() { warn("Achtung"). margin:4px. .3 FeedbackPanelPage. background-color:#ffcccc.getCss()).wicketpraxis. } li.9 Formulare border:1px dotted #cccc00.komponenten. } }).java package de. } } Listing 9. color:red. die jeweils eine Nachricht an den Nutzer erzeugen. } }).

public abstract class AbstractFormPage extends WebPage { private FeedbackPanel _feedbackPanel.3 Basisklasse für alle Beispiele <a wicket:id="link">Klick mich</a><br> <a wicket:id="linkWarnung">Vorsicht</a><br> <a wicket:id="linkFehler">besser nicht klicken</a><br> </body> </html> Je nachdem.1 FeedbackPanel in Aktion 9. dass mindestens ein FeedbackPanel auf der Seite eingebunden ist. Wir erstellen daher eine abstrakte Klasse. add(_feedbackPanel).9. von der wir alle weiteren Beispiele ableiten. Listing 9.. .1). Für die FeedbackPanel-Komponente aktivieren wir die Ajax-Unterstützung (setOutputMarkupId(true)).3 Basisklasse für alle Beispiele Für Formulare ist es wichtig. _feedbackPanel. } protected void updateFeedbackPanel(AjaxRequestTarget target) { target. werden über diese Komponente angezeigt. denn alle Fehlermeldungen. _feedbackPanel = new FeedbackPanel("feedback"). die bei der Abarbeitung von Formularen auftreten.java package de.forms. } protected FeedbackPanel getFeedbackPanel() { return _feedbackPanel. Dabei fügen wir zur Seite eine FeedbackPanel-Komponente und die Referenz auf die CSSDatei hinzu.komponenten. erhält man eine passende Rückmeldung (Abbildung 9..addComponent(_feedbackPanel).wicketpraxis.setOutputMarkupId(true).getCss()).thema.web. Abbildung 9. auf welchen Link man klickt. } } 171 . public AbstractFormPage() { add(Forms.5 AbstractFormPage.

Listing 9. dass der Browser die Datenübertragung startet.forms.html <wicket:extend> <form wicket:id="form"> <button>Abschicken</button> </form> </wicket:extend> } Das Formular ist die einzige Komponente.. Listing 9. wo die abgeleitete Seite ihre Informationen einblendet (<wicket:child/>). Die zweite Methode ist wichtig. Dieser einfache Vorgang kann auf sehr unterschiedlichen Wegen ausgelöst werden.8 StandardFormPage. 9.7 StandardFormPage. } } Listing 9.web. können die Daten übermittelt und das Formular abgesendet werden.html <html> <head><title>Form Page</title></head> <body> <div wicket:id="feedback"></div> <wicket:child/> </body> </html> 9. muss die Stelle definiert werden. Das button-Tag sorgt dafür.4 Formulare absenden Wenn der Nutzer Daten in das Formular eingetragen hat.1 Absenden mit Submit-Button Im folgenden Beispiel erzeugen wir ein einfaches Formular ohne jede Eingabemöglichkeit. Da wir alle weiteren Beispielseiten von dieser Seite ableiten.komponenten.java package de.. .6 AbstractFormPage.wicketpraxis.4.basics. public class StandardFormPage extends AbstractFormPage { public StandardFormPage() { add(new Form("form") { @Override protected void onSubmit() { info("Formular abgeschickt").thema. wenn das FeedbackPanel per Ajax aktualisiert werden muss. Wir überschreiben die onSubmit-Methode des Formulars und erzeugen eine Nachricht an den Nutzer. }).9 Formulare Für den Zugriff auf das FeedbackPanel definieren wir noch zwei Methoden. 172 .

Wicket erzeugt bei beiden Links den notwendigen JavaScript-Aufruf. Listing 9. Formulare auch durch Links abzuschicken.thema.form)). die sich außerhalb des Formulars befinden.4 Formulare absenden 9.komponenten. form. Dass man ein Formular auch durch Klick auf einen Link abschicken kann.basics.2 Button-Komponente Im letzten Beispiel wurde das Formular abgeschickt und verarbeitet. Dabei ist gerade diese Möglichkeit besonders interessant.add(new Button("button2") { @Override public void onSubmit() { warn("Button 2 geklickt").add(new SubmitLink("button3")).add(new Button("button1") { @Override public void onSubmit() { warn("Button 1 geklickt"). form. welcher Button angeklickt wurde.java package de.. welches Ergebnis wir erhalten.und zwei SubmitLink-Komponenten.setOutputMarkupId(true). form.9 FormSubmitButtonPage.10 FormSubmitButtonPage. } }.2 können wir sehen. 173 . add(form).web. form. sodass eine Nachricht erzeugt wird. } } Für die beiden Button-Komponenten überschreiben wir die onSubmit-Methode. Listing 9.4. Dabei ist es möglich. wenn wir auf einen Button klicken. Deshalb ergänzen wir das letzte Beispiel um zwei Button..9.html <wicket:extend> <form wicket:id="form"> <button wicket:id="button1">Button1</button><br> <input type="submit" wicket:id="button2" value="Button2"><br> <a wicket:id="button3">Button3</a> </form> <a wicket:id="button4">Button4</a> </wicket:extend> In Abbildung 9. ohne dass der SubmitButton eine Komponente war. Dazu kann man das entsprechende Formular als Parameter angeben. } }). add(new SubmitLink("button4".forms. public class FormSubmitButtonsPage extends AbstractFormPage { public FormSubmitButtonsPage() { Form form = new Form("form") { @Override protected void onSubmit() { info("Formular abgeschickt").wicketpraxis. kann man an den SubmitLinkKomponenten sehen. } }). .

3 Submit per Ajax Wicket bietet auch eine gute Ajax-Unterstützung für Formulare.thema. Dabei unterstützt uns Wicket mit drei verschiedenen Komponenten. Besonders interessant ist dabei die AjaxFallbackButton-Komponente.9 Formulare Abbildung 9. Form<?> form) { warn("Button 1 geklickt").2 Formulare absenden durch Buttons und Links 9.basics. public class FormAjaxSubmitButtonsPage extends AbstractFormPage { public FormAjaxSubmitButtonsPage() { Form form = new Form("form") { @Override protected void onSubmit() { info("Formular abgeschickt"). Listing 9.form) { @Override protected void onSubmit(AjaxRequestTarget target. form.addComponent(form). Form<?> form) { warn("Button 2 geklickt").add(new AjaxFallbackButton("button1".wicketpraxis.addComponent(getFeedbackPanel()). die wie bei der analogen Link-Komponente dafür sorgt.add(new AjaxButton("button2". wie man ein Formular per Ajax abschickt. target.4. target. form... . dass das Formular auch abgeschickt wird. Das folgende Beispiel soll veranschaulichen. 174 .komponenten.addComponent(getFeedbackPanel()). wenn der Nutzer JavaScript und damit die Ajax-Unterstützung deaktiviert hat. die je nach Anwendungsfall eingesetzt werden können.web.setOutputMarkupId(true). } }.form) { @Override protected void onSubmit(AjaxRequestTarget target. target.java package de.addComponent(form). if (target!=null) { target.forms.11 FormAjaxSubmitPage. form. } } }).

allerdings wird nicht die onSubmit-Methode der Button-Komponente aufgerufen. .9. Das zweite Formular überschreibt dazu die Methode getMethod(). das Formular wird nicht abgeschickt.form) { @Override protected void onSubmit(AjaxRequestTarget target. } } In allen drei Fällen müssen wir die zu aktualisierenden Komponenten zum AjaxRequestTarget hinzufügen. target.thema. in bestimmten Fällen Formulare per HTTP-GET. zu übertragen. Zu beachten ist dabei Folgendes: Hat der Nutzer die AjaxUnterstützung deaktiviert.. Zu guter Letzt passiert beim Klick auf die AjaxSubmitLink-Komponente einfach gar nichts. add(form). Die AjaxButton-Komponente schickt auch das Formular ab.addComponent(form).html <wicket:extend> <form wicket:id="form"> <button wicket:id="button1">Button1</button><br> <input type="submit" wicket:id="button2" value="Button2"><br> </form> <a wicket:id="button3">Button3</a> </wicket:extend> 9. 175 . Listing 9.forms. target.komponenten.4 POST und GET Formulare sollten immer per HTTP-POST-Methode übertragen werden.12 FormAjaxSubmitPage. public class FormMethodPage extends AbstractFormPage { public FormMethodPage() { add(new Form("formPost") { @Override protected void onSubmit() { info("Form mit POST")..java package de. Form<?> form) { warn("Button 3 geklickt"). } }).addComponent(getFeedbackPanel()). also wie einen normalen Seitenaufruf mit in der URL kodierten Parametern.wicketpraxis. add(new AjaxSubmitLink("button3".4. und es werden beide onSubmit-Methoden (Formular und Button) aufgerufen. Daher empfiehlt sich im Regelfall die AjaxFallbackButton-Komponente.4 Formulare absenden } }). Listing 9.web.basics. da sie in beiden Fällen zum gleichen Ergebnis führt.13 FormMethodPage. Trotzdem kann es notwendig sein. dann schickt die AjaxFallbackButton-Komponente das Formular ab.

forms.14 FormMethodPage. add(formGet). kann man das wie in unserem Beispiel durch den Aufruf von setRedirect(false) unterbinden. } @Override protected String getMethod() { return Form.thema.web. } } Ansonsten gleichen sich die beiden Formulare. Form formGet = new Form("formGet") { @Override protected void onSubmit() { info("Form mit GET"). setRedirect(false). dass beim Aktualisieren der Seite durch den Nutzer das Formular nicht noch einmal abgeschickt wird (DoubleSubmit-Problem).html <wicket:extend> <form wicket:id="formPost"> <input type="text" name="Text"> <button>Abschicken(Post)</button> </form> <form wicket:id="formGet"> <input type="text" name="Text"> <button>Abschicken(Get)</button> </form> </wicket:extend> 9. auf der das Ergebnis angezeigt wird. Das geschieht transparent und hat den Vorteil. die in das Textfeld eingegeben werden. muss man der TextField-Komponente ein Modell zuweisen.java package de. Um an die Daten zu gelangen.. Listing 9. das gelesen und geschrieben werden kann.METHOD_GET. Möchte man dieses Verhalten ändern. public class FormTextFieldTypePage extends AbstractFormPage 176 .9 Formulare } }).komponenten. Listing 9. birgt eine kleine Überraschung. Ein LoadableDetachedModel ginge an dieser Stelle also nicht.15 FormTextFieldTypePage. \ komponenten.wicketpraxis. . } }. In der Grundkonfiguration wird der Nutzer nach Absenden eines Formulars auf eine neue Zielseite weitergeleitet. und die onSubmit-Methode wird wie erwartet aufgerufen.5 Textfelder Die einfachste Formularkomponente ist das Textfeld.. Doch was auf den ersten Blick einfach und naheliegend aussieht.textfield.

16 FormTextFieldTypePage.getObject().String 177 .setRequired(true).9. } }. form. Listing 9.lang. dass Wicket auf den generischen Typ des Modells nicht zurückgreifen kann und daher davon ausgeht. denen wir jeweils ein Modell zugewiesen haben.getObject().add(new Button("submit")).String NumberType: class java.html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="text"><br> Zahl: <input wicket:id="zahl"><br> <button wicket:id="submit">Abschicken</button> </form> </wicket:extend> Schicken wir das Formular ab. Die Fehlermeldungen. ohne die Textfelder gefüllt zu haben. textField. werden wir feststellen. Object valueNumber=modelNumber. numberField. form. Form form = new Form("form") { @Override protected void onSubmit() { Object valueText=modelText.getClass()). TextField<Integer> numberField = new TextField<Integer>("zahl".getClass()). \ modelText). dass die gewünschten Daten vom Typ String sind. sind in verschiedenen Sprachen bereits hinterlegt. } } Wir haben zwei Textfelder. die ausgegeben werden. Wenn wir das Formular abschicken und die Werte aus den Modellen auslesen. info("TextType: "+valueText. Dabei sollte in dem einen Textfeld eine Zahl eingegeben werden können. add(form). \ modelNumber). info("NumberType: "+valueNumber.add(textField). form. dass in beiden Fällen ein String und kein Integer zurückgegeben wird. Durch den Aufruf von setRequired() machen wir beide Textfelder zu Pflichtfeldern. final Model<Integer> modelNumber = new Model<Integer>(). Das erklärt sich daraus.lang. TextField<String> textField = new TextField<String>("text".5 Textfelder { public FormTextFieldTypePage() { final Model<String> modelText = new Model<String>(). wenn wir das Formular abschicken.add(numberField).setRequired(true). dann erhalten wir folgende Ausgabe: TextType: class java.

form.setRequired(true). textField2.add(textField). Wir ergänzen daher das eine Textfeld um einen Typparameter. Dabei ermittelt Wicket den passenden Eintrag anhand der Komponenten-ID. add(form). Beim Formulieren der Texte kann man auf verschiedene Variablen zugreifen und diese im Text durch die Angabe eines Platzhalters in der Form ${name} ersetzen lassen. Die Standardfehlermeldung möchten wir in diesem Beispiel abändern.add(new Button("submit")).").wicketpraxis. für alle Komponenten und alle zu erwartenden Fehlermeldungen neue Definitionen in dieser Datei abzulegen.web.class).17 FormWithTextFieldPage. } }. public class FormWithTextFieldPage extends AbstractFormPage { public FormWithTextFieldPage() { Form form = new Form("form") { @Override protected void onSubmit() { info("Hat geklappt"). Ändert sich die ID. ist es nicht nötig.thema. } } Wenn man nun in das Textfeld. muss man auch die ID in der Property-Datei anpassen.setRequired(true). Listing 9. etwas anderes eingibt.forms. Wir ändern nur die Fehlermeldungen für die zweite TextField-Komponente. TextField<Integer> textField2 = \ new TextField<Integer>("text2"..new Model<Integer>().textfield.5. auf den die Komponenten zurückgreifen können. Dazu legen wir neben der Markup-Datei im selben Verzeichnis eine Property-Datei an. form.add(textField2).html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="text"><br> Zahl: <input wicket:id="text2"><br> <button wicket:id="submit">Abschicken</button> </form> </wicket:extend> Da Wicket die Property-Dateien rekursiv verarbeitet. dann wird automatisch ein Fehler erzeugt. 178 .Integer.. Dazu benötigt Wicket nur eine verlässliche Typinformation. form. } @Override protected void onError() { warn("Es gab da einen Fehler.java package de. Listing 9. . in dem Wicket eine Zahl erwartet.18 FormWithTextFieldPage.new Model<String>()). um die Eingabe in den passenden Typ konvertieren zu können.komponenten.1 Typangabe Die Probleme des letzten Beispiels lassen sich relativ einfach umschiffen. TextField<String> textField = \ new TextField<String>("text".9 Formulare 9. textField.komponenten.

4).IConverter=Irgendwie kann ich aus '${input}' in \ '${label}' keine Zahl machen. die verschiedene Attribute bereitstellt. dass die Eingabe „hallo“ in keine Zahl umgewandelt werden konnte (Abbildung 9. .19 FormWithTextFieldPage.java (gekürzt) package de. dann erscheint eine Fehlermeldung.thema. String _text2.Required=Bitte was in '${label}' reinschreiben form.5. sodass wir an dieser Stelle alle auch später notwendigen Attribute definieren. Abbildung 9. dann erscheinen die passenden Fehlermeldungen (Abbildung 9. 9.properties form. Dabei spielt die PropertyModel-Klasse eine wesentliche Rolle. Auch hier kann man durch die geschickte Kombination von verschiedenen Komponenten zu einem wesentlich einfacheren Ansatz kommen. public class StandardTypesBean implements Serializable { int _zahl.web. Auf diese Klasse greifen wir auch in den folgenden Beispielen zurück.3 Formular mit fehlerhafter Eingabe Abbildung 9.forms.beans.3).5 Textfelder Listing 9. double _kommazahl.text2. 179 .4 Fehlerhaft ausgefülltes Formular Wenn für Text ein „hallo“ und für Zahl eine 123 eingetragen wird. Listing 9. Wir legen dazu eine neue Klasse an. Wenn wir nichts in das Formular eintragen.20 StandardTypesBean..text2. String _text. dann kann das Formular erfolgreich abgeschickt werden..wicketpraxis.2 Automatische Typermittlung Selbst für einfache Formulare ist der Aufwand schon recht groß. die darüber Auskunft erteilt.9. Date _datum. Tragen wir für Text und Zahl ein „hallo“ ein.komponenten.

} @Override protected void onError() { StandardTypesBean bean = getModelObject().getText()+".getZahl()+ ".forms. die wir im folgenden Beispiel verwenden möchten. In diesem Fall benutzen wir ein CompoundPropertyModel. Datum: "+bean.. form.9 Formulare getZahl().. Wichtiger Hinweis Wenn das Formular abgeschickt wird. Außerdem wird dieser Format-String für die Darstellung des Eingabefeldes herangezogen.21 TextFieldsPage.yyyy 'um' HH:mm:ss")).thema. Form<StandardTypesBean> form=new Form<StandardTypesBean>("form". Dazu binden wir ein Modell an das Formular. wenn kein Fehler aufgetreten ist.getDatum()).setDatum(new Date()). werden die Werte nur dann in die Bean übertragen. form.. error("Text: "+bean.java package de.getZahl()+ ". info("Text: "+bean.. } Dabei haben wir drei geläufige Datentypen definiert. Dabei kann auch der Typ ermittelt werden. Anschließend werden Zahl und Datum aus dem Inhalt des Eingabefeldes in den entsprechenden Datentyp konvertiert. bean.web. Zahl: "+bean. Datum: "+bean.wicketpraxis.getDatum()).setZahl(). new CompoundPropertyModel<StandardTypesBean>(bean)) { @Override protected void onSubmit() { StandardTypesBean bean = getModelObject(). Beim Datumsfeld haben wir einen Format-String angegeben."dd. } } Wir füllen die Felder aus und schicken das Formular ab. Listing 9.getKommazahl().add(new TextField<String>("Text")). wie die Eingabe interpretiert wird.add(new DateTextField("Datum". sodass die Eingaben automatisch in die richtigen Typen konvertiert werden können.MM.getText()+".add(new TextField<Double>("Zahl")). add(form).. . form.komponenten. Die TextField-Komponenten bekommen das passende Modell über ihre Komponenten-ID.textfield. der dafür verantwortlich ist. } }.komponenten. Zahl: "+bean. 180 . public class TextFieldsPage extends AbstractFormPage { public TextFieldsPage() { StandardTypesBean bean = new StandardTypesBean().

} } 181 . dass das Eingabefeld aktiviert wird. wenn man eine JavaBean als Modell benutzt.web. Dafür gibt es sogar ein entsprechendes HTML-Tag. 9.5 Formular mit Fehlermeldungen Empfehlung In den meisten Fällen ist der Schreibaufwand für ein Formular geringer.java package de. Außerdem sind dann alle Eingabedaten bereits in einem Objekt zusammengefasst und können so als Parameter einfach weitergegeben werden.9. TextField<String> textField = new TextField<String>("text".forms. Listing 9. add(form).textField)). form.add(new SimpleFormComponentLabel("label".formcomplabel. \ Model.komponenten. form.22 TextFieldPage.wicketpraxis. public class SimpleFormCompLabelPage extends AbstractFormPage { public SimpleFormCompLabelPage() { Form form = new Form("form").. textField.5. . Das ermöglicht unter anderem.23 SimpleFormComponentLabelPage.komponenten.6 Label Jedes Eingabefeld hat üblicherweise einen Bezeichner.thema.. Das Auslesen der einzelnen Komponentenmodelle entfällt.html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="Text"><br> Zahl: <input wicket:id="Zahl"><br> Datum: <input wicket:id="Datum"><br> <button>Abschicken</button> </form> </wicket:extend> Wenn wir das erste Mal die Felder mit korrekten Werten füllen und beim zweiten Mal einen kleinen Fehler einbauen.add(textField).setLabel(Model. Abbildung 9.of("etwas Text")).6 Label Listing 9. wenn der Nutzer auf den Bezeichner klickt. erhalten wir ein Ergebnis wie in Abbildung 9.of("")).

Ein Klick auf das Label aktiviert nun das Eingabefeld. } 182 ...24 SimpleFormComponentLabelPage.26 CheckBoxBean.beans. ${name} und ${input} zurückgreifen. wie das Ganze zusammenspielt. 9. <label wicket:id="label" for="texta">etwas Text</label>: <input wicket:id="text" value="" name="text" id="texta"><br> <button>Abschicken</button> </form> </wicket:extend></wicket:child> </body> . Daher erstellen wir uns für dieses Beispiel eine neue Bean-Klasse. <body> <div wicket:id="feedback" id="feedback4"><wicket:panel> </wicket:panel></div> <wicket:child><wicket:extend> <form wicket:id="form" id="form5" method="post" action="?wicket:interface=:1:form::IFormSubmitListener::"> .getListe().html . den Namen und den Inhalt der Eingabe über ${label}.html <wicket:extend> <form wicket:id="form"> <label wicket:id="label">Text</label>: <input wicket:id="text"><br> <button>Abschicken</button> </form> </wicket:extend> Die SimpleFormComponentLabel-Komponente setzt das Attribut „for“ der Komponente und ersetzt den Inhalt des HTML-Tags durch den Rückgabewert aus getLabel() der TextField-Komponente.7 CheckBox Die CheckBox-Komponente erwartet als Datentyp ein Boolean. Anhand des Quelltextes der Ergebnisseite kann man gut erkennen.. In eigenen Fehlermeldungen kann man dann auf das Label..wicketpraxis.forms.9 Formulare Listing 9. Hinweis Das Label der TextField-Komponente entspricht der Komponenten-ID. ohne dass auch der Inhalt des labelTags ersetzt wird. solange man es nicht auf einen anderen Wert setzt... die entsprechende Attribute besitzt. isCheck(). ArrayList<String> _liste=new ArrayList<String>(). benutzt man einfach die FormComponentLabel-Klasse. Listing 9.komponenten.. public class CheckBoxBean implements Serializable { boolean _check. Listing 9.java (gekürzt) package de..25 Ergebnis.thema.. .. Wenn man aber nur das Attribut ersetzen möchte.web.setCheck()..

info("Liste: " + bean.web..add(new CheckBox("Check")).html <wicket:extend> <form wicket:id="form"> Checkbox: <input wicket:id="Check" type="checkbox"><br> <span wicket:id="Liste"></span> <button>Abschicken</button> </form> </wicket:extend> Möchten wir dem Nutzer die Möglichkeit geben.komponenten. Dazu benutzen wir dann aber die CheckGroup-Komponente und fügen jeweils eine Check-Komponente für jeden gewünschten Eintrag hinzu 183 . für die man die CheckBox aktiviert hat.forms.isCheck()). "Stöpsel"))). new CompoundPropertyModel<CheckBoxBean>(new CheckBoxBean())) { @Override protected void onSubmit() { CheckBoxBean bean = getModelObject().wicketpraxis. "Seife". dass wir in diesem Beispiel neben der einfachen CheckBox-Komponente die darauf aufbauende CheckBoxMultipleChoice-Komponente einsetzen möchten.6).asList("Handtuch". . form. liegt daran. add(form).java package de.28 CheckBoxPage.7 CheckBox Dass diese Klasse ein Attribut vom Typ ArrayList<String> besitzt.. \ Arrays.komponenten.add(new CheckBoxMultipleChoice<String>("Liste". } } Wir übergeben der CheckBoxMultipleChoice-Komponente eine Liste mit möglichen Werten.getListe()). info("Check: " + bean.9. } }. lässt sich das einfach bewerkstelligen.thema. Listing 9.6 Die verschiedenen CheckBoxKomponenten Listing 9. Das Attribut wird mit einer Liste gesetzt. Abbildung 9.check. Für jedes Element erzeugt die Komponente dann eine CheckBox (Abbildung 9. public class CheckBoxPage extends AbstractFormPage { public CheckBoxPage() { Form<CheckBoxBean> form = new Form<CheckBoxBean>("form". alle Einträge auf einmal zu selektieren.27 CheckBoxPage. form. die alle Elemente enthält.

komponenten.wicketpraxis.Model.add(new Check<String>("stoepsel"..add(checkGroup). muss man für die ListView-Komponente die Methode setReuseItems(true) aufrufen. Hinweis Wenn man in Formularen eine ListView-Komponente benutzt. form. } } Die CheckGroupSelector-Komponente aktiviert alle Check-Komponenten per JavaScript.Model.30 CheckGroupPage. new CompoundPropertyModel<CheckBoxBean>(new CheckBoxBean())) { @Override protected void onSubmit() { CheckBoxBean bean = getModelObject(). Listing 9.add(new Check<String>("handtuch".thema. Die CheckGroup-Komponente setzt das Attribut anhand der Werte in den CheckKomponenten. CheckGroup<String> checkGroup = new CheckGroup<String>("Liste").java package de. checkGroup.web. Listing 9. An dieser Stelle könnte man auf eine ListView-Komponente zurückgreifen.forms.add(new CheckGroupSelector("alles".add(new Check<String>("seife".Model.of("Seife"))). checkGroup. da sonst die Validierung nicht korrekt funktioniert.check.html <wicket:extend> <form wicket:id="form"> <span wicket:id="Liste"> Handtuch: <input wicket:id="handtuch" type="checkbox"><br> Seife: <input wicket:id="seife" type="checkbox"><br> Stöpsel: <input wicket:id="stoepsel" type="checkbox"><br> 184 .29 CheckGroupPage. add(form).komponenten.checkGroup)). checkGroup. um dynamisch Formularelemente hinzuzufügen. info("Liste: " + bean.9 Formulare Abbildung 9. . public class CheckGroupPage extends AbstractFormPage { public CheckGroupPage() { Form<CheckBoxBean> form = new Form<CheckBoxBean>("form". } }.of("Stöpsel")))..of("Handtuch"))).getListe()).7 Die CheckGroupKomponente (Abbildung 9. checkGroup.7).

web. RadioGroup<Integer> radioGroup = new RadioGroup<Integer>("Zahl"). . was sich gerade in der Darstellung niederschlägt (Abbildung 9.java package de. \ Arrays.radio. radioGroup.8 RadioButton <br> Alles? <input wicket:id="alles" type="checkbox"><br> </span> <button>Abschicken</button> </form> </wicket:extend> 9.setRequired(true). bietet die RadioGroup-Komponente wesentlich mehr Flexibilität.komponenten.html <wicket:extend> <form wicket:id="form"> Zahl: <span wicket:id="Zahl"> 1 <input wicket:id="Zahl1" type="radio">. form.31 RadioButtonPage.Model.of(2))). radioGroup.add(new RadioChoice<String>("Text".add(radioGroup).Model. form. Listing 9.getZahl()). } }.getText()). Auch wenn die RadioChoice-Komponente einfacher zu benutzen ist.asList("Haus".forms. dass das Ergebnis aus nur einem Wert und keiner Liste von Werten besteht. add(form).32 RadioButtonPage.thema. Das bedeutet.. info("Text: " + bean.8). oder 4 <input wicket:id="Zahl4" type="radio"> </span> <br><br> 185 . Listing 9.add(new Radio<Integer>("Zahl4". public class RadioButtonPage extends AbstractFormPage { public RadioButtonPage() { Form<StandardTypesBean> form = new Form<StandardTypesBean>("form". "Auto"))).wicketpraxis. \ new CompoundPropertyModel<StandardTypesBean>( \ new StandardTypesBean())) { @Override protected void onSubmit() { StandardTypesBean bean = getModelObject(). radioGroup.. info("Zahl: " + bean. "Baum". machen wir dieses Eingabefeld zum Pflichtfeld (setRequired(true)).add(new Radio<Integer>("Zahl2".of(1))). radioGroup.Model.of(4))).9. 2 <input wicket:id="Zahl2" type="radio">.8 RadioButton Aus einer Gruppe von Radio-Buttons kann der Nutzer immer nur einen auswählen. } } Da das Attribut „Zahl“ der Bean nicht null sein darf (ist vom Typ int und nicht Integer).komponenten.add(new Radio<Integer>("Zahl1".

bietet sich die SelectOptions-Komponente an.web. Das Wicket-Framework bietet für die verschiedenen Varianten jeweils eigene Komponenten. um die Funktion und die Darstellung nach eigenen Wünschen anzupassen. Das Aussehen jedes einzelnen Eintrags könnte ich auf diese Weise verändern.9. Dafür muss man aber einen IOptionRenderer implementie- ren.. der für einen Wert die Anzeige und auch das Model erstellt.33 SimpleSelectPage.8 RadioButton und RadioGroup 9.thema. wenn man jeden Eintrag des Menüs von Hand hinzufügt. Auch in diesem Bereich hat man die Wahl zwischen Flexibilität und Aufwand. Listing 9.forms. da sich der Name der beiden Komponenten nur durch ein „s“ unterscheidet. Für welchen Anwendungsfall welche Komponente am besten geeignet ist.wicketpraxis. .9 Formulare Text:<br><span wicket:id="Text"></span><br> <button>Abschicken</button> </form> </wicket:extend> Abbildung 9.1 Select Am einfachsten scheint es. Dabei tragen wir die ersten vier Auswahlmöglichkeiten von Hand ein. da jeder Eintrag eine eigenständige Komponente ist. public class SimpleSelectPage extends AbstractFormPage 186 . 9. Möchte man aber Listen unbekannter Länge als Option hinterlegen. sollen die folgenden Beispiele veranschaulichen.komponenten. Hinweis Die SelectOption-Komponente ist nicht zu verwechseln mit der SelectOptionsKomponente.komponenten.select.java package de. In diesem Beispiel fügen wir eine Select-Komponente hinzu.9 Auswahlfelder Das select-Tag bietet sehr viel Möglichkeiten. was recht einfach geschehen kann..

Select select = new Select("Zahl").of(1))).add(new SelectOption<Integer>("schwierig". Da es aber immer wieder vorkommt. select.Model.add(new SelectOption<Integer>("ist".html <wicket:extend> <form wicket:id="form"> <select wicket:id="Zahl"> <option wicket:id="das">1</option> <option wicket:id="ist">2</option> <option wicket:id="zu">3</option> <option wicket:id="schwierig">4</option> <wicket:container wicket:id="options"> <option wicket:id="option" style="color: red. info("Zahl: " + bean.Model. select.of(value). } }.of(3))).of(4))).getZahl()). info("Text: " + bean.renderer)). form.toString(). IOptionRenderer<Integer> renderer=new IOptionRenderer<Integer>() { public String getDisplayValue(Integer object) { return "Wähle "+object. select.9. select. lohnt es sich vielleicht. \ Arrays.6.34 SimpleSelectPage. select.getText()).Model. add(form).8).Model. } public IModel<Integer> getModel(Integer value) { return Model.add(select).add(new SelectOption<Integer>("das".add(new SelectOptions<Integer>("options".asList(5.7.of(2))). \ new CompoundPropertyModel<StandardTypesBean>( \ new StandardTypesBean())) { @Override protected void onSubmit() { StandardTypesBean bean = getModelObject()."></option> </wicket:container> </select> <br> <button>Abschicken</button> </form> </wicket:extend> 187 . dass für bestimmte Fälle nicht auf die Standardkomponenten zurückgegriffen werden kann. } }. } } Die hohe Flexibilität erkaufen wir uns an dieser Stelle mit einem nicht unwesentlichen Schreibaufwand.9 Auswahlfelder { public SimpleSelectPage() { Form<StandardTypesBean> form = new Form<StandardTypesBean>("form". Listing 9. basierend auf diesen einfachen Komponenten eine an die eigenen Bedürfnisse angepasste Komponente zu erstellen.add(new SelectOption<Integer>("zu".

put(Calendar. info("Tag 3: "+bean. _tage.keySet())).9. . dass das Attribut „Tag1“ nicht vom Typ Integer.wicketpraxis.java package de.of((List<Integer>) new ArrayList<Integer>(_tage.. Integer _tag3. IChoiceRenderer<Integer> renderer=new IChoiceRenderer<Integer>() 188 .thema. sondern vom Typ int ist..THURSDAY.TUESDAY. Dabei ist darauf hinzuweisen.forms.select.web."Sonntag"). Welche Auswirkungen das hat.. new CompoundPropertyModel<WochentagBean>(new WochentagBean())) { @Override protected void onSubmit() { WochentagBean bean = getModelObject(). Um die Möglichkeiten zu zeigen. getTag1().getTag1()).java (gekürzt) package de. public class WochentagBean implements Serializable { int _tag1.put(Calendar.put(Calendar. erstellen wir für dieses Beispiel wieder eine passende JavaBean.put(Calendar.put(Calendar.beans... _tage. } In unserem Beispiel erstellen wir eine Map.9 Formulare 9. Listing 9.put(Calendar."Sonnabend")."Mittwoch"). werden wir später feststellen. _tage.35 WochentagBean. static { _tage.String> _tage= \ new LinkedHashMap<Integer. . info("Tag 2: "+bean.thema. Die Liste aller Auswahlmöglichkeiten ergibt sich aus der Liste der Key-Elemente der Map.getTag2().web. _tage.. IModel<List<? extends Integer>> choices = Model. String>().forms.getTag3()).FRIDAY. _tage. info("Tag 1: "+bean.getTag2()). Dabei benutzen wir die Konstanten der Calendar-Klasse als Schlüssel. } }.setTag1()."Donnerstag"). Integer _tag2. Listing 9.komponenten.WEDNESDAY.MONDAY.SUNDAY.put(Calendar.2 DropDownChoice Die DropDownChoice-Komponente nimmt uns noch mehr Arbeit ab. die alle Wochentage aufnimmt."Dienstag")."Montag")."Freitag").komponenten.36 DropDownChoicePage. die diese Komponente bietet. public class DropDownChoicePage extends AbstractFormPage { static LinkedHashMap<Integer. Damit ist dieses Attribut bereits mit 0 vorbelegt. _tage.wicketpraxis.SATURDAY.komponenten. } public DropDownChoicePage() { Form<WochentagBean> form=new Form<WochentagBean>("form"..

choices.toString().properties Tag2." Abbildung 9.9). } public String getIdValue(Integer object.add(new DropDownChoice<Integer>("Tag2".null="Einen Tag wählen. möchte man für die Werte eine sinnvolle Anzeige bekommen.renderer).37 DropDownChoicePage. } }. form.renderer)).9 Auswahlfelder { public Object getDisplayValue(Integer object) { return _tage.renderer)). indem wir neben der Markup-Datei eine Property-Datei mit folgendem Inhalt anlegen.9.38 DropDownChoicePage. add(form). was ja vom Typ int ist und bereits mit einem Wert vorbelegt ist. form. Der Unterschied zwischen „Tag1“ und „Tag2“ wird sichtbar. Listing 9. form.. Dann wird für „Tag2“ ein Hinweis angezeigt. dass noch nichts ausgewählt wurde. Es werden zwei DropDownChoiceund eine ListChoice-Komponente benutzt (Abbildung 9..html <wicket:extend> <form wicket:id="form"> Tag 1: <select wicket:id="Tag1"></select><br> Tag 2: <select wicket:id="Tag2"></select><br> Tag 3: <select wicket:id="Tag3"></select><br> <button>Abschicken</button> </form> </wicket:extend> Auch in diesem Beispiel muss ein entsprechender Renderer implementiert werden. wenn die Seite das erste Mal aufgerufen wird.9 Die Komponenten DropDownChoice und ListChoice 189 . int index) { return object.add(new DropDownChoice<Integer>("Tag1".choices.get(object). } } Listing 9. Die erste DropDownChoiceKomponente benutzt als Attribut „Tag1“. \ setMaxRows(4)).choices.add(new ListChoice<Integer>("Tag3". Diesen Hinweistext können wir einfach ersetzen.

info("Lieblingsfarben: "+bean..9 Formulare 9. Für dieses Beispiel benötigen wir wieder eine JavaBean. welche die Auswahl im Attribut „Lieblingsfarben“ speichert.40 ListMultipleChoicePage."Ocker".41 ListMultipleChoicePage.forms.html <wicket:extend> <form wicket:id="form"> Lieblingsfarben: <select wicket:id="Lieblingsfarben"></select><br> <button>Abschicken</button> </form> </wicket:extend> Die Komponente wird ebenfalls direkt an ein select-Tag gebunden.komponenten. public class LieblingsfarbenBean implements Serializable { List<String> _lieblingsfarben=new ArrayList<String>(). add(form). public class ListMultipleChoicePage extends AbstractFormPage { public ListMultipleChoicePage() { Form<LieblingsfarbenBean> form=new Form<LieblingsfarbenBean>("form".wicketpraxis.add(new ListMultipleChoice<String>("Lieblingsfarben".3 ListMultipleChoice Bisher können wir immer nur einen Eintrag aus der Liste auswählen.komponenten. } }.... new CompoundPropertyModel<LieblingsfarbenBean>( \ new LieblingsfarbenBean())) { @Override protected void onSubmit() { LieblingsfarbenBean bean = getModelObject()."Grün". } Da die auszuwählenden Elemente auch automatisch für die Anzeige geeignet sind.thema. Listing 9. . Für eine Mehrfachauswahl gibt es ebenfalls passende Komponenten."Gelb". benötigen wir keinen ChoiceRenderer zum Aufbereiten der Darstellung. Nun kann man seine Lieblingsfarben auswählen und das Formular absenden.39 LieblingsfarbenBean. Listing 9.9..java package de. 190 . \ setMaxRows(3)).beans. form.forms.wicketpraxis.java (gekürzt) package de. getLieblingsfarben().select.asList("Rot".. } } Listing 9. List<String> farben = Arrays.getLieblingsfarben()). .setLieblingsfarben().web.thema. \ "Schwarz").komponenten.farben).web.

select.komponenten. .renderer. } public String getIdValue(String object.. int index) { return object.9.asList("Rot". public class PalettePage extends AbstractFormPage { public PalettePage() { Form<LieblingsfarbenBean> form=new Form<LieblingsfarbenBean>("form".web.true)). Die Komponente benutzt intern zwei ListMultipleChoice-Komponenten.add(new Palette<String>("Lieblingsfarben". form. Außerdem können die Einträge in der Reihenfolge verändert werden.."Gelb". das wir in unserem Projekt eingebunden haben.forms.getLieblingsfarben()). Der letzte Parameter beim Erzeugen der Palette-Komponente ist true und erlaubt somit die Sortierung der Liste. CollectionModel<String> farben = new CollectionModel<String>( \ Arrays. möchte ich sie kurz vorstellen. add(form). die einmal die ausgewählten und auf der anderen Seite die zur Verfügung stehenden Einträge anzeigen können (Abbildung 9. \ farben.42 PalettePage."Schwarz")). IChoiceRenderer<String> renderer=new IChoiceRenderer<String>() { public Object getDisplayValue(String object) { return object.komponenten.10. } }. info("Lieblingsfarben: "+bean."Ocker". weil sie sehr interessante Möglichkeiten bietet und nur mit Aufwand selbst entwickelt werden kann. \ new CompoundPropertyModel<LieblingsfarbenBean>( \ new LieblingsfarbenBean())) { @Override protected void onSubmit() { LieblingsfarbenBean bean = getModelObject(). Die Komponente gehört zum Paket Wicket-Extensions.wicketpraxis."Grün".9 Auswahlfelder Palette Auch wenn die folgende Komponente eher keine Basiskomponente ist. Listing 9.thema. weshalb wir hier ebenfalls einen implementieren.10). } } Die Komponente erwartet einen Renderer als Parameter. } }.java package de. 191 .

form. Listing 9. wird durch die FileUploadFieldKomponente bereitgestellt.komponenten.upload.apache.forms. eine Datei hochzuladen.*. mit der man in anderen Frameworks unter Umständen noch in Berührung kommt.upload.. und schon haben wir die Liste unserer Lieblingsfarben ermittelt und in die richtige Reihenfolge gebracht (Abbildung 9.html.komponenten. Abbildung 9. Wicket stellt dafür verschiedene Komponenten bereit. public class FileUploadPage extends AbstractFormPage { public FileUploadPage() 192 . import org. dass der Nutzer Daten und Dokumente in einer Webanwendung zur Verfügung stellen muss.web.9 Formulare Listing 9. 9.10 Dateien hochladen In manchen Fällen ist es notwendig.1 FileUpload Die einfachste Möglichkeit.44 FileUploadPage.wicket.java package de.10 Die Interaktionsmöglichkeiten der Palette 9.43 PalettePage. .10. die die zugrundeliegende Komplexität.ajax. versteckt und gleichzeitig interessante Möglichkeiten bieten.thema.extensions.10)..markup.wicketpraxis.html <wicket:extend> <form wicket:id="form"> Lieblingsfarben: <wicket:container wicket:id="Lieblingsfarben"> </wicket:container><br> <button>Abschicken</button> </form> </wicket:extend> Wir wählen ein paar Farben.

45 WicketPraxisApplication.. die Größe und den MimeType ermitteln.form)).apache. \ upload.wicket. Um dem Nutzer etwas mehr Informationen darüber geben zu können. Size: {1}. public class WicketPraxisApplication . form. upload. Listing 9. form. die dann durch die Komponente angezeigt werden können. @Override protected WebRequest newWebRequest(HttpServletRequest servletRequest) { return new UploadWebRequest(servletRequest). { ..java . import javax.html <wicket:extend> <form wicket:id="form"> Datei: <input wicket:id="Upload" type="file"><br> <wicket:container wicket:id="progressbar"></wicket:container> 193 . Damit diese Komponente aber etwas anzeigen kann. kann man eine UploadProgressBar-Komponente hinzufügen.46 FileUploadPage.markup..http.getFileUpload(). \ MimeType: {2}". } } Um den Upload nicht beliebig groß werden zu lassen.megabytes(100)). wie schnell die Daten gerade hochgeladen werden. Auf die hochgeladenen Daten kann man in der onSubmit-Methode zugreifen und so Informationen wie den Dateinamen. form.servlet.getClientFileName(). ...UploadWebRequest.add(new UploadProgressBar("progressbar". Listing 9..form. import org. } } }. \ upload.upload.add(fileUploadField). if (upload!=null) { info(MessageFormat.setMaxSize(Bytes. form.getSize().setMultiPart(true).html.ajax. add(form).9.extensions.HttpServletRequest..10 Dateien hochladen { final FileUploadField fileUploadField = \ new FileUploadField("Upload").getContentType())).. kann man die Größe begrenzen. müssen wir in unserer WebApplication-Klasse eine Anpassung vornehmen.format("Filename: {0}.. Form<Void> form=new Form<Void>("form") { @Override protected void onSubmit() { FileUpload upload = fileUploadField. } .. } Nur der UploadWebRequest stellt die nötigen Daten bereit.

ist das ähnlich einfach wie das Hochladen einer Datei.setMaxSize(Bytes.komponenten.add(new UploadProgressBar("progressbar".forms. Size: {1}.form)).getSize(). . public class MultiFileUploadPage extends AbstractFormPage { public MultiFileUploadPage() { final MultiFileUploadField fileUploadField = \ new MultiFileUploadField("Upload". for (FileUpload upload : uploads) { info(MessageFormat. \ upload.web.setMultiPart(true). upload.5). sodass man vor dem Abschicken die zur Auswahl stehenden Dateien auch wieder aus der Liste entfernen kann.getObject().10. Für die UploadProgressBar-Komponente sind auch in diesem Beispiel dieselben Anpassungen an der Application-Klasse durchzuführen.getClientFileName(). form. Listing 9. form. form.komponenten.getModel().wicketpraxis.megabytes(100)). \ MimeType: {2}".48 MultiFileUploadPage.2 MultiFileUpload Möchte man mehr als eine Datei auf einmal hochladen.thema. } } }. Dabei kann man angeben. form.upload.add(fileUploadField). Form<Void> form=new Form<Void>("form") { @Override protected void onSubmit() { Collection<FileUpload> uploads = \ fileUploadField. Listing 9.47 MultiFileUploadPage.format("Filename: {0}.upload..getContentType())).9 Formulare <button>Abschicken</button> </form> </wicket:extend> 9.java package de. \ new CollectionModel<FileUpload>(new ArrayList<FileUpload>()).html <wicket:extend> <form wicket:id="form"> Datei: <wicket:container wicket:id="Upload"></wicket:container><br> <wicket:container wicket:id="progressbar"></wicket:container> <button>Abschicken</button> </form> </wicket:extend> 194 . } } Die Komponente manipuliert das Formular per JavaScript. wie viele Dateien auf einmal hochgeladen werden können.. add(form).

12 Liste der hochgeladenen Dateien mit erweiterten Informationen 9.11 Gültigkeitsprüfung Wenn man dann Dateien auswählt und hochlädt. Außerdem ist es recht einfach. ob die Eingabe in den gewünschten Typ umgewandelt werden konnte. Abbildung 9. konnte alle Validatoren fehlerfrei durchlaufen werden.11 Gültigkeitsprüfung Bisher konnten wir Eingabefelder zu Pflichtfeldern machen. gibt es einen wichtigen Aspekt. wenn es darum geht. Doch das reicht bei weitem nicht aus. die vom Nutzer eingegebenen Daten auf Richtigkeit zu prüfen. 195 .11).9. Wicket bringt daher eine Reihe vordefinierter Validatoren mit.11 Hochladen von Dateien Abbildung 9. eigene Validatoren zu schreiben.12 die Liste der Dateien und Informationen über Größe. zeigt die UploadProgressBar-Komponente Informationen über die Geschwindigkeit und den Fortschritt an (Abbildung 9. wird wie in Abbildung 9. Wenn die Methode onSubmit() aufgerufen wird. Dateiname und Dateityp angezeigt. Nachdem alle Dateien hochgeladen wurden. Wie in den vorangegangenen Beispielen bereits zu erkennen war. den man im Umgang mit Formularen beachten muss: Formulare schreiben die Daten erst dann in die Modelle. mit denen man die Nutzereingabe prüfen kann. wenn kein einziger Fehler mehr vorhanden ist. Außerdem wurde geprüft.

validators..add( \ StringValidator. Listing 9.maximumLength(8))).13 Verschiedene Gültigkeitsprüfungen 196 .java package de.add(new TextField<String>("Min2".50 StringValidatorPage. welche Regel der entsprechende Validator prüft (Abbildung 9. was die Regel verletzt.add( \ StringValidator.add(new TextField<String>("Max8". form. form.html <wicket:extend> <form wicket:id="form"> Länge 4: <input wicket:id="Len4"><br> Länge 2-4: <input wicket:id="Len24"><br> Länge bis 8: <input wicket:id="Max8"><br> Länge ab 2: <input wicket:id="Min2"><br> <button>Abschicken</button> </form> </wicket:extend> Wenn wir in jedem Feld etwas eingeben.9 Formulare 9.lengthBetween(2.Model.of("")). form.add( \ StringValidator.11.Model. erhalten wir folgendes Ergebnis: Abbildung 9. . add(form).add( \ StringValidator.49 StringValidatorPage.1 StringValidator Für einfache Regeln bezüglich der Länge einer Eingabe bietet Wicket eine Reihe von einfachen Validatoren.minimumLength(2))).exactLength(4))).of("")).Model. 4))). Dafür muss diese Regel durch den Aufruf von setRequired(true) aktiviert werden. form.add(new TextField<String>("Len4".thema. Die Methodennamen lassen erkennen.wicketpraxis.forms.13).of("")). public class StringValidatorPage extends AbstractFormPage { public StringValidatorPage() { Form form=new Form("form"). Listing 9. } } Dabei wird durch das Hinzufügen eines Validators das Eingabefeld nicht automatisch zum Pflichtfeld.komponenten..Model.web.add(new TextField<String>("Len24".of("")).

.add( \ new MaximumValidator<String>("EDCBA"))).= heute: <input wicket:id="Datum"><br> <button>Abschicken</button> </form> </wicket:extend> 9. kann ein MinimumValidator. form. form. sondern wird innerhalb der Komponente gepuffert. ist der zweite Schritt. die das Comparable-Interface implementieren. } } Dabei müssen die Validatoren nicht selbst die Eingaben in die entsprechenden Typen konvertieren. dass der Nutzer eine gültige E-Mail-Adresse eingibt. Listing 9.java package de. MaximumValidator und ein RangeValidator benutzt werden.wicketpraxis. wenn die Konvertierung durchgeführt wurde.thema.9.web. Password und Password2 bereitstellt.add( \ new MaximumValidator<Date>(new Date()))). add(form).11.11 Gültigkeitsprüfung 9.add(new TextField<Double>("Kommazahl").42d))). Dabei wandert das Ergebnis nicht in das Modell.3 E-Mail Häufig möchte man. Listing 9.add(new TextField<String>("Text").komponenten. public class RangeMinMaxValidatorPage extends AbstractFormPage { public RangeMinMaxValidatorPage() { Form<StandardTypesBean> form=new Form<StandardTypesBean>("form".add(new TextField<Integer>("Zahl"). die die Attribute EMail. ob es die Adresse wenigstens theoretisch geben könnte.add( \ new MinimumValidator<Integer>(12))).51 RangeMinMaxValidatorPage. Für die folgenden Beispiele benötigen wir allerdings wieder einmal eine JavaBean. \ new CompoundPropertyModel<StandardTypesBean>( \ new StandardTypesBean()))..11.PI. 197 .add(new TextField<Date>("Datum"). 42 : <input wicket:id="Kommazahl"><br> Text &lt.52 RangeMinMaxValidatorPage.html <wicket:extend> <form wicket:id="form"> Zahl &gt.forms. ob diese E-Mail-Adresse existiert.= EDCBA: <input wicket:id="Text"><br> Datum &lt.2 Minimum und Maximum Für alle Datentypen.add( \ new RangeValidator<Double>(Math. Als Erstes muss geprüft werden.validators. 12: <input wicket:id="Zahl"><br> Kommazahl > Pi &lt. form. Die Validatoren werden erst aufgerufen. form.. Die Überprüfung.

getEMail().java package de. } } Listing 9.add(EmailAddressValidator.. .55 EmailValidatorPage. benutzt den RfcCompliantEmailAddressValidator...53 LoginBean.validators.wicketpraxis. der kann die E-MailAdressen gerne durch einen RFC-konformen Validator prüfen lassen.web. Die Fehlermeldung wird dem Nutzer allerdings weniger helfen.komponenten.. form. Das Wesentlichen dabei ist aber.getEMail()). \ Password: {1}.9 Formulare Listing 9.forms. und so ist auch die Überprüfung dieser Adresse eine komplizierte Angelegenheit. public String toString() { return MessageFormat._password.format("EMail: {0}. Wer es dann doch genau mag und sich an Standards halten möchte.htmll <wicket:extend> <form wicket:id="form"> EMail: <input wicket:id="EMail"><br> <button>Abschicken</button> </form> </wicket:extend> 198 . Password2: {2}". String _password.add(emailTextField). } }.54 EMailValidatorPage. zu erkennen und ihn darauf hinzuweisen. die Fehler. Wer es komplizierter mag.getPassword(). .thema. Listing 9. new CompoundPropertyModel<LoginBean>(new LoginBean())) { @Override protected void onSubmit() { info("EMail: "+getModelObject().wicketpraxis..thema.java (gekürzt) package de. String _password2. } } Das Format einer gültigen E-Mail-Adresse ist kompliziert. TextField<String> emailTextField = new TextField<String>("EMail"). public class EMailValidatorPage extends AbstractFormPage { public EMailValidatorPage() { Form<LoginBean> form=new Form<LoginBean>("form". add(form).forms. die der Nutzer bei der Eingabe machen kann. Ich habe daher in diesem Beispiel die „einfache“ Variante benutzt.. public class LoginBean implements Serializable { String _eMail._password2)._eMail. weil er sie nicht versteht.beans.getInstance()).web.komponenten.. emailTextField.setEMail().

.validators.56 UrlValidatorPage. Ein Palindrom ist.. form.9.57 UrlValidatorPage. dann ist zumindest diese Prüfung wesentlich einfacher als das Prüfen einer E-Mail-Adresse. Listing 9. Die erste Variante lässt alles zu.58 CustomValidatorPage.web.add(new TextField<String>("UrlLimit".Model.wicketpraxis. public class UrlValidatorPage extends AbstractFormPage { public UrlValidatorPage() { Form form=new Form("form").ALLOW_ALL_SCHEMES))).html <wicket:extend> <form wicket:id="form"> Alles geht: <input wicket:id="UrlAll"><br> Standard: <input wicket:id="UrlStandard"><br> Nur http ohne #: <input wicket:id="UrlLimit"><br> <button>Abschicken</button> </form> </wicket:extend> 9.add( \ new UrlValidator())).add( \ new UrlValidator(new String[]{"http"}. .java package de.Model.11. .web. public class CustomValidatorPage extends AbstractFormPage { public CustomValidatorPage() { Form form=new Form("form").of("")).forms..add(new TextField<String>("UrlAll". 199 .forms. Listing 9.thema.Model.11 Gültigkeitsprüfung 9.add(new TextField<String>("UrlStandard".4 URL Wenn der Nutzer eine URL eingeben soll. Die letzte Variante erlaubt nur das HTTP-Protokoll und lässt keine Fragmente zu (das „#unten“ am Ende von http://wicket-praxis.java package de. Für dieses Beispiel implementieren wir einen Palindrom-Validator. der vorwärts wie rückwärts gelesen das Gleiche ergibt. add(form). ein Text. form.thema. form.. } } Dabei gibt es drei Varianten.of("")). kurz gesagt. Listing 9.add( \ new UrlValidator(UrlValidator.wicketpraxis.komponenten.5 Eigene Validatoren So einfach wie das Einbinden ist auch das Erstellen von eigenen Validatoren. HTTP und FTP.de/blog/#unten).11. was wie eine gültige URL aussieht.UrlValidator.of("")). Die zweite Variante erlaubt nur die Protokolle HTTPS.komponenten.NO_FRAGMENTS))).validators.

vars).put("leftInput".Model. if (index>0) { vars.vars).15.index)).of("")). } } if (index>=0) { Map<String. 200 . for (int i=0."NoMatch".put("leftReverse". Danach wird die error-Methode aufgerufen. vars.substring(index)). dann wird eine Map erzeugt und mit relevanten Informationen gefüllt.add( \ new PalindromValidator())).substring(0. String reverse=new StringBuilder(value). } public static class PalindromValidator extends \ AbstractValidator<String> { @Override protected void onValidate(IValidatable<String> validatable) { String value = validatable. add(form). Sollte der Vergleich keine Abweichung finden. und es wird kein Fehler erzeugt. vars. form. break. error(validatable. Dabei handelt sich es um einen Adapter zum jeweiligen Eingabefeld.reverse().charAt(i)!=reverse. vars.put("part". Wir wandeln den bereits konvertierten Wert. in Kleinbuchstaben um.substring(index)).i++) { if (value. index).add(new TextField<String>("Palindrom".charAt(i)) { index=i.setEscapeModelStrings(false).getValue(). } } } } } Der Validator wird mit dem Validatable aufgerufen. Dabei wird der Fehler mit dem zu prüfenden Eingabefeld verknüpft. int index=-1. value. gibt es nichts zu bemängeln. in diesem Fall ist es einfach ein String. reverse.put("reverse". worauf wir in Abschnitt 9.9 Formulare getFeedbackPanel(). Object>(). die durch die AbstractValidator-Klasse bereitgestellt wird. Dann erstellen wir eine gespiegelte Kopie und vergleichen Zeichen für Zeichen das Original mit dem Spiegelbild. } else { error(validatable. value. Diese Information kann man für die Darstellung des Fehlers nutzen.length(). vars. Kommt es jedoch zu Abweichungen.toLowerCase(). reverse)."Partial".Object> vars=new HashMap<String.2 noch zu sprechen kommen werden.toString().put("index".i<value.

Wie man sieht. Das erste und letzte Zeichen \ stimmen schon nicht überein. Geben wir „Lagerrgal“ ein.12 FormValidator Bisher haben wir immer nur ein Eingabefeld geprüft. Wenn man die Prüfung in der onSubmit-Methode durchführt und 201 .9.html <wicket:extend> <form wicket:id="form"> Palindrom: <input wicket:id="Palindrom"><br> <button>Abschicken</button> </form> </wicket:extend> Damit der Nutzer etwas mit der Fehlermeldung anfangen kann. 9.12. ist alles in Ordnung. Deshalb habe ich in diesem Beispiel dieses Verhalten für das FeedbackPanel mit dem Aufruf setEscapeModelStrings(false) deaktiviert. kann der Nutzer unerwünschten HTML-Code in die Seite injizieren.1 Passwortprüfung Das beste Beispiel für einen FormValidator kommt aus der Praxis. erhalten wir eine Fehlermeldung wie in Abbildung 9. Um mehr als ein Eingabefeld prüfen zu können. sodass sie im Fehlertext als Text und nicht als Tag auftauchen.properties Partial=Bei <i>${part}</i>${leftInput} stimmen nur die ersten ${index} \ Zeichen überein: <i>${part}</i>${leftReverse}. Wenn der Nutzer zweimal das gleiche Passwort eingeben muss. müssen wir einen FormValidator implementieren. legen wir eine PropertyDatei mit den passenden Fehlermeldungen an. Abbildung 9. Wenn wir jetzt „Lagerregal“ eingeben.59 CustomValidatorPage. Listing 9. Warnung Da der Inhalt des Eingabefeldes in einer Fehlermeldung dargestellt wird und die Fehlermeldung den Wert aus dem Eingabefeld ungefiltert übernimmt.12 FormValidator Listing 9. die Wicket normalerweise entsprechend umwandelt.60 CustomValidatorPage$PalindromValidator.14 Der PalindromValidator in Aktion 9. Die Eingabe von „<script>alert(‚Sicherheitslücke’)</script>“ ruft eine unerwünschte JavaScript-Funktion auf. benutzen wir in der Property-Datei HTML-Tags.14. NoMatch='${input}' ist kein Palindrom. dann kann man das mit einem einfachen Validator nicht prüfen.

komponenten. add(form). info("Passwort: "+bean. Die PasswordTextField-Komponente ist eine Erweiterung der TextField-Komponente. in diesem Fall der EqualPasswordInputValidator nicht zu einem der beiden Eingabefelder.getPassword2()).passwordTextField2)). Passwort2: "+bean.java package de. Das Passwort bleibt also nicht stehen. new CompoundPropertyModel<LoginBean>(new LoginBean())) { @Override protected void onSubmit() { LoginBean bean = getModelObject().. dass die letzte Eingabe wieder gelöscht wird. . } }. Der EqualPasswordInputValidator wurde vom EqualInputValidator abgeleitet und sorgt dafür.61 FormValidatorPage.62 FormValidatorPage.html <wicket:extend> <form wicket:id="form"> Password: <input type="password" wicket:id="Password"><br> Password2: <input type="password" wicket:id="Password2"><br> <button>Abschicken</button> </form> </wicket:extend> Wenn wir nun das Passwort das zweite Mal etwas anders eingeben. erhalten wir eine Fehlermeldung (Abbildung 9. Listing 9.add(new EqualPasswordInputValidator( \ passwordTextField.. sind die Daten bereits im Modell gelandet. 202 .9 Formulare dann einen Fehler erzeugt. PasswordTextField passwordTextField = \ new PasswordTextField("Password").add(passwordTextField). public class FormValidatorPage extends AbstractFormPage { public FormValidatorPage() { Form<LoginBean> form=new Form<LoginBean>("form". form.web. die dafür sorgt.thema.wicketpraxis. form.validators. PasswordTextField passwordTextField2 = \ new PasswordTextField("Password2"). sondern zur Form-Komponente hinzuge- fügt. Als Parameter erwartet der Validator die beiden Eingabefelder. wird der FormValidator. } } Wie man sieht.forms. Listing 9. Man muss also beide Eingabefelder gleichzeitig prüfen. form.add(passwordTextField2). dass bei Fehlermeldungen das eingegebene Passwort nicht im Klartext in der Fehlermeldung auftaucht.getPassword()+ ".15).

komponenten. a. beschäftigen wir uns in diesem Beispiel nur mit einem ganz kleinen Problem.validators. public String toString() { return MessageFormat..komponenten.63 CalcBean..wicketpraxis. Listing 9. ob die Summe aus A und B der eingegebenen Summe entspricht.64 CustomFormValidatorPage..12 FormValidator Abbildung 9.. Doch vorher erstellen wir eine JavaBean.java package de. getA(). public class CalcBean implements Serializable { int _a.15 Der PasswordValidator bei unterschiedlichen Passwörtern 9.2 Eigene Prüfung Auch einen FormValidator kann man einfach selbst implementieren.web.setA(). final TextField<Integer> b = new TextField<Integer>("B"). B: {1}.java (gekürzt) package de.. Summe: {2}".wicketpraxis.getB(). public class CustomFormValidatorPage extends AbstractFormPage { public CustomFormValidatorPage() { Form<CalcBean> form=new Form<CalcBean>("form".. Listing 9. „B“ und „Summe“ bereithält.thema.format("A: {0}. die die Attribute „A“.beans. worauf es ankommt.thema. final TextField<Integer> a = new TextField<Integer>("A"). 203 . int _b. Damit sich der Aufwand in Grenzen hält und man besser erkennen kann._b.9..forms.12.setRequired(true). } }. final TextField<Integer> summe = new TextField<Integer>("Summe"). } } Unser FormValidator prüft. \ _a. new CompoundPropertyModel<CalcBean>(new CalcBean())) { @Override protected void onSubmit() { info(getModelObject()._summe). .web.forms. int _summe.toString()). .

Wenn die Summen nicht übereinstimmen.put("summe". Dabei veranschaulicht das Listing noch einmal die verschiedenen Möglichkeiten. form. form.html <wicket:extend> <form wicket:id="form"> <input wicket:id="A">+<input wicket:id="B">=<input \ wicket:id="Summe"><br> <button>Testen</button> </form> </wicket:extend> Für dieses Beispiel legen wir die Property-Datei nicht mit dem Klassennamen des Validators an. In diesem Beispiel sprechen wir die Eingabefelder direkt an. form. summe.add(b). wie ein entsprechender Eintrag angelegt werden kann. Object>(). param. error(summe.param). summeInput). welche Eingabefelder für die Prüfung herangezogen werden.65 CustomFormValidatorPage. In der validate-Methode greifen wir nun auf die konvertierten Werte zurück und ermitteln die Summe. 204 . das geprüft werden soll. param. bInput). aInput+bInput).put("a".put("b". "summe".getConvertedInput().getConvertedInput(). if ((aInput+bInput)!=summeInput) { Map<String. } public void validate(Form<?> form) { int aInput = a. int summeInput = summe. } } }). sondern erzeugen eine Property-Datei für die Seite.9 Formulare b. gibt die Methode getDependentFormComponents() die drei Eingabefelder zurück.summe}. aInput). Listing 9.add(a). } } Im Gegensatz zum Validator bekommt der FormValidator das Formular übergeben.add(new AbstractFormValidator() { public FormComponent<?>[] getDependentFormComponents() { return new FormComponent<?>[]{a.put("result". int bInput = b. param.getConvertedInput(). wenn keine Fehler für die abhängigen Komponenten vorliegen.setRequired(true).add(summe). Object> param=new HashMap<String.b. Damit Wicket weiß. Entweder definiert man einen Eintrag nur zum ResourceKey (der zweite Parameter des error-Methoden-Aufrufs) oder man bezieht die Komponentenhierarchie mit ein. param. produzieren wir wie bereits beim Beispiel mit dem PalindromValidator eine Map mit Parametern und erzeugen einen Fehler. add(form). form. Die Prüfung wird dann erst aktiviert.setRequired(true).

in der ein Validator verwendet wird) überschrieben werden können.16 Der CustomFormValidator mit Fehlermeldung Zusammenfassung Eigene Validatoren zu schreiben.B. ist einfach.komponenten.16).web. public class AjaxFormSubmitBehaviorPage extends AbstractFormPage { public AjaxFormSubmitBehaviorPage() { Form<StandardTypesBean> form=new Form<StandardTypesBean>("form". ein Formular per Ajax abschickt..66 CustomFormValidatorPage. Maus oder Tastatur) man z.9.13 Ajax Listing 9.Summe. 9. \ new CompoundPropertyModel<StandardTypesBean>( \ new StandardTypesBean())) { 205 . wenn der Nutzer das Eingabefeld verlässt. 9.forms. Schon erhält man im Laufe der Zeit ein mächtiges Grundgerüst.thema.ajax. .13 Ajax Gerade im Zusammenhang mit Formularen kann man sehr viele Verbesserungen im Bereich der Nutzerinteraktion durch die Verwendung von Ajax erreichen. Abbildung 9. diese Klassen allgemein zu halten und die Texte für die Fehlermeldungen als Property-Datei dieser Komponente zu definieren.13. Man sollte versuchen.B.properties summe=${a}+${b}=${result} und nicht ${summe} oder form. Die folgenden Beispiele zeigen nur einen Ausschnitt des Anwendungsbereichs. Listing 9. in der Property-Datei der Komponente.1 AjaxFormSubmitBehavior In diesem Beispiel schicken wir das Formular immer dann ab. da es vielfältige Möglichkeiten gibt.67 AjaxFormSubmitBehaviorPage.summe=${a}+${b}=${result} und nicht ${summe} Eine fehlerhafte Eingabe führt zu einer passenden Fehlermeldung (Abbildung 9. das man vielfältig einsetzen kann.B. Da die Fehlermeldungen jederzeit lokal (z. bei welcher Interaktion (z. kann man Fehlermeldungen in bestimmten Situationen immer noch gezielt anpassen..wicketpraxis.java package de.

Error: Text: "+form.9 Formulare @Override protected void onSubmit() { info("Text: "+getModelObject(). add(form). geben wir in der onSubmit. dass das Formular tatsächlich abgeschickt wird und auch alle entsprechenden Regeln geprüft werden. habe ich in diesem Beispiel den Button auskommentiert.Submit: Text: "+form. Da die AjaxFormSubmitBehavior-Komponente dafür sorgt.html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="Text"><br> <!–<button>Abschicken</button> –-> </form> </wicket:extend> 206 . } }. Alternativ kann man die Behavior-Komponente auch mit dem Formular als Parameter instanziieren.getModelObject(). wobei dann die Komponente diesen Aufruf durchführt. wenn das Eingabefeld den Focus verliert. form.und der onErrorMethode die Daten des Modells aus.getModelObject(). aber die Ajax-Unterstützung durch den Aufruf von setOutputMarkupId(true) aktivieren. was den JavaScript-Event bezeichnet. } } Dazu übergeben wir dem AjaxFormSubmitBehavior als Parameter „onBlur“. \ add(ajaxBehavior)). Damit man sehen kann. Damit deutlich wird.getText()).setOutputMarkupId(true). } }. updateFeedbackPanel(target). warn("Ajax. der ausgelöst wird. info("Ajax.getText()). AjaxFormSubmitBehavior ajaxBehavior = \ new AjaxFormSubmitBehavior("onBlur") { @Override protected void onSubmit(AjaxRequestTarget target) { Form<StandardTypesBean> form=(Form<StandardTypesBean>) getForm().setRequired(true).getText()). } @Override protected void onError(AjaxRequestTarget target) { Form<StandardTypesBean> form=(Form<StandardTypesBean>) getForm(). dass das Formular ohne jeden Submit-Button abgeschickt wird. updateFeedbackPanel(target). Listing 9.68 AjaxFormSubmitBehaviorPage. dass das entsprechende Formular neu gezeichnet wird.add(new TextField<String>("Text"). form. müssen wir das Formular zwar nicht dem AjaxRequestTarget hinzufügen.

aber keine des Formulars aufgerufen.html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="Text"><br> <!-<button>Abschicken</button> --> </form> </wicket:extend> 9. public class AjaxFormValidatingBehaviorPage extends AbstractFormPage { public AjaxFormValidatingBehaviorPage() { Form<StandardTypesBean> form=new Form<StandardTypesBean>("form".2 AjaxFormValidatingBehavior Aber es geht noch einfacher: Das AjaxFormValidatingBehavior ist von AjaxFormSubmitBehavior abgeleitet und fügt automatisch alle FeedbackPanel oder besser. automatisch zum AjaxRequestTarget hinzu.komponenten. . Dabei werden alle Validatoren dieser Komponente. mit der man allen Komponenten eines Formulars diese Eigenschaft hinzufügen kann.add(new TextField<String>("Text").java package de.13.9..70 AjaxFormValidatingBehaviorPage. ohne das ganze Formular zu übermitteln.web.getText()).forms.. add(form). alle Komponenten. 207 .wicketpraxis.ajax. Listing 9. \ add(ajaxBehavior)). AjaxFormValidatingBehavior ajaxBehavior= \ new AjaxFormValidatingBehavior(form. form."onBlur").3 AjaxComponentUpdatingBehavior Im Gegensatz zu den letzten beiden Implementierungen aktualisiert dieses Behavior nur die entsprechende Komponente.setRequired(true).13 Ajax 9. } } Tipp Die Klasse AjaxFormValidatingBehavior besitzt die statische Methode addToAllFormComponents(). } }.69 AjaxFormValidatingBehaviorPage.13. Listing 9. die IFeedback implementiert haben. \ new CompoundPropertyModel<StandardTypesBean>( \ new StandardTypesBean())) { @Override protected void onSubmit() { info("Text: "+getModelObject(). Dadurch gestaltet sich der Aufruf natürlich etwas einfacher.thema.

Text2: "+bean.komponenten.getModelObject().getText()+ "..getText2()). updateFeedbackPanel(target).getText2()+ " Input: "+textField. textField.getText2()). . form. wird der Wert in das Modell übertragen. info("Ajax. updateFeedbackPanel(target). } @Override protected boolean getUpdateModel() { return true..9 Formulare Wenn für die Komponente kein Fehler erzeugt wurde.add(ajaxBehavior). \ new CompoundPropertyModel<StandardTypesBean>( \ new StandardTypesBean())) { @Override protected void onSubmit() { StandardTypesBean bean = getModelObject().RuntimeException e) { super.setRequired(true). } @Override protected void onError(AjaxRequestTarget target. Text2: "+bean.getText()+". Text2: "+bean. error("Ajax. TextField<String> textField2 = new TextField<String>("Text2").Update: Text: "+bean. 208 .wicketpraxis.ajax.java package de. Dieses Vorgehen bietet sich daher nur an.getConvertedInput()). wenn das Formular später noch komplett übertragen wird.setOutputMarkupId(true). Text2: "+bean.forms.web.thema.Error: Text: "+bean. e).getText()+".onError(target.getConvertedInput()). warn("Text: "+bean.getText()+ ". AjaxFormComponentUpdatingBehavior ajaxBehavior = \ new AjaxFormComponentUpdatingBehavior("onBlur") { @Override protected void onUpdate(AjaxRequestTarget target) { StandardTypesBean bean = form. Listing 9. info("Text: "+bean.71 AjaxComponentUpdatingBehaviorPage.getModelObject(). public class AjaxComponentUpdatingBehaviorPage extends AbstractFormPage { public AjaxComponentUpdatingBehaviorPage() { final Form<StandardTypesBean> form= \ new Form<StandardTypesBean>("form". StandardTypesBean bean = form.getText2()+ " Input: "+textField. } }. final TextField<String> textField = new TextField<String>("Text"). } }. } @Override protected void onError() { StandardTypesBean bean = getModelObject().

add(textField2). dass zwar beide Textfelder gefüllt waren. add(form).html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="Text"><br> Text2: <input wicket:id="Text2"><br> <button>Abschicken</button> </form> </wicket:extend> Das Ergebnis aus Abbildung 9. form.17 AjaxComponentUpdatingBehavior 9.komponenten. } } Wenn man die Methode getUpdateModel() überschreibt und den Rückgabewert auf „false“ setzt.4 OnChangeBehavior Die OnChangeAjaxBehavior-Klasse erbt von der AjaxFormComponentUpdatingBehavior-Klasse und benutzt den JavaScript-Event „onChange“. } }.getText()). \ new CompoundPropertyModel<StandardTypesBean>( \ new StandardTypesBean())) { @Override protected void onSubmit() { info("Text: "+getModelObject().13.. form.java package de. final TextField<String> textField = new TextField<String>("Text"). 209 . Listing 9. form.web.ajax.forms.. Abbildung 9. public class AjaxOnChangeBehaviorPage extends AbstractFormPage { public AjaxOnChangeBehaviorPage() { final Form<StandardTypesBean> form= \ new Form<StandardTypesBean>("form".9.13 Ajax textField2. dass sie denselben Wert zurück liefert wie die Methode der Basisklasse und damit das Setzen des Modells nicht verhindert.setRequired(true). aber nur der Wert des ersten Textfelds ins Modell übernommen wurde.72 AjaxComponentUpdatingBehaviorPage. .setOutputMarkupId(true). Listing 9.wicketpraxis. Die Methode wurde hier so überschrieben. dann wird auch das Modell nicht aktualisiert.73 AjaxOnChangeBehaviorPage.17 zeigt.thema.add(textField).

Listing 9. Während des Eintippens kann man erkennen.html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="Text"><br> <button>Abschicken</button> </form> </wicket:extend> Abbildung 9.18 AjaxOnChangeBehavior 9. wenn das Textfeld korrekt ausgefüllt wurde.ajax.web. dem Nutzer in Abhängigkeit von der letzten Eingabe eine Liste von Vorschlägen zu unterbreiten. public AutoCompleteTextFieldPage() { add(CSSPackageResource. } }.Update: Text: "+form.75 AutoCompleteTextFieldPage. .add(textField.java package de. } @Override protected boolean getUpdateModel() { return false.setRequired(true). add(form).wicketpraxis.9 Formulare OnChangeAjaxBehavior ajaxBehavior=new OnChangeAjaxBehavior() { @Override protected void onUpdate(AjaxRequestTarget target) { info("Ajax. Listing 9.getHeaderContribution( \ 210 .of("").getModelObject().getConvertedInput()). Wie der Name vermuten lässt. updateFeedbackPanel(target). die er dann auswählen kann. geht es darum.komponenten.add(ajaxBehavior)). wie die Daten übermittelt werden (Abbildung 9. dass das Modell nur aktualisiert wird.18).13. form.74 AjaxOnChangeBehaviorPage..5 AutoCompleteTextField Eine besondere Komponente ist das AutoCompleteTextField.getText()+ " Input: "+textField.forms. IModel<String> _text=Model. } } In diesem Beispiel haben wir die Methode getUpdateModel() so überschrieben..thema. public class AutoCompleteTextFieldPage extends AbstractFormPage { IModel<List<String>> _liste=new ListModel<String>( new ArrayList<String>()).

settings) { @Override protected Iterator<String> getChoices(String input) { List<String> result=new ArrayList<String>().getObject()) { if (eintrag. form. Listing 9. settings.9.css")). die über die ListViewKomponente angezeigt werden.add(_text. Form form=new Form("form") { @Override protected void onSubmit() { _liste. autoCompleteTextField._liste) { @Override protected void populateItem(ListItem<String> item) { item.getObject()).getModel())).iterator().add(eintrag). dann sucht die Funktion getChoices() nach passenden Alternativen.setRequired(true).13 Ajax AutoCompleteTextFieldPage.getObject(). Tippt der Nutzer etwas in das Eingabefeld. if ((input!=null) && (input.startsWith(input)) { result.add(autoCompleteTextField). AutoCompleteSettings settings=new AutoCompleteSettings(). settings. } } Alle Eingaben in dem Textfeld werden einer Liste hinzugefügt.setCssClassName("autocomplete"). } }.add(new Label("text". } } } return result. } }).length()>0)) { for (String eintrag : _liste.class.html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="Text"><br> <button>Abschicken</button> </form> <ol> 211 . add(new ListView<String>("liste".setObject(null). add(form). AutoCompleteTextField<String> autoCompleteTextField = new AutoCompleteTextField<String>("Text". indem die vorhandene Liste auf Einträge überprüft wird. _text.item. } }."AutoCompleteTextFieldPage._text. die mit dem bereits eingegebenen Text am Anfang übereinstimmen.76 AutoCompleteTextFieldPage.setPreselect(true).

} div. margin:0px.ajax..css div. } div.web. margin: 0 0 0 0. color:white. .java package de. zeigt das Beispiel der AjaxEditableLabel-Komponente.autocomplete ul { list-style:none.forms. welchen Eintrag man ausgewählt hat.78 AjaxEditLabelPage.19 AutoCompleteTextField 9.selected { background-color: #6060ff.14 AjaxEditableLabel Wie man aus einem Formular und etwas Ajax eine sehr nützliche Komponente bauen kann. border: 1px solid black..wicketpraxis. Dabei wird durch einen Klick auf einen Text (kein Link) dieser Text durch ein Formular ersetzt.9 Formulare <li wicket:id="liste"><span wicket:id="text"></span></li> </ol> </wicket:extend> Damit man sehen kann.autocomplete ul li. padding: 2px. muss man allerdings noch eine passende CSS-Datei angelegen.Model.of("Label")) { @Override protected void onSubmit(AjaxRequestTarget target) { super. Abbildung 9.77 AutoCompleteTextFieldPage. Listing 9. public class AjaxEditLabelPage extends AbstractFormPage { public AjaxEditLabelPage() { add(new AjaxEditableLabel<String>("ajaxLabel". padding: 2px. } Nach ein paar Eingaben können wir bereits auf eine Liste von Vorschlägen zurückgreifen (Abbildung 9. text-align:left.onSubmit(target).19).autocomplete { background-color: white. 212 . margin:0px. in dem dieser Text dann verändert werden kann.thema. padding: 0px.komponenten. in der folgende Informationen enthalten sein sollten: Listing 9.

final ListModel<String> choices = new ListModel<String>( \ Arrays. add(new AjaxEditableChoiceLabel<String>("ajaxChoice".14 AjaxEditableLabel info("Label: "+getDefaultModelObject()). } }).html <wicket:extend> <span wicket:id="ajaxLabel"></span><hr> <span wicket:id="ajaxMultiLine"></span><hr> <span wicket:id="ajaxChoice"></span> </wicket:extend> Auch wenn man die Funktion der einzelnen Komponente aus dem Klassennamen herleiten kann. Die Daten werden übermittelt.of("klicken und wählen").20).Model. add(new AjaxEditableMultiLineLabel<String>( \ "ajaxMultiLine".20 AjaxEditLabel-Varianten 213 . \ Model. } } Listing 9.asList("der erste".9.choices) { @Override protected void onSubmit(AjaxRequestTarget target) { super. info("Choice: "+getDefaultModelObject()).of("Das\nist\nmehrzeilig"))). sobald das Formularfeld den Fokus verliert. Abbildung 9. updateFeedbackPanel(target). wenn man die Komponenten in Aktion sieht (Abbildung 9. ist es einfacher.onSubmit(target)."der zweite".79 AjaxEditLabelPage."der dritte")). updateFeedbackPanel(target). } }).

die an den Nutzer gerichtet waren.81 FormFeedbackPage. add(new FeedbackPanel("feedback1".setRequired(true)). add(f2). soll nicht darauf hindeuten. die zum jeweiligen Formular gehören. Der Filter filtert dann alle Nachrichten heraus..add(new TextField<String>("Text". Form<?> f2=new Form<Void>("form2").15 Erweitertes Feedback Bisher haben wir nur ein FeedbackPanel für alle Informationen genutzt.java package de.80 FormFeedbackPage. public class FormFeedbackPage extends AbstractFormPage { public FormFeedbackPage() { Form<?> f1=new Form<Void>("form1"). Wenn wir ein weiteres FeedbackPanel zur Seite hinzufügen würden (absichtlich oder unabsichtlich. nur eine Auswahl von Nachrichten anzeigen zu lassen. ein weiteres innerhalb einer Komponente befindet).9 Formulare 9.add(new TextField<String>("Text". add(new FeedbackPanel("feedback2". Wie man das Feedback an der richtigen Stelle anzeigt und es an die eigenen Bedürfnisse anpasst. \ new ContainerFeedbackMessageFilter(f2))). Listing 9.forms. dass nur Nachrichten angezeigt werden.1 Feedback zum Formular Das FeedbackPanel bietet die Möglichkeit. f1. dass nur Formulare erlaubt wären. wäre der Nutzer sicher etwas verwirrt.wicketpraxis.thema. 9. soll Thema der folgenden Seiten sein.html <wicket:extend> <h2>Formular 1</h2> <div wicket:id="feedback1"></div> <form wicket:id="form1"> Text: <input wicket:id="Text"><br> <button>Abschicken</button> </form> <h2>Formular 2</h2> <div wicket:id="feedback2"></div> 214 . f2.15. ..feedback. die in einer beliebigen Kindkomponente erzeugt wurden. Listing 9. Dazu wird als zweiter Parameter ein MessageFilter übergeben. der dafür sorgt.web.setRequired(true)).new Model()). \ new ContainerFeedbackMessageFilter(f1))). wenn er eine Fehlermeldung an zwei Stellen zu sehen bekäme.new Model()). } } Dem ContainerFeedbackMessageFilter kann man dabei jede beliebige Komponente als Parameter übergeben. weil sich z.B. Im folgenden Beispiel ist das ein ContainerFeedbackMessageFilter. Dass wir in unserem Beispiel die Formulare als Parameter übergeben haben.komponenten. add(f1).

9.15 Erweitertes Feedback
<form wicket:id="form2"> Text: <input wicket:id="Text"><br> <button>Abschicken</button> </form> </wicket:extend>

Auch wenn die Nachrichten jetzt wie in Abbildung 9.21 zweimal erscheinen, ist doch erkennbar, dass die jeweiligen Feedback-Komponenten nur die Nachrichten der entsprechenden Form-Komponente anzeigen.

Abbildung 9.21 Auf das Formular eingegrenztes Feedback

9.15.2 Feedback für die Komponente
Möchte man genau für eine Komponente die entsprechenden Nachrichten darstellen, benutzt man einfach anstelle des ContainerFeedbackMessageFilter einen ComponentFeedbackMessageFilter. Wie der Name schon sagt, werden alle Nachrichten der Komponente herausgefiltert.
Listing 9.82 FormComponentFeedbackPage.java package de.wicketpraxis.web.thema.komponenten.forms.feedback; ... public class FormComponentFeedbackPage extends AbstractFormPage { public FormComponentFeedbackPage() { Form<?> f1=new Form<Void>("form"); TextField<String> textField = \ new TextField<String>("Text",new Model()); textField.setRequired(true); TextField<String> textField2 = \ new TextField<String>("Text2",new Model()); textField2.setRequired(true); f1.add(textField); f1.add(textField2); f1.add(new FeedbackPanel("feedback", new ComponentFeedbackMessageFilter(textField))); add(f1); } }

215

9 Formulare Das zusätzliche FeedbackPanel zeigt nun alle Nachrichten zum ersten Textfeld an (Abbildung 9.22).
Listing 9.83 FormComponentFeedbackPage.html <wicket:extend> <form wicket:id="form"> <div style="background-color:#f0f0f0;"> Text: <input wicket:id="Text"><br> <div wicket:id="feedback"></div> </div> <br> <div style="background-color:#f0f0f0;"> Text2: <input wicket:id="Text2"><br> </div> <button>Abschicken</button> </form> </wicket:extend>

Abbildung 9.22 Auf die Komponente eingegrenztes Feedback

9.15.3 Feedback als Rahmen
Die FormComponentFeedbackBorder-Komponente markiert die Formularkomponente, in der ein Fehler aufgetreten ist, mit einem roten Stern.
Listing 9.84 FormFeedbackBorderPage.java package de.wicketpraxis.web.thema.komponenten.forms.feedback; ... public class FormFeedbackBorderPage extends AbstractFormPage { public FormFeedbackBorderPage() { Form<?> f1=new Form<Void>("form"); TextField<String> textField = \ new TextField<String>("Text",new Model()); textField.setRequired(true); TextField<String> textField2 = \ new TextField<String>("Text2",new Model()); textField2.setRequired(true); f1.add(new FormComponentFeedbackBorder("feedback").add(textField)); f1.add(textField2); add(f1); } }

216

9.15 Erweitertes Feedback Intern benutzt die Komponente einen ContainerMessageFilter, sodass die Nachrichten der untergeordneten Elemente herausgefiltert werden können.
Listing 9.85 FormFeedbackBorderPage.html <wicket:extend> <form wicket:id="form"> <div wicket:id="feedback"> Text: <input wicket:id="Text"><br> </div> Text2: <input wicket:id="Text2"><br> <button>Abschicken</button> </form> </wicket:extend>

9.15.4 Feedback als Indikator
Den gleichen optischen Effekt erreicht man, in dem man eine FormComponentFeedbackIndicator-Komponente benutzt. Auch in diesem Fall wird bei einem Fehler ein roter

Stern angezeigt.
Listing 9.86 FormFeedbackIndicatorPage.java package de.wicketpraxis.web.thema.komponenten.forms.feedback; ... public class FormFeedbackIndicatorPage extends AbstractFormPage { public FormFeedbackIndicatorPage() { Form<?> f1=new Form<Void>("form"); TextField<String> textField = \ new TextField<String>("Text",new Model()); textField.setRequired(true); FormComponentFeedbackIndicator feedback = \ new FormComponentFeedbackIndicator("feedback"); feedback.setIndicatorFor(textField); TextField<String> textField2 = \ new TextField<String>("Text2",new Model()); textField2.setRequired(true); FormComponentFeedbackIndicator feedback2 = \ new CustomIndicator("feedback2"); feedback.setIndicatorFor(textField2); f1.add(textField); f1.add(feedback); f1.add(textField2); f1.add(feedback2); add(f1); } static class CustomIndicator extends FormComponentFeedbackIndicator { public CustomIndicator(String id) { super(id); } } }

217

9 Formulare Um das Aussehen etwas anzupassen, reicht es, eine eigene Klasse von der Klasse FormComponentFeedbackIndicator abzuleiten und in einem eigenen Markup die Darstellung

anzupassen (Abbildung 9.23).
Listing 9.87 FormFeedbackIndicatorPage.html <wicket:extend> <form wicket:id="form"> Text: <input wicket:id="Text"><span wicket:id="feedback"></span><br> Text2: <input wicket:id="Text2"><span \ wicket:id="feedback2"></span><br> <button>Abschicken</button> </form> </wicket:extend> Listing 9.88 FormFeedbackIndicatorPage$CustomIndicator.html <wicket:panel> <span style="color:red">Fehler</span> </wicket:panel>

Abbildung 9.23 FormComponentFeedbackIndicator in zwei Varianten

9.15.5 Feedback per CSS
Die beiden letzten Komponenten sind eine gute Ausgangsbasis für eine eigene Komponente. Unsere Komponente soll in Abhängigkeit davon, ob Nachrichten vorliegen, das Attribut class mit einem definierten Wert setzen.
Listing 9.89 FormComponentCssFeedbackBorder.java package de.wicketpraxis.web.thema.komponenten.forms.feedback; ... public class FormComponentCssFeedbackBorder extends Border implements IFeedback { private static final Logger _logger = \ LoggerFactory.getLogger(FormComponentCssFeedbackBorder.class); boolean _hasErrors; private CharSequence _cssClass; public FormComponentCssFeedbackBorder(String id,String cssClass) { super(id); _cssClass=cssClass; } @Override protected void onBeforeRender() { super.onBeforeRender(); _hasErrors = Session.get().getFeedbackMessages().messages( \ getMessagesFilter()).size() != 0;

218

9.15 Erweitertes Feedback
} @Override protected void onComponentTag(ComponentTag tag) { super.onComponentTag(tag); if (_hasErrors) tag.put("class", _cssClass); } protected IFeedbackMessageFilter getMessagesFilter() { return new ContainerFeedbackMessageFilter(this); } }

Der Aufbau der Klasse orientiert sich stark an der FormComponentFeedbackBorderKlasse. Der MessageFilter bezieht sich auf alle untergeordneten Komponenten. In der onBeforeRender-Methode wird geprüft, ob Nachrichten vorhanden sind, und ein entsprechendes Feld gesetzt. Wenn die Komponente dargestellt wird, wird das Attribut gesetzt. Das Markup der Komponente muss erstellt werden und sorgt dafür, dass die Kindelemente korrekt eingebunden werden.
Listing 9.90 FormComponentCssFeedbackBorder.html <wicket:border><wicket:body/></wicket:border>

Unsere Komponente kommt im folgenden Beispiel zum Einsatz. Wir übergeben für das Attribut class den Wert „error“. Für diese Style-Klasse haben wir in unserer für alle Beispiele geltenden CSS-Datei (form.css) entsprechende Regeln definiert.
Listing 9.91 FormComponentCssFeedbackBorderPage.java package de.wicketpraxis.web.thema.komponenten.forms.feedback; ... public class FormComponentCssFeedbackBorderPage extends AbstractFormPage { public FormComponentCssFeedbackBorderPage() { Form<?> f1=new Form<Void>("form"); TextField<String> textField = \ new TextField<String>("Text",Model.of("")); textField.setRequired(true); TextField<String> textField2 = \ new TextField<String>("Text2",Model.of("")); textField2.setRequired(true); f1.add(new FormComponentCssFeedbackBorder("feedback","error"). \ add(textField)); f1.add(textField2); add(f1); } } Listing 9.92 FormComponentCssFeedbackBorderPage.html <wicket:extend> <form wicket:id="form"> <div wicket:id="feedback"> Text: <input wicket:id="Text"><br> </div>

219

9 Formulare
Text2: <input wicket:id="Text2"><br> <button>Abschicken</button> </form> </wicket:extend>

Wenn ein Fehler auftritt, wird das Attribut gesetzt, wodurch sich die Darstellung der Komponente wie in Abbildung 9.24 ändert.

Abbildung 9.24 FormComponentCssFeedbackBorder im Fehlerfall

Zusammenfassung Wicket bietet eine reiche Palette an Möglichkeiten, um Rückmeldungen auf Nutzeraktionen an geeigneter Stelle anzuzeigen. Es empfiehlt sich, in Anlehnung an die vorhandenen Komponenten eigene Varianten zu erstellen, die dann in der jeweiligen Anwendung an jeder Stelle genutzt werden können. Dabei ist zu beachten, dass in den letzten Beispielen darauf verzichtet wurde zu prüfen, ob die Nachricht einen Fehler, eine Warnung oder einen Hinweis enthielt. Diese Erweiterung sollte ohne große Schwierigkeiten umzusetzen sein.

9.16

Generierte Formulare
Bisher haben wir nur Formulare betrachtet, wo alle Eingabemöglichkeiten bekannt waren. Das folgende Beispiel soll veranschaulichen, wie Formulare aus veränderlichen Daten generiert werden können (Abbildung 9.25). Dazu erstellen wir eine auf dieses Beispiel zugeschnittene Modellklasse.
Listing 9.93 MapPropertyModel.java package de.wicketpraxis.web.thema.komponenten.forms.dynamic; ... public class MapPropertyModel<V extends Serializable> implements IModel<V> { IModel<? extends Map<String,Serializable>> _model; String _property; public MapPropertyModel(IModel<? extends Map<String, Serializable>> \ model, String property) { _model = model; _property = property; } public V getObject() { return (V) _model.getObject().get(_property); }

220

ofMap(new HashMap<String. Serializable>> model = \ Model.startsWith("!")) { id=id. feedbackBorder. } }.16 Generierte Formulare public void setObject(V object) { _model.. } } Um das Beispiel einfach zu halten.detach(). boolean required=false. TextField<String> textField = new TextField<String>("input".java package de.put(_property.asList("!Name".properties) { @Override protected void populateItem(ListItem<String> item) { String id = item.forms. Listing 9. } FormComponentCssFeedbackBorder feedbackBorder = \ new FormComponentCssFeedbackBorder("border". item. IModel<? extends List<? extends String>> properties = \ Model. wird ein Pflichtfeld mit einem Ausrufezeichen am Anfang markiert. sodass bei den zwei Pflichtfeldern der passende optische Hinweis erscheint (Abbildung 9.id)).setLabel(Model. required=true. if (id.getModelObject().setRequired(required).thema.of(Arrays.getObject(). da so verhindert werden kann.add(feedbackBorder). dass die ListView die Komponenten neu erstellt und so die Fehlermeldungen nicht zugeordnet werden können.9.wicketpraxis.id)). public class DynFormPage extends AbstractFormPage { public DynFormPage() { final IModel<Map<String. } public void detach() { _model. Wichtig ist in diesem Zusammenhang auch der Aufruf von setReuseItems(true).substring(1). } } Die Modellklasse sorgt dafür. \ new MapPropertyModel<String>(model."!Vorname".94 DynFormPage. Um jedes Eingabefeld kommt unsere FormComponentCssFeedbackBorder-Komponente. .komponenten.web. dass der Wert aus dem Eingabefeld in der Map mit dem Schlüssel aus der ID der Komponente abgelegt wird.Serializable>())."Straße". textField.of(id))..setReuseItems(true)). feedbackBorder. textField.add(new ListView<String>("inputs".25)."error").add(new Label("id".add(textField). form. add(form).object). Form form=new Form("form"). 221 .dynamic."Hausnummer")).

dass entweder das übergeordnete oder das untergeordnete Formular unabsichtlich abgeschickt wird (und onSubmit() aufgerufen wird). indem das innere form-Tag einfach in ein div-Tag umgewandelt wird.komponenten. Doch das würde zu Fehler führen. \ new CompoundPropertyModel<StandardTypesBean>(_bean)) { @Override 222 . _subBean=new StandardTypesBean().9 Formulare Listing 9.. wenn man innerhalb eines Formulars eine Komponente benutzt.forms. private StandardTypesBean _subBean.web. public class FormFormPage extends AbstractFormPage { private StandardTypesBean _bean.java package de. public FormFormPage() { _bean = new StandardTypesBean(). Das hat zur Folge.25 Generiertes Formular 9. Listing 9.thema. .95 DynFormPage.96 FormFormPage. doch kann man dieses Problem elegant umgehen. Wicket löst dieses Problem elegant.embedded. weil es nur ein übergeordnetes Formular gibt.wicketpraxis. die selbst ein Formular benutzt? Normalerweise würden auch im Quelltext der Seite zwei form-Tags ausgegeben werden. indem man von Buttons Gebrauch macht und die notwendigen Aktionen in Abhängigkeit von deren Aktivierung auflöst..html <wicket:extend> <form wicket:id="form"> <wicket:container wicket:id="inputs"> <div wicket:id="border"> <span wicket:id="id"></span><input wicket:id="input"><br> </div> </wicket:container> <button>Abschicken</button> </form> </wicket:extend> Abbildung 9.17 Verschachtelte Formulare Was passiert. Das bedeutet zwar. Form<StandardTypesBean> form=new Form<StandardTypesBean>("form". dass in so einem Fall alle Formulare korrekt abgeschickt werden.

} }. form.9. \ new CompoundPropertyModel<StandardTypesBean>(_subBean)) { @Override protected void onSubmit() { showInfo("Sub"). margin:4px.getText()+". ob irgendwo in der Komponentenhierarchie bereits ein Formular vorhanden ist. add(form). können Formulare beliebig in Komponenten verschachtelt werden..html <wicket:extend> <h2>Formular im Formular</h2> <form wicket:id="form" style="background-color:#f0f0f0.add(subForm). Form: "+source)."> Text(sub): <input wicket:id="Text"><br> <button>Abschicken</button> </form> <button>Abschicken</button> </form> </wicket:extend> Listing 9. Abbildung 9. } protected void showInfo(String source) { info("Beans: "+_bean.26 Verschachtelte Formulare Listing 9.html . "+_subBean. Wicket kann die Eingabedaten dabei korrekt zuordnen (Abbildung 9. Da Wicket aber erkennen kann.17 Verschachtelte Formulare protected void onSubmit() { showInfo("Main"). form. subForm..getText()+ ". Form<StandardTypesBean> subForm=new Form<StandardTypesBean>("sub".add(new TextField<String>("Text"))."> Text: <input wicket:id="Text"><br> <form wicket:id="sub" style="background-color:#eee.add(new TextField<String>("Text")). } } In diesem Fall befinden sich beide Formulare innerhalb einer Komponente.26).97 FormFormPage. } }.98 Ergebnis. <body> <div wicket:id="feedback" id="feedback4"><wicket:panel> </wicket:panel></div> 223 .

sondern nach dem „wie“ man die Handhabung der Webanwendung verbessert..9 Formulare <wicket:child><wicket:extend> <h2>Formular im Formular</h2> <form wicket:id="form" style="background-color:#f0f0f0. gestellt werden sollte." \ id="form5" method="post" action=". dass nicht mehr die Frage nach dem „ob“. Dabei ist die Integration von Ajax derart einfach.." \ id="sub6"> Text(sub):<input wicket:id="Text" value="" name="sub:Text"><br> <button>Abschicken</button> </div> <button>Abschicken</button> </form> </wicket:extend></wicket:child> </body> . an der Wicket das form-Tag ausgetauscht hat.. 224 . Die Hervorhebung markiert die Stelle.."> .. um Formulare auf die eigenen Bedürfnisse anzupassen.. mit denen sich in kurzer Zeit selbst komplexe Formulare umsetzen lassen.margin:4px. Die unterschiedlichen Feedback-Komponenten dienen allenfalls als Ausgangspunkt für eigene Entwicklungen. Im Laufe der Zeit entsteht so ein Portfolio an eigenen Komponenten. Text: <input wicket:id="Text" value="" name="Text"><br> <div wicket:id="sub" style="background-color:#eee. Zusammenfassung Wicket bietet interessante Möglichkeiten. die aber recht einfach umzusetzen sind.

. wie man eigene Daten in einer Session ablegt: indem man die abgeleitete Klasse erweitert.1 Eine eigene Session-Klasse Zuerst erstellen wir eine eigene Session-Klasse. Doch zuerst müssen wir die Grundlage dafür schaffen. den Zugriff auf bestimmte Seiten nur angemeldeten Benutzern zu erlauben. Dabei ist zu beachten. Damit klärt sich auch sofort.1 Einfache Variante Die einfachste Variante besteht darin. Listing 10. } 225 .1.. 10.wicketpraxis. InjectorHolder. Damit die Beispiele einfacher nachzuvollziehen sind.inject(this).BEAN_ID) UserDao _userDao. die von der Klasse WebSession abgeleitet wird.10 10 Sessions und Security Bisher haben wir die Session nur unter dem Gesichtspunkt betrachtet. dass der Nutzer sich anmelden kann.session. public SecurePageSession(Request request) { super(request). dass der Zugriff auf die Session durch mehrere Threads erfolgen kann.1 SecurePageSession. dass Wicket den Zustand der Komponenten und Seiten in der Session hält. .apps.getInjector(). erstellen wir eine neue Wicket-Applikation. public class SecurePageSession extends WebSession { @SpringBean(name=UserDao. 10.java package de.session. Integer _userId. Auf den folgenden Seiten beschäftigen wir uns damit. wie man in einer Session Anmeldedaten verwaltet und Seiten vor unberechtigtem Zugriff schützt.

Die Basisklasse erwartet zwei Parameter: Der erste gibt die Klasse (oder in unserem Fall das Interface) an. dirty(). return null.apps.get().getId(). 10. besteht in einer einfachen Markierung durch ein Interface. Generell ist zu empfehlen. } public synchronized User getUser() { if (_userId!=null) return _userDao.java package de. durch Marker-Interfaces (also ohne Methoden wie z. Session-Replikation in einem Cluster betreibt). Diese Entscheidung wird aber nicht durch die Seite selbst getroffen. Der zweite Parameter gibt die Klasse der Seite an. Dazu leiten wir eine Klasse von der Klasse SimplePageAuthorizationStrategy ab. Dadurch verhindert man. auf die der Nutzer umgeleitet wird. auf die der Zugriff nur für angemeldete Nutzer möglich ist.pages.B. das geschützte Seiten identifiziert.get(_userId). public interface SecurePageInterface { } 10. Serializable) oder Annotationen bereitstellt. Das Interface dient nur der Markierung. sondern bereits bevor eine Instanz der Seite erstellt wird.session.3 Strategie Jetzt können wir Seiten markieren. es müssen also keine Methoden implementiert werden. den Zugriff auf Seiten zu reglementieren.2 SecurePageInterface. dass Programmcode zur Nutzerverwaltung in die Komponente einfließt. dass man in die Information. dirty(). empfiehlt es sich. so wenig wie möglich Daten in der Session abzulegen.wicketpraxis.2 Geschützte Seiten Die einfachste Möglichkeit.10 Sessions und Security public synchronized void setUser(User user) { _userId = user.1. Listing 10.1. } } Da die Session serialisiert werden könnte (wenn man z. In diesem Beispiel wird in der Session nur die Datensatz-ID (_userId) abgelegt. welcher Nutzer auf welche Seite zugreifen kann. } public static SecurePageSession get() { return (SecurePageSession) Session.B. 226 . } public synchronized void clearUser() { _userId = null. } public synchronized boolean isUserLogin() { return _userId!=null ? true : false.

getSecuritySettings(). der sich anmelden konnte. } @Override public Class<? extends Page> getHomePage() { return Start. Listing 10.isUserLogin().10.java package de. } } Wenn der Nutzer durch einen Link auf eine Seite gelangt. signInPageClass). von der er nach erfolgreichem Login wieder zurück auf die ursprüngliche Seite geleitet werden kann. public class UserLoginSimplePageAuthStrategy \ extends SimplePageAuthorizationStrategy { public UserLoginSimplePageAuthStrategy(Class<? extends Page> \ signInPageClass) { super(SecurePageInterface. auf der man sich anmelden kann.apps. Zum andern überschreiben wir die Methode.4 SecurePageApplication. Zum einen setzen wir für die Applikation unsere gewünschte Autorisierungsstrategie. springt er auf die Anmeldeseite.init(). 10..session. addComponentInstantiationListener(new SpringComponentInjector(this)).apps. Listing 10... } @Override 227 .session. in der die Session erzeugt wird. ob ein Nutzer autorisiert ist. ..class)).wicketpraxis. kann folglich auf alle Seiten zugreifen.1.get(). machen wir in dem Fall nur daran fest.1 Einfache Variante wenn er noch nicht angemeldet ist. . Jeder Nutzer.3 UserLoginSimplePageAuthStrategy. ob der Nutzer angemeldet ist oder nicht. } @Override protected boolean isAuthorized() { return SecurePageSession. Das sollte daher auch eine Seite sein.java package de.auth.. public class SecurePageApplication extends WebApplication { @Override protected void init() { super. .class.. für die eine Anmeldung nötig ist.class. Die Entscheidung.wicketpraxis.setAuthorizationStrategy( \ new UserLoginSimplePageAuthStrategy(LoginPage.4 WebApplication In der WebApplication-Klasse sind zwei Anpassungen entscheidend.

7 Start.markup. import org.pages. } } 10.html.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.html <wicket:extend> <h1>Startseite</h1> <p>Weiter zur <a wicket:id="link">geschützten Seite</a></p> </wicket:extend> 228 .5 AbstractBasePage. Response response) { return new SecurePageSession(request).10 Sessions und Security public Session newSession(Request request.1. Listing 10. Alle anderen Seiten werden von dieser Seite abgeleitet.8 Start.BookmarkablePageLink.apps.org/TR/html4/strict.pages.html.session. \ ProtectedPage.WebPage. Listing 10. public abstract class AbstractBasePage extends WebPage { } Listing 10.5 Seiten Jetzt müssen wir natürlich noch die fehlenden Seiten erstellen.wicketpraxis. erstellen wir eine abstrakte Basisklasse. Damit die Beispiele schön kurz bleiben.01//EN" "http://www.SecurePage</title> </head> <body> <wicket:child></wicket:child> </body> </html> Die erste Seite ist die Startseite. import org. } } Listing 10.java package de.link.wicket. Auf dieser Seite fügen wir einen Link auf eine geschützte Seite ein.6 AbstractBasePage.apps.apache.class)).w3.wicket.java package de.markup.session. public class Start extends AbstractBasePage { public Start() { add(new BookmarkablePageLink<ProtectedPage>("link".apache. Die Seite ist natürlich noch für jeden Nutzer sichtbar. in der wir das Markup für eine vollständige Seite ablegen.dtd"> <html> <head> <title>Wicket Praxis .wicketpraxis.

Listing 10. public class LoginPage extends AbstractBasePage { @SpringBean(name=UserDao.apps. 229 ..wicketpraxis.java (gekürzt) package de. in das der Nutzer seine Zugangsdaten eintragen muss.BEAN_ID) UserDao _userDao. über die sich ein angemeldeter Nutzer abmelden kann.session. public LoginPage() { add(new FeedbackPanel("feedback")).getPassword().java package de.session..9 ProtectedPage. .class)). Für das Formular erstellen wir uns eine Hilfsklasse.java package de. Listing 10..wicketpraxis..data.setEMail(). welche die Anmeldedaten aufnimmt.pages..session.. auf der sich ein Formular befindet. Durch den Aufruf von continueToOriginalDestination() wird geprüft. . public class LoginBean implements Serializable { String _eMail. .wicketpraxis.. getEMail(). String _password.apps.LogoutPage. } Wenn der Nutzer seine Daten eingibt.apps.pages.. Außerdem fügen wir einen Link zur Seite ein. ob der Nutzer beim Zugriff auf eine geschützte Seite umgeleitet wurde. ob ein passender Datensatz vorhanden ist.html <wicket:extend> <h1>geschützte Seite</h1> <p><a wicket:id="logout">Abmelden</a></p> </wicket:extend> Jetzt benötigen wir noch die Anmeldeseite.12 LoginPage. wird geprüft. Sollte das der Fall sein.1 Einfache Variante Als Nächstes erstellen wir die geschützte Seite. wird direkt zu dieser Seite zurückgesprungen. Listing 10.10. sollte man den Nutzer auf eine passende Seite lenken.10 ProtectedPage. } } Listing 10.. Nach Prüfung des Passworts wird der Nutzer in der Session abgelegt.11 LoginBean. Ist das nicht der Fall. public class ProtectedPage extends AbstractBasePage \ implements SecurePageInterface { public ProtectedPage() { add(new BookmarkablePageLink<LogoutPage>("logout". die wir mit dem Interface entsprechend markieren.

pages.getObject().java package de. User user = _userDao. if (!continueToOriginalDestination()) { setResponsePage(SecurePageApplication.14 LogoutPage..get(). } } Listing 10. form.13 LoginPage.get(). sollte man dem Umweg über eine gesonderte Abmeldeseite gehen.getPassword())) { SecurePageSession.wicketpraxis.html <wicket:extend> <h1>Login</h1> <div wicket:id="feedback"></div> <form wicket:id="form"> <table> <tr> <td>EMail</td><td><input wicket:id="EMail"></td> </tr> <tr> <td>Passwort</td> <td><input wicket:id="Password" type="password"></td> </tr> <tr> <td colspan="2"><button>Anmelden</button></td> </tr> </table> </form> </wicket:extend> Jetzt fehlt nur noch die Seite. benötigen wir keine MarkupDatei für diese Seite.isPasswordValid(loginBean.session. } } else error("EMail oder Passwort falsch (Passwort natürlich)"). form.add(new PasswordTextField("Password")).getHomePage()). Auf diese Weise kann man später wesentlich einfacher Anpassungen vornehmen. } }. } else error("EMail oder Passwort falsch (EMail natürlich)").10 Sessions und Security Form<LoginBean> form=new Form<LoginBean>("form".getEMail()).. \ new CompoundPropertyModel<LoginBean>(new LoginBean())) { @Override protected void onSubmit() { LoginBean loginBean = getModel(). Da wir in unserer Seite direkt zur Startseite weiterleiten.getByEMail(loginBean.setRequired(true)). public class LogoutPage extends AbstractBasePage { public LogoutPage() { 230 .add(new TextField<String>("EMail"). Listing 10. Auch wenn man das Löschen des angemeldeten Nutzers und das Weiterleiten auf die Homepage direkt in jede Seite integrieren könnte. . add(form). die für die Abmeldung verantwortlich ist.apps. if (user!=null) { if (user.setUser(user).

Wenn er dort die korrekten Zugangsdaten eingegeben hat.getHomePage()). public interface SecureComponentInterface { } Auch in diesem Fall benutzen wir ein Marker-Interface.session. die geschützte Seite. Listing 10. Daraufhin wird er. Abbildung 10.java package de.java package de. setResponsePage(SecurePageApplication. sondern die Komponenten entsprechend markieren würde. Dann klickt er den Link zur geschützten Seite an. Listing 10. Unsere Strategie leiten wir allerdings nicht mehr von einer Basisstrategie ab.apps. da er noch nicht angemeldet ist. sobald ein unangemeldeter Benutzer auf eine geschützte Komponente zugreifen möchte.wicketpraxis. } } Der Anmeldeprozess sieht dann wie folgt aus (Abbildung 10.wicketpraxis.2 Marker an Komponenten Je nach Anwendung bilden Seiten nur den Rahmen. wenn man nicht die Seiten.2 Marker an Komponenten SecurePageSession.1): Der Nutzer beginnt auf der Startseite.session. landet er wieder auf der Startseite. auf die Anmeldeseite weitergeleitet. in dem verschiedene Komponenten ihren Platz finden. sondern implementieren das IauthorizationStrategy-Interface direkt. wird er auf sein ursprüngliches Ziel.15 SecureComponentInterface. Auf diese Weise befindet sich die Information über die Schutzbedürftigkeit der Informationen sehr viel dichter an der Quelle.pages. Dann ist eine Anmeldung notwendig. public UserLoginSecureComponentAuthStrategy( \ Class<? extends Page> signInPageClass) 231 . Wenn er auf den Link „Abmelden“ klickt.get().1 Der Anmeldeprozess 10..16 UserLoginSecureComponentAuthStrategy. weitergeleitet.get().clearUser(). public class UserLoginSecureComponentAuthStrategy \ implements IAuthorizationStrategy { Class<? extends Page> _signInPageClass. Da liegt es nahe. .10..apps.auth.

10 Sessions und Security { _signInPageClass=signInPageClass..class. ob eine Instanz der Komponente erzeugt werden darf.. .java . getSecuritySettings(). In der Methode isInstantiationAuthorized() wird geprüft.class). Normalerweise wird in so einem Fall eine passende Fehlermeldung ausgelöst.. behandelt das nächste Beispiel). Wenn diese Methode „false“ zurückgibt.. Wir fügen jetzt eine Komponente ein. } return true.17 SecurePageApplication. getSecuritySettings().setAuthorizationStrategy( \ new UserLoginSecureComponentAuthStrategy(LoginPage. } public <T extends Component> boolean isInstantiationAuthorized( \ Class<T> componentClass) { if (SecureComponentInterface. Action action) { return true. protected void init() { . Da der Nutzer aber die Chance bekommen soll.isUserLogin(). da dieser Marker in diesem Beispiel keine Beachtung findet. Wir prüfen.get(). ist das Erzeugen der Instanz nicht gestattet. überschreiben wir das Verhalten mit einer eigenen Variante des IUnauthorizedComponentInstantiationListener.. auf der der Nutzer sich anmelden kann. ob die Komponente einen angemeldeten Benutzer voraussetzt.. ob der Nutzer angemeldet ist. Listing 10. der den Nutzer auf die Login-Seite weiterleitet. } } Wir übergeben wieder die Klasse der Seite. Außerdem entfernen wir das SecurePageInterface von der Seite. sich anzumelden. und prüfen für diesen Fall.class)). } public boolean isActionAuthorized(Component component. Außerdem müssen wir die Methode isActionAuthorized() implementieren (was das zu bedeuten hat. } }).isAssignableFrom(componentClass)) { return SecurePageSession.setUnauthorizedComponentInstantiationListener( \ new IUnauthorizedComponentInstantiationListener() { public void onUnauthorizedInstantiation( \ final Component component) { throw new RestartResponseAtInterceptPageException( \ LoginPage... } . 232 . die entsprechend markiert ist.

public class ProtectedPage extends AbstractBasePage { public ProtectedPage() { add(new BookmarkablePageLink<LogoutPage>("logout"... ob der Nutzer angemeldet ist.LogoutPage. befindet sich jetzt aber an der richtigen Stelle. IModel<?> model) { super(id..Model. . Action action) { if (component instanceof SecureComponentInterface) { if (!SecurePageSession.. Jetzt prüfen wir in der Methode isActionAuthorized().3 Elemente ausblenden Eine Alternative zur Anmeldepflicht besteht darin.19 ProtectedPage. Listing 10. .3 Elemente ausblenden Listing 10. was vom Aufwand wohl nur sehr schwer zu rechtfertigen sein würde.java package de.auth.pages.java package de. Wir implementieren wieder das IAuthorizationStrategy-Interface und geben dieses Mal in der Methode isInstantiationAuthorized() immer true zurück. public class UserLoginSecureComponentDisableAuthStrategy implements IAuthorizationStrategy { public boolean isActionAuthorized(Component component. } } } Listing 10. einfach auszublenden. die nur ein angemeldeter Benutzer sehen darf.class)).apps.session.wicketpraxis.html <wicket:extend> <h1>geschütze Seite</h1> <p><a wicket:id="logout">Abmelden</a></p> <p><span wicket:id="secureLabel"></span></p> </wicket:extend> Die Anwendung verhält sich jetzt aus Nutzersicht genau wie im ersten Beispiel.session.apps.wicketpraxis.20 UserLoginSecureComponentDisableAuthStrategy. } public static class SecureLabel extends Label \ implements SecureComponentInterface { public SecureLabel(String id. Dabei muss diese Funktion nicht in jeder Komponente implementiert werden.of("Das darf nicht jeder \ sehen"))). model).isUserLogin()) 233 . add(new SecureLabel("secureLabel". 10. Ob Komponenten und damit Informationen vor unberechtigtem Zugriff zu schützen sind.18 ProtectedPage.get(). sodass jede Komponente erzeugt werden kann. Komponenten.10.

java ... } public <T extends Component> boolean isInstantiationAuthorized( \ Class<T> componentClass) { return true. liegt in der Verantwortung der Komponente selbst.21 SecurePageApplication.. } } return true. . getSecuritySettings().setAuthorizationStrategy( \ new UserLoginSecureComponentDisableAuthStrategy()). Welche Konsequenzen das für die Darstellung und Funktion der Komponente hat. gibt es verschiedene Ansatzpunkte für eine geeignete Strategie. Der Aufwand für einen Strategiewechsel ist somit gering. } .. protected void init() { ... Links auf der Seite deaktivieren. Listing 10. Würde man nur für die Action ENABLE prüfen. Wenn ein unangemeldeter Benutzer die Seite lädt.. Zusammenfassung Wie wir in den Beispielen sehen konnten. wie man geschützte Informationen nur angemeldeten Benutzern zugänglich macht. Dabei bezieht sich RENDER auf die Darstellung der Komponente. } } Im diesem Beispiel ignorieren wir den Wert des Parameters „action“.B. Dabei hat die Strategie nur indirekt Einfluss auf die Komponenten. könnte man z. werden die markierten Komponenten einfach nicht angezeigt..10 Sessions und Security { return false. Außerdem muss sich die Komponente nicht selbst um die Darstellung oder den Sprung auf die Anmeldeseite kümmern. Momentan benutzt Wicket nur zwei verschiedene Werte: RENDER und ENABLE. da die Link-Klasse die Information auswertet und statt eines Links einfach nur den Text darstellt. während sich ENABLE auf den Rückgabewert von inEnabled() der Klasse Component bezieht. 234 .

die von Component abgeleitet wurden. Meist stellt sich dann an dieser Stelle recht schnell die Frage: Wie fange ich an? Wie löse ich die alltäglichen Probleme? Wicket ist ein flexibles.11. können unter anderem durch einen Blick in den Code geklärt werden.B. 11. Quelltexte als Anregung Es ist immer hilfreich. dass es für ein Problem nicht nur eine Lösung gibt.1 Die Integration von Spring 11 11 Wicket in der Praxis Nachdem wir nun die wichtigsten Komponenten kennen und gelernt haben. das Thema Navigation können aber nicht mit nur einer Komponente gelöst werden.B. Man kann daher anhand der Basiskomponenten gut erkennen. Komplexe Aufgabenstellungen wie z. Auf den folgenden Seiten möchte ich anhand von ausgewählten Beispielen Anregungen geben. anpassungsfähiges Framework. intensiv in den Quelltexten von Wicket beschäftigt. die in diesem Buch nicht beantwortet wurden. wie man sie einsetzt. der durch Wicket gesetzt wurde. wie die verschiedenen Funktionen zusammenspielen und wie Wicket intern funktioniert. 235 . da sonst der Wert. Fragen. könnten wir so richtig loslegen. Die Quelltexte sind sehr gut dokumentiert. Das hat sich nicht erst beim Schreiben dieses Buches gezeigt. Wichtig ist dabei. im Konstruktor der Klasse durch den deklarierten Wert (in dem Fall null) überschrieben würde. wenn man von einer Sache so viel wie möglich versteht. dass das Feld nicht initialisiert werden darf. Allerdings funktioniert diese Annotation nur für Klassen. Das bedeutet aber auch. wie Sie solche und andere Probleme lösen und die ganzen Puzzleteile zu einem Ganzen zusammenfügen können.1 Die Integration von Spring Bisher haben wir das eine oder andere Mal von der SpringBean-Annotation Gebrauch gemacht. Für das bessere Verständnis der Zusammenhänge habe ich mich z.

java package de. Man muss in diesem Fall den Prozess. auf die dann in dem Beispiel das Feld _userDao zeigt. der automatisch für alle Komponenten (die von der Klasse Component abgeleitet wurden) ausgeführt wird.spring. sondern mit den Informationen aus der Annotation temporär verfügbar gemacht und alle Methodenaufrufe an diese Instanz weitergeleitet..BEAN_ID) UserDao _userDao. ohne dass man sich zusätzlich um das korrekte Serialisieren kümmern muss.11 Wicket in der Praxis Listing 11.. public SimpleNonWicketClass() { InjectorHolder. Listing 11.1 SimpleSpringIntegrationPage.wicketpraxis.new SimpleNonWicketClass(). Möchte man aber z.getEmail())).getByEMail("test@wicket-praxis. in einer Modellklasse eigenständig auf SpringBeans zugreifen._userDao. .thema. . wenn es nur eine von dem Typ gibt @SpringBean(name = UserDao.java package de. public SimpleSpringIntegrationPage() { add(new Label("message". public class ExtendedSpringIntegrationPage extends WebPage { public ExtendedSpringIntegrationPage() { add(new Label("message".wicketpraxis. der zuverlässig serialisiert werden kann.thema. damit z.. } } Auch wenn die Instanz.web. } public static class SimpleNonWicketClass { @SpringBean UserDao _userDao.de").get(1)==null ? "kein Nutzer" : \ "ein Nutzer")).B.inject(this). Dabei wird die eigentliche SpringBean nicht serialisiert.getEMail().web. if (user!=null) return user. reicht die Annotation aus den anfangs geschilderten Gründen nicht aus. } public String getEmail() { User user = _userDao. einer anderen Komponente oder einer Modellklasse die Instanz zu übergeben. public class SimpleSpringIntegrationPage extends WebPage { // geht auch ohne name. der Datenbankzugriff dann in dieser Modellklasse gekapselt ist. } } } 236 .spring. gibt es doch einen wesentlichen Unterschied: Die Instanz ist ein Proxy. wie eine Instanz von UserDao aussieht.getInjector().. return null.2 ExtendedSpringIntegrationPage. für jede Instanz von Hand anstoßen. Es ist also möglich.B.

} Wenn der Navigationspunkt aktiv ist. wenn der Nutzer den Navigationspunkt aktiviert. public interface NavCallbackInterface { public String getName().java package de.11. public boolean isActive(Page page). dem Nutzer verschiedene Seiten. 11.getInjector() den durch die Applikation definierten Injector.nav.wicketpraxis. die Auskunft über den Navigationspunkt gibt.howto..2 Navigation Die meisten Webanwendungen verhalten sich wie Webanwendungen.nav. Wichtig sind an diesem Beispiel vor allem zwei Aspekte: die Struktur der Seitenklassen sowie der Komponenten und die Abstraktion der Navigationsinformation. public AbstractNavCallback(String name) { _name=name.basepage.basepage.web.3 NavCallbackInterface.B. public void onClick(Page page).thema. sie bieten z.howto. .4 AbstractNavCallback. der Datenzugriff in einer Modellklasse gekapselt werden. } 237 .. } public String getName() { return _name. Als Erstes erstellen wir eine abstrakte Informationsschnittstelle.B. public List<NavCallbackInterface> getChilds(Page page). die als Ergebnis eine Liste von Unterpunkten liefern kann..wicketpraxis. Der Aufruf der inject()-Funktion ist an jeder Stelle und in jeder Klasse möglich. Listing 11. sondern soll nur als Anregung dienen.java package de. Die Methode onClick() wird aufgerufen. Diese Lösung ist nicht ohne Weiteres auf die eigene Problemstellung übertragbar. in denen er navigieren kann. Durch den Aufruf der Methode inject() werden die annotierten Felder initialisiert. . Die AbstractNavCallbackKlasse reduziert nur etwas den Schreibaufwand. Im folgenden Beispiel ermöglichen wir die Navigation zwischen verschiedenen Seiten und zu Unterpunkten der ausgewählten Seite. dann wird die Methode getChilds() aufgerufen. Listing 11.thema. public abstract class AbstractNavCallback implements NavCallbackInterface { String _name..web. Auf diese Weise kann z. Das bedeutet.2 Navigation Dazu ermitteln wir über InjectorHolder.

IModel<NavCallbackInterface> callbackModel) { super(id).thema.onBeforeRender(). Listing 11.onClick(getPage()).java package de. initPanel(callbackModel). 238 . . Link<NavCallbackInterface> link = \ new Link<NavCallbackInterface>("link".nav.web. } }. } } Jetzt benötigen wir eine Komponente."Name"). } }. } public NavPanel(String id. if (nameModel.isActive(getPage())? "active" : null. _level=level.howto..11 Wicket in der Praxis public List<NavCallbackInterface> getChilds(Page page) { return null. CascadingLoadableDetachableModel<String. } public boolean isActive(Page page) { return false.. welche die Navigationsstruktur anzeigen kann. Als Parameter wird ein Navigationspunkt übergeben.getObject()==null) setVisible(false).5 NavPanel. } private void initPanel(IModel<NavCallbackInterface> callbackModel) { final IModel<String> nameModel= new PropertyModel<String>(callbackModel. else setVisible(true). NavCallbackInterface> \ classModel=new CascadingLoadableDetachableModel<String. } @Override protected void onBeforeRender() { super.basepage. IModel<NavCallbackInterface> callbackModel. int level) { super(id). \ NavCallbackInterface>(callbackModel) { @Override protected String load(NavCallbackInterface p) { return p. public NavPanel(String id. public class NavPanel extends Panel { int _level=0.wicketpraxis. initPanel(callbackModel). callbackModel) { @Override public void onClick() { getModelObject().

.6 NavPanel.item. Ansonsten wird der Link ausgeblendet. \ "styles/base.basepage. CascadingLoadableDetachableModel<List<NavCallbackInterface>.. wenn wir nachher einen Navigationseinstiegspunkt übergeben.of("level"+_level). 239 . Wir benutzen das Panel also rekursiv.thema."all")). dass wir den Parameter für die Hierarchietiefe um eins erhöhen.add(new Label("name".7 AbstractBasePage." ")). wenn der Navigationspunkt einen Namen hat.add(new AttributeAppender("class".web. Listing 11. der nur Kindelemente zurückliefert.true.getChilds(getPage()).howto. public abstract class AbstractBasePage extends WebPage { public AbstractBasePage() { add(CSSPackageResource.css". } }).2 Navigation link. Diese Funktion wird benötigt.wicketpraxis.add(new AttributeAppender("class".add(new NavPanel("child"." ")). add(link).getModel().getHeaderContribution(AbstractBasePage. nameModel)).html <wicket:panel> <a wicket:id="link"><span wicket:id="name"></span></a> <ul> <li wicket:id="childs" class="menu_line"> <wicket:container wicket:id="child"></wicket:container> </li> </ul> </wicket:panel> Für alle Seiten der Anwendung erstellen wir eine abstrakte Basisklasse. \ NavCallbackInterface> childModel = \ new CascadingLoadableDetachableModel<List<NavCallbackInterface>. der nur angezeigt wird. } } Wir erstellen einen Link. aber selbst nicht angezeigt werden darf.classModel. \ Model. } }. Listing 11. welche die notwendigen CSS-Dateien einbindet.java package de. add(new ListView<NavCallbackInterface>("childs".11. die oberste Instanz der NavPanel-Klasse erzeugt und den Rahmen für den Hauptbereich der Anwendung definiert. childModel) { @Override protected void populateItem(ListItem<NavCallbackInterface> item) { item. link.class._level+1)). Im zweiten Schritt erstellen wir für die Liste aller Kindelemente jeweils ein NavPanel mit dem Unterschied. . link. \ NavCallbackInterface>(callbackModel) { @Override protected List<NavCallbackInterface> load(NavCallbackInterface p) { return p.true.

Das ist notwendig. WebMarkupContainer webMarkupContainer = \ new WebMarkupContainer("main") { @Override public boolean isTransparentResolver() { return true. background-color:#d0d0d0. add(webMarkupContainer).html <html> <head> </head> <body> <div class="content"> <div class="nav"> <wicket:container wicket:id="nav"></wicket:container> </div> <div wicket:id="main" class="main"> <wicket:child></wicket:child> </div> <div class="clear"></div> </div> </body> </html> Listing 11.9 base. } . add(new NavPanel("nav". height:600px.clear { 240 .css . } . dass Elemente. background-color:#f0f0f0. background-color:#e0e0e0. weil neue Komponenten in abgeleiteten Klassen auf derselben Hierarchiestufe hinzugefügt werden.11 Wicket in der Praxis LoadableDetachableModel<NavCallbackInterface> navModel = \ new LoadableDetachableModel<NavCallbackInterface>() { @Override protected NavCallbackInterface load() { return NavInfo.navModel)). Listing 11.nav { width:200px. alternativ auch im Markup innerhalb des Containers gesucht werden.getMainNavigation(). } }. Das bedeutet. padding-left:20px. } Der Rahmen für den Hauptbereich („main“) überschreibt das Attribut für transparentResolver mit „true“.content { width:1024px. margin:auto. } }. } public abstract List<NavCallbackInterface> getNavigations(). float:left.main { width:800px. margin-left:200px. die auf derselben Hierarchiestufe wie der MarkupContainer hinzugefügt werden.8 AbstractBasePage. } .

in die Liste ein. } ."Zweite Seite")).nav.10 NavInfo. Listing 11. list-style: none. } Die NavInfo-Klasse dient als Hilfsklasse. public class NavInfo { public static List<NavCallbackInterface> getPages() { List<NavCallbackInterface> ret=new ArrayList<NavCallbackInterface>(). ret.11..class. } Wenn wir eine Seite hinzufügen. } public void onClick(Page page) {} }. 241 .wicketpraxis.2 Navigation clear:both. welche die Navigationsinformation anhand der Seiteninformation erstellt. } li a. ret. Dazu tragen wir alle Seiten. return ret.howto. } public static NavCallbackInterface getMainNavigation() { return new AbstractNavCallback(null) { public List<NavCallbackInterface> getChilds(Page page) { return getPages().java package de."Start")).basepage.thema. dann kann bei aktivierter Seite die erweiterte Navigationsinformation durch den Aufruf von getNavigations() ermittelt werden. .web. padding-top:4px.nav ul { margin:0px.active { font-weight:bold.thema. .nav ul li { margin:0px. Listing 11. Die Klasse PagNavCallback ist eine Hilfsklasse.level1 { padding-left:10px.howto.basepage. } ..level2 { padding-left:20px.11 PageNavCallback.web.wicketpraxis.class. padding-bottom:4px. die von AbstractBasePage abgeleitet wird. die in der Hauptnavigation angezeigt werden sollen.. padding:0px.add(new PageNavCallback(StartPage.java package de. }. padding:0px.add(new PageNavCallback(SecondPage. } li a.. um an zentraler Stelle die Hauptnavigation verwalten zu können. } .

onBeforeRender().setResponsePage(_pageClass). public PageNavCallback(Class<? extends Page> pageClass.. } public void onClick(Page page) { page. Die Startseite bekommt zwei Navigationspunkte.basepage. Listing 11. } } Jetzt gestaltet sich das Anlegen neuer Seiten recht einfach._clicked) { @Override protected void onBeforeRender() { super. } @Override public List<NavCallbackInterface> getNavigations() { List<NavCallbackInterface> ret=new ArrayList<NavCallbackInterface>(). } public List<NavCallbackInterface> getChilds(Page page) { if (page.. die jeweils einen eigenen Wert in einem Modell ablegen. public class StartPage extends AbstractBasePage { IModel<Integer> _clicked=new Model<Integer>().12 Start.getClass()==_pageClass. } }). } } return null.String name) { super(name).howto. public StartPage() { add(new Label("clicked".thema. setVisible(getDefaultModelObject()!=null ? true : false).wicketpraxis.getNavigations().11 Wicket in der Praxis public class PageNavCallback extends AbstractNavCallback { Class<? extends Page> _pageClass. } public boolean isActive(Page page) { return page. _pageClass=pageClass.java package de. } @Override protected boolean callOnBeforeRenderIfNotVisible() { return true.web. das durch das Label „clicked“ angezeigt wird.getClass()==_pageClass) { if (page instanceof AbstractBasePage) { return ((AbstractBasePage) page). 242 . .

howto.add(new AbstractNavCallback("Klick 1") { public void onClick(Page page) { _clicked.getObject(). Auf diese Weise wird der Text ausgeblendet. ret. wenn die erste oder die durch das Attribut „child“ referenzierte Komponente unsichtbar ist.equals(2). } }). Die zweite Seite besitzt keine eigene Navigation. if (lastClick==null) return false. kann der Nutzer zusätzlich auf zwei Menüpunkte klicken.html <wicket:head> <title>Startseite</title> </wicket:head> <wicket:extend> <h1>Startseite</h1> <wicket:enclosure> <p>Das letzte Mal wurde mit Link <span wicket:id="clicked"></span> geklickt.add(new AbstractNavCallback("Klick 2") { public void onClick(Page page) { _clicked.11..thema. Listing 11. dass alles innerhalb dieses Tags ausgeblendet wird. } @Override public boolean isActive(Page page) { Integer lastClick = _clicked. return ret.</p> </wicket:enclosure> </wicket:extend> Das wicket:enclosure-Tag sorgt dafür.13 StartPage. } @Override public boolean isActive(Page page) { Integer lastClick = _clicked. Listing 11.web.wicketpraxis. public class SecondPage extends AbstractBasePage { public SecondPage(){} 243 .equals(1). Das Markup der Seite reduziert sich auf den Hauptbereich der Anwendung. if (lastClick==null) return false..2 Navigation ret. return lastClick.java package de.getObject().basepage. . } } Wenn also die Startseite ausgewählt wurde.setObject(1). } }). return lastClick.14 SecondPage.setObject(2). wenn noch kein Untermenüpunkt angeklickt wurde.

Wie das Ganze in Aktion aussieht. 244 .15 SecondPage. dass man das Layout der Seite aus Blöcken von div-Tags erzeugt. Um zu demonstrieren. Damit das System funktioniert.2 Zweite Seite 11. das eine Agentur neben den CSSDateien auch meist ein HTML-Grundgerüst liefert. Abbildung 11. das auf die Angaben in der CSS-Datei abgestimmt ist. benutzen wir für das folgende Beispiel die Daten des CSS-Frameworks „960 Grid System“ (http://960. Es kommt nicht selten vor. wie man in so einem Fall vorgehen könnte. dass der erzeugte HTML-Quelltext den Anforderungen der Agentur entsprechen sollte. in dem der Wert im Attribut „class“ die Position und Größe definiert. Dieses CSS-Framework sorgt dafür.html <wicket:head> <title>zweite Seite</title> </wicket:head> <wicket:extend> <h1>zweite Seite</h1> </wicket:extend> Das Gerüst steht.1 und Abbildung 11. Das bedeutet. Das ist aber nicht immer der Fall. müssen wir die Struktur der Anwendung auf die gewünschte HTML-Struktur des Grid-Frameworks anpassen.3 CSS einbinden Im letzten und in den vorangegangenen Beispielen haben wir bereits CSS-Dateien eingebunden. kann man Abbildung 11.1 Startseite Abbildung 11.gs) als Grundlage für unsere Anwendung. und wir haben jetzt eine Anwendung mit interessanten Navigationsmöglichkeiten.2 entnehmen. Dabei konnten wir die CSS-Datei immer an unsere Anwendung anpassen.11 Wicket in der Praxis @Override public List<NavCallbackInterface> getNavigations() { return null. } } Listing 11.

möchte ich dennoch demonstrieren.add(IEConditionalHeader.class.styles.add(CSSPackageResource.thema. \ "grid960/reset.START). ret."all")). Da die Reihenfolge nicht verändert wird.class.class. } } Die ersten drei CSS-Dateien stammen vom CSS-Framework. reicht es. wenn man die Seite mit dem Internet Explorer betrachtet. In der Datei „base. in der die verschiedenen Referenzen dann im Kopfbereich der Seite dargestellt werden.17 IEConditionalHeader.css“ wird nur geladen. .getHeaderContribution(Style.class..getHeaderContribution(Style.add(CSSPackageResource.16 Style.css".wicketpraxis.howto. \ "ieOnly.styles. ret.getHeaderContribution(Style. über einen sogenannten „CSSHack“ eingebunden werden können. ret. ret.java package de. public static final IHeaderContributor END=new IHeaderContributor() { public void renderHead(IHeaderResponse response) { response. } }.css.add(CSSPackageResource..css".class.web. return ret. die nur vom Internet Explorer eingebunden werden sollen.getHeaderContribution(Style. ret."all")). Auch wenn es in unserem Fall nicht notwendig wäre.add(CSSPackageResource. . Listing 11. in der wir alle CSS-Referenzen konfigurieren."all")). ret.getHeaderContribution(Style. wenn wir die für den „CSS-Hack“ notwendigen Ausgaben vor und nach der gewünschten Referenz einbinden. Listing 11.renderString("<!--[if IE]>"). wie CSS-Dateien. } }. \ "grid960/960.header.css."all")). \ "grid960/text. public class Style { public static List<IHeaderContributor> getCss() { List<IHeaderContributor> ret=new ArrayList<IHeaderContributor>().css".css". \ "base..web."all")).wicketpraxis.add(CSSPackageResource. ret.howto. } 245 .11.END).css“ legen wir unsere eigenen Definitionen ab. die Datei „ieOnly.java package de.renderString("<![endif]-->").3 CSS einbinden Wir erstellen eine Klasse.add(IEConditionalHeader.. public class IEConditionalHeader { public static final IHeaderContributor START=new IHeaderContributor() { public void renderHead(IHeaderResponse response) { response.thema.css".

_columns=columns. .page { background-color:#f0f0f0. die nur vom Internet Explorer benutzt wird.int columns) { super(id). public Grid(String id. Wir erstellen uns eine Komponente.19 ieOnly.java package de.B.css Body { background-color:#a5a5a5. } .thema. } Listing 11. Ich glaube.. Dann kann man durch die Angabe einer CSS-Klasse ein Element auf diese Spalten verteilen. } .css. Die zweite CSS-Datei.layout.howto. Dazu ein paar Bemerkungen zum verwendeten Grid-CSS-Framework: Die meisten GridFrameworks teilen eine Seite in eine feste Anzahl von Spalten auf.11 Wicket in der Praxis Die CSS-Dateien müssen in das richtige Verzeichnis entpackt werden. damit die CSS-Dateien korrekt eingebunden werden können. Außerdem erstellen wir noch zwei eigene CSS-Dateien.head { background-color:#e0e0e0. Wenn man vor der Spalte etwas Platz lassen möchte.18 base. durch die Angabe der Klasse „grid_1“ für eine Breite von einer Spalte oder „grid_5“ für eine Breite von fünf Spalten.box { border:1px dashed red.20 Grid. soweit sollte sich das Grundprinzip erschlossen haben. Damit sind alle Vorkehrungen getroffen..web. die diese Regeln und die notwendigen Anpassungen im Quelltext für uns zugänglich machen. Im nächsten Schritt müssen wir eine passende HTML-Struktur erzeugen. IModel<String> columnModel=new LoadableDetachableModel<String>() 246 . ändert die Textfarbe auf Rot. dann benutzt man den Wert „prefix_2“ für zwei Spalten und „suffix_4“. } margin:4px. public class Grid extends WebMarkupContainer { int _columns. } . Listing 11. Listing 11. wenn man danach vier Spalten Abstand definieren möchte. in der wir ein paar Farbanpassungen vornehmen.css Body { color: red.wicketpraxis. Beim verwendeten Framework geschieht das z.

_spaceBefore=spaceBefore." ")).thema.wicketpraxis. Abstände vor und hinter einem Element zu definieren. } }. Dazu leiten wir eine Klasse von Grid ab.howto. IModel<String> postfixModel=new LoadableDetachableModel<String>() { @Override protected String load() { return _spaceAfter!=0 ? "suffix_"+_spaceAfter : null. Die Angabe der Spaltenzahl ist dafür verantwortlich. } }.columnModel.web. int columns." ")). } }. public GridWithSpace(String id. int spaceAfter) { super(id.21 GridWithSpace. add(new AttributeAppender("class". Listing 11. } public Grid setColumns(int columns) { _columns = columns. int _spaceAfter.prefixModel.java package de. add(new AttributeAppender("class".true. } } Die Komponente Grid besitzt kein eigenes Markup.. Wie bereits beschrieben. IModel<String> prefixModel=new LoadableDetachableModel<String>() { @Override protected String load() { return _spaceBefore!=0 ? "prefix_"+_spaceBefore : null. columns).11. _spaceAfter=spaceAfter. welcher Wert im class-Attribut des HTML-Tags gesetzt wird. return this. ist es möglich. .3 CSS einbinden { @Override protected String load() { return "grid_"+_columns. 247 . } public int getColumns() { return _columns.true.. public class GridWithSpace extends Grid { int _spaceBefore.layout.css. die das Setzen der passenden Werte veranlasst. \ int spaceBefore.

asList(1.getCss(). weil es sonst durch das überflüssige HTML-Tag zu Darstellungsproblemen kommt. Listing 11. public class BasePageWithCSS extends WebPage { public BasePageWithCSS() { List<IHeaderContributor> cssList = Style. setRenderBodyOnly(true). Listing 11. .java package de.html.css.Border.22 Line.</div></wicket:border> Jetzt haben wir passende Komponenten erstellt. mit denen wir das Layout der Anwendung beeinflussen können.thema. 248 .23 Line.css.wicket.layout. import org. Wir erzeugen eine Komponente.3).getModelObject(). die in dieser Zeile angezeigt werden sollen. sodass wir alle Elemente.apache.web. an das die Komponente gebunden ist..Arrays. die alle mit einer anderen Breitenangabe versehen werden (Abbildung 11. } } Um ein Element am Anfang einer neuen Zeile darstellen zu können.web. for (IHeaderContributor css : cssList) { add(new HeaderContributor(css)). Listing 11.wicketpraxis. als Kindelemente hinzufügen." ")).4)) { @Override protected void populateItem(ListItem<Integer> item) { Integer index = item.howto.wicketpraxis. public class Line extends Border { public Line(String id) { super(id). die uns diese Arbeit abnimmt. Wir benutzen dafür eine Border-Komponente. Line line = new Line("start").thema. Das ist in diesem Fall notwendig.html <wicket:border><wicket:body/><div \ class="clear">&nbsp.true. } } Durch das Setzen von setRenderBodyOnly() wird das HTML-Tag ausgeblendet. Wir binden die notwendigen CSS-Referenzen in die Seite ein und erzeugen eine Liste von Komponenten.markup.3.24 BasePageWithCSS.howto. Dazu muss vor diesem Block ein div-Tag mit dem Attribut class="clear" eingefügt werden..border.2. den Zeilenumbruch zu erzwingen.java package de.11 Wicket in der Praxis add(new AttributeAppender("class". } add(new ListView<Integer>("list". kann es notwendig sein.postfixModel.

dtd"> <html xmlns="http://www. Trotzdem sollte man darauf drängen.w3.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. aber vor allem das Potential von Wicket so gut wie möglich ausschöpfen kann. } }). item.3 Die Layoutkomponenten in Aktion Zusammenfassung Das Einbinden fremder Daten ist immer mit Aufwand verbunden. da Anpassungen an der HTML-Struktur oder den verwendeten Attributen meist durch Anpassungen innerhalb der Abstraktionsschicht aufgefangen werden können.3 CSS einbinden line.org/TR/xhtml1/DTD/xhtml1-strict.gs">960.8-(index*2).add(line). sodass man die Aufwände klein halten.25 BasePageWithCSS. line.gs</a></h1> </div> <div class="clear">&nbsp. auch für Investitionssicherheit sorgt.add(new Grid("left". 249 .org/1999/xhtml" xml:lang="en" lang="en"> <head></head> <body> <div class="container_16 page"> <div class="grid_16 head"> <h1>Seite mit <a href="http://960.B.4+index.</div> <wicket:container wicket:id="list"> <div wicket:id="start"> <div wicket:id="left"> <div class="box"> Links </div> </div> <div wicket:id="right"> <div class="box"> Rechts </div> </div> </div> </wicket:container> </div> </body> </html> Abbildung 11.add(new GridWithSpace("right".4+index)). dass sich eine Agentur an den Möglichkeiten der eingesetzten Technologie orientiert.0)). } } Listing 11. Doch durch das Erstellen eigener Komponenten können wir eine Abstraktionsschicht einfügen.11.0 Strict//EN" "http://www.w3. die z.

thema.11 Wicket in der Praxis 11.png"). } a. } a..INFO { border:1px solid #d92. Es lohnt sich dann (auch in Anbetracht einer zukünftigen Erweiterungsfähigkeit). die durch das Attribut background-image referenziert werden. border:1px solid #C6D880.web.. dass der Umfang der CSS-Datei den Umfang der Klasse bei Weitem überschreitet.howto. Listing 11. public abstract class ButtonLink<T> extends Link<T> { public enum Type { OK. Die Bilddateien. color:#529214."button. dass man sehr häufig immer wieder dieselben Basiskomponenten benutzt. public ButtonLink(String id.css")). Listing 11. Dazu bindet die ButtonLink-Klasse eine passende CSS-Datei ein und setzt das class-Attribut auf den entsprechenden Wert. display:inline-table.png"). model).26 ButtonLink. in diesen Fällen jeweils eigene Komponenten abzuleiten. a.of(type. IModel<T> model.OK { background-color:#529214.java package de.true. } a. background-image:url("OK.OK.getHeaderContribution( \ ButtonLink. padding-right:4px. padding-left:20px.Model.wicketpraxis. } } Im CSS wird für jede Klasse ein passendes Aussehen definiert. Type type) { super(id. color:#a60. INFO. Außerdem soll der Link (also der Button) über sein Aussehen die Gefährlichkeit seiner Aktion darstellen können.INFO.class.name()))). background-repeat:no-repeat. background-image:url("INFO.OK:hover { background-color:#E6EFC2. background-color: #ff2.CANCEL { text-decoration: none. } a. padding:3px.4 Eigene Basiskomponenten Das Aussehen der Anwendung ist definiert. add(new AttributeModifier("class". color:#fff. add(CSSPackageResource. Dabei ist auffällig. und man stellt fest. background-position:2px 4px. padding-left:14px.INFO:hover { 250 . a. werden von Wicket relativ zur Position der CSS-Datei gesucht. .modify2.27 button. In diesem Beispiel sollen alle Links wie Buttons aussehen. CANCEL }.css a. border:1px solid #497024.

"feedback. } a. Listing 11.getHeaderContribution( \ CustomFeedbackPanel. } } Wir binden auch für diese Komponente eine eigene CSS-Definition ein und ändern außerdem die Fehlerklassen für die Anzeige (getCSSClass()). passen wir noch die Feedback-Komponente an unsere Bedürfnisse an.4 Eigene Basiskomponenten border:1px solid #fd0. color:#c70.class.css")).. filter).. background-color: #ff8.png").css")). egal wie oft die Komponente auf der Seite vorkommt. add(CSSPackageResource. } Bevor wir diese Komponenten verwenden.11. weil wir das Markup vollständig überschrieben und nicht abgeleitet haben. add(CSSPackageResource.toLowerCase(). background-image:url("CANCEL. public class CustomFeedbackPanel extends FeedbackPanel { public CustomFeedbackPanel(String id) { super(id).wicketpraxis.web.28 CustomFeedbackPanel."feedback. Dabei liegt der Unterschied darin. dass sich die Basiskomponente ändert und unser eigenes Markup nicht mehr passt. In beiden Fällen wird die CSSDatei nur einmal im Kopf eingebunden. Listing 11. border:1px solid #fbc2c4. Das birgt die Gefahr.howto.getLevelAsString().CANCEL:hover { background-color:#fbe3e4. .class.CANCEL { background-color:#d12f19. ohne dass wir die Funktionalität der Komponente verändern. color:#d12f19.html <wicket:panel> <div class="feedback" wicket:id="feedbackul"> <div class="replaced" wicket:id="messages"> <span class="replaced" wicket:id="message"></span> </div> </div> </wicket:panel> 251 .java package de.modify2. color:white. border:1px solid #842013. dass in diesem Fall auch ein eigenes Markup definiert wird. } @Override protected String getCSSClass(FeedbackMessage message) { return message.getHeaderContribution( \ CustomFeedbackPanel.29 CustomFeedbackPanel.thema. } public CustomFeedbackPanel(String id. IFeedbackMessageFilter filter) { super(id. } a.

} }). } . color:#025232.Type. mit debug dem Benutzer die passende Rückmeldung zu geben.INFO) { @Override public void onClick() { warn("aufgepasst"). } . add(new ButtonLink<String>("info".wicketpraxis. color:#830. Listing 11.debug { background-color:#C2EFE6. } Auf der folgenden Seite verwenden wir beide Komponenten. border:1px solid #149252.thema. } .feedback div { border: 1px solid black. 252 .Model.java package de.30 feedback.feedback div. margin-bottom:2px.Type. } .OK) { @Override public void onClick() { info("ok"). border:1px solid #529214.of(":("). .howto.11 Wicket in der Praxis Listing 11.css . } } Auch wenn wir nur die drei Meldungstypen info. gibt es trotzdem noch die Möglichkeit.31 OwnComponentsPage.Model.web. color:#325202..modify2.warning { border:1px solid #c70. color:#d12f19. background-color: #ff8.Model.info { background-color:#E6EFC2.of("!"). public class OwnComponentsPage extends WebPage { public OwnComponentsPage() { add(new CustomFeedbackPanel("feedback")).of(":)").feedback div. font-weight: bold.. } }). add(new ButtonLink<String>("ok". sodass wir das Ergebnis unserer Bemühungen betrachten können.feedback div.feedback div. border:1px solid #e57677. } }).Type.error { background-color:#fbe3e4.CANCEL) { @Override public void onClick() { error("cancel"). warn und error benutzen. add(new ButtonLink<String>("cancel". padding:4px.

Klickt man dann auf diesen Link.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. erscheint ein passender Hinweis.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Own Components Page</title> </head> <body> <div wicket:id="feedback"></div> <a wicket:id="ok">Speichern</a> <a wicket:id="info">Merken</a> <a wicket:id="cancel">Abbrechen</a> </body> </html> Wenn man mit der Maus über der Schaltfläche ist.0 Strict//EN" "http://www. eine Komponente durch eine andere Komponente mit derselben ID zu ersetzen. Nutzt man die eigenen Komponenten konsequent. 11. kommen alle Anpassungen an diesen Komponenten automatisch allen Anwendungsteilen zugute.w3. verändert diese ihr Aussehen. Die so kontinuierlich verbesserten Komponenten bieten eine stabile Basis für alle weiteren Projekte.und Feedback-Komponenten Zusammenfassung Eigene Komponenten ersparen Schreibarbeit und sorgen für eine konsistente optische Erscheinung. Abbildung 11.org/TR/xhtml1/DTD/xhtml1-strict.32 OwnComponentsPage. die von dieser Möglichkeit Gebrauch machen.5 Komponententausch Jede Komponente hat eine eigene ID. 253 . Wicket bietet die Möglichkeit.4). Es ist daher naheliegend. Im Folgenden zeigen wir zwei interessante Ansätze. Wicket kann daher jede Komponente eindeutig adressieren.w3. der farblich zur Schaltfläche passt (Abbildung 11.dtd"> <html xmlns="http://www. sodass man Anwendungen mit immer geringerem Aufwand entwickeln kann. wenn man zur Laufzeit Komponenten austauschen kann. Und da alle Komponenten die gleiche Basisklasse verwenden. können alle Komponenten an jeder Stelle verwendet werden.5 Komponententausch Listing 11.4 Eigene Button.11.

Daten unwiderruflich löschen kann. if (target!=null) target. wird die Komponente zurückgetauscht. } class ConfirmPanel extends Panel { AjaxFallbackLink<T> _parent. Das kann unter Umständen die Nutzbarkeit einer Anwendung stark beeinträchtigen.replace. wird die Komponente zurückgetauscht (Abbildung 11.model).IModel<String> message) { super(id). public abstract class AjaxFallbackConfirmLink<T> extends \ AjaxFallbackLink<T> { IModel<String> _message. _message=message. \ IModel<String> message) { super(id. die z.. } @Override public void onClick(AjaxRequestTarget target) { ConfirmPanel confirmLink = new ConfirmPanel(this). public ConfirmPanel(AjaxFallbackLink<T> parent) { super(parent.java package de. kann eine Sicherheitsabfrage sinnvoll sein.5. . sondern stellen einen einfachen eingebetteten Dialog dar. und der Nutzer wird ein weiteres Mal gefragt.5 AjaxFallbackConfirmLink In diesem Beispiel verzichten wir auf die Anzeige eines Dialogs.5).thema. Normalerweise wird in diesem Fall eine Dialogbox geöffnet.B.web. Listing 11. public AjaxFallbackConfirmLink(String id. setOutputMarkupId(true).11 Wicket in der Praxis 11. } public AjaxFallbackConfirmLink(String id.1 AjaxFallbackConfirmLink Wenn der Nutzer eine Aktion durchführt. _message=message. setOutputMarkupId(true).addComponent(confirmLink). Abbildung 11.IModel<T> model. Auch wenn der Nutzer die Aktion bestätigt. Wenn der Nutzer dort die Aktion abbricht. aber die eigentliche Aktion wird im Anschluss durchgeführt..33 AjaxFallbackConfirmLink.howto.wicketpraxis. 254 .getId()).

wenn der Nutzer zweimal an der richtigen Stelle klickt. if (target!=null) target. Diese erstellt den eingebetteten Dialog mithilfe zweier Link und einer LabelKomponente.5 Komponententausch _parent=parent.getModel()) { @Override public void onClick(AjaxRequestTarget target) { ConfirmPanel.this.this. } }). if (target!=null) target. onConfirm(target). } }).html <wicket:panel> <span wicket:id="message"></span> <a wicket:id="yes">Ja</a> <a wicket:id="no">Nein</a> </wicket:panel> Dieses Klasse kann dann ähnlich einfach benutzt werden wie eine normale Link-Klasse.parent. nur dass die zu überschreibende Methode jetzt onConfirm() heißt und nur aufgerufen wird._message)). setOutputMarkupId(true). Zum Zurücktauschen muss sich die Komponente die vertauschte Komponente merken. } @Override protected void onComponentTag(ComponentTag tag) { super.getModel()) { @Override public void onClick(AjaxRequestTarget target) { ConfirmPanel. onCancel(target). parent. Danach tauscht sich diese neue Komponente gegen die Link-Komponente. 255 . tag.11. public void onCancel(AjaxRequestTarget target) { } } In der überschriebenen onClick()-Methode wird eine ConfirmPanel-Komponente erzeugt.replaceWith(_parent). da verschachtelte Links nicht zulässig sind. add(new Label("message".replaceWith(_parent).addComponent(_parent). add(new AjaxFallbackLink<T>("yes".replaceWith(this). Außerdem muss das HTML-Tag noch ersetzt werden.34 AjaxConfirmLink$ConfirmPanel. add(new AjaxFallbackLink<T>("no".onComponentTag(tag). } } public abstract void onConfirm(AjaxRequestTarget target). Listing 11.setName("span").parent.addComponent(_parent).

public class WizardPage extends WebPage { public WizardPage() 256 .addComponent(feedbackPanel). .howto.web. } }). wie man komplexe Nutzerinteraktionen in einer Komponente verstecken und sich auf das Ergebnis der Interaktion konzentrieren kann.Model.replace..web. dass der Zustand des Objekts erhalten bleibt.java package de. } @Override public void onCancel(AjaxRequestTarget target) { if (target!=null) target. <body> <div wicket:id="feedback"></div> <a wicket:id="link">löschen</a> </body> ...replace.11 Wicket in der Praxis Listing 11.html . Das kann man sich zunutze machen.thema. info("Ok. Dann mach ich's"). feedbackPanel.addComponent(feedbackPanel)... } } Listing 11.java package de.2 Wizard Was im letzten Beispiel nicht von Bedeutung war..5.36 ComponentReplacePage. Auch im Markup können wir die Komponente wie einen ganz normalen Link benutzen.thema. Besser nicht.").37 WizardPage. können wieder dieselben Informationen wie vor dem Tausch angezeigt werden. public class ComponentReplacePage extends WebPage { public ComponentReplacePage() { final FeedbackPanel feedbackPanel = new FeedbackPanel("feedback").wicketpraxis. 11. ist für das folgende entscheidend: Eine Komponente ist einfach nur eine Klasse.of("löschen?")) { @Override public void onConfirm(AjaxRequestTarget target) { if (target!=null) target.setOutputMarkupId(true). indem man auf diese Weise eine Wizard-Komponente erstellt. Listing 11.. Wenn man die Komponente zurücktauscht. add(feedbackPanel). Das bedeutet. Der Link-Tausch wird transparent für die Anwendung durchgeführt und ist ein gutes Beispiel dafür. .35 ComponentReplacePage. error("Ok. add(new AjaxFallbackConfirmLink<String>("link"..howto.wicketpraxis.

} class WizardPanel extends Panel { public WizardPanel(String id. in dem eine Zahl eingegeben werden kann.1). } } } Wir erzeugen eine Liste von drei Instanzen der WizardPanel-Klasse. final String wid="panel".get(0)).get(idx)).setRequired(true)).new WizardPanel(wid. idx++. Die Instanzen bekommen alle dieselbe ID. die erste Instanz wird als Kindkomponente hinzugefügt. add(new Link("next") { @Override public void onClick() { Component curPage = WizardPage.. add(form). Model<Integer> model = Model.model))."> </div> <a wicket:id="next">weiter</a> </body> . Listing 11. \ border:1px solid #e0e0e0.asList(new WizardPanel(wid. int idx=list. Listing 11. </wicket:panel> 257 . final List<WizardPanel> list = Arrays.38 WizardPage.3)).5 Komponententausch { add(new FeedbackPanel("feedback")). if (idx>=list.39 WizardPage$WizardPanel.2). curPage. add(new Label("zahl".int start) { super(id). <body> <div wicket:id="feedback"></div> <div wicket:id="panel" style="width:300px. Der Link tauscht die aktuelle Komponente durch die nächste in der Liste aus.. form..of(start).. Am Ende wird die Liste wieder von vorn abgearbeitet.size()) idx=0.add(new TextField<Integer>("zahl".html <wicket:panel> <form wicket:id="form"> Zahl: <input wicket:id="zahl"> <button>Setzen</button> </form> Die letzte Zahl war die <span wicket:id="zahl"></span>. new WizardPanel(wid. add(list. Außerdem wird der letzte Wert durch das Label angezeigt.indexOf(curPage).model). } }). Form<Integer> form = new Form<Integer>("form").replaceWith(list.11.html .get(wid). Die WizardPanel-Komponente beinhaltet ein Formular.this.

41 PageMount.annotation.mount.lang. .. import java.RUNTIME) @Target(ElementType.web. Da der Zustand innerhalb der Komponente gespeichert wurde.class. Im folgenden Beispiel verwalten wir die Pfade.40 MountPath.java package de.6 Suchmaschinenoptimierung Wenn der Suchroboter einer Suchmaschine auf der eigenen Seite landet. sodass der Pfad aus der Liste aller übergeordneten Pfade und dem Pfad der Klasse erzeugt wird. sodass ein geschickter. sodass die Seite auch über diesen Pfad aufgerufen werden kann.thema. } Dabei kann in dieser Annotation auch eine übergeordnete Klasse angegeben werden. aussagekräftiger Name für die Positionierung in den Suchergebnissen entscheidend sein kann. sodass die Information. MountedPage.*. dann wird Seite für Seite auf weiterführende Links untersucht.howto. public class PageMount { public static void mountPages(WebApplication app) { mountQueryString(app.java package de. @Retention(RetentionPolicy. Listing 11. SubMountedPage. über eine Annotation. Der Pfad der Seite fließt dabei oft in die Bewertung der Seite ein.TYPE) public @interface MountPath { String Path(). unter welchem Pfad die Seite angebunden wird auch innerhalb der Klassendefinition zu finden ist.class). wird der letzte Zustand beim Wechsel durch den Klick auf den Link angezeigt. Außerdem muss man dem Umstand Rechnung tragen.11 Wicket in der Praxis Man kann nun bei jedem Schritt etwas anderes eingeben.6. dass sich der Suchroboter einer Suchmaschine stark von einem normalen Browser unterscheidet. 11. } 258 .web.howto.thema.mount.wicketpraxis. unter denen wir verschiedene Seiten anbinden. mountHybrid(app.class).wicketpraxis.1 Pfad für BookmarkablePages Wicket kann jeder Seite einen ansprechenden Pfad geben.. Listing 11. Class<?> Parent() default Object. 11.

Um einen Basispfad zu definieren..page. Listing 11. protected void init() { .java package de. Jetzt legen wir die zwei Seiten an. } private static void getPath(StringBuilder sb. Class<? extends Page> page) { app. die wir nur mit einer Annotation versehen.. .mount(new QueryStringUrlCodingStrategy(getPath(page). Dazu muss die Methode in der WebApplication-Klasse aufgerufen werden.java .web..true)).. } private static void mountHybrid(WebApplication app. sb. Class<?> page) { MountPath mountAnnotation = page.6 Suchmaschinenoptimierung private static void mountQueryString(WebApplication app. getPath(sb. @MountPath(Path="/mountExample") public class BaseClass { } 259 . page).howto.class). return sb. die aber an dieser Stelle nicht behandelt werden. PageMount.43 BaseClass. \ Class<? extends Page> page) { app. Listing 11. Außerdem gibt es noch andere Strategien.11. } } } Die Hilfsklasse dient zum Erzeugen des Pfades anhand der Annotationen der Klassen.page)).mount(new HybridUrlCodingStrategy(getPath(page).Path()).getAnnotation(MountPath.toString().append(mountAnnotation.42 WicketPraxisApplication.mountPages(this).mountAnnotation. Außerdem werden zwei unterschiedliche Strategien zum Anbinden unterstützt. } private static String getPath(Class<? extends Page> page) { StringBuilder sb=new StringBuilder().mount. Man könnte an dieser Stelle auch eigene Strategien implementieren. } .. erstellen wir eine Klasse... Die zwei (noch zu erstellenden) Seitenklassen werden jeweils mit einer der beiden Strategien angebunden..wicketpraxis.thema. if (mountAnnotation!=null) { getPath(sb.Parent()).

sodass sich der Pfad aus den beiden Annotationen zusammensetzt.B=2")). \ MountedPage. \ new PageParameters("A=1.B=2"))). } }). } } Auf der Seite fügen wir drei Links auf dieselbe Seite und drei Links auf eine Unterseite ein.howto. Listing 11.java package de. .11 Wicket in der Praxis Die nächste Seite wird von WebPage abgeleitet.Parent=BaseClass. } }). die Annotation referenziert allerdings die BaseClass-Klasse. } }).new PageParameters("A=1.class. Die drei Links dienen der Unterscheidung der verschiedenen Verlinkungsarten und der Konsequenz für die jeweils resultierende URL.mount.new PageParameters("A=1. \ SubMountedPage. Die Links auf die zwei Seiten dienen der Unterscheidung der beiden Strategien. add(new BookmarkablePageLink<SubMountedPage>("sub".web. @MountPath(Path="/mountedPage". 260 .wicketpraxis.class. add(new Link("directBookmark") { @Override public void onClick() { setResponsePage(SubMountedPage. add(new Link("direct") { @Override public void onClick() { setResponsePage(new SubMountedPage()).B=2")). add(new Link("currentBookmark") { @Override public void onClick() { setResponsePage(MountedPage. add(new Link("currentDirect") { @Override public void onClick() { setResponsePage(new MountedPage()).new PageParameters("A=1.B=2")))..class.thema.class.class) public class MountedPage extends WebPage { public MountedPage() { add(new BookmarkablePageLink<SubMountedPage>("current".. } }).44 MountedPage.

.. <body> <a wicket:id="current">Hier</a> <a wicket:id="currentBookmark">Hier(Bookmark)</a> <a wicket:id="currentDirect">Hier(Link)</a> <a wicket:id="sub">Sub</a> <a wicket:id="directBookmark">Sub(Link . <body> <a wicket:id="current" \ href="..11./mountExample/mountedPage?A=1&amp.web.B=2"> Hier </a> <a wicket:id="currentBookmark" \ href="..BookmarkAble)</a> <a wicket:id="direct">Sub(Instanz)</a> </body> .Parent=MountedPage.. <body> <a wicket:id="link">Sub</a> </body> .45 MountedPage..html .class) public class SubMountedPage extends WebPage { public SubMountedPage() { add(new Link("link") { @Override public void onClick() { setResponsePage(new SubMountedPage()). Wenn wir jetzt die Seite aufrufen./?wicket:interface=:0:currentDirect::ILinkListener::"> Hier(Link) </a> 261 . Listing 11..6 Suchmaschinenoptimierung Listing 11.. @MountPath(Path="/sub".html ..46 SubMountedPage. da sich auf dieser Seite nur ein Link auf eine neue Instanz der Seite befindet. Listing 11. sodass sich auch hier der Pfad entsprechend zusammensetzt.html .thema. } }).mount..48 Ergebnis..../?wicket:interface=:0:currentBookmark::ILinkListener::"> Hier(Bookmark) </a> <a wicket:id="currentDirect" \ href=". } } Die Annotation hat eine Referenz auf die MountedPage-Klasse. dann ergeben sich folgende URLs in der Ergebnisseite: Listing 11.. Die zweite Seite ist einfacher.java package de.47 SubMountedPage.wicketpraxis...howto.

Dazu muss man nur das Attribut 262 .. wenn der Zustand der Seite nicht in Parametern abgebildet werden kann oder soll. Man sollte daher auf jeden Fall eine eigene Fehlerseite erstellen. Legt der Nutzer sich einen Bookmark auf die Seite an. kann es vorkommen.und HybridUrlCodingStrategy liegt in der URL der Ergebnisseite. Jeweils der zweite Link./?wicket:interface=:0:direct::ILinkListener::"> Sub(Instanz) </a> </body> . auf der sich ein Link auf die Startseite der Anwendung befindet. Bei der HybridUrlCodingStrategy wird immer „. Der Unterschied zwischen den beiden Strategien besteht an dieser Stelle darin. dass der Nutzer auch auf die Seite zugreift. sodass Wicket z. auf der er den letzten (zum Fehler führenden) Link angeklickt hat.2 SessionTimeoutPage Da Wicket viele für die Navigation relevante Informationen in der Session ablegt. Das ist dann interessant. sodass Wicket erkennt. die beim Aufruf mit übergeben wurden. Man kann aber durch einen einfachen Trick den Nutzer auf die Seite verweisen. 11. dass nur die ersten Links für eine Suchmaschine verwendbar sind. beim Aufruf durch einen Suchmaschinenroboter den Link nicht zuordnen kann und den Roboter auf eine Fehlerseite weiterleiten wird. der Nutzer wird auch beim „direct“-Link auf eine Seite weiterleitet..6. Daher kann ich für die meisten Anwendungsfälle die Hybrid-Variante empfehlen. Allerdings stimmt dann die Versionsnummer nicht überein. bereits abgelaufen ist. wie die Parameter kodiert sind../?wicket:interface=:0:directBookmark::ILinkListener::"> Sub(Link . Daher legt Wicket einfach eine neue Instanz der Seite an. Dann wird der Nutzer auf eine Fehlerseite weitergeleitet. wo als Zielseite eine Klasse mit Parametern angegeben wurde.. Die anderen Links sind nur für die aktuelle Session gültig. Das bedeutet. obwohl die Session. die unter dieser URL jederzeit aufrufbar ist.11 Wicket in der Praxis <a wicket:id="sub" href=". kann er später zur Seite springen. Mit dem Aufrufen der Fehlerseite bekommt der Nutzer eine neue Session zugewiesen. der Nutzer diese Seite aber als Einsprung benutzen kann.. Die ersten Links auf dieselbe Seite und der erste Link auf die Unterseite fallen wie erwartet aus. leitet den Nutzer auf eine Seite weiter.BookmarkAble) </a> <a wicket:id="direct" \ href=". dass sich der Nutzer auf diese Seite ebenfalls ein Lesezeichen setzen kann./mountExample/mountedPage/sub/A/1/B/2"> Sub </a> <a wicket:id="directBookmark" \ href=". Der Unterschied zwischen der QueryString. Das bedeutet. der Link ist nicht relativ zu Informationen aus der Session.<Versionsnummer>“ (außer für den sub-Link) an die URL anhängt. dass der Nutzer mit einer neuen Session die Seite aufruft. Das bedeutet. deren URL wie von einem Bookmarkable-Link aussieht. die mit dem geöffneten Browser-Fenster assoziiert ist.B.

getHomePage()). ExternalLink link = new ExternalLink("link". Listing 11.49 SessionTimeoutPage. <body> <h1>Ihre Session ist abgelaufen. Es ist und bleibt natürlich ein Trick.get(). referer)).11. </p> <p wicket:id="text"> Rufen Sie die letzte Seite noch einmal unter folgendem Link auf: <br><a wicket:id="link"><span wicket:id="url">Referer</span></a> </p> 263 .add(new Label("url".6 Suchmaschinenoptimierung „Referer“ aus dem HttpHeader des Requests auslesen und prüfen.html . cookies = wc. boolean cookies = true. if (clientInfo instanceof WebClientInfo) { WebClientInfo wc = (WebClientInfo) clientInfo.get().</h1> <p wicket:id="cookie-not-set"> Vermutlich haben Sie das Setzen von Cookies deaktiviert.java package de. } else text.50 SessionTimeoutPage. Einige Funktionen auf unseren Seiten stehen dann nicht zur Verfügung. if ((referer != null) && (referer. der erstaunlich gut funktioniert! Listing 11.thema.setVisible(false). add(start). text.getProperties(). liegt darin.. WicketLinks (z. public class SessionTimeoutPage extends WebPage { public SessionTimeoutPage() { WebRequest wicketRequest = (WebRequest) getRequest().web.howto.add(link). die URL nach dem Auftreten von „wicket:“ zu untersuchen. ob man zu dieser Seite zurückspringen sollte. WebMarkupContainer text = new WebMarkupContainer("text"). String referer = request.. add(text). link.B.indexOf("wicket:") == -1)) { start. Der Trick in der Unterscheidung. BookmarkablePageLink<? extends WebPage> start = \ new BookmarkablePageLink<WebPage>("start".getHeader("Referer"). .setVisible(false). \ Application.setVisible(!cookies)). HttpServletRequest request = wicketRequest. ob man diese URL überhaupt verlinken sollte.mount. ClientInfo clientInfo = Session. zu Seiten die nicht unter einem anderen Pfad angebunden wurden) beinhalten immer diesen Text. referer). Alle anderen URLs sollten keine Probleme verursachen.wicketpraxis. Aber einer. } } Wir zeigen außerdem noch einen Hinweistext an..getClientInfo().. wenn der Nutzer das Setzen von Cookies unterbunden hat.getHttpServletRequest().isCookiesEnabled(). } add(new WebMarkupContainer("cookie-not-set").

Der Einsatz der BookmarkablePageLink-Klasse erscheint nicht zu Unrecht als aufwendig. sind nicht automatisch auch für den Nutzer die beste Art und Weise. import java. die für die Verlinkung herangezogen werden sollen. auch durch den Suchroboter gefunden werden können.web. Durch eine kleine Erweiterung ist es allerdings recht einfach.53 ConfigBean..wicketpraxis. Listing 11. 11.seolinks..11 Wicket in der Praxis <p> <a wicket:id="start">Zurück zur Startseite</a> </p> </body> </html> Wenn man die HybridUrlCodingStrategy einsetzt. public class ConfigBean implements Serializable { 264 .class).6. getApplicationSettings().java package de.52 PublicProperty.thema.. die relevanten Parameter einer Seite in einer JavaBean abzulegen.. die für Suchmaschinen geeignet sind. Zusätzlich markieren wir die Attribute.Serializable.setPageExpiredErrorPage( \ SessionTimeoutPage. @Retention(RetentionPolicy. Jetzt muss diese Seite in der WebApplication-Klasse noch als Fehlerseite gesetzt werden.java .lang..RUNTIME) @Target(ElementType.seolinks. Damit die Seiten. import java. protected void init() { .java package de. . kann man zusätzlich zur Nutzernavigation eine Suchmaschinennavigation in die Anwendung einbauen. sich innerhalb der Anwendung zu bewegen. mit einer passenden Annotation..51 WicketPraxisApplication.howto.wicketpraxis.3 SEO-Links Links. die in den Suchergebnissen der Suchmaschine erscheinen sollen.*. Listing 11.thema. da Wicket im Zweifelsfall eine neue Instanz der Seite erzeugt.howto.web. Listing 11.METHOD) public @interface PublicProperty { } Wie bei Formularen bietet sich es sich an..io. } .. kann man auf eine eigene SessionTimeoutPage verzichten.annotation. wichtige Informationen für den Seitenaufruf in den Parametern der Links abzulegen.

web. Listing 11. @PublicProperty public Integer getSeite() { return _seite. Method[] methods = bean.getAnnotation(PublicProperty.54 BeanPagePropertyUtil. } public void setKategorie(String kategorie) { _kategorie = kategorie. if (annotation!=null) { String name = m. else { if (name. String _kategorie.getName(). Die Informationen aus dem Objekt müssen dann in den zu erzeugenden Link einfließen.11. . 265 . public class BeanPagePropertyUtil { public static <B> PageParameters getBeanPageParameters(B bean) { return new PageParameters(getParameter(bean)).getMethods(). ret.6 Suchmaschinenoptimierung Integer _seite.seolinks. Jetzt müssen die Informationen aus den Parametern beim Seitenaufruf nur noch im Objekt abgelegt werden.getClass(). } public void setSeite(Integer seite) { _seite = seite.. So schließt sich der Kreis.howto._seite=_seite.add(name. ret.substring(3)).add(name.class). for (Method m : methods) { PublicProperty annotation = m. } public ConfigBean getClone() { ConfigBean ret=new ConfigBean(). return ret.startsWith("is")) ret.wicketpraxis. } protected static <B> List<String> getPublicProperties(B bean) { List<String> ret=new ArrayList<String>(). sodass wir die gewünschten Attribute einfach modifizieren können._kategorie=_kategorie.substring(2)).thema. } @PublicProperty public String getKategorie() { return _kategorie.. if (name.startsWith("get")) ret. } } Die getClone()-Methode liefert eine Kopie des Objekts zurück.java package de.

Dabei muss die Annotation auf der getXXX.getString(s). \ getConverterLocator().55 BeanBookmarkablePageLink.getObjectClass()). IConverter converter = \ converterLocator. locale)).put(s. } } } } Entscheidend sind die zwei Methoden getParameter() und setParameter(). .oder isXXX-Methode gesetzt sein..seolinks. } public static <B> Map<String. Das Konvertieren der Parameter wird hierbei mit Bordmitteln durchgeführt.java package de.getObjectClass()).11 Wicket in der Praxis } } } return ret.B> \ extends BookmarkablePageLink<T> { public BeanBookmarkablePageLink(String id. public class BeanBookmarkablePageLink<T extends Page. } } return ret. \ BeanPagePropertyUtil.howto. \ PageParameters pageParameters) { Locale locale = Session.getObject().s).convertToString(value. locale)). String svalue = pageParameters. for (String s : getPublicProperties(bean)) { PropertyModel<Object> propertyModel = \ new PropertyModel<Object>(bean..Object> ret=new HashMap<String. Object value = propertyModel.getLocale().getBeanPageParameters(bean)).getConverter(propertyModel. \ getConverterLocator().get(). } public static <B> void setParameter(B bean. for (String s : getPublicProperties(bean)) { PropertyModel<?> propertyModel = new PropertyModel(bean. IConverterLocator converterLocator = Application. Locale locale = Session. } } 266 .Object> getParameter(B bean) { Map<String.get(). converter. pageClass. Class<T> pageClass.get().getLocale(). if (value!=null) { ret. IConverterLocator converterLocator = Application. B bean) { super(id.setObject( \ converter.wicketpraxis.web. IConverter converter = \ converterLocator.getConverter(propertyModel.thema. Listing 11. Object>().get(). if (svalue!=null) { propertyModel.convertToObject(svalue.s).

configClearSeite. die aus den Seitenparametern in das Objekt übertragen wurden.getClone(). ."Seite"))). Der Aufruf des letzten Links löschte beide Attribute. add(new BeanBookmarkablePageLink<SeoLinksPage.configClearKategorie)).. public class SeoLinksPage extends WebPage { public SeoLinksPage(PageParameters pageParameters) { ConfigBean config=new ConfigBean().configKategorie)). configSeite. add(new BeanBookmarkablePageLink<SeoLinksPage. ConfigBean configClearSeite = config.html <html> <head> <title>Seo Links Page</title> </head> <body> <h1>Willkommen</h1> <p>Sie befinden sich auf Seite <strong> <span wicket:id="Seite">1</span></strong> in der Kategorie <strong><span wicket:id="Kategorie"></span></strong> </p> <p> <a wicket:id="linkSeite">Seite wechseln</a><br> <a wicket:id="linkKategorie">Kategorie wechseln</a><br> <a wicket:id="linkClearSeite">Seite löschen</a><br> <a wicket:id="linkClearKategorie">Kategorie löschen</a><br> </p> </body> </html> 267 . BeanPagePropertyUtil.class. \ new PropertyModel<Integer>(config.56 SeoLinksPage. der des dritten das Attribut Kategorie. ConfigBean>( \ "linkClearSeite". configKategorie. SeoLinksPage. SeoLinksPage. add(new BeanBookmarkablePageLink<SeoLinksPage.setKategorie(null).configClearSeite)). pageParameters). configClearKategorie.setSeite(null). add(new Label("Seite". ConfigBean>( \ "linkSeite".setParameter(config.new PropertyModel<Integer>(config.6 Suchmaschinenoptimierung In der SeoLinksPage-Klasse werden vier Links hinzugefügt.11. ConfigBean configSeite = config.getClone(). } } Auf diese Weise kann ein Suchmaschinenroboter jede Kategorie und jede Seite in dieser Kategorie auslesen und so in die Suchergebnisse einfließen lassen. Listing 11. ConfigBean configKategorie = config.getClone().wicketpraxis. ConfigBean configClearKategorie = config.howto.seolinks.class.class.web.java package de.getClone().thema. ConfigBean>( \ "linkKategorie".configSeite)). add(new BeanBookmarkablePageLink<SeoLinksPage. Der Aufruf des zweiten Links setzt den Wert im Attribut Seite. SeoLinksPage.setKategorie("Angebot").setSeite(1). Listing 11.. SeoLinksPage."Kategorie"))). Der erste Link benutzt die Werte.class. add(new Label("Kategorie".57 SeoLinksPage. ConfigBean>(‚ \ "linkClearKategorie".

*.4 Servlet-Filter In der Grundeinstellung setzt der Servlet-Container Cookies. Das Problem liegt allerdings auch noch an anderer Stelle: Wenn der Nutzer die Seite im Suchergebnis angezeigt bekommt. } 268 . ist dort eine Session-ID als Parameter kodiert.http. wrappedResponse). Interessanter wird dieses Thema im Zusammenhang mit Suchmaschinen. Wenn der Abruf der Folgeseite nicht zeitnah erfolgt. um Informationen.doFilter(request. Das ist auch dann der Fall. chain. das Anhängen der Session als Parameter durch den ServletContainer abzuschalten. FilterChain chain) throws IOException. dass auch dieser Nutzer ebenfalls auf einer Fehlerseite landet. wird der Roboter auf eine Fehlerseite weitergeleitet. um den Nutzer für die Session zu identifizieren. wird allen weiteren Links die Session als Parameter angehängt. import javax. Wenn der Suchmaschinenroboter die Startseite abruft.howto. // JSessionID URL encoding ausschalten HttpServletResponse wrappedResponse = \ wrapResponse(httpRequest. public class DisabledJSessionIDinUrlFilter implements Filter { public void doFilter(ServletRequest request. Es zeigt allerdings auch eindrucksvoll. dass Seiten.6. Die Alternative besteht darin. ServletResponse response. Dass kann man erreichen.wicketpraxis.*. dass ein und dieselbe Seite mehrfach im Suchergebnis auftaucht.java package de.servlet.58 DisableJSessionIDinUrlFilter. Außerdem geht die Suchmaschine davon aus.. dann wird an die URLs für die Links ein jsession=xxx angehängt. ServletException { if (!(request instanceof HttpServletRequest)) { chain. auch unterschiedliche Seiten sind.thema. } HttpServletRequest httpRequest = (HttpServletRequest) request. HttpServletResponse httpResponse = (HttpServletResponse) response..servlet. response). Listing 11. wenn der Nutzer Cookies deaktiviert hat.httpResponse). return. 11.doFilter(request. ist nur eine Alternative. import javax. Wenn der Nutzer aber ohne eine aktive Session direkt eine Seite aufruft.servletfilter. indem man einen eigenen Filter implementiert.web. Dann kann es passieren. Einer Suchmaschine ist der Parameter nicht egal. . Da diese Gruppe mit einem Anteil von rund einem Prozent relativ klein ist. zur Verfügung zu stellen. Es könnte daher passieren. sondern Bestandteil des Seitenaufrufs. die der Nutzer über eine Suchmaschine finden sollte. wie man durch die geschickte Kombination von bestehenden Wicket-Funktionalitäten ausgefallenere Probleme lösen kann. da die Session bereits abgelaufen ist. könnte man diese Nutzer durchaus ignorieren. die unterschiedliche Parameter benutzen.11 Wicket in der Praxis Den Zustand einer Anwendung in URL-Parametern zu kodieren.

"netscape".11. HttpServletResponse httpResponse) { HttpServletResponseWrapper wrappedResponse = \ new HttpServletResponseWrapper(httpResponse) { @Override public String encodeRedirectUrl(final String url) { return url. }. import javax.http. } @Override public String encodeUrl(final String url) { return url. private static final Set<String> _botAgentsSet=new HashSet<String>(). Listing 11.. } @Override public String encodeURL(final String url) { return url.java package de..servletfilter. private static final String[] _botAgents = { "googlebot".web. private static final Set<String> _noBotAgentsSet=new HashSet<String>().wicketpraxis.servlet."opera". "slurp". return wrappedResponse."msie".howto."safari". public class RobotJSessionIDUrlFilter extends DisabledJSessionIDinUrlFilter { private static final String USER_AGENT_HEADER_NAME = "User-Agent". 269 . die prüft. }. . Wenn wir Änderung nur für Suchmaschinen aktivieren wollen.59 RobotJSessionIDUrlFilter. } public void init(FilterConfig filterConfig) throws ServletException { } public void destroy() { } } Dieser Filter schaltet das Setzen von JSessionID-Parametern vollständig ab.*.6 Suchmaschinenoptimierung protected HttpServletResponse wrapResponse( \ HttpServletRequest httpRequest.thema. } }. ob der Nutzer ein Suchmaschinenroboter ist. private static final String[] _noBotAgents = { "firefox". leiten wir eine Klasse ab. } @Override public String encodeRedirectURL(final String url) { return url.

wicketpraxis. } } for (String s : _botAgentsSet) { if (userAgentLowerCase. } private boolean isRobot(HttpServletRequest httpRequest) { String userAgent=httpRequest. \ RobotJSessionIDUrlFilter </filter-class> </filter> .. httpResponse). HttpServletResponse httpResponse) { if (isRobot(httpRequest)) { return super.60 web. } for (String s : _noBotAgents) { _noBotAgentsSet.servletfilter.indexOf(s)>-1) { return false.getHeader(USER_AGENT_HEADER_NAME).toLowerCase(). } public static boolean isRobot(String userAgent) { if (userAgent!=null) { String userAgentLowerCase=userAgent.11 Wicket in der Praxis static { for (String s : _botAgents) { _botAgentsSet.webapp.add(s).. ersetzt man die Filterklasse einfach durch die Basisklasse.howto.wrapResponse(httpRequest.web.add(s). } } Zu beachten ist.robots</filter-name> <filter-class> de. <filter> <filter-name>de. Wenn man das Setzen des Parameters immer abschalten möchte. dass dieser Filter vor dem Filter für die Wicket-Anwendung eingebunden werden muss. return isRobot(userAgent)..xml . 270 .wicketpraxis. Listing 11.. } return httpResponse. for (String s : _noBotAgentsSet) { if (userAgentLowerCase. } } @Override protected HttpServletResponse wrapResponse( \ HttpServletRequest httpRequest. } } } return false.indexOf(s)>-1) { return true.thema.

put("Code".5 Tracking mit Google Analytics Es gibt verschiedene Angebote.onBeforeRender().61 AbstractGoogleAnalyticsPanel. Im folgenden Beispiel beziehe ich mich auf Google Analytics 1. parameter.toString(). getPage().. String>(). String virtualPath = getVirtualPath().class. dass man anderen Tracking-Code in den Anwendungsseiten integrieren muss. parameter). String streamAsString=element. \ newContent.webapp. } }). 11.wicketpraxis. um das Nutzerverhalten auf den eigenen Seiten analysieren zu können. replaceComponentTagBody(markupStream. public abstract class AbstractGoogleAnalyticsPanel extends Panel { public AbstractGoogleAnalyticsPanel(String id) { super(id).com/analytics/ 271 . .robots</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>de.wicketpraxis. String> parameter = \ new HashMap<String.toString()). MapVariableInterpolator newContent= \ new MapVariableInterpolator(streamAsString.howto.. openTag.trackingcode.11.. virtualPath). } @Override protected void onBeforeRender() { super. if (virtualPath==null) virtualPath="". else { virtualPath="\""+virtualPath+"\"". add(new WebMarkupContainer("javascript") { @Override protected void onComponentTagBody(MarkupStream markupStream. } parameter.visitChildren(ExternalLink.get().web.6.put("Path".java package de..thema.6 Suchmaschinenoptimierung <filter-mapping> <filter-name>de. \ 1 http://www. getAnalyticsCode()). Es ist kostenlos und dient hier als Beispiel für den Fall.wicketpraxis.google. Listing 11. HashMap<String. ComponentTag openTag) { MarkupElement element=markupStream.webapp</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> .

trackingcode.length()). public class GoogleAnalyticsCodePage extends WebPage { public GoogleAnalyticsCodePage() { add(new ExternalLink("hanser"."http://www. Listing 11.")))._getTracker("${Code}"). pageTracker.11 Wicket in der Praxis new IVisitor<ExternalLink>() { public Object component(ExternalLink link) { String url = link.62 AbstractGoogleAnalyticsPanel. } Der WebmarkupContainer sorgt dafür.js' type='text/javascript'%3E%3C/script%3E")).com/ga.hanser.html <wicket:panel> <script type="text/javascript"> var gaJsHost =(("https:" == document._initData()."). </script> <script type="text/javascript" wicket:id="javascript"> var pageTracker = _gat.. } /** * Returns Analytics Code * @return UA-xxxx-x Code */ public abstract String getAnalyticsCode(). link. Listing 11.add(new AttributeModifier("onclick". \ 272 . </script> </wicket:panel> Im Markup für die Komponente sind zwei unfreiwillige Zeilenumbrüche mit „\“ markiert und gehören nicht mit zum JavaScript-Code.._trackPageview(${Path}).wicketpraxis. Außerdem wird in der überschriebenen onBeforeRender()-Methode jeder Link auf eine externe Seite mit einem onClick()-Aufruf versehen.substring("http://". } }).CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER. Innerhalb der „javascript“-Komponente werden ${Code} und ${Path} ersetzt. \ new Model<String>( \ "javascript:urchinTracker('/outbound/"+url+"'). pageTracker. /** * Returns VirtualPath if any * @return */ public abstract String getVirtualPath(). if (url. add(new ExternalLink("wicketpraxis".thema.java package de. document." : "http://www.web. .write(unescape("%3Cscript src='" + gaJsHost + \ "google-analytics.howto. dass der JavaScript-Code mit den notwendigen Parametern gefüllt und der korrekte JavaScript-Quelltext erzeugt wird.location.true.startsWith("http://")) { url=url.getDefaultModelObjectAsString(). } return IVisitor.protocol) ? \ "https://ssl.de")).63 GoogleAnalyticsCodePage.

replace('..wicket-praxis.65 Ergebnis .6 Suchmaschinenoptimierung "http://www._trackPageview( \ "de/wicketpraxis/web/thema/howto/trackingcode/GoogleAnalyticsCodePage").de/blog" onclick="javascript:urchinTracker('/outbound/www. } } } Die GoogleAnalyticsPanel-Komponente sollte innerhalb einer Basisklasse. pageTracker. von der alle Seiten abgeleitet werden. } @Override public String getVirtualPath() { return getPage()..de" onclick="javascript:urchinTracker('/outbound/www.wicket. eingebunden werden.11.. '/').. } @Override public String getAnalyticsCode() { return "UA-1234567". </script> </wicket:panel></wicket:container> </body> .hanser..de'). Allerdings können wir jetzt sehen. Listing 11.hanser. <body> <a wicket:id="hanser" href="http://www.64 GoogleAnalyticsCodePage. <body> <a wicket:id="hanser">Hanser Verlag</a><br> <a wicket:id="wicketpraxis">Wicket Praxis</a><br> <wicket:container wicket:id="google"></wicket:container> </body> .html .'.\ praxis. add(new GoogleAnalyticsPanel("google"))._getTracker("UA-1234567"). <script type="text/javascript" wicket:id="javascript"> var pageTracker = _gat.getName(). wie sich die Nutzer auf den Seiten bewegen.. } static class GoogleAnalyticsPanel extends AbstractGoogleAnalyticsPanel { public GoogleAnalyticsPanel(String id) { super(id).. Listing 11._initData(). Das Ergebnis unserer Mühen ist nicht weiter überraschend."> Hanser Verlag</a><br> <a wicket:id="wicketpraxis" href="http://www.wicket-praxis.de/blog'). pageTracker.."> Wicket Praxis</a><br> <wicket:container wicket:id="google"><wicket:panel> .getClass(). 273 ..de/blog"))..

thema. Listing 11. height-1).. Graphics2D graphics = (Graphics2D) ret. wie man die Grafik erzeugt..toByteArray(resource.getResourceStream(). } } 274 .255. wie man die Grafiken in die Seite integriert (Abbildung 11.web. } catch (ResourceStreamNotFoundException e) { throw new WicketRuntimeException(e).TYPE_INT_RGB). width-1.java package de.setBackground(new Color(255.res.6). .7.wicketpraxis.clearRect(0. public class ResourceIOUtil { public static byte[] getByteArrayFrom(PackageResource resource) { try { return IOUtils.drawRect(0.int height) { BufferedImage ret=new BufferedImage(width.java package de. 0. die ein Rechteck mit rotem Rand erzeugt. } } } 11. graphics.. graphics.res. width-1. die man dynamisch erzeugt hat.setColor(new Color(255. graphics. \ BufferedImage. Grafiken anzuzeigen. public class BufferedImages { public static BufferedImage getRedBorderImage(int width. gibt es zwei Wege.0)).height. Doch Ressourcen können beliebige Daten darstellen. Listing 11..web.11 Wicket in der Praxis 11. aus vorhandenen Dateien auslesen zu können. . Die Klasse BufferedImages dient uns als einfache Grafikbibliothek. \ getInputStream()).thema. Die folgende Klasse findet nur in unseren Beispielen Verwendung.wicketpraxis.255)). height-1).1 Dynamisch erzeugte Grafiken Mit Wicket ist es sehr einfach. Je nachdem.howto.howto. 0. } catch (IOException e) { throw new WicketRuntimeException(e).67 BufferedImages. graphics. return ret.images.66 ResourceIOUtil. die normalerweise dynamisch erzeugt würden. um Daten.0.7 Ressourcen Bisher haben wir nur einfache Grafiken und CSS-Dateien in unsere Seiten eingebunden.getGraphics().

add(new ResourceLink<Resource>("imageLink". 275 . graphics.200)). 20).setImage(BufferedImages. also vor dem Ausliefern der Seite.. Dabei ist wichtig zu erwähnen. public class DynamicImagePage extends WebPage { public DynamicImagePage() { BufferedDynamicImageResource imageResource= \ new BufferedDynamicImageResource("jpeg").wicketpraxis. } } Diese Grafiken kann ich wie jede andere Ressource der Image-Komponente übergeben. } }. 0. getHeight()). Außerdem kann ich die Grafik über einen ResourceLink herunterladen. erzeugt werden.thema. Die zweite Grafik wird innerhalb von render() mit Inhalt gefüllt und als PNG-Datei ausgeliefert (Standardeinstellung).web. 2. Dieses Vorgehen führt dazu. ist sie bereits erzeugt und wird ausgeliefert. add(new Image("image2".howto. imageResource. Listing 11..68 DynamicImagePage.drawString(new Date(). graphics.setColor(new Color(255.255. dass die Seite erst ausgeliefert wird..7.. return true. Listing 11. obwohl die Grafiken noch nicht erzeugt wurden. Wenn dann in einem anderen Request auf die Grafik zugegriffen wird.255)).html .69 DynamicImagePage.clearRect(0. <body> <img wicket:id="image"/><br> <a wicket:id="imageLink">als Link</a><br><br> <img wicket:id="image2"/><br> </body> .toString().50) { @Override protected boolean render(Graphics2D graphics) { graphics. graphics.200. Sie wird nicht erst in diesem Moment erzeugt. Dabei wird die Grafik in eine JPEG-Datei umgewandelt. getWidth().images. weil wir als Parameter auch diesen Dateityp ausgewählt haben. können wir auf eine andere Alternative zurückgreifen.renderedResource)). add(new Image("image". dass die Grafiken spätestens während der Render-Phase.java package de.getRedBorderImage(100.imageResource)).res. die wir in Abschnitt 11.. Damit die Seite ausgeliefert werden kann. wenn die letzte Grafik erzeugt wurde..4 behandeln werden.imageResource)). RenderedDynamicImageResource renderedResource= \ new RenderedDynamicImageResource(200.11.setBackground(new Color(200.7 Ressourcen Das Ergebnis des Aufrufs der Grafikbibliothek können wir über die passende ResourceKlasse einbinden. . 100)).

die in der Datenbank abgelegt wurde.images.java package de.6 Dynamisch erzeugte Bilddaten 11.11 Wicket in der Praxis Abbildung 11.gif")."test.class. } }.2 Automatisch generierte Thumbnails Um aus einer Grafik.getByteArrayFrom(res).new ThumbnailImageResource(res. add(new Image("image".70 ImageFromDatabasePage.get( ImageFromDatabasePage.7).128))). } @Override public byte[] getData() { PackageResource res=PackageResource. } } } 276 .wicketpraxis. Listing 11.new ThumbnailImageResource(res.res)). add(new Image("thumbnail128". return ResourceIOUtil. schnell ein kleineres Vorschaubild zu erzeugen.web.. } static class DatabaseImageResource extends DynamicWebResource { @Override protected ResourceState getResourceState() { return new ResourceState() { @Override public String getContentType() { return "image/gif". kann man auf die Klasse ThumbnailImageResource zurückgreifen (Abbildung 11. .thema. Der zweite Parameter gibt die maximale Größe an.res.64))).7.howto. add(new Image("thumbnail64".. public class ImageFromDatabasePage extends WebPage { public ImageFromDatabasePage() { WebResource res=new DatabaseImageResource().

. <body> <img wicket:id="image"/> <img wicket:id="thumbnail128"/> <img wicket:id="thumbnail64"/> </body> . Abbildung 11.java package de.. Manchmal ist es notwendig.7 Automatisch generierte Thumbnails 11.res.forms..csv"). add(form). die die Daten aus der Datenbank liest und über getContentType() und getData() bereitstellt.2.3". dass man nur bei korrekter Eingabe bestimmter Daten ein Dokument herunterladen darf. } } 277 . Listing 11.get().7 Ressourcen Die Klasse DatabaseImageResource ist nur Stellvertreter für eine Resource-Klasse. zeigt das folgende Beispiel. ResourceStreamRequestTarget target= \ new ResourceStreamRequestTarget(output.thema...web. public class ResourceFromFormPage extends WebPage { public ResourceFromFormPage() { Form form = new Form("form") { @Override protected void onSubmit() { StringResourceStream output= \ new StringResourceStream("1.howto."data.72 ResourceFromFormPage."text/plain").71 ImageFromDatabasePage.setRequestTarget(target). Wie man ein Dokument als Ergebnis durch Absenden eines Formulars bereitstellt..wicketpraxis. .11. } }. Listing 11. RequestCycle.7.html .3 Download durch Formular Man kann jede Ressource über einen ResourceLink zum Download bereitstellen.

müssen wir die Ressource in unserer WebApplication-Klasse einbinden. Auf diese Weise kann man natürlich jede beliebige Ressource ausgeben.. Dabei wird allerdings jeweils eine eigene Instanz der Ressource erzeugt. Die so eingebundene Ressource kann dann über einen konfigurierbaren Pfad direkt angesprochen und so auch in andere Anwendungen eingebunden werden. Abbildung 11..wicketpraxis.74 DynamicSharedResource.4 Shared Resources Bisher haben wir alle Ressourcen direkt eingebunden. public class DynamicSharedResource extends DynamicWebResource { 278 .html .. können wir wie bisher die Ressource einfach direkt einbinden. Dass der Name der folgenden Klasse DynamicSharedResource lautet.11 Wicket in der Praxis In diesem Beispiel wird ein String als einfache CSV-Datei bereitgestellt (Abbildung 11.7..8).73 ResourceFromFormPage. <body> <form wicket:id="form"> <button>CSV erstellen</button> </form> </body> .8 Dialog zum Speichern der CSVDaten 11.thema.howto. Auf die Ressource wird dann über eine ID zugegriffen.. Möchten wir aber dieselbe Instanz der Ressource verwenden.java package de.. unterstreicht nur den Einsatzzweck.res. hat aber sonst keinerlei Bewandtnis. Listing 11. Da der Zustand der Ressource nicht mehr an die Session des Anwenders gebunden ist. müssen alle Anpassungen über Parameter an die Ressource weitergereicht werden. Listing 11.shared. Möchten wir die Ressource an verschiedenen Stellen der Anwendung benutzen.web. .

Die Ressource kann jetzt über die ID referenziert und das Verhalten durch die Angabe einer Parameterliste verändert werden.gif".. Der Aufruf von ResourceReference ohne Angabe einer Basisklasse benutzt die Application-Klasse. } }. Die Ressource kann dann über die ID und die Klasse Application referenziert werden. } PackageResource res = PackageResource.getByteArrayFrom(res).get( \ DynamicSharedResource.getSharedResourceKey()). Damit müssen wir die Ressource in der WebApplication-Klasse bekannt machen.break. SharedResources sharedResources = getSharedResources().. new DynamicSharedResource()). die beim Aufruf übergeben werden können. sharedResources. mountSharedResource("dynamicSharedResPath". case 2: image="test2. .getInt("Nr". final int nr = parameters.gif".gif".. image).75 WicketPraxisApplication. switch (nr) { case 1: image="test1. } } Die Klasse unterscheidet sich von den anderen Beispielen nur im Aufruf von getParameters() und greift so auf die Parameter zu. protected void init() { ..11.java .. } .. sondern muss den Rückgabewert von getSharedResourceKey() benutzen.break. } @Override public byte[] getData() { return getImage(nr).class.-1). darf man allerdings nicht die ID. return new ResourceState() { @Override public String getContentType() { return "image/gif". Listing 11. return ResourceIOUtil. } public byte[] getImage(int nr) { String image = "testUnknown. \ new ResourceReference("dynamicSharedRes"). Dazu wird die Instanz unter der ID „dynamicSharedRes“ eingebunden..7 Ressourcen @Override protected ResourceState getResourceState() { ValueMap parameters = getParameters().. 279 . Um den Pfad einer so eingebundenen Ressource zu ändern.add("dynamicSharedRes".

html . wie man einen RSS-Feed bereitstellen würde..11 Wicket in der Praxis Listing 11.new ResourceReference("dynamicSharedRes"))).howto.shared.78 Ergebnis.thema.res..wicketpraxis.thema.. Listing 11. public class RssFeedResource extends DynamicWebResource { @Override protected ResourceState getResourceState() { return new ResourceState() { @Override public String getContentType() { 280 .. Außerdem können wir die Ressourcen unter einer selbst definierten URL zur Verfügung stellen.res.77 SharedResourcePage. was man alles mit Ressourcen machen kann.java package de..new ResourceReference("dynamicSharedRes")..79 RssFeedResource.web. } } Listing 11. Wir liefern daher einfach nur eine passende XML-Datei aus. dass der von uns definierte Pfad und die übergebenen Parameter für den Aufruf benutzt werden.5 RSS-Feed Wir wissen jetzt. Im folgenden Beispiel sehen wir uns an.web.shared.. 11.wicketpraxis.. public class SharedResourcesPage extends WebPage { public SharedResourcesPage() { add(new Image("image". Im Quelltext der Ergebnisseite kann man erkennen. Listing 11. add(new Image("image2". \ new ValueMap("Nr=2"))).. <body> <img wicket:id="image"><br> <img wicket:id="image1"><br> <img wicket:id="image2"><br> </body> . .java package de.. Auf das Erzeugen der notwendigen Daten verzichten wir in diesem Beispiel. . \ new ValueMap("Nr=1")))..new ResourceReference("dynamicSharedRes"). add(new Image("image1".76 SharedResourcePage.7.html . <body> <img wicket:id="image" src="dynamicSharedResPath"><br> <img wicket:id="image1" src="dynamicSharedResPath/Nr/1"><br> <img wicket:id="image2" src="dynamicSharedResPath/Nr/2"><br> </body> .howto..

11.7 Ressourcen
return "text/xml"; } @Override public byte[] getData() { PackageResource res = PackageResource.get( \ DynamicSharedResource.class, "feed.xml"); return ResourceIOUtil.getByteArrayFrom(res); } }; } } Listing 11.80 feed.xml <?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" \ xmlns:wicket="http://wicket.apache.org"> <author> <name>Michael Mosmann</name> </author> <title>Titel des Weblogs</title> <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id> <updated>2003-12-14T10:20:09Z</updated> <entry> <title>Titel des Weblog-Eintrags</title> <link href="http://example.org/2003/12/13/atom-beispiel"/> <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id> <updated>2003-12-13T18:30:02Z</updated> <summary type="html">Zusammenfassung des Weblog-Eintrags</summary> <content type="html">Volltext des Weblog-Eintrags</content> </entry> </feed>

Die XML-Datei ist ein Ausschnitt aus einem RSS-Feed, der für dieses Beispiel bereinigt wurde.
Listing 11.81 RssFeedPage.java package de.wicketpraxis.web.thema.howto.res.shared; ... public class RssFeedPage extends WebPage { public RssFeedPage() { add(new ResourceLink<Resource>("rss", new ResourceReference("rssFeed"))); } }

Wie man sieht, benutzen wir einfach einen ResourceLink, den wir in anderen Beispielen benutzt haben, um eine Ressource zum Download anbieten zu können. Allerdings kann die Komponente an verschiedene Tags gebunden werden. Damit die Ressource im Kopfbereich der Seite landet, kann man wie in diesem Beispiel die Komponente einfach an dieser Stelle positionieren oder, wenn man den Link in einer Komponente hinzufügen möchte, die Komponente innerhalb von <wicket:head></wicket:head> referenzieren.
Listing 11.82 RssFeedPage.html <html> <head> <title>SharedResource Page</title>

281

11 Wicket in der Praxis
<link rel="alternate" type="application/rss+xml" \ title="RSS Feed der Seite" wicket:id="rss" /> </head> <body> <p>der RSS-Link ist im Header</p> </body> </html>

Jetzt müssen wir die Ressource noch unter der ID, die wir in der Seitenklasse benutzt haben, zur Verfügung stellen. Dabei binden wir die Ressource an den Pfad „rss“.
Listing 11.83 WicketPraxisApplication.java ... protected void init() { ... SharedResources sharedResources = getSharedResources(); sharedResources.add("rssFeed", new RssFeedResource()); mountSharedResource("rss", new ResourceReference( "rssFeed").getSharedResourceKey()); ... } ...

Jetzt hat die Seite einen RSS-Feed, der z.B. mit Firefox abonniert werden kann (Abbildung 11.9).

Abbildung 11.9 Firefox-Dialog zum Abbonieren eines RSS-Feeds

11.8

Links auf Seiten und Ressourcen
Bisher haben wir davon profitiert, dass alle URLs von Wicket verwaltet werden. Wenn man die URL einer Seite oder Ressource aber z.B. in einer E-Mail verschicken möchte, sollte man davon absehen, die URL einfach fest einzubinden. Mit Wicket ist es möglich, die URL einer Seite (nicht nur der gerade aufgerufenen) oder einer Ressource zur Laufzeit zu ermitteln. Im folgenden Beispiel ermitteln wir die URL zu einer Seite und einer Ressource und zeigen sie an.
Listing 11.84 LinksPage.java package de.wicketpraxis.web.thema.howto.links; ... public class LinksPage extends WebPage

282

11.8 Links auf Seiten und Ressourcen
{ public LinksPage() { String resourceUrl = urlFor(new ResourceReference( \ LinksPage.class,"resource.txt")).toString(); String pageUrl = urlFor(LinksPage.class, \ new PageParameters("P=1")).toString(); add(new Label("resourceUrl",resourceUrl)); add(new Label("pageUrl",pageUrl)); add(new Label("absResourceUrl", \ RequestUtils.toAbsolutePath(resourceUrl))); add(new Label("absPageUrl",RequestUtils.toAbsolutePath(pageUrl))); } } Listing 11.85 LinksPage.html <html> <head> <title>Links Page</title> </head> <body> <span wicket:id="resourceUrl"></span><br> <span wicket:id="pageUrl"></span><br> <span wicket:id="absResourceUrl"></span><br> <span wicket:id="absPageUrl"></span><br> </body> </html>

Die URL auf eine Seite, die man außerhalb einer Wicket-Anwendung benutzt, sollte man immer an einen selbst definierten Pfad binden, sodass eine Anpassung der Seite nicht dazu führt, dass Seitenaufrufe auf eine veraltete URL ins Leere laufen.
Listing 11.86 WicketPraxisApplication.java ... protected void init() { ... mountSharedResource("linksPageResource", new ResourceReference( \ LinksPage.class,"resource.txt").getSharedResourceKey()); mountBookmarkablePage("linksPage", LinksPage.class); ... } ...

Auf der Ergebnisseite werden jetzt die Links in relativer und absoluter Schreibweise angezeigt. Dabei ist zu beachten, dass der Servername sich aus dem Seitenaufruf ergibt. Wird die Anwendung auf einem Server mit dem Namen „wicket-praxis.de“ aufgerufen, wird „localhost:8080“ entsprechend ersetzt.
Listing 11.87 Ergebnis linksPageResource linksPage/P/1 http://localhost:8080/de.wicketpraxis--webapp/linksPageResource http://localhost:8080/de.wicketpraxis--webapp/linksPage/P/1

283

11 Wicket in der Praxis

11.9

Optimierungen
Wicket bietet sehr viele Einstellmöglichkeiten, wobei ich die wichtigsten erwähnen möchte. Die Funktion ergibt sich meistens aus dem Namen des Funktionsaufrufes.

11.9.1 Applikation
Wicket hat drei verschiedene Fehlerseiten. Die interessanteste ist die Seite, die angezeigt wird, wenn die Sitzung des Nutzers abgelaufen ist. Außerdem kann man die maximale Dateigröße für Datei-Uploads setzen. Die Anpassung kann in jedem Formular überschrieben werden.
Listing 11.88 Application.java ... protected void init() { ... IApplicationSettings applicationSettings = getApplicationSettings(); applicationSettings.setAccessDeniedPage(Page.class); applicationSettings.setInternalErrorPage(Page.class); applicationSettings.setPageExpiredErrorPage(Page.class); applicationSettings.setDefaultMaximumUploadSize(Bytes.megabytes(12)); String configurationType = getConfigurationType(); if (configurationType.equals(DEVELOPMENT)) { } if (configurationType.equals(DEPLOYMENT)) { } ... } ...

Um zwischen Test- und Produktivbetrieb (DEVELOPMENT, DEPLOYMENT) unterscheiden zu können, kann man den Rückgabewert von getConfigurationType() prüfen. Wenn nichts anderes konfiguriert wurde, läuft Wicket im Development-Modus.

11.9.2 Konverter
Wenn man applikationsweit eigene Converter definieren möchte, sollte man die Methode getConverterLocator() überschreiben und einen eigenen ConverterLocator implementieren. Für alle nicht neu zu setzenden Converter greift man einfach auf den Wicketeigenen ConverterLocator zurück.

11.9.3 Debug
Die Fehlersuche gestaltet sich mit Wicket bereits besonders angenehm. Doch auch in diesem Bereich glänzt Wicket durch innovative Möglichkeiten.

284

11.9 Optimierungen
Listing 11.89 Application.java ... protected void init() { ... IDebugSettings debugSettings = getDebugSettings(); debugSettings.setAjaxDebugModeEnabled(true); debugSettings.setComponentUseCheck(true); debugSettings.setDevelopmentUtilitiesEnabled(true); debugSettings.setLinePreciseReportingOnAddComponentEnabled(false); debugSettings.setLinePreciseReportingOnNewComponentEnabled(false); debugSettings.setOutputComponentPath(false); debugSettings.setOutputMarkupContainerClassName(false); ... IRequestLoggerSettings requestLoggerSettings = \ getRequestLoggerSettings(); requestLoggerSettings.setRequestLoggerEnabled(false); ... } ...

Die Aufrufe entsprechen den Standardeinstellungen im Development-Modus. Man sollte keine der Einstellungen im Produktivbetrieb aktivieren. Die zeilengenaue Fehlermeldung sollte man nur in Notfällen aktivieren, da diese Einstellung erheblichen Einfluss auf die Geschwindigkeit der Anwendung hat. Auf diese Funktion musste bisher ich noch nicht zurückgreifen, sodass ich vorerst von der Verwendung abraten würde. Wesentlich hilfreicher sind da die beiden Attribute OutputComponentPath und OutputMarkupContainerClassName. Das Aktivieren des ersten Attributs führt dazu, dass der Pfad der Wicket-Komponente im Attribut wicket:path ausgegeben wird. Das Setzen von OutputMarkupContainerClassName veranlasst Wicket, im Quelltext der Seite den Klassennamen der Komponente vor und nach der Komponente als Kommentar einzubetten. Auf diese Weise kann man schnell erkennen, in welcher Komponente das fehlerhafte Element eingebunden ist. Das Aktivieren des RequestLoggers führt zu einer detaillierteren Ausgabe des aktuellen Aufrufs, was unter Umständen für die Fehlersuche nützlich sein kann. Auch von dieser Möglichkeit musste ich bisher keinen Gebrauch machen.
Listing 11.90 RequestLogger-Ausgabe 21.05.2009 08:44:21 org.apache.wicket.protocol.http.RequestLogger log INFO: time=162, \ event=BookmarkablePage[de.wicketpraxis.web.thema.howto.KapHowto()], \ response=BookmarkablePage[de.wicketpraxis.web.thema.howto.KapHowto()], \ sessionid=null,sessionsize=1794,activerequests=0,maxmem=265M,total=25M,used=18M

11.9.4 Ressource
Je nach eingesetzter Infrastruktur kann es notwendig sein, Anpassungen an den Einstellungen rund um das Thema Ressource vorzunehmen.

285

11 Wicket in der Praxis
Listing 11.91 Application.java ... protected void init() { ... IResourceSettings resourceSettings = getResourceSettings(); resourceSettings.setAddLastModifiedTimeToResourceReferenceUrl(false); resourceSettings.setDisableGZipCompression(false); // kann evtl. zu Problemen führen, wenn die js-lib nicht sauber ist.. resourceSettings.setJavascriptCompressor( \ new DefaultJavascriptCompressor()); ... } ...

Der Internet Explorer ist bekannt dafür, dass er Ressourcen aggressiv puffert. Das bedeutet, dass veränderte CSS-Dateien, Bilddaten und andere Ressourcen nicht korrekt aktualisiert werden. Durch das Setzen von AddLastModifiedTimeToResourceReferenceUrl veranlasst man Wicket, als Parameter das Modifikationsdatum der Datei an die URL der Ressource anzuhängen. Damit ändert sich bei jeder Anpassung der Ressource auch die URL. Wicket liefert Ressourcen komprimiert aus. Wenn man auch die Webseiten komprimiert ausliefern möchte, muss man auf einen Kompressionsfilter zurückgreifen. Damit die Ressourcen dann nicht doppelt komprimiert werden (weil es neben dem zweifelhaften Nutzen auch zwangsläufig zu Fehlern kommt), muss man die Wicket-eigene Komprimierung deaktivieren. Wicket komprimiert außerdem JavaScript-Quelltext. Das kann unter Umständen zu Fehlern führen, wenn der JavaScript-Code nicht korrekt von Leerzeichen und anderen überflüssigen Inhalten befreit wurde. Wenn man solche Fehler feststellt, kann eine Änderung des Kompressors Abhilfe schaffen. Zusammenfassung In diesem Kapitel haben wir Seiten mit Navigation versehen, fremde CSS-Dateien eingebunden und die Seitenstruktur entsprechend angepasst. Außerdem haben wir die Anwendung suchmaschinentauglich gemacht und JavaScript-Code eingebunden, der eine Nutzernachverfolgung zur Seitenoptimierung ermöglicht. Das Erzeugen von Grafiken, Dokumenten oder RSS-Feeds sollte nach diesem Kapitel ebenfalls keine Herausforderung darstellen, sodass wir spätestens jetzt in der Lage sind, unsere eigene Webanwendung zu entwickeln.

286

1. dass man vergessen hat. Dazu muss man Folgendes beachten: Während man mit add() Komponenten hinzufügt. aber nicht mit add() hinzugefügt hat. dass Wicket der Meinung ist. die im Komponentenbaum nicht vorkommt. wird schon geprüft. die dazu führen können.2 Komponente ist bereits vorhanden Es gibt verschiedene Situationen. konnte man eine große Fehlerquelle erfolgreich ausschließen. dass man eine Komponente referenziert.1 Häufige Fehlerquellen Wicket bietet wenig Spielraum für schwer zu lokalisierende Fehler. Jetzt kann es aber vorkommen. Wie man die letzten Klippen umschifft und bei Fehlern schnell auf deren Ursache kommt. 12. Für beide Fälle gibt Wicket passende Fehlermeldungen aus. Wenn Wicket behauptet. ist es auch nicht unwahrscheinlich. ob es in der aktuel- 287 . ist man meistens schon fast am Ziel. Wenn der Code kompiliert werden konnte.1 Komponenten fehlen Die häufigsten Fehlerquellen sind Abweichungen zwischen dem Komponentenbaum und den Referenzen im Markup. 12. 12. Oder man hat eine Komponente im Markup referenziert. die man sich genau durchlesen sollte. Die folgenden Fehlerquellen sollte man Hinterkopf haben. Das erklärt sich recht einfach: Wenn der Code kompiliert werden konnte. dass man nicht auch mit Wicket Fehler machen kann. eine Komponente im Markup zu referenzieren. ist Ziel des folgenden Kapitels. Das bedeutet aber nicht.12 12 Fehlersuche Normalerweise gestaltet sich die Fehlersuche in Wicket-Anwendungen relativ einfach.1. dass man die Komponente zwar erstellt. damit die Suche kurz bleibt. die es an der Stelle nicht gibt. dass es eine Komponente mit dieser ID schon gibt.

Wenn man mal das Muster erkannt hat. ob diese Komponente im Markup überhaupt referenziert wird.web.2 Unit-Tests Wenn man auf so einem bequemen Ruhekissen ruht. . Das bedeutet. dass bei einer Listenlänge von mindestens zwei (was häufig der Fall ist) Wicket die Fehlermeldung ausgibt. wenn man für die Fehlersuche eine Komponente einem Unit-Test unterzieht. public class CustomPanel extends Panel { 288 . wenn man eine Komponente hinzufügt und sich sicher ist. Das kann besonders bei Border-Komponenten auftreten. sondern dazu übergehen. kann es aber unter Umständen sinnvoll sein. ob man die Komponenten an das ListItem und nicht an die ListView gehängt hat.. liegt am häufigsten daran. ist das Testen von Komponenten und Seiten so einfach. dann findet man auch diesen Fehler recht schnell. dass es eine Komponente mit dieser ID bereits gibt. an das die per Ajax zu aktualisierende Komponente gebunden ist.thema. Je nachdem.add() schreibt man nur add()). Man sollte der Versuchung widerstehen und prüfen. welche Anforderungen an eine Anwendung gestellt werden. Die Verwendung von wicket:container anstelle eines div-Tags kann dazu führen. dass der JavaScript-Aufruf zum Tauschen des Inhalts kein passendes HTML-Tag finden kann. 12. Listing 12. für jede Komponente und für jede Seite Unit-Tests zu schreiben.unittests.3 Ajax funktioniert nicht Dass Ajax nicht funktioniert. Es ist zu diesem Zeitpunkt irrelevant.1.1 CustomPanel. man konzentriert sich auf die Programmierung und startet die Anwendung nur. fällt es schwer. da sich die internen Komponenten auf derselben Hierarchieebene befinden. in der entsprechenden Methode zu prüfen. sondern zur ListView-Komponente hinzu (statt item. dass man oft schneller ist.java package de.12 Fehlersuche len Hierarchieebene bereits eine Komponente mit der gleichen ID gibt. Wie man in den folgenden Beispielen sehen kann. ob man eine ID doppelt vergeben hat. Im einfachsten Fall hat man wirklich eine Komponente mit der ID bereits hinzugefügt. In den meisten anderen Fällen gibt Wicket entsprechende Fehlermeldungen aus. In einer ListView fügt man die Komponente fälschlicherweise nicht zum ListItem-. Spannender wird es. dass es diese ID noch nicht gibt. um der Anwendung optisch den letzten Schliff zu verpassen. sich in dieser Ruhe stören zu lassen..wicketpraxis. Der komplizierteste Fall ist gleichzeitig der häufigste. Im Laufe der Zeit sinkt die Fehlerquote schnell ab. 12. Für unser Beispiel erstellen wir eine Komponente mit einem AjaxFallbackLink.debug. dass es kein geeignetes HTMLTag gibt.

5 TestCustomPanel...apache.html .wicket.2 CustomPanel.debug. import org..debug. } else { info("Link ohne Ajax").setOutputMarkupId(true). Jetzt testen wir die Komponente auf der Seite und die Komponente unabhängig von der Seite. final FeedbackPanel feedbackPanel = new FeedbackPanel("feedback"). Dazu müssen wir die folgenden Testklassen unter src/test/java ablegen. } } Listing 12.web. add(feedbackPanel). Listing 12. add(new AjaxFallbackLink("link") { @Override public void onClick(AjaxRequestTarget target) { if (target!=null) { info("Link per Ajax").java package de.4 UnitTestPage.thema. . } } Listing 12. target. } } }). 289 .wicketpraxis.web.java package de.unittests.2 Unit-Tests public CustomPanel(String id) { super(id).3 UnitTestPage.html <wicket:panel> <div wicket:id="feedback"></div> <a wicket:id="link">Klick mich</a> </wicket:panel> Diese Komponente binden wir auf einer neuen Seite ein. <body> <wicket:container wicket:id="mypanel"></wicket:container> </body> .unittests..wicketpraxis.markup...12.html.addComponent(feedbackPanel).thema. feedbackPanel. Listing 12.WebPage. public class UnitTestPage extends WebPage { public UnitTestPage() { add(new CustomPanel("mypanel")).

debug. Der zweite Test bettet die Komponente in einer Testkomponente ein. kann an dieser Stelle leider nicht behandelt werden. Dann kann man prüfen.class). kann Wicket die Klasse der Komponente. dass Unit-Tests mit Wicket sehr einfach zu realisieren sind. Welche Möglichkeiten die WicketTester-Klasse bietet. Dabei ist die ID „mypanel“ die ID der Komponente und „link“ die ID des Links. Markup Wie in Abschnitt 11.markup.assertComponentOnAjaxResponse("panel:feedback"). add(new CustomPanel("custom")). tester.false).12 Fehlersuche public class TestCustomPanel { public void testPage() { WicketTester tester=new WicketTester(). die den entsprechenden Teil der Seite darstellt.wicketpraxis. tester. Listing 12. public class DebugMarkupPage extends WebPage { public DebugMarkupPage() { add(new EmptyPanel("empty")).9.. } }). im Kommentar der Seite ausgeben lassen. Man sieht. eine passende Message erzeugt wurde.startPage(UnitTestPage. } public void testPanel() { WicketTester tester=new WicketTester().6 DebugMarkupPage.assertInfoMessages(new String[]{"Link ohne Ajax"}).java package de. tester. } } Der erste Test testet die Komponente auf der Seite.clickLink("mypanel:link". Dazu wird der WicketTester mit der Seite gestartet und dann der Link mit dem Pfad „mypanel:link“ angeklickt. } } 290 . Auf diese Weise findet man gerade in komplizierten Anwendungen schnell die Komponente.startPanel(new TestPanelSource() { public Panel getTestPanel(String panelId) { return new CustomPanel(panelId). tester. die ID unserer Komponente wird von Wicket erzeugt und lautet „panel“. die geändert werden muss. sodass der Nachrichtentext ein anderer sein sollte. . ob z.web. tester.clickLink("panel:link"). sollte sich aber durch die sprechenden Methodennamen schnell erschließen lassen.B.assertInfoMessages(new String[]{"Link per Ajax"}).. tester. tester.3 beschrieben. Wir aktivieren wieder den Link. allerdings dieses Mal per Ajax.thema.

Auf diese Weise kann man die möglichen Fehlerquellen um Größenordnungen reduzieren.markup. dass man Komponenten auch unabhängig von einer konkreten Anwendung entwickeln kann.wicketpraxis. In der Ergebnisseite sehen wir die Klassen aller Komponenten..2 Unit-Tests Listing 12.MARKUP FOR org.panel.FeedbackPanel END -></wicket:panel></div> <a wicket:id="link" id="link2" ..panel.MARKUP FOR org. Man sollte dabei trotzdem nicht vergessen.apache.wicket.debug.7 DebugMarkupPage.markup.MARKUP FOR org.wicketpraxis.thema.8 Ergebnis. <body> <div wicket:id="empty"></div> <div wicket:id="custom"></div> </body> .EmptyPanel BEGIN --> <!-.web..markup..markup.EmptyPanel END -></wicket:panel></div> <div wicket:id="custom"><wicket:panel><!-.html.thema.. >Klick mich</a> <!-.CustomPanel BEGIN --> <div wicket:id="feedback" id="feedback1"><wicket:panel><!-.CustomPanel END -></wicket:panel></div> </body> </html><!-.12.wicket.html <html> .web. Dabei wird man feststellen.unittests..wicket.web.html.MARKUP FOR de. 291 .Page Class de.panel.html .apache.wicket.apache.unittests.debug. die zum persönlichen Arbeitsstil passen.FeedbackPanel BEGIN --> <!-.MARKUP FOR org..MARKUP FOR de.panel.html. <body> <div wicket:id="empty"><wicket:panel><!-. Listing 12.thema.markup.wicketpraxis. dass man sich auf einige wenige Möglichkeiten konzentriert.debug.html..DebugMarkupPage --> Zusammenfassung Die Fehlersuche gestaltet sich mit dieser Vielzahl von Hilfen besonders einfach.apache.

.

die mit Wicket realisiert wurde. welche praktischen Erfahrungen ich mit Wicket sammeln konnte. die bei der Entwicklung einer Webanwendung mit Wicket eine Rolle spielen. Dann werden Sie vielleicht feststellen. finden Sie am besten über meine Einträge zu Wicket unter Delicious: http://delicious. Das zweite Beispiel (http://www. an deren Entwicklung ich beteiligt war. Schreiben Sie mir einfach eine E-Mail an michael@mosmann. möchte ich an dieser Stelle zwei Anwendungen hervorheben. Quellen.de/vergleich/termingeld/vergleich) ist ein Teil einer Anwendung. 293 .green-radish. dass das Buch nur der Anfang einer spannenden Entwicklung war.mosmann/wicket. die Anwendung in ein CMS (in diesem Fall Typo3) zu integrieren. Wenn Sie dann Ihre erste eigene Anwendung umsetzen. dass das Buch nur ein kleiner erster Schritt gewesen ist. haben Sie in den zurückliegenden Kapiteln alle wichtigen Aspekte. Ich möchte mich an dieser Stelle für die Aufmerksamkeit bedanken.de) ist ein Online-Shop. Ich wünsche Ihnen viel Freude mit Wicket und würde mich über jede Rückmeldung zum Buch oder anderen interessanten Themen freuen.vergleich. Damit Sie eine Vorstellung davon bekommen. Das erste Beispiel (http://dynamisch.com/michael.13 13 Anfang oder Ende? Wenn Sie es bis hierher geschafft haben.de. Dabei bestand die besondere Herausforderung darin. die mir während meiner Arbeit mit Wicket geholfen haben. werden Sie feststellen. der vollständig mit Wicket realisiert wurde. kennen gelernt.

.

41 Application 84 siehe auch WebApplication Applikationsschicht 24 E Eclipse Installation 7 Projektdateien 27 F Feedback 85. 214 ComponentFeedbackMessageFilter 215 ContainerFeedbackMessageFilter 214 CustomFeedbackPanel 251 FeedbackMessage 85 FeedbackPanel 170 FormComponentCssFeedbackBorder 218 FormComponentFeedbackBorder 216 FormComponentFeedbackIndicator 217 MessageFilter 214 Formular 169 Ajax 205 AjaxButton 175 AjaxComponentUpdatingBehavior 207 AjaxFallbackButton 174 AjaxFormSubmitBehavior 205 AjaxFormValidatingBehavior 207 AjaxSubmitLink 175 AutoCompleteTextField 210 Button 173 CheckBox 182 CheckBoxMultipleChoice 183 CheckGroup 183 CheckGroupSelector 184 B Behavior 144 Attribute anpassen 145 onComponentTag() 144 Border 120 C Component 51. 10 Anwendungsschicht 8. 81 siehe auch Komponente ComponentBorder 125 D Datenbank Konfiguration 20. 37 Produktivdatenbank 20. 30 Testdatenbank 22 Datenhaltung 8 siehe auch Persistenz Dependency Injection 8 siehe auch Spring Framework 295 . 140 Event 103 setOutputMarkupId() 103 setOutputMarkupPlaceholderTag() 106 Anwendungslogik 8. 30.Register A Ajax 102 AjaxRequestTarget 102. 170.

260 ExternalLink 141 IndicatingAjaxFallbackLink 139 IndicatingAjaxLink 140 onClick() 138 Popup 141 ResourceLink 143 SubmitLink 144 Tricks 140 H Hibernate 9 HQL 10 Schemagenerierung 32 I InjectorHolder siehe auch Spring Framework SpringBean 237 Installation 7 J Java. 191 MultiFileUpload 194 OnChangeBehavior 209 onSubmit() 172 Palette 191 POST 175 RadioButton 185 RadioChoice 185 RadioGroup 185 Select 186 SubmitLink 173 TextField 176 UploadProgressBar 193 UploadWebRequest 193 Validatoren 195 siehe auch Validator Fragment 119 K Komponente 51. 53 AjaxEditableLabel 212 AjaxFallbackConfirmLink 254 ButtonLink 250 callOnBeforeRenderIfNotVisible() 83. 129. 83. 124 detach() 83 getPage() 84 getParent() 81 getPath() 84 ID 81 isVisible() 99 IVisitor 82 Komponentenbaum 81 Modell 84 onBeforeRender() 52. Installation 7 JavaBean 2.Register ChoiceRenderer 190 DropDownChoice 188 FileUpload 192 FormComponentLabel 182 GET 175 Label 181 ListChoice 189 ListMultipleChoice 190. 83 Sichtbarkeit 99 Vererbung 85 siehe auch Vererbung Wizard 256 Konventionen 1 Konverter 55. 101. 124 onDetach() 52. 101. 181 Jetty 24 Maven-Plugin 26 starten 46 296 . 284 IConverter-Interface 56 IConverterLocator 56 G Google Analytics 271 L Label 127 MultiLineLabel 127 Link 137 AbstractLink 137 Ajax 138 AjaxFallbackLink 139 AjaxLink 140 AjaxSubmitLink 144 BookmarkablePageLink 137.

22. 55 CompoundPropertyModel 74 DaoModel 67 detach() 55.Register Liste 149 ColumnListView 154 DataGridView 159 DataProvider 156. 53 getVariation() 94 Variationen 94 Maven Abhängigkeiten 15 artifactId 15 groupId 15 Installation 7 POM 11. 38 Präsentation 10 Präsentationsschicht 8. 5. 26 Persistenz 9. 61 Panel 117 ParentPom 16. 164 DataTable 161 DataView 156 DefaultDataTable 162 GridView 158 ListView 152. 285 BufferedDynamicImageResource 275 Download 277 RenderedDynamicImageResource 275 ResourceLink 277 297 . 84 als XML 130 BookmarkablePage 258 CSS 116 JavaScript 116 PageParameter 110 RedirectPage 112 URL 115 WebPage 109 PageMap 50 PageStore 51. 61 IModel-Interface 55 Kaskadierung 62. 33 @Transactional 35 AbstractDao 34. 274. 65 LoadableDetachableModel 62 Model.of() 60 modelChanged() 58 modelChanging() 58 PropertyModel 72 ResourceModel 76 Serialisierung 61 StringResourceModel 78 verändern 58 Model-View-Controller 10 Q Quickstart 4. 17 Projektverwaltung 11 version 15. 184 PropertyListView 153 RefreshingView 150 RepeatingView 149 Locale 94 Modus Deployment 47 Development 47 MVC 55 siehe auch Model-View-Controller P Page 50. 41 Property Datei 98 XML-Format 99 Zeichenkodierung 99 M Markup 3. 12 R Request Behandlung 51 RequestCycle 51 Resource 98. 16 Modell 53. 36 AbstractDaoList 163 DaoInterface 33 DoInterface 33 Persistenz-Tests 13.

31. 42 WebApplication 50 Webarchiv 26 WebMarkupContainer 126 wicket:border 124 wicket:child 86. 29 siehe auch Spring Framework Spring-Framework 10 Konfiguration 29 Style 94 Subversion 7 Installation 8 Suchmaschinenoptimierung 258 BeanBookmarkablePageLink 266 W war 24 siehe auch Webarchiv WebApp 24. 288 UserDao siehe auch Persistenz AbstractDao 36 298 .Register Shared Resources 278 URL 282 Verzeichnis 95 WebResource 276 ResourceReference 116. 134 RSS-Feed 280 V Validator AbstractValidator 200 EmailAddressValidator 197 EqualPasswordInputValidator 202 FormValidator 201 getDependentFormComponents() 204 MaximumValidator 197 MinimumValidator 197 Palindrom-Validator 199 RangeValidator 197 StringValidator 196 UrlValidator 199 Validatable 200 validate() 204 Variation 94 VCS 8 siehe auch Versionskontrolle Vererbung einfach 85 für Fortgeschrittene 91 Markup 87 mit eigenem Markup 87 ohne eigenes Markup 86 Versionskontrolle 7 S Schema-Update 21. 88 wicket:container 89 wicket:enclosure 100 wicket:extend 88 wicket:head 86 wicket:message 131 wicket:panel 86 T Tomcat 24 U Unit-Tests 12. 84 DisableJSessionIDinUrlFilter 268 eigene Session-Klasse 225 get() 84 RobotJSessionIDUrlFilter 269 setReuseItems() siehe auch Liste ListView 184 Sicherheit 5 Anmeldeseite 229 geschützte Komponente 231 geschützte Seite 229 IAuthorizationStrategy 231 ProtectedPage 229 SimplePageAuthorizationStrategy 226 Spring Framework Konfiguration 46 SpringBean 235 Spring 26. 40 Security 225 siehe auch Sicherheit SEO 264 siehe auch Suchmaschinenoptimierung Session 50.

de/computer .und Lernfähigkeiten stärken.hanser. wie Sie Ihre Wetware refaktorieren können – also Ihr Gehirn umgestalten und neu verdrahten –. Technikfreak oder analytischer Denker. nicht in einem Editor. Egal ob Programmierer. dieses Buch wird Ihnen dabei helfen. indem Sie Ihre eigenen Denk. Software entsteht in unseren Köpfen. wie Ihr Gehirn arbeitet und wie Sie Vorteile daraus ziehen können. damit Sie Ihre Arbeit effektiver und kreativer angehen können. Deshalb wird es Zeit. pragmatischer an das Denken und Lernen heranzugehen.und Verhaltenstheorie und durch Erkenntnisse der kognitiven und Neurowissenschaften.Im Kopf fängt alles an. Mehr Informationen zu diesem Buch und zu unserem Programm unter www. Sie werden überraschende Aspekte darüber kennen lernen. einer IDE oder einem Design-Tool. Manager. ISBN 978-3-446-41643-7 Andy Hunt führt Sie in diesem Buch durch Lern. Sie werden sehen. Wissensarbeiter. Hunt Pragmatisches Denken und Lernen 265 Seiten.

· die echten Anforderungen zu finden. · gegen Redundanz anzugehen.Konzentrieren Sie sich auf das Wesentliche! Hunt/Thomas Der Pragmatische Programmierer 331 Seiten. · dynamischen und anpassbaren Quelltext zu schreiben. Mehr Informationen zu diesem Buch und zu unserem Programm unter www. Beispielen und interessanten Analogien. ISBN 978-3-446-22309-7 Der Pragmatische Programmierer veranschaulicht zahlreiche Best Practices der Softwareentwicklung mit Hilfe von Anekdoten.de/computer . · Teams von Pragmatischen Programmierern zu bilden und · durch Automatisierung sorgfältiger zu entwickeln.hanser. · effektiv zu testen. Wer dieses Buch liest. lernt. · die Anwender zu begeistern.

ehemaliger Google-Mitarbeiter und Web-Analyse-Experte. Von nützlichen Hinweisen und technischen Kniffen bei der Implementierung und dem Tracking sämtlicher Online-MarketingAktivitäten. stellt in diesem Praxisbuch die vielfältigen Funktionen dieses Tools umfassend vor.Mehr Transparenz = mehr Erfolg Aden Google Analytics Implementieren. Ein täglicher Begleiter für alle Aktivitäten im Online-Marketing! Mehr Informationen zu diesem Buch und zu unserem Programm unter www. Tipps und Tricks? Haben Sie das Tool wirklich perfekt implementiert und Ihren individuellen Bedürfnissen angepasst? Nutzen Sie es vollständig aus und leiten konkrete Aktionen aus den vorhandenen Zahlen ab? Timo Aden.de/computer .hanser. 357 Seiten ISBN 978-3-446-41905-6 Sicher haben Sie von Google Analytics schon gehört oder nutzen es bereits. Aber kennen Sie auch alle Feinheiten. über die effektive Anwendung der Benutzeroberfläche und Berichte bis hin zur Ableitung von konkreten Aktionen – dieser Praxisleitfaden deckt sämtliche Bereiche von Google Analytics ab. Profitieren. Interpretieren.

Mehr Informationen zu diesem Buch und zu unserem Programm unter www. der neue Star unter den mobilen Plattformen. das Gelernte sofort auszuprobieren. wälzt unter der Führung von Google den Markt der mobilen Applikationen um.Android auf dem Vormarsch Mosemann/Kose Android Anwendungen für das Handy-Betriebssystem erfolgreich programmieren 384 Seiten ISBN 978-3-446-41728-1 Android.de/computer . Viele praxisnahe Beispiele und Tipps helfen Ihnen.hanser. Dieses Praxisbuch zeigt Ihnen. Auf der Webseite zum Buch können Sie mit Hilfe von Lernvideos und weiteren Beispielen Ihr Wissen vertiefen. wie Sie schnell qualitativ hochwertige AndroidApplikationen entwickeln und sie erfolgreich vermarkten.

Er zeigt auch. wie Scrum in großen Projekten mit mehreren Teams.hanser. Sie lernen die Regeln. Mehr Informationen zu diesem Buch und zu unserem Programm unter www.. Scrum! Gloger Scrum Produkte zuverlässig und schnell entwickeln 2. Product Owner oder Teammitglied an einem Scrum-Projekt beteiligt sind oder aber erst wissen wollen. Egal ob Sie als Kunde. wie Sie Scrum einführen und leben können. Zudem ist dieses Praxisbuch eine hervorragende Unterstützung für die Zertifizierung zum ScrumMaster. wie Sie Scrum erfolgreich einsetzen.Auf die Plätze. aktualisierte Auflage 334 Seiten ISBN 978-3-446-41913-1 In diesem Buch erfahren Sie. ScrumMaster. die über viele Standorte verteilt sind. Führungskraft. wie Teams durch weitgehende Selbstorganisation und durch kontinuierliche Planung Produkte erfolgreich liefern.de/computer . funktioniert. Strukturen und Rollen von Scrum kennen. fertig. Boris Gloger – er war der erste zertifizierte ScrumTrainer in Europa – beschreibt. was Scrum eigentlich ist: Hier erhalten Sie einen umfassenden Überblick und wertvolle Tipps.

Mehr Informationen zu diesem Buch und zu unserem Programm unter www. wie Persistenz-Probleme gelöst werden. dieses Framework aktiv kennen zu lernen.Frischer Wind für Java. POJOs) . Der Leser erfährt. wie einfache und effiziente JEE-Applikationen mit Spring entwickelt werden. Diese zweite Auflage des Bestsellers Spring in Action deckt die Version 2.de/computer . Walls Spring im Einsatz 676 Seiten. wie mit asynchronen Nachrichten umgegangen wird und wie man Remote Services erstellt und nutzt.0 und alle ihre neuen Features ab. ISBN 978-3-446-41240-8 Spring ist ein frischer Wind in der Java-Landschaft. Dieses Framework für Java EE verbindet die Macht von Enterprise Applikationen mit der Einfachheit von einfachen Java-Objekten (Plain Old Java Objects.und macht so dem Java-Entwickler das Leben leicht. Kleine Code-Beispiele und eine schrittweise ausgebaute eigene Anwendung zeigen. Das Buch beginnt mit den grundlegenden Konzepten von Spring und führt den Leser rasch dazu.hanser.

Sign up to vote on this title
UsefulNot useful