mercoledì 31 agosto 2011

Java - Il mio comparatore personale

Nel tutorial precedente abbiamo ordinato una lista di oggetti di tipo ContactExample implementando un compratore al volo direttamente nel codice e specificando quindi il metodo compare che manipolasse le proprietà del nostro oggetto.

L'uso di classi private interne ad un'altra classe non è inusuale. Per quanto possa sembrare che vada contro la logica stessa della programmazione ad oggetti, in questi casi la cosa ha senso perché il provider dei dati così come il comparatore, era strettamente legato all'oggetto di manipolazione ovvero ContactExample.
E' infatti inutile creare una classe una classe esterna di comparazione legata ad un oggetto se questo verrà usato solo nella Iterator di chi manipola l'oggetto stesso.

In pratica è come creare una classe per gestire il cambio shimano su una bici che però può essere solo il modello X25 della Bianchi. Tanto vale creare una classe privata dentro la classe X25 perché la logica di riutilizzo dell'oggetto Java si perde con la restrizione dell'oggetto stesso.

E' però interessante vedere come avremmo dovuto fare nel caso masochistico in cui volessimo esternare l'oggetto comparatore.

Ecco la classe risultante:


package it.trewps.heavyexamples.web;

import java.util.Comparator;

public class ContactExampleComparator implements Comparator<ContactExample> {
    
    Boolean ascendingOrder = true;
    String    probe           = "name";
    
    public ContactExampleComparator(Boolean ascending, String probe){
        this.ascendingOrder = ascending;
        this.probe            = probe;
    }
    
    @Override
    public int compare(ContactExample o1, ContactExample o2) {
       int sortDirection = (this.ascendingOrder) ? 1 : -1;
       if ("name".equals(this.probe)){
           return sortDirection * (o1.getContactName().compareTo(o2.getContactName())); 
       }
       if ("surname".equals(this.probe)){
           return sortDirection * (o1.getContactSurname().compareTo(o2.getContactSurname())); 
       }
       return sortDirection  * (o1.getContactEmail().compareTo(o2.getContactEmail()));
    }

}

La prima cosa che salta all'occhio è il fatto che al costruttore occorre passare due parametri che prima erano gratuiti. Implementando infatti la classe dentro l'altro, questa aveva lo scope di tutte le variabili tra cui il metodo getSort().
Qui invece devo passargliele dall'esterno perché questa classe tecnicamente è stand-alone. I due parametri indicano quindi l'etichetta su cui sto ordinando (il famoso "cippalippa") ed il tipo di ordinamento (ascendente o discendente).

Il metodo compare è invece identico al precedente con ovviamente solo il cambio di nome variabili.

Lo stretto legame tra questo comparatore e l'oggetto è anche esplicitato nel prototipo che come vediamo implementa Comparator ma legato al tipo ContactExample.

Wicket: Dati in Tabella ordinabili

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.


martedì 30 agosto 2011

Wicket: Dati in Tabella e Panel

Rieccomi al volo per spiegare velocissimamente la storia dei Panel.

Nel tutorial precedente per spiegare le Table ho creato una intera pagina con dentro la tabella dovendo poi ricorrere al barbatrucco dell'estensione della WelcomePage ed alla adozione del tag child dell'HTML per far capire a Wicket che dentro ci sarebbe stato del codice Wicket creato da qualcuno che mi avrebbe esteso.

La panel è simile come concetto a livello di codice ma è gestita in maniera diversa a livello logico.

Convertire il codice precedente è estremamente banale. Create una classe TablePanel.java copiandoci dentro tutto il contenuto della TablePage.java cambiando solo il nome della classe, il fatto che estende Panel invece che Page ed infine cambiate il costruttore come segue:

public TablePanel (String id){
    super(id);
    buildPage();
}

A differenza delle Page, i Panel hanno bisogno di un ID di tipo stringa che sarà quello associato al tag wicket:id presente nel codice della pagina di chi lo chiamerà. NB. La chiamata al costruttore del padre è obbligatoria.
A differenza infatti della Page di prima, in questo caso esplicito chiaramente dove andrà messo il panel.

<body>
    <div id="pageContainer">
        <div wicket:id="content"></div>
    </div>
    <div wicket:id="tablePanel"></div>
