Skip to content


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