Skip to content


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.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.data;
02
 
03
import java.io.Serializable;
04
 
05
public class SomeBean implements Serializable
06
{
07
  String _vorname;
08
 
09
  String _name;
10
 
11
  int _alter;
12
 
13
  public SomeBean(String vorname, String name, int alter)
14
  {
15
    _vorname=vorname;
16
    _name=name;
17
    _alter=alter;
18
  }
19
 
20
  public String getVorname()
21
  {
22
    return _vorname;
23
  }
24
 
25
  public String getName()
26
  {
27
    return _name;
28
  }
29
 
30
  public int getAlter()
31
  {
32
    return _alter;
33
  }
34
}
35

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

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.data;
02
 
03
import java.io.Serializable;
04
 
05
public class SomeBeanFilter implements Serializable
06
{
07
  String _name;
08
  String _vorname;
09
 
10
  public String getName()
11
  {
12
    return _name;
13
  }
14
 
15
  public void setName(String name)
16
  {
17
    _name = name;
18
  }
19
 
20
  public String getVorname()
21
  {
22
    return _vorname;
23
  }
24
 
25
  public void setVorname(String vorname)
26
  {
27
    _vorname = vorname;
28
  }
29
 
30
  public boolean match(SomeBean bean)
31
  {
32
    boolean ret=true;
33
 
34
    if (_name!=null)
35
    {
36
      if (!bean.getName().startsWith(_name)) ret=false;
37
    }
38
    if (_vorname!=null)
39
    {
40
      if (!bean.getVorname().startsWith(_vorname)) ret=false;
41
    }
42
 
43
    return ret;
44
  }
45
}
46

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

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.data;
02
 
03
import java.util.ArrayList;
04
import java.util.List;
05
 
06
public class SomeBeanGenerator
07
{
08
  public static List<SomeBean> getBeans(int size, SomeBeanFilter filter)
09
  {
10
    List<SomeBean> ret=new ArrayList<SomeBean>();
11
 
12
    for (int i=0;i<size;i++)
13
    {
14
      SomeBean bean = new SomeBean(getVorname(i),getName(i),getAlter(i));
15
      if (filter!=null)
16
      {
17
        if (filter.match(bean))
18
        {
19
          ret.add(bean);
20
        }
21
      }
22
      else ret.add(bean);
23
    }
24
 
25
    return ret;
26
  }
27
 
28
  private static int getAlter(int pos)
29
  {
30
    return (pos % 7) + (pos/9);
31
  }
32
 
33
  static String getVorname(int pos)
34
  {
35
    switch (pos % 5)
36
    {
37
      case 0: return "Klaus";
38
      case 1: return "Susi";
39
      case 2: return "Petra";
40
      case 3: return "Axel";
41
    }
42
    return "Bert";
43
  }
44
 
45
  static String getName(int pos)
46
  {
47
    switch (pos % 8)
48
    {
49
      case 0: return "Schmidt";
50
      case 1: return "Meier";
51
      case 2: return "Schulz";
52
      case 3: return "Schuster";
53
      case 4: return "Müller";
54
      case 5: return "Francis";
55
      case 6: return "Friedrich";
56
    }
57
    return "Sommerfeld";
58
  }
59
}
60

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.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
001
package de.wicketpraxis.web.blog.pages.questions.data;
002
 
003
import java.util.Collections;
004
import java.util.Comparator;
005
import java.util.Iterator;
006
import java.util.List;
007
 
008
import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortState;
009
import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider;
010
import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.IFilterStateLocator;
011
import org.apache.wicket.extensions.markup.html.repeater.util.SingleSortState;
012
import org.apache.wicket.model.IModel;
013
import org.apache.wicket.model.Model;
014
 
