Questo post fa parte di una serie dedicata ad Aurelia e ASP.NET Core.
Se fino ad oggi abbiamo navigato sotto costa, è arrivato il momento di rompere gli indugi e puntare la prua al largo. Nel proseguo della nostra esplorazione dell'universo Aurelia proverò a costruire un'applicazione concreta, per evitare di cadere nel solito tranello delle presentazioni apparentemente perfette che poi crollano al primo caso reale. Io realizzerò un'applicazione per supportare on-line un gioco di ruolo, voi potete ovviamente seguire la stessa strada o realizzare qualcosa di diverso.
I requisiti della nostra applicazione
Prima di buttarci nel codice, devo necessariamente spendere due parole per chiarire il dominio applicativo: per chi non lo sapesse un gioco di ruolo è quello in cui una serie di giocatori si calano nella parte di personaggi immaginari e vivono le avventure (generalmente in stile fantasy) narrate da un altro giocatore, detto master, che rappresenta il deus ex-machina della storia. Nel mio caso sto parlando del GdR "originale", in cui i giocatori si riuniscono attorno ad un tavolo e il master racconta la storia e dipana le vicende. Si dà però il caso che nel frattempo i giocatori hanno messo su famiglia e non possono più vedersi con la facilità di prima. Urge quindi trasformare le sessioni reali in sessioni virtuali (su Skype) e urge un supporto informatico per tenere traccia dell'evoluzione dei personaggi (l'equivalente digitale della scheda cartacea).
Ora che abbiamo chiaro il contesto, vediamo quali sono i primi requisiti dell'applicazione:
- Come ospite, voglio diventare membro della community per poter giocare;
- Come membro, voglio autenticarmi per poter accedere ai miei dati e alle mie partite;
- Come utente, voglio vedere la lista delle partite cui sto partecipando;
- Come utente, voglio iniziare una nuova partita come master;
- Come utente, voglio unirmi ad una partita in corso come giocatore.
Cominciare gli sviluppi dai primi due requisiti, oltre che essere tremendamente noioso, ci obbligherebbe ad un nuovo percorso teorico di studio delle modalità di autenticazione/autorizzazione di una SPA, tenendo anche conto che non ho alcuna intenzione di gestire registrazioni, nomi utenti e password ma voglio invece sfruttare i servizi offerti dai principali provider di autenticazione (Facebook, Google, Microsoft, …). Dato che vogliamo fermamente mettere un primo abbozzo dell'applicazione sotto l'albero di Natale, daremo i primi due requisiti per acquisiti e cominceremo a sviluppare dal terzo.
Per iniziare assumiamo quindi di avere un utente registrato ed autenticato che nella sua home page voglia: a) vedere la lista delle partite cui è coinvolto; b) dare inizio ad una nuova partita e c) unirsi ad una partita in corso.
Per quanto detto fino adesso, dovrebbe essere ormai chiaro che un'applicazione Aurelia si compone di due componenti: la View, che contiene il template grafico da utilizzare, e il ViewModel, che contiene i dati con cui idratare tale template per comporre la pagina finale. Un'ipotetica pagina di visualizzazione di un utente, ad esempio, avrà un certo template grafico (sempre uguale) che sarà poi riempito di volta in volta con i dati specifici del particolare utente.
Nel nostro caso, come prima cosa abbiamo quindi bisogno di un file JavaScript che conterrà le informazioni sulle partite, e di un file HTML che avrà lo scheletro della UI da far vedere all'utente. Abbiamo visto che per convenzione Aurelia carica una pagina che corrisponde alla coppia app.js/app.html. Non ci vogliamo ancora addentrare nei gorghi della configurazione, quindi per ora ci limiteremo a riusare gli stessi due file dell'esempio precedente, semplicemente aggiornandone il contenuto.
Il primo ViewModel
Se vogliamo mostrare un elenco di partite, un buon punto di partenza è avere un array contenente i dati delle stesse. In futuro tali dati arriveranno da una chiamata ad un apposito servizio ASP.NET Core, per il momento è più che sufficiente dichiarare un array hard-coded nel costruttore della classe che rappresenta il nostro ViewModel usando la sintassi ES2015:
Cominciamo con il descrivere una partita con un modello molto semplice che arricchiremo nel corso del tempo: Un identificativo univoco della partita (id), il nome della stessa (title), il nome del giocatore che svolge le funzioni di master (master) e due flag che ci dicono se possiamo unirci (canJoin) o se possiamo partecipare alla sessione di gioco corrente (canPlay).
Come vedete non c'è alcun obbligo di chiamare la classe con lo stesso nome del file (anche se è buona norma per i ViewModel per non generare confusione). Dato che già so che "a regime" la nostra coppia si chiamerà games.js/games.html, ho chiamato la classe direttamente Games.
Oltre ai dati sulle partite esistenti, per soddisfare gli ultimi due requisiti abbiamo bisogno dei due metodi per creare una nuova partita e per unirsi ad una partita esistente. Anche in questo caso ci limitiamo momentaneamente a due metodi fake con il solo scopo di verificare il funzionamento della pagina:
La prima View
Per capire come definire il template diamo prima un'occhiata al risultato finale che vogliamo ottenere:
Vogliamo quindi un panel bootstrap per ogni partita, il cui header sarà uguale al nome della partita (title) e il cui body riporterà il nome del master (master). Un button sempre nel body ci consentirà di unirsi alla partita (canJoin = true) o di accedere alla stessa (canPlay = true), mentre un altro button fuori dall'elenco permetterà di creare una nuova partita. Le partite "giocabili" saranno evidenziate in verde, le altre in rosso. Il frammento di HTML necessario ad ottenere il risultato di cui sopra (al netto delle informazioni sulle singole partite) è questo:
In questa porzione di HTML dobbiamo inserire il title (1) e il master (2). Dobbiamo inoltre fare in modo che il button (3) sia visibile solo se canJoin = true e che il suo click esegua il metodo join del ViewModel e che il link (4) sia invece visibile se canPlay = true. A seconda del valore di canPlay dobbiamo quindi aggiungere il class
panel-success o quello panel-danger al panel (5). Tutto ciò va ovviamente ripetuto per ogni partita del nostro elenco duplicando l'intero div (6). Infine dobbiamo chiamare il metodo create del ViewModel al click sul button (7).
Cos'è il binding?
Il problema diventa quindi quale meccanismo utilizzare per gestire questo travaso di dati dal ViewModel alla View.
L'approccio più semplice è ovviamente dato da un'interazione "forte" tra le due parti. Il ViewModel conosce perfettamente la struttura della View ed è in grado di individuare le parti che devono essere idratate e di iniettare nelle stesse i valori presenti al suo interno (ad esempio utilizzando jQuery con una sintassi tipo $('#title').html(this.title). Questa operazione può essere tipicamente portata a termine al momento del caricamento, fornendo all'utente una pagina già completa di tutte le informazioni.
Le cose però si complicano quando dobbiamo gestire la visibilità di alcuni elementi o quando dobbiamo gestire una lista con la conseguente necessità di clonare e iniettare nuove porzioni di HTML. Ancor peggio quando il passaggio delle informazioni deve essere bidirezionale, quando cioè le informazioni aggiornate dall'utente sulla View devono essere ritrasmesse al ViewModel, tipicamente per dare avvio ad un processo di business o ad una semplice operazione di salvataggio delle stesse. Nasce infatti l'esigenza di individuare il momento in cui riportare le informazioni sul ViewModel, intercettando una serie di eventi sulla pagina (come il click sul pulsante di submit o la digitazione in un campo di input), e in breve tempo la gestione di questi scambi di dati può diventare molto complessa e tremendamente fragile per via dello stretto collegamento tra le due parti che costringe a tenere allineato il ViewModel ad ogni cambiamento della View.
Per ovviare a questa serie di problematiche, i framework che si basano sul pattern MVVM (come Aurelia) utilizzano un meccanismo detto binding. In cosa consiste? In parole povere si tratta di una sintassi specifica che consente di specificare all'interno della View i punti in cui è necessaria l'interazione con un corrispettivo elemento del ViewModel, che sia il valore di una proprietà che deve essere riportato sulla UI, un metodo che deve essere invocato al click di un pulsante o ancora un flag che determina la visibilità o meno di un elemento grafico. La sintassi è ovviamente riconosciuta dal framework che è quindi in grado di processare la View, individuare i punti di interazione e idratare la UI con i valori provenienti dal ViewModel. In aggiunta il framework si prende anche carico della successiva gestione della vita della View, intercettando gli eventi scatenati dall'interazione dell'utente, modificando il ViewModel e di riflesso aggiornando ulteriormente la View stessa. Tutto ciò potendo mantenere il ViewModel del tutto agnostico rispetto alla View.
Nel prossimo post vedremo quali strumenti Aurelia ci mette a disposizione per gestire il binding.
Merry coding and happy new year!