</body>

Il codice della WelcomePage.html cambierà in questo modo. Aggiungo un tag wicket:id chiamato tablePanel nella quale metterò il pannello.

Il codice html del pannello sarà identico a quello della vecchia pagina con la differenza che il tag iniziale invece che wicket:extend sarà wicket:panel.

<wicket:panel>

<table cellspacing="0" class="dataview">
    <tbody>
       <tr wicket:id="trContacts">
         <td><span wicket:id="tdName"></span></td>
         <td><span wicket:id="tdSurname"></span></td>
       </tr>
    </tbody>
</table>

<div wicket:id="navigator"></div>

</wicket:panel>

Infine la chiamata nella WelcomePage.java che adesso verrà nuovamente chiamata per davvero nella classe Application.

package it.trewps.heavyexamples.web;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;

public class WelcomePage extends WebPage{
    
    public WelcomePage(){
        add(new Label("content","Hello World"));
        add(new TablePanel("tablePanel"));
    }
    
}

Come vedete chiamo nuovamente la add passandogli la new del pannello e la stringa dell'ID.

Il risultato a video è esattamente lo stesso della precedente soluzione con il vantaggio però della riusabilità.

Dove infatti pensare che nella vostra applicazione questo Pannello potrebbe contenere dati stand-alone legati magari ad un DB e che debbano essere visualizzate all'occorrenza nelle pagine che vogliamo. Con questo sistema abbiamo creato di fatto un "componente wicket".

Immaginate per esempio un sistema di login o un carrello di un e-commerce e capirete cosa si intende per estendibilità e usabilità degli oggetti.

Ovviamente nessuno vi vieta di insere più di una volta il pannello nella stessa pagina a patto ovviamente che lo chiamate con ID differenti.

Poniamo che abbiate un panel che visualizza un banner caricato tramite un parametro, potete avere una colonna di banner aggiungendo Panel istanziati con un parametro diverso ogni volta che discrimina quale banner visualizzare.

Wicket: Dati in Tabella e Pagine che si Estendono

Come secondo esempio andiamo a vedere qualcosa di più spinto.

Non importa cosa faccia la vostra applicazione perché prima o poi dovrete mostrare qualche dato in una tabella. Che siano dati presi da un DB, da filesystem, inseriti in una FORM, alla fine sempre una tabella dovrete mostrare.

Wicket permette di stampare dati in una moltitudine di maniere. Qui elenco le classi del framework che potreste usare:
Ognuna di esse permette di gestire varie tipologie di dati e di manipolazione delle stesse. Molti usano la semplice DataTable che risulta forse la più intuitiva.

Parte 1 - La cosa della riusabilità

Alla fine del post su Hello World vi ho detto che una delle caratteristiche belle di Wicket è il riutilizzo del codice. L'esempio che segue non è esattamente adiacente a questa filosofia per i motivi che vi andrò a spiegare però è bene che ci spenda due parole sopra a riguardo.

In Wicket le componenti possono vivere da sole e richiamate all'occorrenza. In parte è normale se pensate che parliamo di un linguaggio ad oggetti. In qualsiasi linguaggio ad oggetti, se create una classe bicicletta stand alone, questa potrà essere usata ed istanziata ovunque da chi la vuole usare.

In Wicket la cosa funzione ad un livello ancora superiore. Come vedremo in un prossimo esempio quando parleremo di PANEL, un oggetto può vivere da solo (usando altre classi ovviamente e perché no, altri PANEL) ed essere chiamata dove serve.

Per esempio un PANEL che gestisce il classico TOTEM LOGIN può essere costruito a parte gestendo internamente l'interrogazione a database per poi essere usato sia da varie pagine della nostra applicazione che da altre applicazioni in altri progetti.

Nell'esempio che segue andremo a creare una tabella dinamica con dentro dei dati fittizi. La faremo creare come codice di una vera pagina e non di un pannello. Questo ovviamente "congela" la tabella all'interno della pagina.
A differenza di PANEL che possono essere sparse ovunque (anche la stessa più volte dentro la stessa pagina), una PAGE può solo sottostare ad una gerarchia di pagina. Quindi la nostra pagina table (che chiameremo tablePage) dovrà necessariamente estendere la WelcomePage di prima se vogliamo che anche il codice di quest'ultima venga stampanto.
NON possiamo infatti chiedere alla WelcomePage di stampare dentro di sé il contenuto di un'altra pagina perché la gerarchia funziona all'inverso.

