INTRODUZIONE AD ALPINEJS – Un Framework JavaScript robusto e leggero

Oggi la nascita di framework, librerie e strumenti JavaScript è all’ordine del giorno e spesso è difficile districarsi in questo oceano di alternative.

A volte, inoltre, non vi è necessità di ricorrere a grandi framework come React o Vue, specialmente quando le interazioni con l’utente e la quantità di javascript sono limitate e il codice più o meno ripetitivo. Recentemente è emerso un piccolo e funzionale framework che può essere utile in questi casi: AlpineJS.

Alpine è fortemente ispirato a Vue (da cui prende la sintassi) e ad Angular (per le direttive) e promette la natura reattiva e dichiarativa dei grandi framework come Vue o React a un costo minore in termini di curva di apprendimento e complessità.

Una differenza sostanziale rispetto a questi grandi framework (ad esclusione di Angular) risiede nell’utilizzo del DOM reale e non del Virtual DOM. Con Alpine quindi è possibile mantenere il DOM e aggiungere i comportamenti nel modo più opportuno ritenuto, aggiungendo JS direttamente nel markup.

Perché AlpineJS?

Per quale motivo dovrei prendere in considerazione l’idea di imparare un nuovo framework JavaScript, in un mondo pieno di framework più completi e con una community enorme alle spalle?

Sicuramente un framework completo copre tutte le necessità funzionali anche quando si ha a che fare con piccole realtà applicative, ma in alcuni casi potrebbe sembrare di sparare ad una mosca con un cannone…

Il progetto Alpine è relativamente fresco (prima pubblicazione novembre 2019), ha grandi potenzialità e sia la community che il framework stesso sono in crescita, è molto più leggero dei grandi colossi di cui sopra (solamente 7.6kB utilizzando il caricamento dello script da CDN), elastico, veloce e di semplice utilizzo, soprattutto se si ha già avuto a che fare con Vue o Angular o con il concetto di binding.

