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:

package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;

import java.io.Serializable;

public class PlzOrt implements Serializable
{
  String _plz;
  
  String _ort;
  
  public PlzOrt(String plz, String ort)
  {
    _plz=plz;
    _ort=ort;
  }
  
  public String getPlz()
  {
    return _plz;
  }
  
  public String getOrt()
  {
    return _ort;
  }
}
package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class PlzOrtListFactory implements Serializable
{
  List<PlzOrt> _all=new ArrayList<PlzOrt>();
  
  {
    _all.add(new PlzOrt("23562", "Lübeck"));
    _all.add(new PlzOrt("23858", "Reinfeld"));
    _all.add(new PlzOrt("14199", "Berlin"));
    _all.add(new PlzOrt("70619", "Stuttgart"));
    
  }
  
  public PlzOrtListFactory()
  {
  }
  
  public List<PlzOrt> getList(String plz)
  {
    List<PlzOrt> ret=null;
    if ((plz!=null) && (plz.length()>0))
    {
      ret=new ArrayList<PlzOrt>();
      for (PlzOrt po : _all)
      {
        if (po.getPlz().startsWith(plz)) ret.add(po);
      }
    }
    return ret;
  }
}

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:

package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;

import org.apache.wicket.Response;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AbstractAutoCompleteRenderer;
import org.apache.wicket.markup.html.form.FormComponent;

public class PlzOrtRenderer extends AbstractAutoCompleteRenderer<PlzOrt>
{
  FormComponent<?> _ortInput;
  
  public PlzOrtRenderer(FormComponent<?> ortInput)
  {
    _ortInput=ortInput;
    _ortInput.setOutputMarkupId(true);
  }
  
  @Override
  protected String getTextValue(PlzOrt object)
  {
    return object.getPlz();
  }

  @Override
  protected void renderChoice(PlzOrt object, Response response, String criteria)
  {
    response.write(object.getPlz() + "-" + object.getOrt());
  }
  
  @Override
  protected CharSequence getOnSelectJavascriptExpression(PlzOrt plzort)
  {
    StringBuilder js = new StringBuilder();
    js.append("wicketGet('").append(_ortInput.getMarkupId()).append("').value ='" + plzort.getOrt() + "';");
    js.append("input");
    return js.toString();
  }

}

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.

package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteBehavior;
import org.apache.wicket.markup.html.form.FormComponent;


class PlzAutoCompleteBehavior extends AutoCompleteBehavior<PlzOrt>
{
  PlzOrtListFactory _listFactory;
  
  public PlzAutoCompleteBehavior(PlzOrtListFactory listFactory, FormComponent<?> ortInput)
  {
    super(new PlzOrtRenderer(ortInput));
    
    _listFactory=listFactory;
  }

  @Override
  protected Iterator<PlzOrt> getChoices(String input)
  {
    List<PlzOrt> list = _listFactory.getList(input);
    if (list!=null) return list.iterator();
    return Collections.EMPTY_LIST.iterator();
  }
}

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:

package de.wicketpraxis.web.blog.pages.questions.form.autocomplete;

import org.apache.wicket.extensions.ajax.markup.html.autocomplete.DefaultCssAutocompleteTextField;
import org.apache.wicket.markup.html.CSSPackageResource;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.Model;

public class FormAutoCompletePage extends WebPage
{
  public FormAutoCompletePage()
  {
    add(CSSPackageResource.getHeaderContribution(DefaultCssAutocompleteTextField.class,"DefaultCssAutocompleteTextField.css"));
    
    Form<Void> form = new Form<Void>("form");
    
    TextField<String> plz = new TextField<String>("plz",Model.of(""));
    TextField<String> ort = new TextField<String>("ort",Model.of(""));
    
    plz.add(new PlzAutoCompleteBehavior(new PlzOrtListFactory(),ort));
    
    form.add(plz);
    form.add(ort);
    
    add(form);
  }
}

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>
  <head>
    <title>FormAutoCompletePage</title>
  </head>
  <body>
    <form wicket:id="form">
      Postleitzahl: <input wicket:id="plz" autocomplete="off" ></input><br>
      Ort: <input wicket:id="ort" autocomplete="off" ></input><br>
      <input type="submit" value="Button"> 
    </form>
  </body>
</html>

Und so sieht es dann aus:

wicket-combined-autocomplete-behavoir

Tags:

Veröffentlicht in Wicket, .