015
public class SomeBeanDataProvider implements ISortableDataProvider<SomeBean>, IFilterStateLocator
016
{
017
  ISortState _sortState=new SingleSortState();
018
  SomeBeanFilter _filter=new SomeBeanFilter();
019
 
020
  final static int LIST_SIZE=123;
021
  private List<SomeBean> _list;
022
 
023
  public Iterator<? extends SomeBean> iterator(int first, int count)
024
  {
025
    initList();
026
 
027
    List<SomeBean> ret=_list;
028
    if (ret.size()>(first+count))
029
    {
030
      ret = ret.subList(first, first+count);
031
    }
032
 
033
    return ret.iterator();
034
  }
035
 
036
  public IModel<SomeBean> model(SomeBean object)
037
  {
038
    return Model.of(object);
039
  }
040
 
041
  public int size()
042
  {
043
    initList();
044
 
045
    return _list.size();
046
  }
047
 
048
  public void detach()
049
  {
050
    _list=null;
051
  }
052
 
053
  public ISortState getSortState()
054
  {
055
    return _sortState;
056
  }
057
 
058
  public void setSortState(ISortState state)
059
  {
060
    _sortState=state;   
061
  }
062
 
063
  public Object getFilterState()
064
  {
065
    return _filter;
066
  }
067
 
068
  public void setFilterState(Object state)
069
  {
070
    _filter=(SomeBeanFilter) state;
071
  }
072
 
073
 
074
 
075
 
076
  private void initList()
077
  {
078
    if (_list==null)
079
    {
080
      final int nameSort;
081
      final int alterSort;
082
      if (_sortState!=null)
083
      {
084
        nameSort = _sortState.getPropertySortOrder("name");
085
        alterSort = _sortState.getPropertySortOrder("alter");
086
      }
087
      else
088
      {
089
        nameSort = ISortState.NONE;
090
        alterSort = ISortState.NONE;
091
      }
092
 
093
      _list = getSortedList(nameSort, alterSort,_filter);
094
    }
095
  }
096
 
097
 
098
  private List<SomeBean> getSortedList(final int nameSort, final int alterSort,SomeBeanFilter filter)
099
  {
100
    List<SomeBean> result = SomeBeanGenerator.getBeans(LIST_SIZE,filter);
101
 
102
    Collections.sort(result,new Comparator<SomeBean>()
103
    {
104
      public int compare(SomeBean o1, SomeBean o2)
105
      {
106
        int compName=o1.getName().compareTo(o2.getName());
107
        int compAlter=new Integer(o1.getAlter()).compareTo(o2.getAlter());
108
        switch (nameSort)
109
        {
110
          case ISortState.NONE:
111
            compName=0;
112
            break;
113
          case ISortState.DESCENDING:
114
            compName=-compName;
115
            break;
116
        }
117
        switch (alterSort)
118
        {
119
          case ISortState.NONE:
120
            compAlter=0;
121
            break;
122
          case ISortState.DESCENDING:
123
            compAlter=-compAlter;
124
            break;
125
        }
126
        if (compName!=0) return compName;
127
        return compAlter;
128
      }
129
    });
130
 
131
    return result;
132
  }
133
}
134

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.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.datatable;
02
 
03
import java.util.ArrayList;
04
import java.util.List;
05
 
06
import org.apache.wicket.extensions.markup.html.repeater.data.table.DefaultDataTable;
07
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
08
import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
09
import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterForm;
10
import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterToolbar;
11
import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.TextFilteredPropertyColumn;
12
import org.apache.wicket.markup.html.WebPage;
13
import org.apache.wicket.model.Model;
14
 
15
import de.wicketpraxis.web.blog.pages.questions.data.SomeBean;
16
import de.wicketpraxis.web.blog.pages.questions.data.SomeBeanDataProvider;
17
 
18
public class DataTablePage extends WebPage
19
{
20
  public DataTablePage()
21
  {
22
    List<IColumn<SomeBean>> columns=new ArrayList<IColumn<SomeBean>>();
23
    columns.add(new TextFilteredPropertyColumn<SomeBean,String>(Model.of("Vorname"),"vorname"));
24
    columns.add(new TextFilteredPropertyColumn<SomeBean,String>(Model.of("Name"),"name","name"));
25
    columns.add(new PropertyColumn<SomeBean>(Model.of("Alter"),"alter","alter"));
26
 
27
    SomeBeanDataProvider dataProvider=new SomeBeanDataProvider();
28
 
29
    FilterForm form=new FilterForm("form",dataProvider);
30
 
31
    DefaultDataTable<SomeBean> dataTable = new DefaultDataTable<SomeBean>("dataTable",columns,dataProvider,10);
32
    dataTable.addTopToolbar(new FilterToolbar(dataTable,form,dataProvider));
33
    form.add(dataTable);
34
 
35
    add(form);
36
  }
37
}
38

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 |  copy code |? 
01
<html>
02
  <head>
