Skip to content


Wicketbuch aus dem dpunkt Verlag

Wicket ist ein Webframework, dass bereits heute eine hohe Popularität genießt. Es werden zunehmen Projekte mit Wicket realisiert, das Angebot an freien Komponenten ist enorm groß (Wicket Stuff) und die Community erfreut such großer Beliebtheit. Trotzdem gibt es Widerstand aus den lagern der etablierten Frameworks, die daran zweifeln, dass Wicket auch für sogenannte "Enterprise"-Anwendungen geeignet ist (einen Beweis, dass JSF geeignet ist, hat IMHO auch noch niemand geliefert). Wie bei jedem neuen Framework sollte man sich auf der einen Seite damit beschäftigen, aber auf der anderen Seite auch abwägen, ob das Framework eine Zukunft hat. Gerade der letzte Punkt ist besonders schwer einzuschätzen und ist nur indirekt messbar, in dem man z.B. die veröffentliche Literatur zu diesem Thema als Anhaltspunkt für eine Bewertung nimmt. Und da sieht es eigentlich ganz gut aus, denn es gibt neben den einglischsprachigen auch bereits zwei deutsche Bücher zum Thema.

Man profitiert selbst als erfahrender Entwickler von Ideen und Sichtweisen, die andere Entwickler bei der Lösung ihrer Probleme gesammelt haben. Daher lese ich die verschiedenen Blogs, Erfahrungsberichte und Beiträge auf der Mailingliste. Aus diesem Grund habe ich das andere deutsche Wicketbuch aus dem dpunkt Verlag bereits fest eingeplant und musste nun leider feststellen, dass das Erscheinungsdatum auf den Januar 2010 verschoben wurde.

Es werden einfach noch zu viele Projekte mit JSF umgesetzt. Ein zweites deutsches Buch sollte eigentlich Signal genug sein, dass Wicket eine ernst zu nehmende Alternative ist. Ich drücke den Autoren die Daumen, dass sie das Projekt erfolgreich abschließen (ich weiß, wie hart das sein kann) und freue mich auf neue Erkenntnisse und Sichtweisen.

Tags:

Veröffentlicht in Allgemein, Wicket, .

Wicket Komponentenübersicht

Ich habe in den letzten Tagen damit angefangen, die verschiedenen Componenten, die Wicket bereits in dem Basisbibliotheken bietet, zu visualisieren. Den ersten Versuch habe ich noch von Hand durchgeführt, um schnell festzustellen, dass man auf diese Weise nicht besonders schnell vorwärts kommt. Der zweite und damit aktuelle Ansatz ist da schon sehr viel versprechend. Es werden die Wicket-Quellen geparst und alle Klassendefinitionen eingelesen, die Abhängigkeiten untereinander ermittelt, verschiedene Filter angewendet um die Darstellung auf ein Thema einzugrenzen und das ganze dann mit dem dot-Programm aus dem Graphviz-Paket in ein Diagramm umgewandelt.

Die folgenden Grafiken sind das vorläufige Zwischenergebnis. Es ist nicht auszuschließen, dass es Fehler gibt. Für jeden gemeldeten Fehler bin ich daher sehr dankbar, aber auch Anregungen sind natürlich herzlich willkommen.

Wicket Kompontenten - Basis

Wicket Komponenten inklusive Extensions

Wicket Modelle

Wicket Behavior

Wicket Listener

Wicket RequestTarget

Tags:

Veröffentlicht in Technologie, Wicket, .

Praxisbuch Wicket in der Google Books

Ich bin vor kurzen durch Zufall drauf gestoßen, dass mein eigenes Buch im Google Books Index erscheint. Ich wusste zwar, das der Verlag diese Option erwähnt hatte, war mir dessen aber nicht bewusst.

Nun ist dieses ein kontroverses Thema, das ich jetzt in der Rolle als Autor natürlich von einem neuen Standpunkt aus betrachten kann. Und da muss ich feststellen, dass sich meine erste Einschätzung nicht geändert hat:

  • Aus Nutzersicht ist es großartig, dass man auch Bücher durchsuchen kann.
  • Man kann auch Bücher durchsuchen, die man (noch) nicht besitzt.
  • Ich kaufe mir ein Buch, damit ich es in der Hand halten kann und drin blättern kann, durch Notizen ergänzen kann, es an einem beliebigen Ort (mit Licht) bequem lesen kann.

