Skip to content


Buchkritik bei it-republik.de

Danke für die gute Rezension.

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Allgemein, Wicket.

Tagged with , .


Wicket – kombiniertes AutoCompleteBehavoir

Eine der ersten Demonstrationen für die Möglichkeiten von Ajax bestand darin, dass man dem Nutzer während er Eingabe von Informationen Vorschläge unterbreitete, was er denn wohl gemeint haben könnte. Das ist lange her und gehört zum guten Ton einer moderneren Webanwendung. Wicket liefert das notwendige Werkzeug bereits mit, so dass sehr einfach ist, den Nutzer auf diese Art zu unterstützen.

Etwas komplizierter wird es, wenn man dem Nutzer Vorschläge unterbreiten möchte, die nicht nur für ein Eingabefeld relevant sind, sondern wie z.B. bei der Postleitzahl auch den Wert eines anderen Eingabefeldes beeinflussen kann. Im folgenden Beispiel versuchen wir, dem Nutzer bei der Eingabe einer Postleitzahl auch den Ort mit anzuzeigen und den Wert in das Ortsfeld zu übernehmen.

Normalerweise kommen die Daten für die Postleitzahlen aus einer Datenbank, in unserem Beispiel begnügen wir uns mit einer sehr kleinen Auswahl:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;
02
 
03
import java.io.Serializable;
04
 
05
public class PlzOrt implements Serializable
06
{
07
  String _plz;
08
 
09
  String _ort;
10
 
11
  public PlzOrt(String plz, String ort)
12
  {
13
    _plz=plz;
14
    _ort=ort;
15
  }
16
 
17
  public String getPlz()
18
  {
19
    return _plz;
20
  }
21
 
22
  public String getOrt()
23
  {
24
    return _ort;
25
  }
26
}

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;
02
 
03
import java.io.Serializable;
04
import java.util.ArrayList;
05
import java.util.List;
06
 
07
public class PlzOrtListFactory implements Serializable
08
{
09
  List<PlzOrt> _all=new ArrayList<PlzOrt>();
10
 
11
  {
12
    _all.add(new PlzOrt("23562", "Lübeck"));
13
    _all.add(new PlzOrt("23858", "Reinfeld"));
14
    _all.add(new PlzOrt("14199", "Berlin"));
15
    _all.add(new PlzOrt("70619", "Stuttgart"));
16
 
17
  }
18
 
19
  public PlzOrtListFactory()
20
  {
21
  }
22
 
23
  public List<PlzOrt> getList(String plz)
24
  {
25
    List<PlzOrt> ret=null;
26
    if ((plz!=null) && (plz.length()>0))
27
    {
28
      ret=new ArrayList<PlzOrt>();
29
      for (PlzOrt po : _all)
30
      {
31
        if (po.getPlz().startsWith(plz)) ret.add(po);
32
      }
33
    }
34
    return ret;
35
  }
36
}
37

Die Methode getList() liefert dann die passende Teilmenge für die eingegebene Postleitzahl. Wenn der Nutzer also “23″ eingeben hat, werden die ersten beiden Einträge aus der Liste gewählt.

Wicket hat die Möglichkeit, dem Nutzer Vorschläge unterbreiten zu können, von der Darstellung dieser Vorschläge getrennt. Die relevanten Anpassungen müssen in unserem Fall in der Darstellungskomponente vorgenommen werden. Dazu leiten wir einen eigenen Renderer von einer passenden Wicket-Basisklasse ab:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;
02
 
03
import org.apache.wicket.Response;
04
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AbstractAutoCompleteRenderer;
05
import org.apache.wicket.markup.html.form.FormComponent;
06
 
07
public class PlzOrtRenderer extends AbstractAutoCompleteRenderer<PlzOrt>
08
{
09
  FormComponent<?> _ortInput;
10
 
11
  public PlzOrtRenderer(FormComponent<?> ortInput)
12
  {
13
    _ortInput=ortInput;
14
    _ortInput.setOutputMarkupId(true);
15
  }
16
 
17
  @Override
18
  protected String getTextValue(PlzOrt object)
19
  {
20
    return object.getPlz();
21
  }
22
 
23
  @Override
24
  protected void renderChoice(PlzOrt object, Response response, String criteria)
25
  {
26
    response.write(object.getPlz() + "-" + object.getOrt());
27
  }
28
 
29
  @Override
30
  protected CharSequence getOnSelectJavascriptExpression(PlzOrt plzort)
31
  {
32
    StringBuilder js = new StringBuilder();
33
    js.append("wicketGet('").append(_ortInput.getMarkupId()).append("').value ='" + plzort.getOrt() + "';");
34
    js.append("input");
35
    return js.toString();
36
  }
37
 
38
}