Quello che avremmo potuto fare è invece riempire la WelcomePage con il codiche restituito da vari Panel chiamati all'occorrenza.

Parte 2 - Piccoli Cambiamenti all'esempio Hello World.

Andiamo a vedere velocemente i piccoli cambiamenti che accadranno per far funzionare il tutto.

Abbiamo detto sopra che la pagina TablePage dovrà estendere la WelcomePage. Questo significa che l'applicazione non istanzierà più la WelcomePage ma direttamente la TablePage che nel suo costruttore chiamerà ovviamente quello della sua superclasse ovvero WelcomePage.

public Class<? extends Page> getHomePage() {
        return TablePage.class;
    }

Ecco come cambia il metodo getHomePage. TablePage estenderà la WelcomePage e ne chiamerà il costruttore (come vedremo dopo).

Il prossimo cambiamento è più interessante. Noi stiamo estendo la WelcomePage ma se non specifichiamo qualcosa, come fa il Wicket a sapere dove andrà messo il codice HTML della TablePage?

Per fare questo inseriamo nel codice HTML WelcomePage.html il seguente tag:

<wicket:child></wicket:child>

Questo tag dice a Wicket che la dentro ci andrà qualcosa messo da qualcuno che mi estenderà. Nel nostro caso TablePage. Questo tag potete metterlo in un punto qualsiasi, legale ovviamente secondo lo standard W3C, del vostro HTML.

Parte 3 - Dati Fittizi Cercasi

Ora decidiamo cosa stampare esattamente in questa tabella che ancora non sappiamo com'è fatta. Per iniziare direi di fare una semplice tabella con 2 colonne con Nome e Cognome.

Per fare le cose per bene vogliamo mappare queste cose in una classe apposita. Potremmo ovviamente stampare direttamente le stringe nel punto in cui creiamo la tabella ma noi vogliamo mostrare una manipolazione di dati effettiva che potreste dover usare.

Poniamo che un tizio compili una form e voi dobbiate fare una query al DB restituendo i contatti di una agenda. Ogni contatto è plausibile che sia mappato su un oggetto che ne condivide i campi.

In questo caso quindi creeremo una semplice classe ContactExample con dentro due variabili di tipo stringa Name e Surname.

package it.trewps.heavyexamples.web;

import java.io.Serializable;

public class ContactExample implements Serializable{

    private static final long serialVersionUID = 1L;
    
    private String contactName         = "";
    private String contactSurname     = "";
    
    public ContactExample(String name, String surname){
        setContactName (name);
        setContactSurname (surname);
    }

    public void setContactName(String contactName) {
        this.contactName = contactName;
    }

    public String getContactName() {
        return contactName;
    }

    public void setContactSurname(String contactSurname) {
        this.contactSurname = contactSurname;
    }

    public String getContactSurname() {
        return contactSurname;
    }
    
}

Il codice è molto semplice. Nel costruttore passo Nome e Cognome e inizializzo le mie due variabili interne che possiedono ognuna i classici metodi GET e SET.
Mi soffermo solo su quel implements Serializable. Dato che parliamo di web application, occorre dire a Java e Wicket che questi dati sono serializzabili.

Parte 4 - TablePage.html

Prima di vedere il codice java della classe, più complesso, vediamo come si comporta l'HTML della nostra TablePage.

Ovviamente non ci aspettiamo una pagina web completa con HTML, HEAD e tutto il resto dato che la stiamo ereditando dalla WelcomePage. Noi dobbiamo in qualche modo attaccarci a quel tag child visto sopra.

<wicket:extend>

<table cellspacing="0" class="dataview">
    <tbody>
       <tr wicket:id="trContacts">
         <td><span wicket:id="tdName"></span></td>
         <td><span wicket:id="tdSurname"></span></td>
       </tr>
    </tbody>
</table>

<div wicket:id="navigator"></div>

</wicket:extend>

