Skip to content


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 , , .


Wicket – lose Koppelung von Komponenten

Auch bei Webanwendungen entstehen schnell komplexe Oberflächen. Es ist nur eine Frage der Zeit, bis man Komponenten, die miteinander interagieren sollen, gegenseitig bekannt macht. Diese Vorgehensweise ist limitiert und außerdem sehr aufwendig. Wie ich bereits im Buch beschrieben habe, kann man die Koppelung von Komponenten aufweichen, die per Ajax neu gezeichnet werden müssen. Dabei ist es zu kurz gedacht, dass man die Entkoppelung von Komponenten nur aus diesem Grund forciert.

Der Unterschied zwischen einem normalen Request und einem Ajax-Request liegt darin, dass bei einem Ajax-Request nur Teile der Seite neu gerendert werden. Dazu müssen die Komponenten markiert werden, die in der Zielseite ersetzt werden sollen. Wenn es sich um einen normalen Request handelt, ist das nicht notwendig, da die ganze Seite und damit alle Komponenten neu gerendert werden.

Solange sich alle Aktionen auf die Änderungen von Daten beziehen, die dann entweder per Ajax neu dargestellt werden oder beim Darstellen der ganzen Seite automatisch angezeigt werden, besteht eigentlich kein Grund, mehr Aufwand in das Entkoppeln von Komponenten zu stecken. Doch oft ist das Ändern der Daten nicht trivial, der Code verteilt sich unwillkürlich auf verschiedene Komponenten.

Um dieser Entwicklung entgegen zu wirken, erweitert man das Event-Konzept einfach auch auf normale Requests. Im folgenden Code werden daher alle notwendigen Klassen aufgeführt. Die im Buch verwendeten Klassen können durch diese ersetzt werden.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
1
package de.wicketpraxis.web.blog.pages.questions.events;
2
 
3
public interface EventListenerInterface
4
{
5
  public void notifyAjaxEvent(AbstractEvent event);
6
}

Das EventListenerInterface muss jede Komponente implementieren, dass auf Events reagieren muss.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.events;
02
 
03
import org.apache.wicket.Component;
04
import org.apache.wicket.Page;
05
import org.apache.wicket.Component.IVisitor;
06
import org.apache.wicket.ajax.AjaxRequestTarget;
07
 
08
public class AbstractEvent
09
{
10
  Component _source;
11
  AjaxRequestTarget _requestTarget;
12
 
13
  protected AbstractEvent(Component source,AjaxRequestTarget requestTarget)
14
  {
15
    _source=source;
16
    _requestTarget=requestTarget;
17
  }
18
 
19
  public Component getSource()
20
  {
21
    return _source;
22
  }
23
 
24
  public void fire()
25
  {
26
    Page page = _source.getPage();
27
    if (page instanceof EventListenerInterface)
28
    {
29
      ((EventListenerInterface) page).notifyAjaxEvent(this);
30
    }
31
    page.visitChildren(EventListenerInterface.class, new AjaxEventVisitor(this));
32
  }
33
 
34
  public void update(Component component)
35
  {
36
    if (_requestTarget!=null) _requestTarget.addComponent(component);
37
  }
38
 
39
  protected static class AjaxEventVisitor implements IVisitor<Component>
40
  {
41
    AbstractEvent _event;
42
 
43
    protected AjaxEventVisitor(AbstractEvent event)
44
    {
45
      _event=event;
46
    }
47
 
48
    public Object component(Component component)
49
    {
50
      ((EventListenerInterface) component).notifyAjaxEvent(_event);
51
      return IVisitor.CONTINUE_TRAVERSAL;
52
    }
53
  }
54
}

Die Eventklasse wurde dahingehend erweitert, dass jetzt auch für die Seite geprüft wird, ob das EventListernerInterface implementiert wurde. Ob Ajax benutzt wurde oder nicht, wird in der update()-Methode überprüft, so dass man diese Prüfung nicht mehr selbst vornehmen muss.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.events;
02
 
03
import org.apache.wicket.Component;
04
import org.apache.wicket.ajax.AjaxRequestTarget;
05
 
06
public class ChangeEvent extends AbstractEvent
07
{
08
  int _change;
09
 
10
  protected ChangeEvent(Component source, AjaxRequestTarget requestTarget, int change)
11
  {
12
    super(source, requestTarget);
13
 
14
    _change=change;
15
  }
16
 
17
  public int getChange()
18
  {
19
    return _change;
20
  }
21
}

Der ChangeEvent wurde abgeleitet und um eine Information erweitert. In der folgenden Komponente wird diese Information ausgewertet:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.events;
02
 
03
import org.apache.wicket.markup.html.basic.Label;
04
import org.apache.wicket.markup.html.panel.Panel;
05
import org.apache.wicket.model.IModel;
06
 
