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

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.

package de.wicketpraxis.web.blog.pages.questions.transparent.lazy;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.html.panel.Panel;

public abstract class AbstractLazyPanel extends Panel
{
  boolean _lazyInitCalled;
  
  public AbstractLazyPanel(String id)
  {
    super(id);
  }

  @Override
  protected void onBeforeRender()
  {
    if (!_lazyInitCalled)
    {
      _lazyInitCalled=true;
      lazyInit();
    }
    super.onBeforeRender();
  }

  protected abstract MarkupContainer lazyInit();
}

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:

package de.wicketpraxis.web.blog.pages.questions.transparent.lazy;

import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.html.WebMarkupContainer;

public abstract class BasePanel extends AbstractLazyPanel
{
  public BasePanel(String id)
  {
    super(id);
  }

  @Override
  protected MarkupContainer lazyInit()
  {
    WebMarkupContainer border = new WebMarkupContainer("border");
    add(border);
    return border;
  }
}

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

<wicket:panel>
  Base
  <div style="border:1px solid blue" wicket:id="border">
    <wicket:child />
  </div>
  Base End
</wicket:panel>

Die davon abgeleitete Klasse sieht dann wie folgt aus:

package de.wicketpraxis.web.blog.pages.questions.transparent.lazy;

import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.Model;

public class SubPanel extends BasePanel
{
  public SubPanel(String id)
  {
    super(id);
  }

  @Override
  protected MarkupContainer lazyInit()
  {
    MarkupContainer root = super.lazyInit();
    root.add(new Label("label",Model.of("Sub")));
    return root;
  }
}
<wicket:extend>
  <span wicket:id="label"></span>
</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...

Tags:

Veröffentlicht in Allgemein, Refactoring, Wicket, .

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.

package de.wicketpraxis.web.blog.pages.questions.lazy;

import org.apache.wicket.markup.html.panel.Panel;

public abstract class AbstractLazyPanel extends Panel
{
  boolean _lazyInitCalled;
  
  public AbstractLazyPanel(String id)
  {
    super(id);
  }

  @Override
  protected void onBeforeRender()
  {
    if (!_lazyInitCalled)
    {
      _lazyInitCalled=true;
      lazyInit();
    }
    super.onBeforeRender();
  }

  protected abstract void lazyInit();
}

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

package de.wicketpraxis.web.blog.pages.questions.lazy;

import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.Model;

public class TestPanel extends AbstractLazyPanel
{
  public TestPanel(String id)
  {
    super(id);
  }

  @Override
  protected void lazyInit()
  {
    add(new Label("label",Model.of("Label")));
    add(new SimplePanel("panel"));
  }
}
<wicket:panel>
  <span wicket:id="label"></span>
  <br>
  <wicket:container wicket:id="panel"></wicket:container>
</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.

Tags:

Veröffentlicht in Allgemein, Refactoring, Wicket, .

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.

Tags:

Veröffentlicht in Allgemein, Wicket, .

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.

package de.wicketpraxis.web.blog.pages.questions.events;

public interface EventListenerInterface
{
  public void notifyAjaxEvent(AbstractEvent event);
}

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

package de.wicketpraxis.web.blog.pages.questions.events;

import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.Component.IVisitor;
import org.apache.wicket.ajax.AjaxRequestTarget;

public class AbstractEvent
{
  Component _source;
  AjaxRequestTarget _requestTarget;
  
  protected AbstractEvent(Component source,AjaxRequestTarget requestTarget)
  {
    _source=source;
    _requestTarget=requestTarget;
  }
  
  public Component getSource()
  {
    return _source;
  }
  
  public void fire()
  {
    Page page = _source.getPage();
    if (page instanceof EventListenerInterface)
    {
      ((EventListenerInterface) page).notifyAjaxEvent(this);
    }
    page.visitChildren(EventListenerInterface.class, new AjaxEventVisitor(this));
  }
  
  public void update(Component component)
  {
    if (_requestTarget!=null) _requestTarget.addComponent(component);
  }
  
  protected static class AjaxEventVisitor implements IVisitor<Component>
  {
    AbstractEvent _event;
    
    protected AjaxEventVisitor(AbstractEvent event)
    {
      _event=event;
    }
    
    public Object component(Component component)
    {
      ((EventListenerInterface) component).notifyAjaxEvent(_event);
      return IVisitor.CONTINUE_TRAVERSAL;
    }
  }
}

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.