In unserer Variante werden folgende Anpassungen vorgenommen: Für das zweite Eingabefeld wird die Ausgabe der MarkupId aktiviert, da wir uns später im Javascript darauf beziehen. Die Methode getTextValue() liefert den Wert zurück, der dann im Eingabefeld für die Postleitzahl erscheint. In der Methode renderChoice() wird die Auswahl gerendert. In unserem Fall zeigen wir Postleitzahl und Ort an. Damit der Ortsname korrekt im anderen Eingabefeld erscheint, übergeben wir durch das Überschreiben der Methode getOnSelectJavascriptExpression() die nötigen Javascript-Aufrufe. Die erste Zeile ermittelt das Objekt und setzt das Atrribut “input” auf den gewünschten Wert. Dabei ist zu beachten, dass die Zeile “input” den Wert darstellt, der in das Postleitzahlfeld geschrieben werden soll. Es sollte an dieser Stelle keine return-Anweisung aufgerufen werden.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;
02
 
03
import java.util.Collections;
04
import java.util.Iterator;
05
import java.util.List;
06
 
07
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteBehavior;
08
import org.apache.wicket.markup.html.form.FormComponent;
09
 
10
 
11
class PlzAutoCompleteBehavior extends AutoCompleteBehavior<PlzOrt>
12
{
13
  PlzOrtListFactory _listFactory;
14
 
15
  public PlzAutoCompleteBehavior(PlzOrtListFactory listFactory, FormComponent<?> ortInput)
16
  {
17
    super(new PlzOrtRenderer(ortInput));
18
 
19
    _listFactory=listFactory;
20
  }
21
 
22
  @Override
23
  protected Iterator<PlzOrt> getChoices(String input)
24
  {
25
    List<PlzOrt> list = _listFactory.getList(input);
26
    if (list!=null) return list.iterator();
27
    return Collections.EMPTY_LIST.iterator();
28
  }
29
}

Wir leiten für dieses Beispiel unsere eigenes Behavior passend ab (Natürlich ist es möglich, das Behavior und den Renderer allgemeingültiger zu gestalten. Darauf wurde aber zu Gunsten der Lesbarkeit verzichtet.) Hinweis: Der Rückgabewert von getChoices() darf in diesem Fall nicht null sein.

Jetzt können wir endlich das Formular bauen und das Behavior einsetzen:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;
02
 
03
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.DefaultCssAutocompleteTextField;
04
import org.apache.wicket.markup.html.CSSPackageResource;
05
import org.apache.wicket.markup.html.WebPage;
06
import org.apache.wicket.markup.html.form.Form;
07
import org.apache.wicket.markup.html.form.TextField;
08
import org.apache.wicket.model.Model;
09
 
10
public class FormAutoCompletePage extends WebPage
11
{
12
  public FormAutoCompletePage()
13
  {
14
    add(CSSPackageResource.getHeaderContribution(DefaultCssAutocompleteTextField.class,"DefaultCssAutocompleteTextField.css"));
15
 
16
    Form<Void> form = new Form<Void>("form");
17
 
18
    TextField<String> plz = new TextField<String>("plz",Model.of(""));
19
    TextField<String> ort = new TextField<String>("ort",Model.of(""));
20
 
21
    plz.add(new PlzAutoCompleteBehavior(new PlzOrtListFactory(),ort));
22
 
23
    form.add(plz);
24
    form.add(ort);
25
 
26
    add(form);
27
  }
28
}

Für die Darstellung der Auswahl müssten wir jetzt noch etwas CSS bemühen. Das ersparen wir uns in diesem Beispiel, in dem wir die CSS-Datei der DefaultCssAutocompleteTextField-Klasse einbinden.