Die Büchersuche ist eine gute und sinnvolle Ergänzung. Ich kann mir nicht vorstellen, das die Büchersuche ursächliche vom Kauf abhält. Vielmehr könnte der Inhalt ausschlaggebend sein, so dass natürlich ein schlechtes Buch eher verliert und daher vielleicht doch nicht gekauft wird. Das was sich als nachteilig für den Autor darstellt, sieht ein Nutzer natürlich anders. Doch die Antwort auf dieses Problem sollte eher ein höherer Anspruch an sich selbst sein.

Da ich noch häufiger Nutzer als Autor bin, finde ich diese Entwicklung natürlich großartig. Ob ich dabei einer selbstgefälligen Naivität zum Opfer gefallen bin, wird sich noch zeigen.

Tags:

Veröffentlicht in Allgemein, .

DataTable vs. DataView - Wicket-Komponenten wiederverwendet

Um Daten zur Anzeige zu bringen, reicht im einfachsten Fall eine ListView-Komponente. Wenn die Anforderungen komplexer werden, macht sich der Einsatz der DataTable-Komponente recht bald bezahlt. Welche Möglichkeiten diese Komponente bietet und wie man mit ein wenig Mehraufwand auf außergewöhnliche Anforderungen reagieren kann, zeigt der folgende Beitrag.

Vorarbeiten

Damit das Beispiel für jeden Nachvollziehbar wird, müssen wir einige Vorarbeiten leisten. Zuerst erstellen wir eine Klasse, die einen Eintrag in der Datenbank symbolisiert.

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

import java.io.Serializable;

public class SomeBean implements Serializable
{
  String _vorname;
  
  String _name;
  
  int _alter;
  
  public SomeBean(String vorname, String name, int alter)
  {
    _vorname=vorname;
    _name=name;
    _alter=alter;
  }
  
  public String getVorname()
  {
    return _vorname;
  }

  public String getName()
  {
    return _name;
  }

  public int getAlter()
  {
    return _alter;
  }
}

Um Datensätze zu filtern, erstellen wir eine Hilfsklasse, in der die Werte für den Filter abgelegt werden.

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

import java.io.Serializable;

public class SomeBeanFilter implements Serializable
{
  String _name;
  String _vorname;
  
  public String getName()
  {
    return _name;
  }

  public void setName(String name)
  {
    _name = name;
  }
  
  public String getVorname()
  {
    return _vorname;
  }
  
  public void setVorname(String vorname)
  {
    _vorname = vorname;
  }
  
  public boolean match(SomeBean bean)
  {
    boolean ret=true;
    
    if (_name!=null)
    {
      if (!bean.getName().startsWith(_name)) ret=false;
    }
    if (_vorname!=null)
    {
      if (!bean.getVorname().startsWith(_vorname)) ret=false;
    }
    
    return ret;
  }
}

Damit wir nicht eine Liste von Daten von Hand erzeugen müssen, erstellen wir einen Generator, der die Datenbank simuliert.

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

import java.util.ArrayList;
import java.util.List;

public class SomeBeanGenerator
{
  public static List<SomeBean> getBeans(int size, SomeBeanFilter filter)
  {
    List<SomeBean> ret=new ArrayList<SomeBean>();
    
    for (int i=0;i<size;i++)
    {
      SomeBean bean = new SomeBean(getVorname(i),getName(i),getAlter(i));
      if (filter!=null)
      {
        if (filter.match(bean))
        {
          ret.add(bean);
        }
      }
      else ret.add(bean);
    }
    
    return ret;
  }
  
  private static int getAlter(int pos)
  {
    return (pos % 7) + (pos/9);
  }

  static String getVorname(int pos)
  {
    switch (pos % 5)
    {
      case 0: return "Klaus";
      case 1: return "Susi";
      case 2: return "Petra";
      case 3: return "Axel";
    }
    return "Bert";
  }
  