package de.wicketpraxis.web.blog.pages.questions.events;

import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;

public class ChangeEvent extends AbstractEvent
{
  int _change;
  
  protected ChangeEvent(Component source, AjaxRequestTarget requestTarget, int change)
  {
    super(source, requestTarget);
    
    _change=change;
  }

  public int getChange()
  {
    return _change;
  }
}

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

package de.wicketpraxis.web.blog.pages.questions.events;

import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;

public class CounterPanel extends Panel implements EventListenerInterface
{
  IModel<Integer> _counter;
  
  public CounterPanel(String id,IModel<Integer> counter)
  {
    super(id);
    
    setOutputMarkupId(true);
  
    _counter=counter;
    
    add(new Label("counter",_counter));
  }

  public void notifyAjaxEvent(AbstractEvent event)
  {
    if (event instanceof ChangeEvent)
    {
      int change = ((ChangeEvent) event).getChange();
      Integer cur = _counter.getObject();
      
      info("Aktuell: "+cur+" Change: "+change);
      
      _counter.setObject(cur+change);
      event.update(this);
    }
  }
}

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

<wicket:panel>
  <span wicket:id="counter"></span>
</wicket:panel>

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

package de.wicketpraxis.web.blog.pages.questions.events;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;

public class ActionPanel extends Panel
{
  public ActionPanel(String id,IModel<Integer> change)
  {
    super(id);
    
    AjaxFallbackLink<Integer> link = new AjaxFallbackLink<Integer>("link",change)
    {
      @Override
      public void onClick(AjaxRequestTarget target)
      {
        new ChangeEvent(ActionPanel.this,target,getModelObject()).fire();
      }
    };
    link.add(new Label("change",change));
    add(link);
  }
}
<wicket:panel>
  <a wicket:id="link"><span wicket:id="change"></span></a>
</wicket:panel>

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

package de.wicketpraxis.web.blog.pages.questions.events;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.Model;

public class EventPage extends WebPage
{
  public EventPage()
  {
    add(new EventFeedbackPanel("feedback"));
    
    add(new ActionPanel("changeAdd1",Model.of(1)));
    add(new ActionPanel("changeSub1",Model.of(-1)));
    
    add(new CounterPanel("counter",Model.of(5)));
  }
  
  static class EventFeedbackPanel extends FeedbackPanel implements EventListenerInterface
  {
    public EventFeedbackPanel(String id)
    {
      super(id);
      
      setOutputMarkupId(true);
    }
    
    public void notifyAjaxEvent(AbstractEvent event)
    {
      event.update(this);
    }
  }
}

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

<html>
  <head>
    <title>EventPage</title>
  </head>
  <body>
    <div wicket:id="feedback"></div>
    
    <wicket:container wicket:id="changeAdd1"></wicket:container>
    <wicket:container wicket:id="changeSub1"></wicket:container>
    
    <div wicket:id="counter"></div>
  </body>
</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.

Tags:

Veröffentlicht in Allgemein, Refactoring, Wicket, .

Anfang und Ende

Ich wünsche allen ein gesundes neues Jahr. 2009 hat sich verabschiedet und 2010 zeigt sich bereits jetzt von seiner besten Seite (wenn man Schnee mag). Ich freue mich auf die vielen spannenden Themen, die das neue Jahr bereithält.

Auch wenn es anders kommt, als man sich denkt, wage ich hier doch mal ein paar Vorhersagen. Manches davon ist offensichtlich und wird vermutlich schon deshalb nicht eintreten, Anderes ist frei erfunden und hat damit die besten Chancen, zum Volltreffer zu werden.

Nachdem 2009 eigentlich ein Scala-Jahr war, wird es auch 2010 in diesem Bereich interessant bleiben. Zu meiner Überraschung konnte allerdings auch Java durch den Scala-Hype profitieren. Einige fühlten sich wohl durch die Möglichkeiten von Scala entsprechend angestachelt, dass selbst für Dinge wie Scala Traits Implementierungen (z.B. java-mixins) für Java geschrieben wurden. Java ist zwar etwas angestaubt aber eben noch nicht tot. Und was Google mit GWT und Android alles auf Basis von Java anstellt, zeigt deutlich, dass da noch Platz für Innovationen ist. Das bedeutet, dass es auch 2010 nicht verkehrt ist, auf Java zu setzen.

