World Wide Wicket 2.0

DataTables – Filmauswahl strukturiert

Herr K. freut sich erneut über die schnellen Fortschritte und beginnt, die Filmauswahlseite zu implementieren. Hierfür erstellt er zunächst die neue Klasse SelectMoviePage, die sich wie alle anderen Seiten von AbstractVideoStorePage ableitet. Auf dieser Seite soll nun die Liste der aktuell verfügbaren Filme angezeigt werden. Da die Filme in einer Tabelle dargestellt werden sollen, entscheidet sich Herr K. für die Verwendung einer DataTable (Abb. 2).

Abb. 2: DataTable mit Sortierung im Header

Mit DataTables können Informationen tabellarisch dargestellt, gefiltert, sortiert und sogar paginiert werden. Um DataTables nutzen zu können, deklariert Herr K. zunächst die Abhängigkeit auf das Wicket-Extensions-Projekt [5] in seiner pom.xml:

org.apache.wicketwicket-extensions1.4.9

Das Wicket-Extensions-Projekt bietet viele nützliche Komponenten, u. a. auch die zuvor bereits erwähnte DataTable. Sie bezieht die anzuzeigenden Daten aus einem DataProvider. Dieser implementiert das Interface IDataProvider:

public interface IDataProvider extends IDetachable
{
Iterator extends T> iterator(int first, int count);
  int size();
IModel model(T object);
}

Die Methode iterator(int first, int count) liefert einen Iterator für die Elemente des DataProviders, die angezeigt werden sollen. Die Methode size()liefert die Anzahl aller über den DataProvider verfügbaren Objekte. Die Methode model(T object) ist eine Factory-Methode und erzeugt aus einem geladenen Domänenobjekt ein entsprechendes Modell.

Da Herr K. sehr viel Wert auf Benutzerfreundlichkeit legt, möchte er die Filmliste nach verschiedenen Kriterien sortierbar machen. Er implementiert hierfür zusätzlich das Interface ISortStateLocator in seinem DataProvider:

public interface ISortStateLocator extends IClusterable
{
ISortState getSortState();
void setSortState(ISortState state);
}

Über die Klasse SortState wird später von der DataTable festgelegt, nach welchem Attribut, und ob auf- oder absteigend sortiert wird. Herr K. weiß nun genug und erstellt die Klasse MovieDataProvider, die die Klasse SortableDataProvider erweitert. Diese implementiert die zuvor diskutierten Interfaces bereits (Listing 4).

Listing 4
public class MovieDataProvider extends SortableDataProvider{
private List movieList;

@SpringBean(name = "videoFacade")
private VideoStoreFacade facade;

public MovieDataProvider() {
InjectorHolder.getInjector().inject(this);
movieList = facade.getMovies();
setSort(new SortParam("title",true));
}

public Iterator iterator(int first, int count) {
Collections.sort(movieList, new PropertyAwareComparator(getSort()));
return movieList.iterator();
}
public int size() {
return movieList.size();
}

public IModel model(Movie movie) {
return Model.of(movie);
}
private class PropertyAwareComparator implements Comparator {
  private SortParam param;

public PropertyAwareComparator(SortParam param){
this.param = param;
}
public int compare(Movie o1, Movie o2) {
boolean ascending = param.isAscending();
String property = param.getProperty();
PropertyModel m1 = new PropertyModel(o1, property);
PropertyModel m2 = new PropertyModel(o2, property);

int result = m1.getObject().compareTo(m2.getObject());
if(!ascending)
result*=-1;
return result;
}}}

Betrachten wir die Klasse ein wenig genauer. Zunächst findet sich im Konstruktor folgende Zeile: InjectorHolder.getInjector().inject(this); .

Am Anfang des Artikels hatten wir zusammen mit Herrn K. die Anbindung an das Spring Framework diskutiert. Das Injizieren von Spring Beans (mit @SpringBean) funktioniert aber standardmäßig nur direkt in Wicket-Komponenten, da das Wicket-Framework nur bei deren Instantiierung über den bereits diskutierten Listener-Mechanismus benachrichtigt wird.

Sollen Spring Beans auch in anderen Klassen (wie dem hier verwendeten DataProvider) verwendet werden, so muss dies explizit angefordert werden. Das funktioniert in allen Klassen, insbesondere auch in Wicket-Modellen. Über die Anweisung setSort(new SortParam(„title“,true)); setzt Herr K. die initiale Sortierung auf das Attribut „title“ der Klasse Movie. In der Methode iterator()wird die aktuelle Filmliste anhand dem aus der DataTable gesetzten SortParam sortiert. Die Sortierung erfolgt über die Klasse PropertyAwareComparator. Der Comparator holt sich die entsprechenden Attribute über die Verwendung von den bereits im letzten Artikel angesprochenen PropertyModels.

Genauso interessant wie der DataProvider ist die Implementierung der DataTable in der Klasse SelectMoviePage. Zunächst erstellt sich Herr K. das entsprechende HTML-Template:


DataTables rendern ihr Markup selbst. Im Template muss nichts weiter als ein <table/>-Tag mit einer entsprechenden wicket:id deklariert werden. Im Java-Code erzeugt Herr K. eine Instanz einer DataTable und übergibt dieser den zuvor erzeugten MovieDataProvider:

MovieDataProvider movieDataProvider = new MovieDataProvider();
DataTable dataTable = new DataTable("movieTable",createMovieColumnList(),movieDataProvider,5);

Die DataTable erwartet im Konstruktor die obligatorische Komponenten-ID, eine Liste von IColumns, die die Spalten der Tabelle repräsentieren, einen DataProvider sowie die Anzahl der Elemente, die angezeigt werden sollen.

Die Liste von IColumns erzeugt Herr K. in der privaten Methode createMovieColumnList (Listing 5). Jede IColumn-Instanz rendert für jeden Datensatz ein ensprechendes <td/>-Element innerhalb des aktuellen <tr/>.

Listing 5
private IColumn[] createMovieColumnList() {
[...]
IColumn actorsColumn = new PropertyColumn(new Model("Schauspieler"),"actors"){
public void populateItem(Item> item,
String componentId, IModel rowModel) {
item.add(new ActorsFragment(componentId,SelectMoviePage.this, new Model((Serializable)rowModel.getObject().getActors())));
}
public String getCssClass() {
return "movieColumnMedium";
}};
  IColumn prizeColumn = new PropertyColumn(new Model("Preis / Tag"), "prizePerDay", "prizePerDay"){
public String getCssClass() {
return "movieColumnSmall";
}
};
IColumn submitColumn = new HeaderlessColumn() {
public void populateItem(Item> cellItem,
String componentId, IModel rowModel) {
cellItem.add(new LinkFragment(componentId,SelectMoviePage.this, rowModel));
}
[...]
}
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.