  static String getName(int pos)
  {
    switch (pos % 8)
    {
      case 0: return "Schmidt";
      case 1: return "Meier";
      case 2: return "Schulz";
      case 3: return "Schuster";
      case 4: return "Müller";
      case 5: return "Francis";
      case 6: return "Friedrich";
    }
    return "Sommerfeld";
  }
}

Um Daten durch die DataTable-Komponente zur Anzeige zu bringen, müssen wir das IDataProvider-Interface implementieren. Da wir die Daten auch sortieren und filtern möchten, implementieren wir zusätzlich das ISortableDataProvider und das IFilterStateLocator-Interface.

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

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

import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortState;
import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider;
import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.IFilterStateLocator;
import org.apache.wicket.extensions.markup.html.repeater.util.SingleSortState;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;

public class SomeBeanDataProvider implements ISortableDataProvider<SomeBean>, IFilterStateLocator
{
  ISortState _sortState=new SingleSortState();
  SomeBeanFilter _filter=new SomeBeanFilter();
  
  final static int LIST_SIZE=123;
  private List<SomeBean> _list;
  
  public Iterator<? extends SomeBean> iterator(int first, int count)
  {
    initList();
    
    List<SomeBean> ret=_list;
    if (ret.size()>(first+count))
    {
      ret = ret.subList(first, first+count);
    }
    
    return ret.iterator();
  }

  public IModel<SomeBean> model(SomeBean object)
  {
    return Model.of(object);
  }

  public int size()
  {
    initList();
    
    return _list.size();
  }

  public void detach()
  {
    _list=null;
  }

  public ISortState getSortState()
  {
    return _sortState;
  }

  public void setSortState(ISortState state)
  {
    _sortState=state;   
  }
  
  public Object getFilterState()
  {
    return _filter;
  }

  public void setFilterState(Object state)
  {
    _filter=(SomeBeanFilter) state;
  }

  
  

  private void initList()
  {
    if (_list==null)
    {
      final int nameSort;
      final int alterSort;
      if (_sortState!=null)
      {
        nameSort = _sortState.getPropertySortOrder("name");
        alterSort = _sortState.getPropertySortOrder("alter");
      }
      else
      {
        nameSort = ISortState.NONE;
        alterSort = ISortState.NONE;
      }
      
      _list = getSortedList(nameSort, alterSort,_filter);
    }
  }


  private List<SomeBean> getSortedList(final int nameSort, final int alterSort,SomeBeanFilter filter)
  {
    List<SomeBean> result = SomeBeanGenerator.getBeans(LIST_SIZE,filter);
    
    Collections.sort(result,new Comparator<SomeBean>()
    {
      public int compare(SomeBean o1, SomeBean o2)
      {
        int compName=o1.getName().compareTo(o2.getName());
        int compAlter=new Integer(o1.getAlter()).compareTo(o2.getAlter());
        switch (nameSort)
        {
          case ISortState.NONE:
            compName=0;
            break;
          case ISortState.DESCENDING:
            compName=-compName;
            break;
        }
        switch (alterSort)
        {
          case ISortState.NONE:
            compAlter=0;
            break;
          case ISortState.DESCENDING:
            compAlter=-compAlter;
            break;
        }
        if (compName!=0) return compName;
        return compAlter;
      }
    });
    
    return result;
  }
}

Erläuterungen

Die implementieren Schnittstellen stellen im wesentlichen folgende Funktionen bereit:

  • Anzahl der Einträge in der Datenbank (in unserem Beispiel fest auf 123 eingestellt)
  • ein Ausschnitt der Einträge (von, bis)
  • Eine Implementierung von ISortState (welche Spalte ist in welche Richtung sortiert)
  • Ein Objekt,in dem die Filterwerte abgelegt werden (SomeBeanFilter)

Auf diese Weise kann man die Ergebnisliste sortieren, filtern und einen Ausschnitt davon darstellen. Außerdem ist bekannt, wie viel Einträge vorhanden sind.

DataTable - DefaultDataTable