Il codice include solo la parte strettamente necessaria a stampare la tabella. Ripeto. Un codice di questo tipo ha poco senso perché una cosa del genere avrebbe avuto più senso in un Panel.
Ad ogni modo la cosa importante è il tag wicket:extend che serve a Wicket per dire che quel pezzo di codice sarà appeso a qualcosa che avrà un tag child.

La seconda cosa importante sono i tre ID di wicket nella quale appenderemo i dati derivati dal nostro Data Provider ovvero il nome e cognome. E' importante notare che abbiamo messo una sola riga con due colonne ma in realtà la tabella sarà popolata di più righe pari al numero totale di elementi nella lista restituitami dal data provider.

Parte 5 - TablePage.java

Andiamo ora ad analizzare il codice più importante e quello che farà tutto. L'ho pesantemente commentato così da scrivere meno roba qui e lasciare che ogni istruzione abbia la sua spiegazione.

package it.trewps.heavyexamples.web;

import java.util.ArrayList;
import org.apache.wicket.Page;
import org.apache.wicket.markup.html.basic.Label;
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.markup.repeater.data.ListDataProvider;

public class TablePage extends WelcomePage {

    //creo una variabile myContact di tipo ContactExample
    //Non serve specificare se privata o pubblica perché è a uso strettamente interno
    //Se mettessi privati la IDE mi romperebbe le balle per crearci i metodi GET e SET
    //In realtà usandola solo nel metodo someDataRetriever, potrei anche dichiarla direttamente
    //dentro al metodo
    ContactExample myContact = null; 
    //creo una variabile di tipo ArrayList che conterrà ContactExample
    //In Java è buona norma specificare anche il tipo di dato che l'array dovrà contenere
    ArrayList<ContactExample> myContactList          = new ArrayList<ContactExample>();
    
    public TablePage(){
        super();
        buildPage();
    }
    
    public void buildPage(){
        //Chiamo il metodo che riempie i dati
        //In realtà esso agisce già su myContactList essendo globale alla classe
        //ma avendo specifica che deve ritornarla, la rimetto nuovamente nella
        //variabile globale della classe. Avrei potuto mettere void come tipo di ritorno
        //alla classe e lanciare il metodo senza settarlo a niente (es. someDataRetriever();)
        myContactList = someDataRetriever();
        
        //Creo un Data view che deve avere come primo parametro l'ID wicket su cui mappare (nel
        //nostro caso un TR dato che avremo più righe. Poi un ListDataPrivider ovvero qualcosa che
        //ritorna in dati veri e propri. 
        //La IDE vi chiederà di estendere il metodo populateItem che serve a dire alla classe come
        //e dove deve prendere i dati da mettere dentro. Nel nostro caso prendiamo oggetti di tipo
        //ContactExample a sua volta presi da myContactList che è passata come vedete come variabile
        //al costruttore. A questo punto il sistema, differentemente a come potete pensare, non crea
        //dei TD riempiendoli poi con nome e cognome ma fa il contrario. Associat alle due variabile 
        //una LABEL ovvero un'area sensibile sulla pagina legata a Nome e Cognome. Sarà poi Wicket
        //che prendendo dataView, si occuperà di stampare il tutto
        final DataView dataView = new DataView("trContacts", new ListDataProvider(myContactList)) {
                private static final long serialVersionUID = 1L;
                public void populateItem(final Item contactItem) {
                    final ContactExample singleContact = (ContactExample) contactItem.getModelObject();
                    contactItem.add(new Label("tdName"        , singleContact.getContactName()));
                    contactItem.add(new Label("tdSurname"    , singleContact.getContactSurname()));
                }
         };
         
         //Qui imposto il numero di elementi per pagina
         dataView.setItemsPerPage(10);
         //Qui aggiungo la variabile al flusso Wicket della pagina per riempire
         //i tag wicket con ID = qualcosa (nel nostro caso trContract e le colonne con tdName e tdSurname)
         add(dataView);
         //Qui aggiungo un navigator ovvero il classico page scroller per fare avanti e indietro
         add(new PagingNavigator("navigator", dataView));
    }
    
    
    //Questo sarebbe un fittizzio metodo che prende dati da qualche parte
    public ArrayList<ContactExample> someDataRetriever(){
        //Dato che non ho un DB o qualcosa da cui prendere i dati
        //Mi creo un nome base Mario Rossi a cui attacco un numero progressivo
        //creato dentro un FOR
        String name     = "Mario ";
        String surname    = "Rossi ";
        
        //Alla fine di questo ciclo avrò un array di oggetti di tipo ContactExample
        //le cue due variabili interne saranno inizializzate a roba tipo:
        // Mario 1 Rossi 2, Mario 2 Rossi 4 ecc...
        for (int i = 1; i < 50; i++) {            
            //Contateno le due stringe base con la conversione da char a String di dynamicChar
            //che era a sua volta un cast da intero a char (riga sopra)
            this.myContact = new ContactExample(name+Integer.toString(i),
                                                surname+Integer.toString((i*2)));
            this.myContactList.add(this.myContact);
        }
        return myContactList;
    }
    
}

