Il Template Engine Freemarker - University of L`Aquila

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> </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> </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