No. 103

Titolo originale: Node at Work: A Walkthrough

Pubblicato in: JavaScript

Scritto da Garann Means

Nel mio primo articolo, “Migliori mockup nel browser con Node.js”, ho spiegato perché Node.js rende più semplice la progettazione di applicazioni e come cominciare. Adesso è il momento di vedere il nuovo processo di design in azione.

Piuttosto che cercare di capire tutti i requisiti e gli schemi API solo per realizzare i comp con del contenuto fittizio inserito direttamente nel codice e con interazioni fittizie con il server, per poi dover buttare via tutto quando si tratta di dover implementare le cose "realmente". Con Node.js potete evitare l'inserimento del contenuto e produrre codice lato client pronto per la beta version.

Il processo sembra un po' il buon vecchio design nel browser, ma con più JavaScript e con un livello aggiunti:

  1. Design del layout e degli stili
  2. Convertire il markup in un template JavaScript
  3. Crere una funzione di inizializzazione
  4. Creare un semplice server Node.js
  5. Aggiungere al server un modello di "data object"
  6. Aggiungere le funzioni server per mandare pagine statice e JSON
  7. Richiedere e utilizzare il JSON lato client

Vi spaventa? Non preoccupatevi: il primo step richiede un'infinità di tempo in più rispetto a qualunque altro step, quindi se avete già il design, troverete tutti gli altri step molto più che gestibili.

In questa guida, creeremo una feature per un finto negozio d'arte. Se volete seguire il tutto passo passo da casa, potete clonare il mio repository GitHub. (Se avete bisogno di una mano per l'installazione, leggete il README, o date una sbirciata alla live demo. Spiegherò più avanti tutti gli step e commenterò il codice qui sotto.)

Creare i template

Una volta che avete un design robusto e il markup che lo accompagna, convertirlo in un template che potete usare per tutti gli esempi è più efficiente che creare un markup duplicato per ciascuno. La parte difficile è finita: avete già pensato a dove verranno usati i data point nel design che avete realizzato. Con queste scelte ancora fresche nella vostra mente, andate indietro e contrassegnate l'HTML con i dati nel linguaggio di template che preferite.

Per il mio esempio, userò un negozio che vende stampe artistiche. Ecco una snippet del mio markup iniziale:

<h2>Two Acrobats with a Dog</h2>
<h3>Pablo Picasso</h3>
<img src="/img/102.jpg" alt="Two Acrobats with a Dog" class="active" />
<ul class="info">
	<li>8" x 11"</li>
	<li>acid-free paper</li>
	<li>suitable for matting</li>
</ul>
<span class="price">$49.99</span>

Pensate ai vostri template come a posti in cui definire i requisiti sia per i dati sia per la loro formattazione lato client. È fantastico se potete anche riutilizzarli per il rendering client-side, ma potrebbe non essere rilevante per la vostra applicazione. Fintanto che avrete dei buoni dati, è irrilevante convertirli da un linguaggio di template all'altro, quindi non state a tormentarvi troppo su quale template engine usare.

Tuttavia, avete bisogno di un template engine che funzioni sia nel browser sia in Node.js. Se non siete sicuri, cercate il vostro template engine su GitHub e verificate che ci sia una guida per l'installazione via npm nel manuale, così come uno script minified per l'utilizzo sul client. Io prediligo doT.js, quindi ecco di nuovo quella snipped con il markup per aggiungere i dati mediante doT:

<h2>{{=it.title}}</h2>
<h3>{{=it.artist.name}}</h3>
<img src="/img/{{=it.id}}.jpg" alt="{{=it.title}}" class="active" />
<ul class="info">
	{{~it.info :info_item}}
	<li>{{=info_item}}</li>
	{{~}}
</ul>
<span class="price">{{=it.price}}</span>

Mi piace salvare i miei template nella loro directory allo stesso livello della mia directory JavaScript, così adesso lo salvo come tmpl/detail.dot.

Inizializzare il client

Dal momento che vogliamo essere in grado di usare i nostri template sia in Node sia nel browser, hanno bisogno di essere memorizzati fuori dall'HTML e caricati e compilati quando apriamo la pagina. Per cominciare, salviamo la versione minified del template engine e aggiungiamo un tag script alla pagina, per includerlo. Una volta fatto ciò, potete caricare il template, compilarlo e poi continuare con qualunque altro lavoro di inizializzazione nel vostro file JavaScript principale. Sto usando jQuery nel mio esempio, pertanto il mio codice ha questo aspetto:

var detailTmpl;

$.when( 
	$.get( "tmpl/detail.dot", function( tmpl ) {
		detailTmpl = doT.template( tmpl );
	}, "text" ) 
).then( init );

Cos'è quella misteriosa funzione init? È dove metto qualsiasi interazione che voglio aggiungere al mio mockup statico. Per il momento sto solo creando un'interazione, quindi la mia funzione init è piuttosto semplice:

function init() {
	$( "div.content" ).on( "click", "div.result", showDetail );
}