In questo articolo vedremo una Todo App andando a cogliere alcuni delle principali funzionalità del framework; è possibile trovare il codice di questo progetto su GitHub (https://github.com/zaronc/alpinejs4GiunecoTech).

Come funziona

A differenza di altri framework (come jQuery) che “interrogano” il DOM per recuperare gli elementi, Alpine sfrutta il concetto di binding (così come Vue, React, Knockout).

Come suggerisce la documentazione dell’autore, il framework mette a disposizione 14 direttive e 6 magic properties (come le definisce l’autore stesso).

Il binding è di tipo bidirezionale, ovvero il valore di un input element viene sincronizzato con il contenuto di una variabile lato js ed ad ogni modifica su una delle due parti questa si ripercuote automaticamente anche sull’altra, aggiornando il valore. Con Alpine, il binding è reso possibile da diverse direttive, prima tra tutte la x-model.

Per esempio, questo input element, che nell’applicazione proposta permette di inserire un nuovo Todo, viene “legato” alla variabile newTodo in questo modo:

<input type="text" x-model="newTodo" placeholder="Cosa devo fare?">

Esempio: Todo App

fig. 1

La prima direttiva necessaria per partire è la x-data, che ci permette di dichiarare le variabili e i comportamenti (metodi) che utilizzeremo all’interno del componente in cui è inserita. Inoltre da questa direttiva dipendono tutte le altre direttive e le magic properties, nessuna di queste infatti può esistere al di fuori di un blocco (<div>) contenente la direttiva x-data. Dove non vi fosse necessità di definire un x-data è possibile inserirla vuota:

<div x-data: "">

La struttura prevede che alla direttiva venga assegnato un JSON:

<div x-data="[JSON data object]">...</div>

o in alternativa una funzione che restituisce un JSON, come nel caso di esempio:

<div x-data="toDoList()">...<div/>


Per la nostra Todo App avremo bisogno di due variabili e qualche metodo, definite all’interno della funzione toDoList() (di cui sono mostrate solo le definizioni):

In questo modo vengono dichiarate proprietà e funzioni utilizzate dal componente <div> di cui sopra. È importante notare che questo oggetto restituito dalla funzione toDoList() esiste solamente nello contesto di tale <div>.

NOTA: Nel codice sopra riportato ho volutamente lasciato l’implementazione del metodo addToDo(), in quanto unico punto dove si ha una struttura chiara della composizione dell’oggetto todo, che verrà richiamato nell’HTML successivamente.

In situazioni molto semplici (come un mostra / nascondi elemento) è possibile assegnare direttamente nella direttiva un valore di default:

<div x-data="{show:false}">
    <p x-show="show">Mostra questo p solo se "show" è a true</p>
    <button @click="show =! show">toggle show</button>
</div>

in questo caso, per esempio, l’elemento <p> è nascosto di default e al click del bottone viene mostrato (e nascosto al click successivo, mostrato al click ancora successivo e così via).

Dopo aver visto il concetto di binding e le sue direttive nel paragrafo precedente, riprendiamo l’esempio dell’input text utilizzato con la x-model in precedenza, riportando la versione completa di quella riga di codice, che ci permette di parlare di un’altra direttiva: la x-on.

<input type="text" x-model="newTodo" placeholder="Cosa devo fare?" class="..." x-on:keydown.enter="addToDo">

NOTA: per questa direttiva esiste anche una forma compatta: la parte “x-on:” può essere sostituita con “@”.

Questa fondamentale direttiva ci permette di gestire qualsiasi evento; in questo caso l’evento è il keydown. Quando un evento viene triggerato, come nel classico onkeydown vanilla, viene eseguita la funzione js assegnata come valore alla direttiva (che è definita sempre all’interno del modello toDoList()).

Per quanto riguarda il “.enter” che segue l’evento keydown, questo è denominato modifier. Sugli eventi keydown e keyup infatti si possono applicare modificatori per tutte le KeyboardEvent.key javascript come “enter”, “escape”, “caps-lock”, “shift” in formato kebab-case.

Questi modificatori esistono anche per altri eventi e per altre direttive come la x-bind, che vedremo più avanti. Un altro esempio di modificatore si può notare nel codice più in basso, in corrispondenza dell’evento click, il click.away, ovvero un modificatore che permette di triggerare l’evento click solo quando il click avviene al di fuori della input box su cui vi era il focus.

Tornando all’esempio, quando viene premuto Invio, viene eseguita la funzione addTodo(), che inserisce il nuovo todo nella lista (implementazione del metodo sopra) e, grazie al binding bidirezionale, l’interfaccia si aggiorna mostrandolo in coda agli altri.

Per ciclare su una lista (array nella pratica) di elementi, il framework ci mette a disposizione la direttiva x-for.

Questa direttiva può essere inserita soltanto all’interno del tag di apertura dell’elemento <template> e consente di generare codice HTML ciclando sugli elementi di un array js: nel caso di esempio “(todo, index) in todos”, todos indica l’array che si intende ciclare, mentre la coppia di valori (todo, index) indica il todo che stiamo ciclando e il relativo indice nell’array. Il codice HTML all’interno del tag <template> viene generato tante volte quanti sono gli elementi all’interno di todos.

Nell’esempio sopra riportato la x-for è necessaria per stampare delle “card” contenti i todo inseriti dall’utente, come si può vedere in fig.1.

Per quanto riguarda la x-bind, questa può essere utilizzata per settare il valore di qualsiasi attributo HTML valutando un’espressione js che restituisca il valore da settare o il booleano (laddove questo è supportato da HTML). Per esempio, nel caso trattato in questo articolo:

<input type="checkbox" @change="toggleToDoCompleted(index, $event.target.checked)" x-model="todo.completed" 
x-bind:disabled="todo.isEditing" class="mt-4">

la checkbox diventa “disabled” quando l’utente sta modificando un todo.

NOTA: anche per questa direttiva esiste una forma compatta: è possibile rimuovere dalla definizione “x-bind” lasciando solamente i “:” seguiti dal nome dell’attributo.

Nel caso invece dell’attributo class, questa direttiva torna molto utile quando vogliamo applicare determinate classi css al soddisfacimento di determinate condizioni. Ad esempio, nel codice completo riportato sopra, vengono applicate diverse classi solo quando si sta modificando un todo, ovvero il flag todo.isEditing è impostato a true, per rendere l’effetto di “disabilitato” all’interfaccia.

Magic properties

Delle 6 magic properties citate all’inizio di questo articolo, nell’esempio riportato ne abbiamo usata solamente una (dato che le altre non erano necessarie), la $event.

<input type="checkbox" @change="toggleToDoCompleted(index, $event.target.checked)" ... >

Questa magic property permette di accedere all’evento scaturito passando ad una funzione l’intero evento, oppure, se non vi è necessità di trattare l’intero evento, una proprietà specifica (come nell’esempio proposto). In questo caso, alla funzione passiamo l’indice dell’array che identifica il todo all’interno dell’array e il valore true o false a seconda della spunta della checkbox.

Altre due magic properties cui vale la pena accennare sono la $el e la $refs. La prima permette di accedere all’elemento radice in cui è definita, in altre parole l’elemento più “esterno”, ovvero quello in cui è definito l’x-data, per esempio:

<div x-data="{testEl(element){ console.log(element.id) }}" id="id-root">
    <span id="useless-span">
        <button @click="testEl($el)">Stampa id radice</button>
    </span>
</div>

al click del bottone verrà stampato in console “id-root”, l’elemento passato alla funzione testEl() sarà infatti il <div> più esterno. Inoltre, come nel caso della $event è possibile accedere alle proprietà dell’elemento direttamente dall’ HTML, per esempio sarebbe stato possibile passare direttamente l’id alla funzione testEl() con @click=”testEl($el.id)”.

La seconda magic property, $refs, lavora in combo con una direttiva: la x-ref, e permette di accedere all’elemento che contiene lo stesso nome di quest’ultima, molto utile quando si ha necessità di manipolare manualmente il DOM. Per esempio

<input type="checkbox" @change="$refs.refDiv.innerText='Ciao'">
<span x-ref="refDiv"></span>

al check della checkbox viene inserito del testo all’interno di un altro elemento, in questo caso uno <span>. Anche in questo caso, come detto per la $el, si ha accesso a tutte le proprietà dell’elemento direttamente nell’HTML.

Conclusioni: bilancio positivo

Ciò che maggiormente colpisce di Alpine è la sua semplicità di utilizzo, ma anche di organizzazione: tutto ciò che serve in un blocco di codice HTML (che può essere un’intera pagina, come nel caso di esempio, ma anche singoli componenti) ha il suo corrispettivo blocco JavaScript, semplice e pulito.

Un piccolo punto debole si può riscontrare nel poco supporto online; la community sta ancora crescendo e se si ha un problema o un dubbio non è facile reperire informazioni, anche se la documentazione ufficiale copre esaustivamente quasi tutti i topic.

Purtroppo non è stato possibile trattare tutti i dettagli in questo articolo, ma il framework offre un sacco di direttive e ha molte potenzialità. Speriamo che il progetto continui e non venga abbandonato a sé stesso; per adesso l’autore non ha intenzione di mollare! 😀

Infine, link utili: per accedere alla documentazione ufficiale (https://github.com/alpinejs/alpine#learn) del progetto presente su GitHub e al codice (https://github.com/zaronc/alpinejs4GiunecoTech) utilizzato in questo articolo.