03
    <title>DataTable Page</title>
04
  </head>
05
  <body>
06
    <form wicket:id="form">
07
      <input type="hidden" wicket:id="focus-tracker"></input>
08
      <input type="hidden" wicket:id="focus-restore"></input>
09
      <table wicket:id="dataTable"></table>
10
    </form>
11
  </body>
12
</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.

 Java(TM) 2 Platform Standard Edition 5.0 |  copy code |? 
01
package de.wicketpraxis.web.blog.pages.questions.dataview;
02
 
03
import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByLink;
04
import org.apache.wicket.extensions.markup.html.repeater.data.table.NavigatorLabel;
05
import org.apache.wicket.markup.html.WebMarkupContainer;
06
import org.apache.wicket.markup.html.WebPage;
07
import org.apache.wicket.markup.html.basic.Label;
08
import org.apache.wicket.markup.html.form.Form;
09
import org.apache.wicket.markup.html.form.TextField;
10
import org.apache.wicket.markup.html.navigation.paging.PagingNavigator;
11
import org.apache.wicket.markup.repeater.Item;
12
import org.apache.wicket.markup.repeater.data.DataView;
13
import org.apache.wicket.model.CompoundPropertyModel;
14
 
15
import de.wicketpraxis.web.blog.pages.questions.data.SomeBean;
16
import de.wicketpraxis.web.blog.pages.questions.data.SomeBeanDataProvider;
17
import de.wicketpraxis.web.blog.pages.questions.data.SomeBeanFilter;
18
 
19
public class DataViewPage extends WebPage
20
{
21
  public DataViewPage()
22
  {
23
    SomeBeanDataProvider dataProvider=new SomeBeanDataProvider();
24
 
25
    Form<SomeBeanFilter> form = new Form<SomeBeanFilter>("form",new CompoundPropertyModel<SomeBeanFilter>(dataProvider.getFilterState()));
26
    form.add(new TextField<String>("name"));
27
    form.add(new TextField<String>("vorname"));
28
 
29
    form.add(new OrderByLink("sortName","name",dataProvider));
30
    form.add(new OrderByLink("sortAlter","alter",dataProvider));
31
 
32
    DataView<SomeBean> dataView = new DataView<SomeBean>("dataView",dataProvider)
33
    {
34
      @Override
35
      protected void populateItem(Item<SomeBean> item)
36
      {
37
        item.setDefaultModel(new CompoundPropertyModel<SomeBean>(item.getModel()));
38
        item.add(new Label("vorname"));
39
        item.add(new Label("name"));
40
        item.add(new Label("alter"));
41
      }
42
    };
43
    dataView.setItemsPerPage(10);
44
 
45
    form.add(new PagingNavigator("navigator",dataView));
46
    form.add(new NavigatorLabel("label",dataView));
47
 
48
    form.add(dataView);
49
 
50
    add(form);
51
  }
52
}
53

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 |  copy code |? 
01
<html>
02
  <head>
03
    <title>DataTable Page</title>
04
  </head>
05
  <body>
06
 
07
    <form wicket:id="form">
08
      <table>
09
        <thead>
10
          <tr>
11
            <td colspan="3">
12
              <span wicket:id="label"></span>
13
              <span wicket:id="navigator"></span>
14
            </td>
15
          </tr>
16
          <tr>
17
            <th>Vorname</th>
18
            <th><a wicket:id="sortName">Name</a></th>
19
            <th><a wicket:id="sortAlter">Alter</a></th>
20
          </tr>
21
          <tr>
22
            <th><input wicket:id="vorname"></th>
23
            <th><input wicket:id="name"></th>
24
            <th></th>
25
          </tr>
26
        </thead>
27
        <tbody>
28
          <tr wicket:id="dataView">
29
            <td><span wicket:id="vorname"></span></td>
30
            <td><span wicket:id="name"></span></td>
31
            <td><span wicket:id="alter"></span></td>
32
          </tr>
33
        </tbody>
34
      </table>
35
 
36
    </form>
37
  </body>
