Freemarker Dispense per il corso di Ingegneria del Web Revisione 05/11 Giuseppe Della Penna ([email protected]) Dipartimento di Informatica Università degli studi dell'Aquila Corso di Ingegneria del Web I Template e Freemarker: Introduzione ai Template I Template e Freemarker Introduzione ai Template I template sono un concetto molto utile per la programmazione delle applicazioni web, in quanto permettono di separare nettamente d in maniera molto efficace la logica (il codice, Java nel nostro caso) dagli aspetti di presentazione (HTML, CSS, ecc). Molti avranno già sentito parlare dei template in associazione con PHP, tuttavia anche per le applicazioni web scritte in Java esistono ottime librerie di supporto alla definizione di templates. Ma cos'è, in effetti, un template? In estrema sintesi, un template è una documento in cui sono presenti dei placeholder (segnaposto) al posto dei dati veri e propri. Il template, cioè, contiene solo la parte statica (sempre uguale) del documento. Un template HTML è un template per una pagina web, o parte di essa. Ad esempio, invece di scrivere la seguente pagina <html> <head> <title>Homepage di Paperino</title> </head> <body> <p>Benvenuto Paolino Paperino!</p> </body> </html> potremmo scriverne un template più generico nella forma: <html> <head> <title>Homepage di {COGNOME}</title> </head> <body> <p>Benvenuto {NOME} {COGNOME}!</p> </body> </html> e usarlo come homepage per ciascun utente, semplicemente sostituendo (via codice) i placeholder tra parentesi graffe con i relativi dati. Questo, in effetti, si potrebbe ottenere anche generando dinamicamente tutta la pagina qui sopra tramite il codice di una servlet: String nome="...", cognome="..."; out.println("<html><head>"); out.println("<title>Homepage di "+cognome+"</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>Benvenuto "+nome+" "+cognome+"!</p>"); out.println("</body>"); ma in questo modo il codice HTML sarebbe mescolato con quello Java, e modificare l'aspetto visuale dell'applicazione diventerebbe difficilissimo! Con i template, invece, possiamo scrivere le pagine HTML del sito con il nostro editor preferito, inserendo gli opportuni placeholder, salvarle all'interno della nostra web application e poi istruire il codice Java a sostituire i placeholder con determinati valori e inviare al browser il documento risultante: Revisione 05/11 Pagina 2/14 Corso di Ingegneria del Web I Template e Freemarker: Freemarker template + dati = pagina HTML completa Ma i template, come vedremo, permettono di fare anche di più. Freemarker La libreria per template che useremo nel nostro corso è Freemarker (http://freemarker.sourceforge.net/). Dopo averla scaricata, è sufficiente includere la libreria freemarker.jar nella nostra applicazione web. Vedremo ora i principali componenti di un template scritto con Freemarker, per poi passare alla programmazione Java con la libreria. Un template HTML gestito con Freemarker è costituito da un semplice file. All'interno del file, potremo inserire tutto il codice HTML statico, esattamente come faremmo se si trattasse di una pagina web "normale". Per convenzione, comunque, useremo l'estensione .ftl.html per questo tipo di files, in modo da distinguerli dalle pagine web "normali". Una qualunque pagina web, comunque, è un template valido, che in fase di esecuzione Freemarker si limita a inviare al borwser così com'è. Per sfruttare veramente i template, ovviamente, bisogna cominciare con l'inserire dei placeholder nel file. Le Interpolations Per approfondimenti, si veda la rispettiva sezione nella guida di Freemarker (http://freemarker.sourceforge.net/docs/dgui_template_exp.html). Le interpolations sono i placeholder di freemarker, cioè le parti "dinamiche" della pagina, che verranno sostituite via codice. Una interpolation è semplicemente un'espressione racchiusa tra "${ }", come quella inserita all'interno del frammento HTML che segue: <p>Ciao ${utente.nome} ${utente.cognome}, oggi è {data}</p> Le espressioni usate nelle interpolations fanno riferimento al cosiddetto data model, cioè alla struttura dei dati che, tramite Java, invieremo al template per compilarlo. Descriveremo il data model e la corrispondente struttura delle interpolations nelle prossime lezioni. Per ora, si assuma di poter usare nelle interpolations qualsiasi espressione semplice Java del tipo "oggetto.proprietà" o "oggetto[indice]". Tuttavia, si ricordi che tutte le variabili usate nelle espressioni di Freemarker provengono dal suo data model, e non hanno nulla a che vedere con le variabili del programma Java. Vediamo ora alcune utili caratteristiche delle interpolations. Valori di default E' possibile specificare un valore di default da stampare nel caso in cui il valore dell'espressione dell'interpolation sia vuoto (o null), con la seguente sintassi: ${espressione!"valore di default"} Revisione 05/11 Pagina 3/14 Corso di Ingegneria del Web I Template e Freemarker: Le Interpolations Espressioni complesse E' possibile inserire in una interpolation delle vere e proprie espressioni Java, che facciano riferimento a costanti (numeri, stringhe tra virgolette) e a variabili provenienti dal data model del template. Ad esempio: ${valore*2 + 1} ${o.s + ": "} Nel primo esempio avremo in output il risultato dell'espressione matematica descritta, dove la variabile valore è prelevata dal data model del template. Nel secondo esempio concateniamo la stringa prelevata dalla variabile o.s con la stringa costante ": ". Attenzione, però: inserire espressioni complesse nel template significa inevitabilmente spostare una parte della logica del programma dal sorgente Java al template stesso, ed è sbagliato! Le espressioni vanno usate nei template solo quando siano strettamente attinenti alla visualizzazione dei dati. Negli esempi precedenti, avremmo potuto scrivere ${valore_derivato} $(o.s): dove valore_derivato è una variabile calcolata da Java con la stessa espressione usata nella interpolation precedente, e inserita nel data model del template, mentre nel secondo caso i due punti sono stati inseriti nel template semplicemente come costante di seguito alla interpolation. Built-in I built-in sono funzioni interne di Freemarker che si possono facilmente applicare ai valori delle intepolations per ottenere effetti molto utili. Per applicare una built-in a una interpolation è sufficiente concatenarne il nome usando un punto interrogativo: ${var?html} $(var?trim?html): Nel primo esempio, applichiamo il built-in chiamato html al valore della variabile var. Il secondo esempio mostra come è possibile applicare più built-in (trim e html) nella stessa interpolation. Alcuni built-in utilissimi sono listati di seguito. • • • • • • • html: restituisce una stringa con i caratteri riservati dell'html opportunamente sostituiti con le loro entità. Da usare sempre! cap_first: mette in maiuscolo la prima lettera della stringa lower_case: mette in minuscolo tutta la stringa upper_case: mette in maiuscolo tutta la stringa trim: elimina gli spazi prima e dopo la stringa size: restituisce la lunghezza dalla sequenza, se il valore della interpolation è un'array int: restituisce la parte intera di un numero ${nome?lower_case?cap_first?html} Nell'esempio, formattiamo (correttamente) un nome mettendolo tutto in minuscolo, tranne l'iniziale, e convertendo in entità gli eventuali caratteri riservati. Si vedano tutti i built-in nella guida di freemarker (http://freemarker.sourceforge.net/docs/ref_builtins.html). Revisione 05/11 Pagina 4/14 Corso di Ingegneria del Web I Template e Freemarker: Le Interpolations A questo punto siamo in grado di sviluppare un esempio più. Prendiamo quindi in considerazione la pagina generata da una servlet tramite il metodo makeDetail descritto di seguito. private void makeDetail(int idUtente, Profilo profilo, PrintWriter out) { HTMLHelpers.printPageHeader(out, "Dettaglio utente"); out.println("<h1>Dettaglio utente " + profilo.getNickname() + "</h1>"); out.println("<table border=\"1\">"); out.println("<tr><th>Nome</th><td>" + profilo.getNome() + "</td></tr>"); out.println("<tr><th>Cognome</th><td>" + profilo.getCognome() + "</td></tr>"); out.println("<tr><th>Nickname</th><td>" + profilo.getNickname() + "</td></tr>"); out.println("<tr><th>Email</th><td>" + (profilo.getEmail() != null ? profilo.getEmail() : "Non disponibile") + "</td></tr>"); out.println("<tr><th>Cellulare</th><td>" + (profilo.getTelefono() != null ? profilo.getTelefono() : "Non disponibile") + "</td></tr>"); out.println("<tr><th>Nazione</th><td>" + profilo.getNazione() + "</td></tr>"); out.println("</table>"); HTMLHelpers.printPageFooter(out); } Il metodo appena esposto utilizza altre classi (Profilo), tuttavia a noi interessano solo i metodi richiamati dal codice per leggerne i dati. Inoltre, i metodi printPageHeader e printPageFooter sono utilizzati per mandare in output rispettivamente la parte comune di apertura di una pagina HTML (fino all’apertura del body) e la corrispondente chiusura. Possiamo facilmente trasformare il codice visto sopra in un file HTML che usa delle interpolations al posto dei dati: basta copiare il codice contenuto nelle varie println e sostituire delle interpolations nei punti in cui venivano inseriti valori prelevati da variabili, ricordandoci di riportare nel template anche il codice HTML generato dalle chiamate alla classe di utilità HTMLHelpers. <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Dettaglio utente</title></head> <body> <h1>Dettaglio utente ${profilo.nickname?html}</h1> <table border="1"> <tr><th>Nome</th><td>${profilo.nome?html}</td></tr> <tr><th>Cognome</th><td>${profilo.cognome?html}</td></tr> <tr><th>Nickname</th><td>${profilo.nickname?html}</td></tr> <tr><th>Email</th><td>${(profilo.email?html)!"Non disponibile"}</td></tr> <tr><th>Cellulare</th><td>${(profilo.telefono?html)!"Non disponibile"}</td></tr> <tr><th>Nazione</th><td>${profilo.nazione?html}</td></tr> </table> <p><a href="main">Nuova ricerca</a></p> </body> </html> Possiamo quindi iniziare a raccogliere i nuovi templates all'interno della nostra applicazione web. Per farlo, creiamo una directory "templates" all'interno delle "Web Pages" (che, ricordiamo, contengono la parte "statica" del contesto) dell'applicazione. Revisione 05/11 Pagina 5/14 Corso di Ingegneria del Web I Template e Freemarker: Le Directives Le Directives Per approfondimenti, si veda la rispettiva sezione nella guida di Freemarker (http://freemarker.sourceforge.net/docs/dgui_template_directives.html) Le directives sono l'altra parte fondamentale del linguaggio di Freemarker. Le directives sono usate per "programmare" template più complessi, in cui ad esempio sono previste delle iterazioni o segmenti condizionali. Le directives hanno la seguente sintassi: <#nomedirettiva parametri> eventuale corpo della direttiva </#nomedirettiva> Come si può notare, le direttive hanno una forma simile ai tag XML, ma una sintassi lievemente diversa. Come in XML, le direttive possono essere nidificate (in maniera corretta, rispettando la corrispondenza tra apertura e chiusura). Vediamo ora le direttive più utili. La direttiva #include La direttiva #include è molto utile per includere un template in un altro. Il template incluso verrà elaborato come fosse parte di quello corrente, comprese le sue (eventuali) directives e interpolations. La sintassi della direttiva è molto semplice: <#include espressione> Notare che questa direttiva non prevede un tag di chiusura (nè una forma compressa come per i tag XML). L'espressione deve avere valore stringa (può essere, al limite, anche una costante) e corrispondere al path del file da includere. Ecco alcuni esempi: <#include "corpo.html"> <#include "/templates/" + outline.bodytemplate> La direttiva #if La direttiva #if permette di inserire condizionalmente nell'output una parte del template. La sua sintassi è la seguente: <#if condizione> ... <#elseif condizione> ... <#else> ... </#if> dove ciascuna condizione è un'espressione valida nel corpo di una clausola if di Java, che può fare riferimento a costanti e a variabili del data model del template. I rami #elseif ed #else possono essere omessi. La semantica è identica a quella dei normali costrutti if: verrà interpretata solo la parte di template per cui la condizione è vera (o il ramo else, se presente, quando nessuna condizione è vera). Ciò significa che il frammento contenuto nell' #if può contenere altre direttive e interpolations. Si veda l'esempio seguente: <#if (lista?size > 0)> Numero elementi: ${lista?size}, cliccare <a href="${link}">qui</a> per l'elenco. Revisione 05/11 Pagina 6/14 Corso di Ingegneria del Web I Template e Freemarker: Le Directives <#else> <i>lista vuota!</i> </#if> Come si può notare, è possibile usare i built-in anche per manipolare i valori usati nelle espressioni condizionali. Se la dimensione dell'array lista è maggiore di zero, Freemarker elaborerà il corrispondente frammento di template, che comprende anche due interpolations (una delle quali usata come valore per un attributo href: le interpolations si possono usare ovunque!), altrimenti stamperà un messaggio statico. Tornando al nostro caso di studio, possiamo usare la direttiva appena vista per creare un template per una pagina di ricerca che si presenta in due forme diverse a seconda che l'utente abbia o non abbia effettuato login. Usando la direttiva #if, possiamo "fondere" queste due pagine in un unico template. Il codice Java originale usato nella per generare le pagine di ricerca è il seguente: //questo metodo genera la pagina di ricerca per gli utenti loggati private void makeLoggedPage(PrintWriter out) { HTMLHelpers.printPageHeader(out, "Social Network Search"); out.println("<h1>social network search</h1>"); out.println("<p>Ciao, come utente registrato potrai effettuare ricerche sul network e visualizzare i dettagli dei profili.</p>"); //generiamo il form di ricerca makeQueryForm(out); out.println("<hr/>"); //inseriamo il bottone di logout out.println("<form method=\"post\" action=\"logout\">"); out.println("<p><input value=\"logout\" name=\"logout\" type=\"submit\"/></p>"); out.println("</form>"); HTMLHelpers.printPageFooter(out); } //questo metodo genera la pagina di ricerca per gli utenti non loggati private void makeNotLoggedPage(PrintWriter out) { HTMLHelpers.printPageHeader(out, "Social Network Search"); out.println("<h1>social network search</h1>"); out.println("<p>Ciao, come utente anonimo potrai effettuare ricerche sul network ma non potrai visualizzare i dettagli dei profili. Effettua la login per accedere a tutte le funzionalità!</p>"); //generiamo il form di ricerca makeQueryForm(out); out.println("<hr/>"); //generiamo il form di login out.println("<form method=\"post\" action=\"login\">"); out.println("<p>Username: <input name=\"u\" type=\"text\"/></p>"); out.println("<p>Password: <input name=\"p\" type=\"password\"/></p>"); out.println("<p><input value=\"login\" name=\"login\" type=\"submit\"/></p>"); out.println("</form>"); HTMLHelpers.printPageFooter(out); } //questo metodo crea il form di ricerca private void makeQueryForm(PrintWriter out) { out.println("<form method=\"get\" action=\"query\">"); out.println("<p>Nome o parte del nome: <input name=\"q\" type=\"text\"/></p>"); out.println("<p><input value=\"ricerca\" name=\"cerca\" Revisione 05/11 Pagina 7/14 Corso di Ingegneria del Web I Template e Freemarker: Le Directives type=\"submit\"/></p>"); out.println("</form>"); } Da questo codice possiamo estrarre due template: uno principale <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Social Network Search</title></head> <body> <h1>Social Network Search</h1> <#if logged> <p>Ciao, come utente registrato potrai effettuare ricerche sul network e visualizzare i dettagli dei profili.</p> <#else> <p>Ciao, come utente anonimo potrai effettuare ricerche sul network ma non potrai visualizzare i dettagli dei profili. Effettua la login per accedere a tutte le funzionalita'!</p> </#if> <#include "query_form.ftl.html"> <hr/> <#if logged> <form method="post" action="logout"> <p><input value="logout" name="logout" type="submit"/></p> </form> <#else> <form method="post" action="login"> <p>Username: <input name="u" type="text"/></p> <p>Password: <input name="p" type="password"/></p> <p><input value="login" name="login" type="submit"/></p> </form> </#if> </body> </html> E un template secondario, chiamato query_form.ftl.html, incluso dal primo: <form method="get" action="query"> <p>Nome o parte del nome: <input name="q" type="text"/></p> <p><input value="ricerca" name="cerca" type="submit"/></p> </form> Nell'esempio, la direttiva #if utilizza la variabile logged (che dovrà essere inserita nel data model del template!) per decidere cosa stampare sulla pagina. Inoltre, si utilizza la direttiva #include per inserire nel template la form di ricerca, scritta in un file separato per aumentare la modularità dell'applicazione. La direttiva #list Questa importantissima direttiva permette di elaborare iterativamente il contenuto di array e liste passati attraverso il data model del template. In questo modo, ad esempio, è possibile visualizzare liste generiche scrivendo solo pochi frammenti di template. Vediamo la sintassi della direttiva: <#list espressione as var> ... </#list> Revisione 05/11 Pagina 8/14 Corso di Ingegneria del Web I Template e Freemarker: Le Directives Dove espressione deve essere di tipo lista o array (come vedremo più avanti, parlando del data model), mentre var è la variabile del loop. Come per il costrutto for di Java, Freemarker elaborerà il corpo della direttiva per ogni elemento della lista, attribuendo ad ogni iterazione il suo valore alla variabile di loop var. Potremmo allora completare l'esempio di sopra come segue: <#if (lista?size > 0)> <ul> <#list lista as elemento> <li>${elemento}</li> </#list> </ul> <#else> <i>lista vuota!</i> </#if> Se lista non è vuota, viene creata una lista HTML (<ul>) e vengono stampati i suoi elementi come punti dell'elenco (<li>). Si noti come il codice del template si limiti a mostrare come formattare un singolo elemento della lista, lasciando a Freemarker il compito di ripeterlo, nell'output, il numero necessario di volte. Questo semplifica notevolmente la stesura del template! Tornando al nostro caso di studio, vogliamo generare una tabella contenente i dati di base di tutti i profili risultanti dalla ricerca. In particolare, nel sorgente Java originale un ciclo for viene utilizzato per stampare una riga della tabella per ogni profilo, come segue: //questo metodo crea la tabella dei risultati di ricerca, iterando su //una lista di profili. A sconda del valore del flag logged, il sistema //presenta o no il bottone per visualizzare i dettagli del profilo. private void makeResultTable(List<Profilo> result, PrintWriter out, boolean logged) { HTMLHelpers.printPageHeader(out, "Risultati della ricerca"); out.println("<table border=\"1\">"); out.println("<tr><th>Nome</th> <th>Cognome</th> <th>Nickname</th> <th>Nazionalità</th>"); //se siamo loggati, aggiungiamo alla tabella una colonna per il bottone "dettagli" if (logged) { out.println("<th>&nbsp;</th>"); } out.println("</tr>"); //iteriamo sulla lista dei profili, generando le righe della tabella for (int i = 0; i < result.size(); ++i) { Profilo p = result.get(i); out.println("<tr><td>" + p.getNome() + "</td><td>" + p.getCognome() + "</td><td>" + p.getNickname() + "</td><td>" + p.getNazione() + "</td>"); if (logged) { out.println("<td><a href=\"profilo?idUtente=" + p.getIdUtente() + "\">[dettagli]</a></td>"); } out.println("</tr>"); } out.println("</table>"); HTMLHelpers.printPageFooter(out); } E' quindi naturale portare il codice nel template HTML utilizzando la direttiva #list. Anche in questo caso definiamo un template principale: <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" Revisione 05/11 Pagina 9/14 Corso di Ingegneria del Web I Template e Freemarker: Il Data Model di Freemarker "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Risultati della Ricerca</title></head> <body> <h1>La ricerca ha prodotto ${profili?size} risultati.</h1> <#if (profili?size>0)> <#include "risultati_table.ftl.html"> </#if> <p><a href="main">Nuova ricerca</a></p> </body> </html> Che importa, se ci sono risultati da visualizzare, un template specifico per la tabella: <table border="1"> <tr><th>Nome</th><th>Cognome</th><th>Nickname</th><th>Nazionalita</th> <#if logged> <th>&nbsp;</th> </#if> </tr> <#list profili as profilo> <tr> <td>${profilo.nome?html}</td> <td>${profilo.cognome?html}</td> <td>${profilo.nickname?html}</td> <td>${profilo.nazione?html}</td> <#if logged> <td><a href="profilo?idUtente=${profilo.idUtente}">dettagli</a></td> </#if> </tr> </#list> </table> Nel file, viene prima di tutto usata una direttiva #if per decidere se la lista non è vuota. In caso ci siano elementi di mostrare, viene incluso un secondo file che realizza la tabella dei risultati iterando, tramite la direttiva #list, sulla variabile profili. Ogni profilo viene quindi associato alla variabile di loop profilo ed elaborato dal corpo della #list, al cui interno, oltre alle interpolations, usiamo ancora la direttiva #if per decidere se stampare o no il link ai dettagli. Il Data Model di Freemarker Per approfondimenti, si veda la rispettiva sezione nella guida di Freemarker (http://freemarker.sourceforge.net/docs/pgui_datamodel.html) Come abbiamo già detto più volte, tutte le variabili usate nelle espressioni di Freemarker, siano esse semplici interpolations o usate all'interno di directives, fanno riferimento a costanti o a variabili provenienti dal data model del template. Il data model non è altro che una particolare struttura dati passata al template al momento della sua compilazione. Le tre componenti fondamentali di un data model (cioè gli elementi che possono essere usati per costruirlo) sono le seguenti: • • • Scalari: stringhe, numeri, booleani, date Hash: associazioni tra nomi e valori Sequenze: liste di valori Revisione 05/11 Pagina 10/14 Corso di Ingegneria del Web I Template e Freemarker: Il Data Model di Freemarker Le Hash Le strutture principali che ospitano le variabili del data model sono le hash. Una hash(table) associa infatti nomi a valori, che nel template saranno interpretati come nomi e valori di variabile. Il modo più semplice per predisporre un hash in Java è creare un'istanza dell'interfaccia Map, ad esempio tramite la classe HashMap, come nell'esempio che segue: import java.util.HashMap; ... HashMap m = new HashMap(); m.put("nome","Paolino"); m.put("cognome","Paperino"); A questo punto, ipotizzando di passare m come data model a un template, quest'ultimo "vedrebbe" due variabili, nome e cognome, con i rispettivi valori, richiamabili tramite le interpolations ${nome} e ${cognome}. Freemarker permette di nidificare a piacimento le strutture dati: quindi, ad esempio, il valore di una variabile inserita in una hash può essere un'altra hash. Vediamo un esempio import java.util.HashMap; ... HashMap m = new HashMap(); m.put("nome","Paolino"); m.put("cognome","Paperino"); HashMap m2 = new HashMap(); m2.put("utente",m); m2.put("data",Calendar.newInstance()); A questo punto, ipotizzando di passare m2 come data model a un template, quest'ultimo "vedrebbe" due variabili, utente e data. Tuttavia, la variabile utente non è uno scalare, ma una struttura con altre due variabili, nome e cognome, questa volta scalari. Per accedere a queste variabili nidificate, si usa la consueta notazione "punto", come nella programmazione a oggetti: potremmo quindi scrivere il seguente template basato sul data model m2: <p>Ciao ${utente.nome} ${utente.cognome}, oggi è {data}</p> L'espressione ${utente.nome} presuppone quindi che il valore di ${utente} sia a sua volta una hash contenente la variabile nome. Attenzione: il data model passato al template deve sempre essere una hash, eventualmente con altre strutture nidificate. Le Sequenze Una sequenza è una lista di valori su cui è possibile iterare e, in certi casi, su cui è possibile applicare l'operatore di array [x] per estrarre l'x-esimo elemento. In java una sequenza si ottiene passando nel data model un array o un'istanza di oggetto che implementi l'interfaccia java.util.List, come ad esempio la classe ArrayList. Vediamo un esempio. import java.util.ArrayList; import java.util.HashMap; ... ArrayList l = new ArrayList(); l.put("a"); l.put("b"); Revisione 05/11 Pagina 11/14 Corso di Ingegneria del Web I Template e Freemarker: Il Data Model di Freemarker l.put("c"); int[] l2 = {1,2,3}; HashMap m = new HashMap(); m.put("utente","Pippo"); m.put("lista",l); m.put("listaintera",l2); Il data model rappresentato dalla hash m conterrà un valore scalare (utente) e due sequenze, l e l2, create rispettivamente da un ArrayList e da un array Java. Usare una sequenza nelle espressioni Freemarker è semplice: è possibile ad esempio iterarvi sopra usando la direttiva #list, o estrarre un particolare elemento usando la notazione [x] (questa notazione è valida per qualsiasi sequenza, anche se non è stata costruita usando a un array Java). Vediamo un esempio d'uso di questo data model: <p>Ciao ${itente}.Il valore del primo elemento della listaintera è ${listaintera[0]}.</p> <#if (lista?size > 0)> <ul> <#list lista as elemento> <li>${elemento}</li> </#list> </ul> <#else> <i>La lista è vuota!</i> </#if> Ovviamente i tipi hash, sequenza e scalare possono essere mescolati a piacere: import java.util.ArrayList; import java.util.HashMap; ... ArrayList l = new ArrayList(); for (int i=0; i<3; ++i) { HashMap me = new HashMap(); me.put("val",new Integer(i)); l.add(me); } HashMap m = new HashMap(); m.put("nome","Paolino"); m.put("cognome","Paperino"); HashMap m2 = new HashMap(); m2.put("utente",m); m2.put("lista",l); Il data model di questo esempio dispone delle variabili ${utente.nome} (scalare), ${utente.cognome} (scalare), ${lista} (sequenza), ${lista[0].val} (scalare), ${lista[1].val} (scalare) e ${lista[2].val} (scalare). I JavaBeans nel Data Model Un'ultima caratteristica interessante già implementata in Freemarker è la possibilità di usare dei JavaBeans come valori di variabili. Un bean è, essenzialmente, un oggetto con una serie di proprietà accessibili tramite metodi pubblici get/set, come quello che segue: class MyBean { private String nome, cognome; /* metodi "getter" */ public String getNome() {return nome;} Revisione 05/11 Pagina 12/14 Corso di Ingegneria del Web I Template e Freemarker: Programmazione di Freemarker public String getCognome() {return cognome;} /* metodi "setter" */ public void setNome(String nome) {this.nome = nome;} public void setCognome(String cognome) {this.cognome = cognome;} } I beans sono usatissimi per realizzare strutture dati all'interno dei programmi. Utilizzato nel data model di Freemarker, un bean viene visto come una hash contenente le variabili corrispondenti alle proprietà del bean. Vediamo un esempio: import java.util.HashMap; ... MyBean b = new MyBean(); b.setNome("Paolino"); b.setCognome("Paperino"); HashMap m = new HashMap(); m.put("utente",b); Nel data model rappresentato da m, avremo le due variabili ${utente.nome} e ${utente.cognome}, dove nome e cognome sono in effetti proprietà estratte dal bean associato alla variabile utente. Programmazione di Freemarker Veniamo infine all'interfaccia di Freemarker verso il linguaggio Java. Per usare questa libreria, bisogna prima di tutto importare tutte le classi del package freemarker.template. Inzializzazione della libreria Per inizializzare il motore dei templates (di solito questa operazione viene eseguita una sola volta) è sufficiente creare e impostare un’istanza della classe Configuration. Vediamo come procedere nell'esempio che segue: import freemarker.template.*; import java.util.*; ... Configuration cfg = new Configuration(); cfg.setDirectoryForTemplateLoading(new File("/directory/templates")); cfg.setObjectWrapper(new DefaultObjectWrapper()); cfg.setDefaultEncoding("ISO-8859-1"); cfg.setTemplateExceptionHandler( TemplateExceptionHandler.HTML_DEBUG_HANDLER); Il metodo setDirectoryForTemplateLoading imposta la directory in cui Freemarker cercherà i file dei templates che caricheremo in seguito. Il metodo setObjectWrapper imposta l'interprete usato da Freemarker per convertire gli oggetti Java in hash, sequenze e scalari. Di solito è sufficiente usare il DefaultObjectWrapper fornito da Freemarker. Il metodo setDefaultEncoding, opzionale, imposta l'encoding usato da Freemarker durante la lettura dei template. Infine, il metodo setTemplateExceptionHandler imposta il sistema di gestione degli errori di compilazione dei template (non degli errori delle servlet che li usano!). Il valore usato nell'esempio permette di visualizzare messaggi di errori molto dettagliati utili al debugging. In una servlet è possibile usare il metodo setServletContextForTemplateLoading al posto di setDirectoryForTemplateLoading per specificare facilmente una directory relativa al contesto della web application. Vedremo questo metodo in uso nel nostro caso di studio. Revisione 05/11 Pagina 13/14 I Template e Freemarker: Programmazione di Freemarker Corso di Ingegneria del Web Compilazione dei Template A questo punto, quando dovremo mandare in output un template, debitamente compilato, potremo seguire i seguenti semplici passi: Template t = cfg.getTemplate("homepage.html.ftl"); //il template Map data = new HashMap(); //il data model ... t.process(data, out); Utilizziamo l'oggetto cfg precedentemente inizializzato per caricare un Template, specificandone il nome (ricordando che la directory di caricamento è stata già specificata nella configurazione). Creiamo quindi un data model tramite una HashMap che riempiremo con i dati che ci servono. Infine, chiamiamo il metodo process dell'oggetto Template appena creato, passandogli il data model e un Writer su cui scrivere i dati risultanti dalla compilazione del template. In una servlet, questo Writer sarà quello ottenuto dalla chiamata a request.getWriter(). This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA. Revisione 05/11 Pagina 14/14