Die DefaultDataTable-Komponente unterscheidet sich von der DataTable-Komponente im wesentlichen nur dadurch, dass bereits die Seitennavigation und die Spaltenüberschriften eingeblendet werden. Daher greifen wir in diesem Beispiel direkt auf diese Komponente zurück.

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

import java.util.ArrayList;
import java.util.List;

import org.apache.wicket.extensions.markup.html.repeater.data.table.DefaultDataTable;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterForm;
import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterToolbar;
import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.TextFilteredPropertyColumn;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.model.Model;

import de.wicketpraxis.web.blog.pages.questions.data.SomeBean;
import de.wicketpraxis.web.blog.pages.questions.data.SomeBeanDataProvider;

public class DataTablePage extends WebPage
{
  public DataTablePage()
  {
    List<IColumn<SomeBean>> columns=new ArrayList<IColumn<SomeBean>>();
    columns.add(new TextFilteredPropertyColumn<SomeBean,String>(Model.of("Vorname"),"vorname"));
    columns.add(new TextFilteredPropertyColumn<SomeBean,String>(Model.of("Name"),"name","name"));
    columns.add(new PropertyColumn<SomeBean>(Model.of("Alter"),"alter","alter"));
    
    SomeBeanDataProvider dataProvider=new SomeBeanDataProvider();
    
    FilterForm form=new FilterForm("form",dataProvider);
    
    DefaultDataTable<SomeBean> dataTable = new DefaultDataTable<SomeBean>("dataTable",columns,dataProvider,10);
    dataTable.addTopToolbar(new FilterToolbar(dataTable,form,dataProvider));
    form.add(dataTable);
    
    add(form);
  }
}

Zuerst definieren wir eine Liste von Spalten, in der wir dann verschieden Spaltentypen verwenden. Die TextFilteredPropertyColumn vereint die Eingabe eines Filters, die Sortierung der Spalte und die Anzeige der Spalte in einer Komponente. Da für die Spalte "Vorname" das Attribut für die Sortierung weggelassen wurde, kann nach dieser Spalte auch nicht sortiert werden. Die letzte Spalte ist die einfachste Form einer Spaltendefinition und dient nur der Anzeige. Damit die Filter zum Einsatz kommen können, muss man die Tabelle mit einem Formular umschließen. Dazu muss die FilterForm-Komponente verwendet werden. Außerdem muss man zusätzlich eine FilterToolbar hinzufügen, damit die Eingabefelder auch angezeigt werden.

Da die DataTable-Komponente generisch ist (man kann Spalten hinzufügen oder weglassen, man kann die FilterToolbar deaktivieren, etc.), muss man in so einem Fall keine Anpassungen am Markup vornehmen. Das Markup sieht daher auch recht übersichtlich aus.

<html>
  <head>
    <title>DataTable Page</title>
  </head>
  <body>
    <form wicket:id="form">
      <input type="hidden" wicket:id="focus-tracker"></input>
      <input type="hidden" wicket:id="focus-restore"></input>
      <table wicket:id="dataTable"></table>
    </form>
  </body>
</html>

Auffällig sind hier die zwei versteckten Eingabefelder, die die Komponente für (mich nicht ganz nachvollziehbare) Javascript-Fokus-Funktionen benutzt. Außerdem muss man feststellen, dass die DataTable-Komponente einfach nur an einen Table-Tag gebunden wurde. Das restliche Markup bringt die Komponente mit. Das bedeutet gleichzeitig, dass man kaum Einfluss auf das Markup innerhalb der Tabelle hat, wenn man die Komponente nicht mit einem eigenen Markup überschreibt.

datatable-komponente-filter-sort

DataView

Wenn man doch mehr Kontrolle über das Markup haben möchte, kann man sich aus dem Fundus der Komponenten bedienen, die auch bei der DataTable zum Einsatz kommen. Im folgenden stellen wir eine funktionsäquivalente Komponente mit einer DataView-Komponente und anderen Komponenten nach.

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

import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByLink;
import org.apache.wicket.extensions.markup.html.repeater.data.table.NavigatorLabel;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigator;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.model.CompoundPropertyModel;