Le cose interessanti di questa classe sono molteplici. Vediamo le più importanti.

Il costruttore ovviamente chiama il costruttore del padre ovvero WelcomePage e poi la sua funzione che popola il codice del proprio HTML andando ovviamente a prendere i dati dal data provider che qui è simulato da un metodo chiamato someDataRetriever.

Abbiamo creato due variabili globali alla classe per gestire un oggetto ContactExample ed un array di oggetti dello stesso tipo.

Questi viene popolato dentro al for che riempie l'array di oggetti di tipo ContactExample semplicemente impostare nome e cognome Mario Rossi con dei numeri attaccati per far capire che sono diversi.

La parte fondamentale è però il codice DataView di cui riprendiamo un estratto:

myContactList = someDataRetriever();
        
        final DataView dataView = new DataView("trContacts", new ListDataProvider(myContactList)) {
                private static final long serialVersionUID = 1L;
                public void populateItem(final Item contactItem) {
                    final ContactExample singleContact = (ContactExample) contactItem.getModelObject();
                    contactItem.add(new Label("tdName"        , singleContact.getContactName()));
                    contactItem.add(new Label("tdSurname"    , singleContact.getContactSurname()));
                }
         };

Per prima cosa otteniamo il nostro elenco di contatti chiamando someDataRetriever().
Creiamo quindi una variabile di tipo DataView chiamata banalmente dataView. Gli passiamo come primo parametro il famoso label della TR che lui dovrà iterare e come secondo parametro una istanza diretta di ListDataProvider.

Tutto il gioco è infatti gestito da quest'ultima classe che in ingresso prende ovviamente l'ArrayList con i contatti. Ovviamente questa è una classe generica e non sa che tipo di oggetti saranno eventualmente contenuti nella lista (nel costro caso un ExampleContact). Per questo motivo viene richiamato il metodo getModelObject che è come dire: "Non so bene cosa avrò ma lo mappo comunque nudo e crudo sull'item che sto iterando". Se guardate infatti dopo posso chiamare i due metodi interni all'oggetto ContactExample per ottenere nome e cognome. In pratica il sistema prende una lista di oggetti, li mappa su un ITEM nudo e crudo tramite la getModelObject e poi usa l'item come se stesse usando l'oggetto originale anche se questo è automaticamente mappato sugli oggetti della lista. In questo modo può settare l'iterator con i Label dei TD al cui interno va a mettere i dati dell'oggetto ContactExample.

Infine nel codice aggiungiamo anche un Navigator ovvero la classica barra per scorrere le pagine dato che gli abbiamo detto che ne vogliamo 10 per pagina.

A differenza di altri linguaggi, non ci interesseremo di parametri passati via get, di fare paginazioni a mano ecc...

Il bello di Wicket è che a noi tocca solo pensare all'HTML e alla logica dei dati gestiti in Java. Al resto ci pensa lui.

Qua sotto un esempio del risultato.
Abbiamo nuovamente il nostro Hello World grazie alla chiamata al costruttore della WelcomePage.
Al posto del tag child abbiamo la nostra bella tabella (con un css per rendere gialle le celle) ed in basso la nostra navigation bar. A questo punto possiamo abbellire tramite CSS.

Come detto sopra tra l'altro, la navigation bar funziona automaticamente. I link vengono creati da wicket e sarà il server e wicket a gestirli.