Eine kleine Besonderheit ist zu beachten: Man muss die Vorschläge, die der Browser dem Nutzer macht, unterbinden, damit die beiden Auswahlmöglichkeiten nicht kollidieren. Dazu fügt man im Markup das Attribut “autocomplete” mit dem Wert “off” hinzu.

 HTML |  copy code |? 
01
<html>
02
  <head>
03
    <title>FormAutoCompletePage</title>
04
  </head>
05
  <body>
06
    <form wicket:id="form">
07
      Postleitzahl: <input wicket:id="plz" autocomplete="off" ></input><br>
08
      Ort: <input wicket:id="ort" autocomplete="off" ></input><br>
09
      <input type="submit" value="Button"> 
10
    </form>
11
  </body>
12
</html>

Und so sieht es dann aus:

wicket-combined-autocomplete-behavoir

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Wicket.

Tagged with , , , .


Wicket – No TransparentResolver

Wie ich im letzten Beitrag bereits angkündigt habe, kann man tatsächlich das TransparentResolver-Problem mit Hilfe der verzögerten Initialisierung lösen. Dazu muss die AbstractLazyPanel-Klasse nur ein wenig verändert werden.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.transparent.lazy;
02
 
03
import org.apache.wicket.Component;
04
import org.apache.wicket.MarkupContainer;
05
import org.apache.wicket.markup.html.panel.Panel;
06
 
07
public abstract class AbstractLazyPanel extends Panel
08
{
09
  boolean _lazyInitCalled;
10
 
11
  public AbstractLazyPanel(String id)
12
  {
13
    super(id);
14
  }
15
 
16
  @Override
17
  protected void onBeforeRender()
18
  {
19
    if (!_lazyInitCalled)
20
    {
21
      _lazyInitCalled=true;
22
      lazyInit();
23
    }
24
    super.onBeforeRender();
25
  }
26
 
27
  protected abstract MarkupContainer lazyInit();
28
}

Die Methode lazyInit() liefert die Komponente zurück, unter der alle Kindkomponenten eingehangen werden müssen. Das sieht in eigenen Komponenten dann wie folgt aus:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.transparent.lazy;
02
 
03
import org.apache.wicket.MarkupContainer;
04
import org.apache.wicket.markup.html.WebMarkupContainer;
05
 
06
public abstract class BasePanel extends AbstractLazyPanel
07
{
08
  public BasePanel(String id)
09
  {
10
    super(id);
11
  }
12
 
13
  @Override
14
  protected MarkupContainer lazyInit()
15
  {
16
    WebMarkupContainer border = new WebMarkupContainer("border");
17
    add(border);
18
    return border;
19
  }
20
}

Hier erstellen wir einen Container, den wir an ein div-Tag binden.

 HTML |  copy code |? 
1
<wicket:panel>
2
  Base
3
  <div style="border:1px solid blue" wicket:id="border">
4
    <wicket:child />
5
  </div>
6
  Base End
7
</wicket:panel>

Die davon abgeleitete Klasse sieht dann wie folgt aus:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.transparent.lazy;
02
 
03
import org.apache.wicket.MarkupContainer;
04
import org.apache.wicket.markup.html.basic.Label;
05
import org.apache.wicket.model.Model;
06
 
07
public class SubPanel extends BasePanel
08
{
09
  public SubPanel(String id)
10
  {
11
    super(id);
12
  }
13
 
14
  @Override
15
  protected MarkupContainer lazyInit()
16
  {
17
    MarkupContainer root = super.lazyInit();
18
    root.add(new Label("label",Model.of("Sub")));
19
    return root;
20
  }
21
}

 HTML |  copy code |? 
1
<wicket:extend>
2
  <span wicket:id="label"></span>
3
</wicket:extend>

Wie man sehen kann, wurde in diesem Beispiel sowohl Markup- als auch Komponentenvererbung benutzt. Trotzdem konnte auf den Einsatz von TransparentResolver verzichtet werden. Die Komponentenhierarchie entspricht der im Markup.

Ich hätte nicht gedacht, dass die Lösung so nahe liegt. Feedback wie immer willkommen…

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Allgemein, Refactoring, Wicket.

