Separare la parte dei template dalla logica applicativa è una parte cruciale quando si sviluppa una web application. Essenzialmente ci sono due tipi di template: template lato server, usati da un engine durante il caricamento della pagina, e template lato client, usati per lavorare con le applicazioni Javascript e le richieste Ajax.

In scenari in cui la propria web application dipende fortemente da Ajax, può essere difficile mantenere dei template sia lato client che lato server senza incorrere in duplicazioni. Dunque può essere utile scegliere un template engine che fornisca un supporto sia che client che server e che permetta la massima riusabilità.

Mustache sta diventando velocemente una scelta popolare per questo genere di compiti. Fornisce infatti delle implementazioni per una moltitudine di linguaggi server e client.

In un articolo precedente abbiamo mostrato come usare Mustache con un linguaggio lato client (javascript). Se non avete alcuna esperienza con Mustache ne è consigliata la lettura. Su questo articolo infatti ci concentreremo su come creare dei template con Mustache server-side per poi condividerli anche col lato client.

Introduzione a Mustache per PHP

L’implementazione di Mustache in php è chiamata Mustache.php, si può scaricare usando Composer (il gestore delle dipendenze per eccellenza in PHP), oppure dal sito ufficiale sulla pagina GitHub.

Con composer è sufficiente aggiungere al file composer.json le righe che seguono e dal terminale ed eseguire composer install o composer update:

{
    "require": {
        "mustache/mustache": "2.0.*"
    }
}

Ora diamo un’occhiata su come gestire il template con Mustache.php:

<?php
require 'vendor/autoload.php';
 
$tpl = new Mustache_Engine();
echo $tpl->render('Hello, {{tag}}!', array('tag' => 'World'));

La prima istruzione include l’autoloader di Composer (in alternativa si può includere direttamente l’autoloader di Mustache se non abbiamo usato Composer per gestire le dipendenze). Dopo viene istanziato un nuovo oggetto appartenente alla classe Mustache_Engine. E dell’oggetto istanziato viene usato il metodo render per associare ad un template dei valori. Il template è il primo parametro in questo caso, mentre il secondo parametro è l’array di valori.

Solitamente non si passa il template in linea come visto sopra. I template dovrebbero essere tenuti nei file appositi, in una directory dedicata. E’ possibile configurare il percorso ai file di template quando si istanzia la class Mustache_Engine.

<?php
$mustache = new Mustache_Engine(array(
   'loader' => new Mustache_Loader_FilesystemLoader('../templates')
));
 
$tpl = $mustache->loadTemplate('nome_template');
 
echo $tpl->render(array('tag' => 'World'));

Il metodo costruttore della classe Mustache_Engine accetta come parametro un array di configurazione, a cui si può associare alla voce “loader” un percorso alla directory che conterrà i template per l’istanza considerata. Ulteriori parametri di configurazione possono essere visti sulla Wiki di Mustache.php.
Nello scenario sopra i file di template risiedono appunto nella directory chiamata “templates”. Il metodo loadTemplate, carica il template nome_template.mustache presente nella directory “templates”. A questo punto al metodo render è sufficiente passare solo l’array di valori.

Compresa la dinamica di base possiamo passare a vedere come condividere i template fra linguaggi server-side e client-side.

Condividere template fra PHP e Javascript

Il nostro obiettivo principale è quello di condividere i template fra PHP (linguaggio server-side) e Javascript (linguaggio client-side). Per le basi del nostro esempio, assumiamo di avere un sito di e-commerce con 2 categorie di prodotti: Libri e Film.
Quando l’utente visita il sito per la prima volta, gli verranno elencati tutti i prodotti in una lista singola, come mostrato sotto:

Esempio condivisione template mustache

Potete facilmente vedere come i dettagli di ogni prodotto siano differenti, dunque abbiamo bisogno di separare i template per le due categorie.

I due template saranno chiamati rispettivamente libri.mustache e film.mustache.
libri.mustache sarà così composto:

{{#prodotti}}
{{#libro}}
<div>
<p>Titolo libro: {{titolo}}</p>
<p>Autore libro: {{autore}}</p>
<p>Prezzo: {{prezzo}}</p>
</div>
{{/libro}}
{{/prodotti}}

Quello sopra è un template d’esempio di una sezione dedicata al loop, con alcune variabili specifiche del prodotto. Se una delle chiavi non viene passata al template, semplicemente non verrà visualizzato nulla.

Al caricamento della pagina, tutti i prodotti devono essere prelevati dal database e mostrati all’utente, quindi usiamo PHP per effettuare questa operazione iniziale, usando sia il data set che il template:

<?php
$books = array();
$result = $db->query('SELECT titolo, autore, prezzo FROM libri'); // query SQL
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
    $row['libro'] = true;
    $books[] = $row;    
}
$result->closeCursor();
 
$movies = array();
$result = $db->query('SELECT nome, prezzo, cast FROM film');
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
   $row['film'] = true;
   $movies[] = $row;    
}
 
$booksTmpl = $this->mustache->loadTemplate('libri');
$moviesTmpl = $this->mustache->loadTemplate('film');
 
$data = array(
    'prodotti' => array_merge($books, $movies)
);
$html = $booksTmpl->render($data);
$html .= $moviesTmpl->render($data);
 
echo $html;

Usiamo due query per ottenere libri e film dal database e salvarli in un array per poi passarli ai template. Nell’esempio sopra viene usato PDO per la connessione al database, si può usare qualsiasi altro metodo si preferisca.

I template per i libri e i film vengono caricati separatamente in due variabili usando il metodo loadTeamplte(), e si passa l’array di prodotti ad ogni metodo render() del template. A questo punto viene applicato il template ai dati sui prodotti e restituito un output html.

Per filtrare i risultati in base alla tipologia, si potrebbe optare per un approccio lato server, ma tutto il codice di markup non farà altro che incrementare l’overhead del server. Non vogliamo inviare lo stesso markup in continuazione. Qui è dove entrano in gioco i template lato client.
Possiamo fare in modo che i template necessari vengano caricati dal browser sul client e i dati necessari caricati dal server usando AJAX.

Per rendere i template disponibili al client, possiamo far in modo che vengano iniettati direttamente dal server, all’interno di tag script, per un utilizzo futuro attraverso javascript.
Il codice all’interno di questi tag infatti non verrà mostrato, nè verrà eseguito da javascript se impostiamo il tipo a text/mustache.

<script id="booksTmpl" type="text/mustache">
<?php
echo file_get_contents(dirname(__FILE__) . '/templates/libri.mustache');
?>
</script>
<script id="moviesTmpl" type="text/mustache">
<?php
echo file_get_contents(dirname(__FILE__) . '/templates/film.mustache');?>
</script>

Per usare questi template in Javascript, abbiamo bisogno di includere la libreria Mustache.js nel documento.
Potete ottenere una copia di mustache.js dalla pagina GitHub e includerlo nel documento così:

<script type="text/javascript" src="mustache.js"></script>

Una volta che l’utente seleziona l’opzione di filtro, possiamo ottenere i dati necessari attraverso una richiesta AJAX.
Solo i template vengono resi subito disponibili dal PHP al caricamento della pagina. Per i dati bisogna effettuare una richiesta AJAX di questo tipo:

<script>
$("#booksFilter").click(function () {
   $.ajax({
       type: "GET",
       url: "ajax.php?type=libri"
   }).done(function (msg) {
       var template = $("#booksTmpl").html();
       var output = Mustache.render(template, msg);
       $("#products").html(output);
   });
});
</script>

La lista dei libri è caricata dal Database e restituita in formato JSON al client. Il codice dei template lo possiamo invece prendere dai tag script in cui sono contenuti attraverso $("#booksTmpl").html(). Sia i template che i dati possono dunque essere facilmente passati alla funzione render di Mustache JS.

Riepilogo

Lavorare con template engine separati può portare alla duplicazione dei template. Fortunatamente Mustache ha molte implementazioni che ci permettono di usare gli stessi template condivisi in più ambienti. Questo permette una certa mantenibilità per il codice.

Commentare pure o condividete i vostri suggerimenti!