Nachdem JSF 2.0 wie zu erwarten war, nicht alle Probleme, die JSF bis jetzt angesammelt hat, lösen wird, gehe ich davon aus, dass wie schon 2009 immer mehr Projekte auch mit Wicket realisiert werden. Da Wicket als Basis schon sehr ausgereift ist, wird sich der Zustrom an Entwicklern vermutlich eher auf Integrationsthemen auswirken: Web-Worker, Javascript-Frameworks und andere noch unbekannte Entwicklungen. Ich erwarte außerdem (unabhängig von meinem eigenen Teilprojekt auf wicketstuff.org) ein paar Standardvorlagen für Wicket-Projekte.

Nachdem Groovy in der neuesten Version alle Sprachfeatures hat, die für eine saubere Wicket-Integration notwendig sind, wird es auch in diesem Bereich interessant. Die Bedenken, die ich trage, richten sich vielmehr an die Plattform Groovy/Grails an sich. Es gibt Entwicklungen, die bei mir Bauchschmerzen hervorrufen (Grape - Dependency Management als Annotation auf einem Import - WTF)), die merkwürdig sind und auf die Frage "Warum?" vermutlich nur mit einem "Weil es geht." beantwortet werden.

Alles in Allem, es bleibt und wird spannend. Ich wünsche allen (unabhängig von der technologischen Präferenz) ein gutes und erfolgreiches Jahr.

Tags:

Veröffentlicht in Allgemein, JSF, Wicket, .

Ein gesundes neues Jahr

Wicket Ajax Tipp - AjaxRequestTarget nachträglich ermitteln

Gestern wurde ich gefragt, ob und wie man denn vermeiden kann, dass man das AjaxRequestTarget immer als Funktionsargument weiterreichen muss, wenn man an anderer Stelle darauf zugreifen möchte. Ich muss zugeben, dass ich die einfache Antwort nicht sofort wusste (was, wie man sehen wird, erstaunlich ist). Ich wusste aber, dass mir kürzlich ein Stück Wicket-Code begegnet ist, wo ermittelt werden musste, ob ein AjaxRequest oder ein normaler Request abgearbeitet wird.

Ich machte mich also erneut auf die Suche und fand die Code-Zeile in der AjaxFallbackButton-Klasse. Ich war kurz davor ein paar Zeilen Code daraus abzuleiten und warf nur einen flüchtigen Code in die AjaxRequestTarget-Klasse, um festzustellen, das es das natürlich schon gibt.

AjaxRequestTarget target=AjaxRequestTarget.get();

So einfach geht das. So einfach, dass ich es fast übersehen hätte.

Tags:

Veröffentlicht in Allgemein, Wicket, .

Wicket Form Submit - mit und ohne Ajax

Webanwendungen ohne Formulare gibt es nicht. Dabei ist die korrekte Formularbehandlung alles andere als trivial. Zum Glück bietet Wicket eine ausgezeichnete Unterstützung für Formulare, die kaum Wünsche offen lässt. Doch auch mit Wicket sind ein paar Dinge zu beachten, um die verschiedenen Interaktionsmöglichkeiten des Nutzers korrekt zu verarbeiten.

Wicket bietet die Möglichkeit, neben dem normalen Abschicken eines Formulars auch auf die angeklickten Submit-Buttons reagieren zu können. Damit man aber auch zuverlässig unterscheiden kann, ob der Nutzer das Formular durch ein Enter im Textfeld oder durch einen Klick auf einen Button abgeschickt hat, muss man ein paar Vorbereitungen treffen.

Für das Formular legen wir eine Bean an, welche die Daten aufnimmt.

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

import java.io.Serializable;

public class FormBean implements Serializable
{
  String _name;
  
  public String getName()
  {
    return _name;
  }
  
  public void setName(String name)
  {
    _name = name;
  }
}

Für unser Beispiel benötigen wir ein Formular, einen AjaxFallbackButton, einen normalen Button und einen Button, den wir dazu benutzen, denn Fall herauszubekommen, wenn der Nutzer einfach nur Enter gedrückt hat. Das FeedbackPanel sollte man auch nicht vergessen.

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

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxFallbackButton;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;

public class FormSubmitPage extends WebPage
{
  int _counter;
  