import de.wicketpraxis.web.blog.pages.questions.data.SomeBean;
import de.wicketpraxis.web.blog.pages.questions.data.SomeBeanDataProvider;
import de.wicketpraxis.web.blog.pages.questions.data.SomeBeanFilter;

public class DataViewPage extends WebPage
{
  public DataViewPage()
  {
    SomeBeanDataProvider dataProvider=new SomeBeanDataProvider();
    
    Form<SomeBeanFilter> form = new Form<SomeBeanFilter>("form",new CompoundPropertyModel<SomeBeanFilter>(dataProvider.getFilterState()));
    form.add(new TextField<String>("name"));
    form.add(new TextField<String>("vorname"));
    
    form.add(new OrderByLink("sortName","name",dataProvider));
    form.add(new OrderByLink("sortAlter","alter",dataProvider));
    
    DataView<SomeBean> dataView = new DataView<SomeBean>("dataView",dataProvider)
    {
      @Override
      protected void populateItem(Item<SomeBean> item)
      {
        item.setDefaultModel(new CompoundPropertyModel<SomeBean>(item.getModel()));
        item.add(new Label("vorname"));
        item.add(new Label("name"));
        item.add(new Label("alter"));
      }
    };
    dataView.setItemsPerPage(10);
    
    form.add(new PagingNavigator("navigator",dataView));
    form.add(new NavigatorLabel("label",dataView));
    
    form.add(dataView);
    
    add(form);
  }
}

Zuerst erstellen wir ein Formular, dass die Daten in der FilterBean des DataProviders ablegt. Wie im oberen Beispiel fügen wir alle weiteren Komponenten als Kindkomponenten zu diesem Formular hinzu. Für die Sortierung greifen wir auf die OrderByLink-Komponente zurück, die auch in der DataTable-Komponente benutzt wird. Die DataView-Komponente erwartet als Datenlieferanten ebenfalls eine IDataProvider-Implementierung. Anders als bei der DataTable greifen wir in diesem Fall nicht auf einer Liste von Spalten zurück, sondern erzeugen beliebige Komponenten, die wir dann später im Markup in die entsprechenden Spalten sortieren. Das wir für das item das DefaultModel überschreiben, dient nur zur Förderung einer gewissen Schreibfaulheit, da man sich so für jedes Label die Angabe eines Models erspart. Wir begrenzen außerdem die angezeigten Datensätze auf 10 Einträge. Der PagingNavigator und das NavigatorLabel werden ebenso in der DataTable-Komponente benutzt, so dass wir auch dafür keine eigene Komponente schreiben müssen.

Zusätzlich zum Markup definieren wir außerdem noch eine Properties-Datei, um die angezeigten Texte anzupassen (z.B. Tooltips bei den Links).

<html>
  <head>
    <title>DataTable Page</title>
  </head>
  <body>
    
    <form wicket:id="form">
      <table>
        <thead>
          <tr>
            <td colspan="3">
              <span wicket:id="label"></span>
              <span wicket:id="navigator"></span>
            </td>
          </tr>
          <tr>
            <th>Vorname</th>
            <th><a wicket:id="sortName">Name</a></th>
            <th><a wicket:id="sortAlter">Alter</a></th>
          </tr>
          <tr>
            <th><input wicket:id="vorname"></th>
            <th><input wicket:id="name"></th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <tr wicket:id="dataView">
            <td><span wicket:id="vorname"></span></td>
            <td><span wicket:id="name"></span></td>
            <td><span wicket:id="alter"></span></td>
          </tr>
        </tbody>
      </table>
    
    </form>
  </body>
</html>
PagingNavigator.last=Ende
PagingNavigator.first=Start
PagingNavigator.previous=Eins zurück
PagingNavigator.next=Eins weiter
PagingNavigation.page=Zur Seite ${page}
NavigatorLabel=Gezeigt wird ${from} bis ${to} von ${of}

Wie man sieht, muss man sehr viel mehr Aufwand für das richtige Markup betreiben. Allerdings eröffnen sich dadurch natürlich wesentlich mehr Möglichkeiten, direkt auf das Markup Einfluss nehmen zu können. dataview-komponente-filter-sort