Questo codice può essere reso molto più elegante usando Require.js con il suo text plugin. Questo va oltre lo scopo di questa demo, ma vi incoraggio vivamente ad utilizzarlo in produzione.

Gestiremo il rendering del template in showDetail(), ma dobbiamo aggiungere un server e un data store prima di scrivere quella funzione dal momento che adesso ci mancano i dati da rendere.

Creare un server

Se ricarico adesso la mia pagina e apro la console del browser, ottengo un errore JavaScript. Questo succede perché sto cercando di caricare il mio template con una XMLHttpRequest (XHR) su una pagina che viene servita dal file system, in violazione alla same origin policy. Non posso nemmeno controllare che il mio template funzioni finchè non viene correttamente gestita (ad esempio, da un server).

Per creare un semplice server Node che mi permetta di far girare il mio XHR, faccio alcune cose:

  • Sposto tutti i miei asset esistenti in una nuova sottodirectory chiamata public
  • Apro il terminale o la riga di comando sulla directory di lavoro e scrivo npm install express
  • Aggiungo un file server.js alla directory di lavoro

Potremmo scrivere tutto da zero, ovviamente, ma non è necessario per un server base. Il framework Express fornisce un numero di astrazioni di concetti e applicazioni server. Per la versione iniziale del server, l'unica di cui abbiamo bisogno è la sua capacità di servire risorse statiche. Possiamo usarlo per aggiungere quattro righe di codice a server.js:

var express = require( "express" ),
	app = express();

app.use( express.static( __dirname + "/public" ) );

app.listen( 3000 );

Una volta che si avvia il server scrivendo node server.js nel terminale o nella riga di comando che abbiamo aperto, si può vedere la propria pagina su http://localhost:3000 (aggiungere un filename se necessario) e l'errore relativo al caricamento del template dovrebbe essere scomparso.

Aggiungere dati server-side

È sicuramente positivo essere in grado di usare XHR, ma noi stiamo creando il server Node per usarlo come una rappresentazione del vero server e i veri server memorizzano dati. Sebbene non sia difficile creare un data store che funzioni con un sever Node, è ancora meno difficle crearne un grande object literal. Per un mockup, è tutto quello di cui abbiamo bisogno. Uno degli obiettivi qui consiste nel definire i data object che abbiamo bisogno di supportare nel nostro nuovo design, quindi il formato di questo oggetto può essere determinato dal template che abbiamo appena aggiunto. Per esempio, ho bisogno di un oggetto strutturato, qualcosa così:

var products = {
	"102": {
		id: 102,
		title: "Two Acrobats with a Dog",
		artist: {
			name: "Pablo Picasso"
		},
		price: "$49.99",
		info: [
			"8\" x 11\"",
			"acid-free paper",
			"suitable for matting"
		]
	}
};

Notate che products potrebbe tranquillamente essere un array, ma io voglio poter trovare rapidamente i miei prodotti per ID, una volta che ne avrò più di uno nel mio finto data store. A parte questo piccolo colpo di scena, i dati appaiono esattamente come il contenuto hard-coded nel mio HTML originale. Se voglio aggiungere altri dati, incluse cose che potrebbero rompere il layout in modi imprevedibili, posso semplicemente copiare questa struttura e fare delle sostituzioni. Beh, più o meno.

Far ritornare i dati dal server

Se avete già avuto a che fare con altri framework server-side, creare degli endpoint per XHR potrebbe fare paura, ma Express lo rende facilissimo. Non abbiamo bisogno di alcun setup speciale per definire un server endpoint come target di una richiesta asincrona. Tutto ciò che dobbiamo fare è definire il percorso sul server dove volete accettare le richieste e un callback. Il callback riceve un request object (per fare cose come ottenere i dati che sono stati passati) e un response object (per definire quello che ritorniamo al client). Per ritornare i dati nel mio oggetto products, aggiungo alcune righe di codice alla fine di server.js:

app.get( "/detail/:id", function( req, res ) {
	res.send( products[ req.params.id ] );
});

app.listen( 3000 );

Visto? Facile. Se faccio ripartire il server e vado su http://localhost:3000/detail/102, dovrei vedere il mio object data. Per analizzare quello che succede con l'ID nel path, abbiamo chiamato i dati in quella posizione nel path "id" con il pezzo :id, che poi è disponibile come una proprietà di reg.params.

I nomi e le posizioni dei parametri dipendono da noi e se il nostro percorso fosse super complesso, potremmo usare anche le espressioni regolari per splittare molteplici pezzi di dati. Express ci dà anche la possibilità di accettare dati dalla stringa della query o da un POST. Di tutti i pezzi che stiamo creando, comunque, i path sono quelli che più probabilmente cambieranno in produzione, perciò è a vantaggio nostro mantenerli quanto più leggibili possibile.

Oltre a mandare dei puri dati al client, vogliamo anche essere in grado di mandare l'HTML, in caso un utente venga linkato direttamente ai dettagli del prodotto o non abbia JavaScript a sua disposizione. Potremmo anche volere HTML per il nostro proprio uso via XHR, se troviamo che il rendering client-sia ci rallenti troppo. Quindi, aggiungiamo un secondo endpoint sotto a quello che abbiamo creato, proprio per questo scopo:

app.get( "/product/:id", function( req, res ) {
	res.render( "detail", products[ req.params.id ] );
});

Per amore della semplicità e poiché il primo path ha servito JSON per un overlay mentre questo fornisce un'intera pagina, ho utilizzato diversi nomi di percorso, ma ho tenuto lo stesso pattern. Questa volta, invece della funzione di invio della risposa, uso render(). Express fornisce un po' di magia per far sì che il rendering del template funzioni da subito, ma dal momento che sto usando doT invece di Jade (il template engine di default di Express), devo fare del setup aggiuntivo.

Per prima cosa devo tornare nel terminale o alla riga di comando, fermare il server Node ed installare il mio template engine usando npm install doT e il modulo consolidate (che fornisce a Express la compatibilità con un certo numero di template engine molto comuni) utilizzando npm install consolidate. Adesso li ho entrambe nella mia directory node_modules e posso usarli in server.js.

Dal momento che si accede a doT (e probabilmente anche al template engine che avete scelto) attraverso consolidate, l'unico modulo aggiuntivo che devo richiedere all'inizio di server.js è consolidate.

var express = require( "express" ),
	app = express(),
	cons = require( "consolidate" );

Voglio continuare a mandare alcune delle mie altre pagine staticamente, quindi aggiungo i parametri di configurazione del template sotto la riga app.use già presente nel mio codice:

app.use( express.static( _dirname + "/public" ) );
app.engine( "dot", cons.dot );
app.set( "view engine", "dot" );
app.set( "views", _dirname + "/public/tmpl" );

Quelle tre nuove righe impostano doT (così come viene esposto da consolidate) come il view engine, registrano i file che terminano in .dot come template e dicono a Express di cercare i template da usare in /public/tmpl. Quindi, quando Node vede res.render( "detail", { ... } ), sa come espandere "detail" in /public/tmpl/detail.dot e renderlo come un template doT. Ora posso far ripartire il mio server, andare su http://localhost:3000/product/102 e vedere il mio template reso staticamente, senza dover creare un file server-side separato.

Fetch dei dati dinamici

Il nostro template funziona ora come una pagina statica, ma c'è un altro step per far sì che il nostro mockup venga popolato con i dati dal server. Ricordate la funzione showDetail del nostro script client-side principale? È ora di approfondirla.

Nel mio semplice esempio, l'overlay che verrà popolato dal mio template esiste di già come div nascosto sulla pagina principale e appare quando l'utente clicca su un div che contiene un riassunto del contenuto. Questo div ha un attributo data in cui memorizzare l'ID del prodotto che corrisponde alla proprietà key e id nel mio data object lato server. Una volta che avviene l'evento click e viene richiamato showDetail(), ho solo bisogno di fare quanto segue:

function showDetail( e ) {
	var id = $( this ).data( "id" );
	$.get( "detail/" + id, function( info ) {
		$( "div.detail" ).html( detailTmpl( info ) );
		$( "div.detail" ).show();
	}
}

Il path di cui sopra è lo stesso che ho definito in server.js. Se avete scelto un nome diverso per il vostro, usate quel nome nel client. Quando ricevo il data object dal mio server, lo passo a detailTmpl(), la versione compilata del mio template. Il risultato della funzione detailTmpl è l'HTML che deve popolare il mio overlay.

Avanti

Ecco fatto! Un mockup che imita le interazioni che ci saranno con il server di produzione ma sul client, senza il bisogno di avere dati inseriti nel codice o workaround temporanei. Nonostante il semplice esercizio, il processo che ho delineato illustra una buona parte del setup necessario per creare altri workflow che richiedono interazioni lato server. Per esempio, posso riempire il mio finto data store con più prodotti e usarlo per restituire la pagina iniziale che attiva il mio overlay senza dover rivisitare i dati fittizi e la mia applicazione mostrerà i valori corretti in qualunque view che gli aggiungo.

Se volete esplorare oltre l'utilizzo di HTML e JSON, considerate l'aggiunta di Socket.io per permettere l'interazione real-time con più client o Require.js per gestire gli asset sul client. Potreste anche spostare il CSS in template e mandare differenti build del vostro sito a diversi browser o device. Il vostro mockup può essere tanto sofisticato e riflettere quanti più requisiti di prodotto volete. Alla fine, la parte del leone del vostro codice client-side è fatta e pronta all'uso.

Illustrazioni: Carlo Brigatti

Share/Save/Bookmark
 

Discutiamone

Ti sembra interessante? Scrivi tu il primo commento


Cenni sull'autore

Garann Means

Garann MeansGarann ha cominciato a fare siti negli anni '90 e, dopo aver passato un po' di tempo come developer a tutto tondo, ora ha il privilegio di poter trascorrere i suoi giorni scrivendo perlopiù in JavaScript. Ha scritto un libro su Node.js, organizza dei meetup locali, è speaker in varie conferenze e generalmente cerca di rimanere coinvolta nella sua comunità di sviluppatori. Potete trovarla anche su Twitter.