  public FormSubmitPage()
  {
    final FeedbackPanel feedbackPanel = new FeedbackPanel("feedback");
    feedbackPanel.setOutputMarkupId(true);
    add(feedbackPanel);
    
    CompoundPropertyModel<FormBean> formModel = new CompoundPropertyModel<FormBean>(new FormBean());
    Form<FormBean> form = new Form<FormBean>("form",formModel)
    {
      @Override
      protected void onSubmit()
      {
        info("Form Submit: "+_counter++);
      }
    };
    
    form.add(new TextField<String>("name"));
    
    form.add(new Button("defaultSubmit")
    {
      @Override
      public void onSubmit()
      {
        info("Default Button: "+_counter++);
      }
    });
    
    form.add(new Button("submit")
    {
      @Override
      public void onSubmit()
      {
        info("Button: "+_counter++);
      }
    });
    
    form.add(new AjaxFallbackButton("ajaxSubmit",form)
    {
      @Override
      protected void onSubmit(AjaxRequestTarget target, Form<?> form)
      {
        if (target!=null)
        {
          info("AjaxSubmit Button(ajax): "+_counter++);
          target.addComponent(feedbackPanel);
        
          _counter=0;
        }
        else
        {
          info("AjaxFallbackButton: "+_counter++);
        }
      }
    });
    
    add(form);
  }
  
  @Override
  protected void onBeforeRender()
  {
    _counter=0;
    super.onBeforeRender();
  }
}

Damit man die Reihenfolge besser erkennen kann, mit der die onSubmit-Methoden aufgerufen werden, wird ein Zähler (_counter) hochgezählt und vor dem Darstellen des Formulars wieder zurückgesetzt. In diesem Beispiel liegt die entscheidende Information im Markup. Dabei ist die Reihenfolge der verschiedenen Submit-Komponenten wichtig.

<html>
  <head>
    <title>FormSubmitPage</title>
  </head>
  <body>
    <div wicket:id="feedback"></div>
    
    <form wicket:id="form">
      <input wicket:id="defaultSubmit" type="submit" value="" style="border:0px; width: 0px; height: 0px; visibility: collapse; display: compact;">
      Name <input wicket:id="name"><br>
      <input wicket:id="ajaxSubmit"  type="submit" value="AjaxButton">
      <input wicket:id="submit" type="submit" value="Button"> 
    </form>
  </body>
</html>

Der Button mit der Wicket-ID defaultSubmit muss vor allen anderen Submit-Komponenten stehen. Dadurch wird beim Abschicken des Formulars durch Enter dieser Button ausgewählt. Wenn einer der anderen Buttons angeklickt wird, dann wird das Formular über diesen Button abgeschickt. Doch warum soviel Aufwand? Die onSubmit()-Methode des Formulars wird doch in jedem Fall aufgerufen.

Der Aufwand ist notwendig, wenn man abhängig davon, ob der Nutzer auf einen Button oder eben auf keinen Button geklickt hat, eine Aktion ausführen möchte. Das bedeutet, dass man die Aktion nicht in onSubmit ausführen kann. Es ist allerdings auch nicht möglich, im onSubmit() der Buttonkomponenten ein Flag zu setzen, dass dann in der onSubmit()-Methode des Formulars aufgerufen wird, da die Methode beim AjaxFallbackButton erst nach dem onSubmit() der Komponente aufgerufen wird.

Folgende Ergebnisse erhält man, wenn man das Formular a) per Enter, b) per AjaxButton und c) per Button abschickt:

Submit per Enter

Submit per AjaxButton

Submit per Button

Wenn Javascript deaktiviert ist, dann erhält man folgende Ausgabe: Submit per Enter

Submit per AjaxButton

Submit per Button

Wenn man dieses Vorgehen beherzigt, funktioniert die Anwendung auch mit deaktiviertem JavaScript genauso zuverlässig. Das freut den Nutzer und in diesem Fall auch den Entwickler:)

Tags:

Veröffentlicht in Technologie, Wicket, .

Praxisbuch Wicket - Übersicht

Hinterher ist man meistens schlauer, hofft man. Nachdem ich gerade per Twitter auf eine JSF-Mindmap gestoßen bin (dank @ptrthomas), dachte ich mir, dass es hilfreich sein könnte, die Mindmap, die ich mir bei der Arbeit am Buch erstellt habe, auch anderen zur Verfügung zu stellen. Perfektionismus führt manchmal dazu, dass man nie ankommt, daher hier sofort das ungefilterte Ergebnis.

Wicket Struktur - Übersicht aus der Arbeit am Buch

Tags:

Veröffentlicht in Allgemein, JSF, Wicket, .