Im Bild sieht man die Komponente mit einem eingetragenen Wert für einen Filter und nach Alter sortiert.

Zusammenfassung

Wie man an diesem Beispiel erkennen kann, bietet Wicket sehr komplexe und mächtige Komponenten. Trotzdem kann man jederzeit eigene Anpassungen vornehmen und durch den geschickten Einsatz von Teilkomponenten recht schnell die gewünschte Flexibilität erreichen. Der Aufwand hält sich dabei in Grenzen. Allerdings fehlen das eine oder andere Mal ausreichend Dokumentationen oder Beispiele, so dass man nicht um ein Studium der Quelltexte umhinkommt. Doch je mehr Verbreitung Wicket findet, wird auch dieses Problem über kurz oder lang gelöst werden können.

Tags:

Veröffentlicht in Allgemein, Wicket, .

Migration zu Wicket: Model

Wicket ist ein Webframework nach dem MVC-Prinzip. Dabei orientiert sich Wicket in vielen Dingen eher an Grundprinzipien der Anwendungsentwicklung von Desktop-Anwendungen. Eine wesentliche Rolle spielen dabei die Modelle, die in Komponenten benutzt werden und die die Daten liefern, die durch verschiedenen Komponenten zur Anzeige gebracht werden. Dabei gibt es aber gerade in diesem Bereich sehr viele Irritationen. Problematisch erweist sich dabei vielleicht der Umstand, dass man auch ohne Modelle zu benutzen, mit Wicket bereits Daten zur Anzeige bringen kann. Man versteht dann erst einmal nicht, wie sich dass von einer Anwendung z.B. in PHP unterscheiden soll. Betrachtet man die Label-Komponente (die häufig für die Darstellung herangezogen wird) näher, stellt man fest, dass auch der Aufruf ohne Model intern dazu führt, dass der Übergabeparameter in ein Model gelegt wird.

In einem kleinen Beispiel möchte ich veranschaulichen, wie die verschiedenen Model-Klassen zum Einsatz kommen können und warum man immer Modelle benutzen sollte.

Wir erstellen zuerst eine Model-Klasse, die eine Liste von Ergebnissen liefert. In diesem Fall werden die Einträge zwar fest kodiert, könnten aber genauso gut aus der Datenbank kommen.

package de.wicketpraxis.web.blog.pages.migration.model;

public class Result
{
  String _name;
  
  Double _betrag;
  
  public Result(String name, double betrag)
  {
    _name=name;
    _betrag=betrag;
  }
  
  public String getName()
  {
    return _name;
  }
  
  public Double getBetrag()
  {
    return _betrag;
  }
}

package de.wicketpraxis.web.blog.pages.migration.model;

import java.util.ArrayList;
import java.util.List;

import org.apache.wicket.model.LoadableDetachableModel;

public class ResultListModel extends LoadableDetachableModel<List<Result>>
{
  @Override
  protected List<Result> load()
  {
    List<Result> ret=new ArrayList<Result>();
    ret.add(new Result("Popcorn",3.15));
    ret.add(new Result("Eis",2.80));
    ret.add(new Result("Eintritt",5.50));
    return ret;
  }
}

Das LoadableDetachableModel ist immer dann die beste Wahl, wenn Daten dynamisch erzeugt werden sollen und jedes mal sicher gestellt werden soll, dass die aktuellsten Daten verwendet werden. Wir das Model während eines Request geladen, werden die Daten so lange vorgehalten, bis die detach()-Methode aufgerufen wird, und die Liste zurückgesetzt wird. Das Model liefert uns in diesem Fall 3 Datensätze der Result-Klasse.

Die Beispielliste ist eine Liste von Posten auf einer Rechnung. Wir möchten aber zum Betrag auch noch den MwSt-Anteil ausweisen. Dazu schreiben wir ein allgemeines Model, dass von einer Erweiterung des LoadableDetachableModel abgeleitet ist: CascadingLoadableDetachableModel.

package de.wicketpraxis.web.blog.pages.migration.model;

import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;

import de.wicketpraxis.web.blog.model.CascadingLoadableDetachableModel;