07
public class CounterPanel extends Panel implements EventListenerInterface
08
{
09
  IModel<Integer> _counter;
10
 
11
  public CounterPanel(String id,IModel<Integer> counter)
12
  {
13
    super(id);
14
 
15
    setOutputMarkupId(true);
16
 
17
    _counter=counter;
18
 
19
    add(new Label("counter",_counter));
20
  }
21
 
22
  public void notifyAjaxEvent(AbstractEvent event)
23
  {
24
    if (event instanceof ChangeEvent)
25
    {
26
      int change = ((ChangeEvent) event).getChange();
27
      Integer cur = _counter.getObject();
28
 
29
      info("Aktuell: "+cur+" Change: "+change);
30
 
31
      _counter.setObject(cur+change);
32
      event.update(this);
33
    }
34
  }
35
}

Man beachte, dass für die Komponente setOutputMarkupId() aufgerufen wird, da diese Komponente evtl. per Ajax aktualisiert werden kann.

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

Jetzt benötigen wir noch eine Komponente, die diesen Event auslöst:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.events;
02
 
03
import org.apache.wicket.ajax.AjaxRequestTarget;
04
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
05
import org.apache.wicket.markup.html.basic.Label;
06
import org.apache.wicket.markup.html.panel.Panel;
07
import org.apache.wicket.model.IModel;
08
 
09
public class ActionPanel extends Panel
10
{
11
  public ActionPanel(String id,IModel<Integer> change)
12
  {
13
    super(id);
14
 
15
    AjaxFallbackLink<Integer> link = new AjaxFallbackLink<Integer>("link",change)
16
    {
17
      @Override
18
      public void onClick(AjaxRequestTarget target)
19
      {
20
        new ChangeEvent(ActionPanel.this,target,getModelObject()).fire();
21
      }
22
    };
23
    link.add(new Label("change",change));
24
    add(link);
25
  }
26
}

 HTML |  copy code |? 
1
<wicket:panel>
2
  <a wicket:id="link"><span wicket:id="change"></span></a>
3
</wicket:panel>

Nachdem wir alle Komponten erstell habe, benutzen wir sie in einer Anwendung:

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.events;
02
 
03
import org.apache.wicket.markup.html.WebPage;
04
import org.apache.wicket.markup.html.panel.FeedbackPanel;
05
import org.apache.wicket.model.Model;
06
 
07
public class EventPage extends WebPage
08
{
09
  public EventPage()
10
  {
11
    add(new EventFeedbackPanel("feedback"));
12
 
13
    add(new ActionPanel("changeAdd1",Model.of(1)));
14
    add(new ActionPanel("changeSub1",Model.of(-1)));
15
 
16
    add(new CounterPanel("counter",Model.of(5)));
17
  }
18
 
19
  static class EventFeedbackPanel extends FeedbackPanel implements EventListenerInterface
20
  {
21
    public EventFeedbackPanel(String id)
22
    {
23
      super(id);
24
 
25
      setOutputMarkupId(true);
26
    }
27
 
28
    public void notifyAjaxEvent(AbstractEvent event)
29
    {
30
      event.update(this);
31
    }
32
  }
33
}

Wie man sieht, habe ich auch ein FeedbackPanel erstellt, dass auf Events (und in diesem Fall jedes) reagiert.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
<html>
02
  <head>
03
    <title>EventPage</title>
04
  </head>
05
  <body>
06
    <div wicket:id="feedback"></div>
07
 
08
    <wicket:container wicket:id="changeAdd1"></wicket:container>
09
    <wicket:container wicket:id="changeSub1"></wicket:container>
10
 
11
    <div wicket:id="counter"></div>
12
  </body>
13
</html>

Wenn Komponenten per Ajax aktualisiert werden müssen, funktioniert das nur, wenn es auch ein Html-Tag mit der passenden ID gibt, dass per Javascript gefunden und ersetzt werden kann. Daher muss man die Komponente dann an ein Html-Tag binden, die Einbettung über wicket:container funktioniert nicht.

Betätigt man nun einen der Links in der AktionPanel-Komponente, dann wird der Zähler in der CounterPanel-Komponenten angepasst und eine entsprechende Meldung im FeedbackPanel angezeigt. Wenn man nun das CounterPanel einfach ausbaut, passiert nichts. Das zeigt zum einen, dass die Komponenten wirklich unabhängig voneinander sind, zum anderen aber auch, welches Risiko man eingeht: es kann vorkommen, dass Events nicht verarbeitet werden.

Dieses Beispiel veranschaulicht, wie einfach man Komponenten entkoppeln kann. Doch dieses Beispiel ist nur eine einfache Anwendung. Wie weit man diese Vorgehensweise in der Praxis treiben kann, möchte ich in ein paar Sichtpunkten erwähnen:

  • Anwendung der Möglichkeiten von Vererbung auf Events
  • Prüfung, ob ein Event einen Empfänger erreicht hat
  • Kaskadierung von Events (wenn Event A eintrifft, wird Event B ausgelöst)
  • Mehr als ein Sender und mehr als einen Empfänger für Events
  • Ein Event kann mit und ohne Ajax ausgelöst werden, ohne dass der Code auf Empfängerseite angepasst werden muss.

Ich hoffe, dass dient als Anregung oder Vorlage. Komplexen Oberflächen mit Wicket steht nun nichts mehr im Weg.

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

Posted in Allgemein, Refactoring, Wicket.

Tagged with , , , , , .