38
</html>

 PROPERTIES |  copy code |? 
1
PagingNavigator.last=Ende
2
PagingNavigator.first=Start
3
PagingNavigator.previous=Eins zurück
4
PagingNavigator.next=Eins weiter
5
PagingNavigation.page=Zur Seite ${page}
6
NavigatorLabel=Gezeigt wird ${from} bis ${to} von ${of}
7

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.

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

Andere Beiträge

Posted in Allgemein, Wicket.

Tagged with , , , , .


8 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Aniko says

    Hallo Michale,

    Danke für deinen Beitrag. Es war eine große Hilfe für mich mit Wicket die erste Schritt zu machen.
    Ich habe einige Fragen und Bemerkungen:

    (0, Sprichst du English? Es wäre einfacher für mich auf English zu kommunizieren…)

    1, Ich vorschlage den SomeBeanDataProvider.iterator() in die Folgende verwandeln:
    if (ret.size() > (first + count)) {
    ret = ret.subList(first, first + count);
    } else {
    ret = ret.subList(first, ret.size());
    }

    2, Brauchst du keine ‚submit button’ für Filterung (filtering)? Falls nicht, wie wird die Applikation über den Zustand (oder die Änderung) der Eingabefelder informiert.

    3, Soviel ich verstehe DataView ist mehr flexibler als DataTable. Ist es richtig? Kannst du mir Beispiel geben was kann ich tun mit DataView, das kann ich mit DataTable nicht erreichen?

    Danke für deine Hilfe.

    Aniko

    • michael says

      Hi Aniko,

      > (0, Sprichst du English? Es wäre einfacher für mich auf English zu kommunizieren…)

      yes.. hope good enough

      > 1, Ich vorschlage den SomeBeanDataProvider.iterator() in die Folgende verwandeln:
      > if (ret.size() > (first + count)) {
      > ret = ret.subList(first, first + count);
      > } else {
      > ret = ret.subList(first, ret.size());
      > }

      you are right:)

      > 2, Brauchst du keine ‚submit button’ für Filterung (filtering)?
      > Falls nicht, wie wird die Applikation über den Zustand (oder die Änderung) der Eingabefelder informiert.

      with Firefox this example will work, but InternetExplorer need a submit-Button.. so it works, if you write it into the markup..
      hey, another bug you have found.. :)

      > 3, Soviel ich verstehe DataView ist mehr flexibler als DataTable.
      > Ist es richtig? Kannst du mir Beispiel geben was kann ich tun mit DataView, das kann ich mit DataTable nicht erreichen?

      DataTable has its own markup, so it’s a little big harder to change the L&F .. so if you need more freedom, you can take a DataView and build it by your own..
      The point in my blogpost is: If you need some changes, that you can NOT do with DataTable, you can build it with DataView and have much more freedom in doing so.

      Michael:)

      • Aniko says

        Hi Michael,
        (Sorry about the typo in my last post.)

        Thanks for your answer.

        Another question:
        Do you know whether I can (and if yes, how) switch _off_ the pagination in case of DataTable? I mean I would like to see all item in the first (i.e. the one and only) page.
        Thanks in advance.

        Aniko

  2. michael says

    simply use a value big enough for rowsPerPage/itemsPerPage . ..
    change “dataView.setItemsPerPage(10);” to “dataView.setItemsPerPage(1000);”

    mm:)

  3. Aniko says

    Yes, of course, this can be a solution. I meant some ‘nice’ one where I do not need to consider the possible maximal amount of the records. If there are maximum…
    Anyway, thanks for answering.
    Aniko

    • michael says

      you can build your own AbstractDataGridView implementation to match your requirements..

      or…

      maybe ListView is the one you want…

  4. Mirko says

    Hallo Michael,

    auch ich habe Frage zu DataView: wie geht man am besten vor, wenn man z.B. nur bedingt eine Spalte rendern möchte?

    Beispiel: wicket:id=”vorname”

    - In Java: if (bedingung) item.add(new Label(“vorname”)); ???
    - In HTML: wie geht das?

    Danke für deine Hilfe.

    Mirko

    • michael says

      Soll die Spalte ausgeblendet werden, oder soll der Inhalt unsichtbar sein?



Some HTML is OK

or, reply to this post via trackback.