public class MwStAnteilModel extends CascadingLoadableDetachableModel<Double, Double>
{
  static final Double MWST=19.0;
  
  public MwStAnteilModel(IModel<? extends Double> parent)
  {
    super(parent);
  }

  @Override
  protected Double load(Double brutto)
  {
    if (brutto!=null)
    {
      return brutto*MWST/100.0;
    }
    return null;
  }
}

Das Model entnimmt dem referenzierten Model den Betrag und ermittelt den MwSt-Anteil, den es seinerseits bereitstellt. Der Wert wird auch hier bis zum Aufruf von detach() gepuffert. Als Referenzmodell kann jede Klasse dienen, die IModel implementiert.

Jetzt fügen wir die Bestandteile zusammen:

package de.wicketpraxis.web.blog.pages.migration.model;

import java.util.List;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.PropertyListView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;

import de.wicketpraxis.web.blog.model.CascadingLoadableDetachableModel;

public class SomeModelsPage extends WebPage
{
  public SomeModelsPage()
  {
    ResultListModel resultList=new ResultListModel();
    
    add(new PropertyListView<Result>("list",resultList)
    {
      @Override
      protected void populateItem(ListItem<Result> item)
      {
        IModel<Result> model = item.getModel();
        item.add(new Label("name",new PropertyModel<String>(model,"name")));
        item.add(new Label("betrag"));
        item.add(new Label("mwst",new MwStAnteilModel(new PropertyModel<Double>(model,"betrag"))));
      }
    });

    IModel<Double> summeModel = new CascadingLoadableDetachableModel<Double, List<Result>>(resultList)
    {
      @Override
      protected Double load(List<Result> liste)
      {
        if (liste!=null)
        {
          double summe=0;
          for (Result r : liste)
          {
            summe=summe+r.getBetrag();
          }
          return summe;
        }
        return null;
      }
    };
    
    add(new Label("summe",summeModel));
    add(new Label("mwst",new MwStAnteilModel(summeModel))); 
  }
}

Zuerst erstellen wir eines Instanz des ResultListModel. Die Ergebnisliste wird durch eine PropertyListView dargestellt. Der Unterschied zur ListView-Komponente besteht darin, dass bei Komponenten ohne eigenes Model die Id als Property-Expression herangezogen wird, um das Model für die Anzeige der Daten zu ermitteln. Die Schreibarbeit, die man für das Label "name" aufwenden müsste, kann man auf diese Weise erheblich reduzieren. Wie man beim Label mit der Id "mwst" erkennen kann, können die Modelle beliebig geschachtelt werden.

Um die Summe aller Einträge zu ermitteln, könnten wir jetzt die Ergebnisliste durchgehen und die Summe berechnen. Doch der Konstruktor einer Komponente wird ja nur einmal aufgerufen. Ändert sich durch eine Aktion auf der Seite oder in einer Komponente etwas an den zugrunde liegenden Daten, dann wird die Summe so nicht automatisch aktualisiert. Daher erstellen wir ein Model, dass diese Berechnung immer wieder durchführt. Dann können wir die aktuelle Summe und auch hierfür den MwSt-Anteil anzeigen lassen. Sollte sich an den Daten in der Datenbank etwas ändern, dann werden automatisch alle angezeigten Werte neu berechnet.

In diesem Beispiel sind mehrere wichtige Themen versteckt:

  • Alle Daten sollten in einem Model abgelegt werden. Daten sollten nicht im Konstruktor einer Komponente aufbereitet werden.
  • Für Modelle, die nicht an eine Komponente gebunden sind, wird die detach()-Methode nicht automatisch aufgerufen. Die CascadingLoadableDetachableModel-Klasse behandelt diesen Umstand entsprechend.
  • Model-Klassen kann man wiederverwenden.
  • Das Beispiel veranschaulicht, wie man z.B. die Summe aller sichtbaren Einträge über den Model-Weg ermitteln kann.

Ich hoffe, ich konnte wieder etwas Licht in das Dunkel rund um die Model-Klassen bringen. Fragen und Anregungen gerne in dem Kommentaren oder per Email.

Tags:

Veröffentlicht in Migration, Wicket, .