Tagged with , , , .


Wicket – verzögerte Initialisierung

Nicht immer ist es möglich oder sinnvoll, im Konstruktor einer Komponente bereits die vollständige Komponentenstruktur anzulegen. Wie man Komponenten zu einem späteren Zeitpunkt anlegen kann, kann man in den Repeater-Klassen ansehen. Aber es geht wesentlich einfacher. Nach dem Erstellen einer Komponente wird als nächstes (für den Fall,dass die Komponente dargestellt wird) onBeforeRender() aufgerufen. Wir können die fehlenden Komponenten in diesem Methodenaufruf erstellen. Wir müssen nur darauf achten, dass diese Initialisierung nur einmal durchgeführt wird. Am besten verpacken wir das ganze in eine eigene Klasse.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.lazy;
02
 
03
import org.apache.wicket.markup.html.panel.Panel;
04
 
05
public abstract class AbstractLazyPanel extends Panel
06
{
07
  boolean _lazyInitCalled;
08
 
09
  public AbstractLazyPanel(String id)
10
  {
11
    super(id);
12
  }
13
 
14
  @Override
15
  protected void onBeforeRender()
16
  {
17
    if (!_lazyInitCalled)
18
    {
19
      _lazyInitCalled=true;
20
      lazyInit();
21
    }
22
    super.onBeforeRender();
23
  }
24
 
25
  protected abstract void lazyInit();
26
}

Eigene Komponenten werden dann von dieser Klasse abgeleitet. Dieser Umbau hat keine Veränderung des Markup zur Folge, da keinerlei Hilfskomponenten eingefügt wurden.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.lazy;
02
 
03
import org.apache.wicket.markup.html.basic.Label;
04
import org.apache.wicket.model.Model;
05
 
06
public class TestPanel extends AbstractLazyPanel
07
{
08
  public TestPanel(String id)
09
  {
10
    super(id);
11
  }
12
 
13
  @Override
14
  protected void lazyInit()
15
  {
16
    add(new Label("label",Model.of("Label")));
17
    add(new SimplePanel("panel"));
18
  }
19
}

 HTML |  copy code |? 
1
<wicket:panel>
2
  <span wicket:id="label"></span>
3
  <br>
4
  <wicket:container wicket:id="panel"></wicket:container>
5
</wicket:panel>

Wie man sieht, lässt sich die neuen Klasse sehr einfach als Ersatz für die Panel-Klasse benutzen. Vielleicht ist dieser Ansatz ausbaufähig, so dass man durch eine verspätete Initialisierung auch die Probleme rund um TransparentResolver umgehen kann.

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Allgemein, Refactoring, Wicket.

Tagged with , , , .


Wicket und Eclipse

Um mit Wicket Anwendungen entwickeln zu können, benötigt man eine vernünftige Java-Entwicklungsumgebung, die auch Html-Dateien bearbeiten kann. Das reicht für den Anfang. Doch im Laufe der Zeit wünscht man sich für verschiedene Dinge eine besser Unterstützung, weil man damit besonders häufig in Berührung kommt und der Effekt daher spürbar wäre. Manchmal erzielen kleine Dinge daher eine große Wirkung.

So habe ich mich bisher gescheut, für Wicket entsprechende Templates in Eclipse zu Pflegen, weil ich dachte, dass sich der Aufwand nicht lohnt, weil man davon nicht ausreichend profitiert. Das war bis vor ein paar Tagen so. Als ich dann merkte, dass ich immer häufiger “wicket:id” tippen musste, fügte ich dafür ein passendes Template in Eclipse hinzu. Es funktionierte wie erwartet und ich konnte etwas flüssiger arbeiten. Schon am darauf folgenden Tag benutzte ich in einem anderen Projekt ein neuen Workspace. Als ich das erste mal wieder “wicket:id” schreiben wollte und nun auf die Unterstützung von Eclipse wartete, fiel mir auf, was mir fehlt: in dem Projekt waren die Wicket-Templates noch nicht eingebunden.

Damit man sich nicht selbst hinsetzen muss und diese Templates in Eclipse konfigurieren muss, hier die Datei zum download.

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in Allgemein, Wicket.

Tagged with , , .