Nel precedente tutorial abbiamo visto come creare una tabella andando a lavorare sulle dataview. Abbiamo però detto che molti usano invece le
datatable che come vedremo permettono di abilitare la comoda utlity per ordinare le colonne.
Andiamo quindi a modificare il precedente tutorial per attuare la nostra nuova versione della stampa.
Dato che questo esempio è importante, riporterò nuovamente il contenuto delle principali classi.
Parte 1 - Modificare la WelcomePage.java
In realtà la pagina viene modificata solo leggermente. Occore modificare nel costruttore il richiamo ad un nuovo pannello (quello che conterrà la
SortableDataTable) ed ovviamente l'HTML che andrà ad accogliere il tutto.
public WelcomePage(){
add(new Label("content","Hello World"));
//add(new TablePanel("tablePanel"));
add(new SortableDataTablePanel("tablePanel"));
}
<body>
<div id="pageContainer">
<div wicket:id="content"></div>
</div>
<div wicket:id="tablePanel"></div>
</body>
Nel costruttore abbiamo aggiunto la add alla SortableDataTablePanel (che andremo a realizzare a breve) associandogli un ID che è proprio quello che vediamo nell'HTML sotto nel Div. La realizzazione della tabella vera e propria è ovviamente demandata al pannello.
Parte 2 - SortableDataTablePanel.java
public class SortableDataTablePanel extends Panel{
private static final long serialVersionUID = 1L;
public SortableDataTablePanel (String id){
super(id);
buildMySortableTable();
}
public void buildMySortableTable(){
List<IColumn<ContactExample>> columns = new ArrayList<IColumn<ContactExample>>();
columns.add(new PropertyColumn<ContactExample>(Model.of("contactName"),"name","contactName"));
columns.add(new PropertyColumn<ContactExample>(Model.of("contactSurname"),"surname", "contactSurname"));
columns.add(new PropertyColumn<ContactExample>(Model.of("contactEmail"),"email","contactEmail"));
DefaultDataTable<ContactExample> table = new DefaultDataTable<ContactExample>("datatable", columns, new SomeDataProvider(), 10);
add(table);
}
}
Il pannello della table è ovviamente tutta concentrata sulla stampa della table che viene fatta nel metodo
buildMySortableTable su cui spendiamo due parole.
Il codice può sembrare complesso ma in realtà la prima parte è banale.
La prima istruzione serve a creare un insieme di COLONNE. In Java possiamo creare oggetti concatenando tipologie di oggetti. Nel nostro caso sto creando un
List di oggetti (mappati poi sul tipo ArrayList) di tipo
IColumn (che è un oggetto Wicket) che conterrà oggetti di tipo
ContactExample. La variabile si chiamerà
columns.
Nelle tre righe sotto sto quindi aggiungendo colonne alla lista. La lista si aspetta un oggetto compatibile con
IColumn che in questo caso è
PropertyColumn. IColumn è una interfaccia di Wicket mentre PropertyColumn è una delle classsi Wicket che la implementa. Ce ne sono altre e magari in un prossimo esempio vediamo che crearne una nostra.
La
PropertyColumn è molto importate e va analizzata per bene. Esistono essenzialmente due tipi di costruttori validi ed essenzialmente dicono che roba ci sarà dentro la colonna, la proprietà dell'oggetto dentro che andrà stampata e se si vuole abilitare o meno l'ordinamento della colonna.
Nel nostro caso abbiamo tutti e tre i parametri. Come vediamo abbiamo istanziato l'oggetto PropertyColumn specificando l'oggetto al suo interno ovvero ContactExample.
Il primo parametro indica il tipo del dato che andrà messo dentro. Richiede un oggetto di tipo Model quindi useremo model.of per fargli convertire la stringa in un modello.
Il tutto poteva anche essere scritto con:
new Model<String>("contactSurname")
ma
model.of è più veloce.
Il secondo parametro normalmente sarebbe la proprietà dell'oggetto da mappare nella colonna ma se la vogliamo ordinabile, allora mettiamo una sigla con la quale potrò triggerare l'ordinamento. La stringa può essere qualsiasi, anche "cippalippa". L'importante è poi ricordarsi quando occorrerà dire al sistema cosa fare a riguardo. Nel nostro caso abbiamo messo un più consono "name".
Il terzo parametro, che nel caso di colonne non ordinabili sarebbe il secondo, indica quale proprietà dell'oggetto dentro la colonna dobbiamo usare.
E' importante notare che questo NON è legato all'ordinamento. Non stiamo infatti dicendo che se premo
cippalippa si ordina per la proprietà
contactName. Questa associazione viene fatta da un'altra parte.
L'ultima istruzione importante è la creazione dell'oggetto DefaultDataTable che come costruttore si aspetta:
- La label di wicket dentro cui stampare la tabella;
- La lista di colonne secondo la tipologia sopra esposta;
- I dati da metterci (e qui li vediamo tra poco);
- Il numero di righe per "pagina" della tabella;
Parte 3 - Il DataProvider
Andiamo ora a capire come gestiamo i dati da buttarci dentro. A differenza del precedente tutorial, in questo caso separiamo l'oggetto che fornisce i dati in una classe a parte perché il nucleo di tutta la stampa e l'ordinamento si gioca qua.
package it.trewps.heavyexamples.web;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
public class SomeDataProvider extends SortableDataProvider<ContactExample>{
private static final long serialVersionUID = 1L;
ArrayList<ContactExample> myContactList = new ArrayList<ContactExample>();
ContactExample myContact = null;
public SomeDataProvider(){
setSort("name",true);
this.someDataRetriever();
}
@Override
public IModel<ContactExample> model(ContactExample object) {
return Model.of(object);
}
@Override
public Iterator<? extends ContactExample> iterator(int first, int count) {
List<ContactExample> data = new ArrayList<ContactExample>(myContactList);
Collections.sort(data, new Comparator<ContactExample>() {
public int compare(ContactExample o1, ContactExample o2) {
int sortDirection = getSort().isAscending() ? 1 : -1;
String sortProbe = getSort().getProperty();
if ("name".equals(sortProbe)){
return sortDirection * (o1.getContactName().compareTo(o2.getContactName()));
}
if ("surname".equals(sortProbe)){
return sortDirection * (o1.getContactSurname().compareTo(o2.getContactSurname()));
}
return sortDirection * (o1.getContactEmail().compareTo(o2.getContactEmail()));
}
});
return data.subList(first,Math.min(first + count, data.size())).iterator();
}
@Override
public int size() {
return myContactList.size();
}
public ArrayList<ContactExample> someDataRetriever(){
String name = "Mario ";
String surname = "Rossi ";
String email = "mario_[[token]]@alice.it";
for (int i = 1; i < 50; i++) {
this.myContact = new ContactExample(name+Integer.toString(i),
surname+Integer.toString((i*2)),
email.replace("[[token]]",Integer.toString(i))
);
this.myContactList.add(this.myContact);
}
return myContactList;
}
}
Per prima cosa occorre estendere la
SortableDataProvider di Wicket e se abbiamo una buona IDE questo ci obbligherà ad implementare 3 metodi che nel codice sopra sono preceduti da
@Override.
L'unico metodo veramente nostro è quel someDataRetriever preso dal vecchio tutorial dove ci inventiamo dei dati fittizzi. E' ovvio che qua dentro probabilmente avremmo avuto chiamate al DB.
Il metodo più semplice da implementare è quel
size() che serve alla logica per fare la paginazione. Deve infatti sapere quanti elementi stiamo gestendo in totale. Non a caso ritorna semplicemente la linghezza della lista di oggetti
ContactExample che abbiamo riempito.
L'altro metodo da implementare è model ma questo è abbastanza gratuito perché serve semplicemente a dire alla logica applicativa il modello di oggetto da restituire. Usando il già visto model.of risolviamo il problema. Il metodo tra l'altro è customizzato ad HOC per gestire un
ContactExample. Questo ovviamente ci porta a ricordare che un DataProvider è strettamente legato all'oggetto che andrà a maneggiare.
Andiamo quindi a vedere l'ultimo e potente metodo che riportiamo qua sotto:
@Override
public Iterator<? extends ContactExample> iterator(int first, int count) {
List<ContactExample> data = new ArrayList<ContactExample>(myContactList);
Collections.sort(data, new Comparator<ContactExample>() {
public int compare(ContactExample o1, ContactExample o2) {
int sortDirection = getSort().isAscending() ? 1 : -1;
String sortProbe = getSort().getProperty();
if ("name".equals(sortProbe)){
return sortDirection * (o1.getContactName().compareTo(o2.getContactName()));
}
if ("surname".equals(sortProbe)){
return sortDirection * (o1.getContactSurname().compareTo(o2.getContactSurname()));
}
return sortDirection * (o1.getContactEmail().compareTo(o2.getContactEmail()));
}
});
return data.subList(first,Math.min(first + count, data.size())).iterator();
}
Anche questo metodo va necessiariamente implementato. Per chi non lo sapesse l'
Iterator è un oggetto Java fondamentale per poter scorrere qualche struttura dati che presuppone un elenco di cose. Dato che stiamo parlando di
DataProviding e che quindi è presumibile che ritorneremo dei dati, occorre dire al sistema come questi dati devono essere gestiti. Per usare un gergo più comune qua dentro stiamo definendo come funziona il cursore che maneggerà i dati della lista.
La prima istruzione crea una lista chiamata
data sulla base della lista
myContactList creata nell'altro metodo.
Chiamiamo poi il metodo sort dell'oggetto
Collections per abilitare l'ordinamento. La Collections per chi non lo sapesse, è il tipo primitivo di oggetto Java per gestire un insieme di qualcosa. Gli oggetti esistenti in Java per gestire insiemi di daty (liste, array, map ecc...) sono figlie della
Collections.
Il metodo sort prende ovviamente la lista di robe da ordinare ed un comparatore ovvero un oggetto che gli dice come deve comparare il contenuto per fare l'ordinamento.
E' ovvio che se ho liste di interi o di tipi semplici, la sort potrà essere lanciata nuda e cruda perché i tipi base di java hanno già implementata la comparator.
Ma qui abbiamo a che fare con oggetti che Java non sa come manipolare. L'oggetto
ContactExample può essere fatto in qualsiasi modo quindi saremo noi a specificare come dovrà essere gestito l'ordinamento in base a determinati parametri in ingresso che guardacaso sono quelli specificati nella
PropertyColumn (il famoso "cippalippa").
Il comparator indica come devono essere gestiti i dati per dire al sistema
chi è più grande di chi. Il comparator deve avere un metodo chiamato banalmente compare che deve ritornare un intero avendo in ingresso due elementi della tipologia che stiamo usando (in questo caso
ContactExample). La compare deve tornare:
0 : se sono uguali;
-1 : se oggetto 1 > oggetto 2;
1 : se oggetto 1 < oggetto 2;
Nel nostro caso l'intero da ritornare è però condizionato dal tipo di ordinamento che vogliamo fare. Noi abbiamo tre possibili ordinamenti e per questo motivo ho due IF per capire se hanno cliccato sulla colonna Name, Surname o Email.
Questo parametro è dato dalla
String sortProbe = getSort().getProperty();
Il metodo getSort() è gratuito grazie al fatto che stiamo estendnedo la SortableDataProvider e contiene varie informazioni sull'ordinamento in corso. Noi infatti ne abbiamo usate due:
getProperty(): Indica su quale parametro stiamo ordinando (il famoso "cippalippa");
isAscending(): Indica chiaramente se è ASC o DESC;
In base a questi due parametri, setto un intero e poi ritorno il valore corrispondente in base al richiamo di un banale
compareTo tra le due proprietà su cui devo fare il confronto.
A questo punto abbiamo finito. Il sistema a questo punto ha tutto quello che gli serve per stampare la tabella.