Alpine.js

Fins ara hem treballat amb este procés:

  • definim dades en JavaScript
  • seleccionem elements del DOM
  • creem elements amb createElement
  • els afegim amb appendChild
  • reaccionem a events amb addEventListener
  • obtenim dades externes amb fetch

Tot això funciona, però implica escriure bastant codi JavaScript.

Ara farem un canvi important:

deixarem de manipular el DOM manualment i descriurem la interfície directament en el HTML

Per a això utilitzem Alpine.js.


Què és Alpine.js

Alpine.js és una llibreria de JavaScript que permet afegir comportament dinàmic al HTML d’una forma molt més simple.

Amb Alpine podem:

  • guardar dades
  • mostrar dades en la pàgina
  • reaccionar a events
  • repetir elements
  • treballar amb dades obtingudes amb fetch

Per tant, Alpine no substituïx JavaScript. El que fa és permetre que moltes coses que abans fèiem amb JavaScript i DOM manual ara es facen d’una manera més directa.


Relació amb el DOM

En el tema anterior, per mostrar una llista fèiem això:

const noms = ["Anna", "Marc", "Pau"];

const llista = document.querySelector("#llista");

noms.forEach(nom => {
    const li = document.createElement("li");
    li.textContent = nom;
    llista.appendChild(li);
});

Ací hem de:

  • seleccionar el contenidor
  • recórrer les dades
  • crear elements
  • afegir-los al DOM

Amb Alpine, este procés es simplifica molt.

No deixem de treballar amb el DOM, però deixem de manipular-lo manualment.


Afegir Alpine al projecte

Per utilitzar Alpine, hem de carregar la llibreria.

<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

Esta línia s’afegeix normalment dins del <head> o abans de tancar el <body>.

El navegador carregarà Alpine i, a partir d’eixe moment, podrem utilitzar els seus atributs especials en el HTML.


Primer concepte: x-data

El punt de partida d’Alpine és x-data.

x-data definix un espai de dades dins del HTML.

Exemple

<div x-data="{ missatge: 'Hola món' }">
    <p x-text="missatge"></p>
</div>

Resultat en la pàgina:

Hola món

Què està passant realment

En este exemple:

  • x-data crea un espai de dades
  • dins tenim una propietat anomenada missatge
  • x-text="missatge" mostra el seu valor

Açò és important perquè ara les dades i la vista queden molt més connectades.

No fa falta seleccionar el <p> amb querySelector ni modificar-lo amb textContent.


Relació amb el que ja sabem

Abans fèiem això:

<p id="text"></p>
const text = document.querySelector("#text");
text.textContent = "Hola món";

Amb Alpine:

<div x-data="{ missatge: 'Hola món' }">
    <p x-text="missatge"></p>
</div>

El resultat és el mateix, però el codi és molt més curt i directe.


x-text

x-text servix per mostrar text dins d’un element.

Exemple

<div x-data="{ nom: 'Anna' }">
    <h2 x-text="nom"></h2>
</div>

Resultat:

Anna

Alpine agafa el valor de nom i el col·loca dins de l’element.

Això equival, en la pràctica, a fer un textContent des de JavaScript, però sense escriure el JavaScript manualment.


Canviar dades amb events

En el tema del DOM utilitzàvem addEventListener.

Per exemple:

boto.addEventListener("click", function() {
    titol.textContent = "Nou títol";
});

Amb Alpine utilitzem x-on.

Exemple

<div x-data="{ comptador: 0 }">
    <button x-on:click="comptador = comptador + 1">Sumar</button>
    <p x-text="comptador"></p>
</div>

Cada vegada que fem clic en el botó, el valor de comptador augmenta.

Forma abreujada

També es pot escriure així:

<button @click="comptador = comptador + 1">Sumar</button>

Les dos formes són correctes.


Què està passant

En este exemple:

  • x-data conté la dada comptador
  • @click executa codi quan fem clic
  • x-text mostra el valor actual

No hem seleccionat cap element amb querySelector, no hem creat cap event amb addEventListener i no hem modificat el DOM manualment.


x-model

Fins ara, quan treballàvem amb inputs en JavaScript, fèiem això:

const input = document.querySelector("#nom");
console.log(input.value);

Amb Alpine tenim x-model.

x-model connecta un input amb una dada.

Exemple

<div x-data="{ nom: '' }">
    <input type="text" x-model="nom">
    <p x-text="nom"></p>
</div>

Quan l’usuari escriu en l’input, el paràgraf s’actualitza automàticament.

Què està passant

  • nom comença buit
  • x-model="nom" fa que el valor de l’input i la dada nom estiguen connectats
  • x-text="nom" mostra el valor actual

Això és una gran diferència respecte al DOM manual.

Abans havíem de:

  • seleccionar l’input
  • llegir value
  • escoltar l’event input
  • actualitzar un altre element

Ara tot això queda resolt amb una sola connexió.


x-show

x-show permet mostrar o ocultar un element segons una condició.

Exemple

<div x-data="{ visible: true }">
    <button @click="visible = !visible">Mostrar / ocultar</button>
    <p x-show="visible">Este text es pot ocultar</p>
</div>

Si visible és true, el paràgraf es mostra. Si visible és false, el paràgraf s’oculta.

Això és útil quan volem controlar la visibilitat d’elements sense eliminar-los del document.


x-bind

x-bind permet assignar atributs de forma dinàmica.

Exemple

<div x-data="{ imatge: 'foto1.jpg' }">
    <img x-bind:src="imatge" alt="Imatge">
</div>

Forma abreujada:

<img :src="imatge" alt="Imatge">

Això fa que l’atribut src depenga del valor de la dada imatge.

És l’equivalent a modificar atributs amb JavaScript, però ara la relació queda declarada directament en el HTML.


Repetir elements amb x-for

Este és un dels punts més importants perquè connecta directament amb el que fèiem amb forEach.

Exemple amb dades dins del HTML

<div x-data="{ noms: ['Anna', 'Marc', 'Pau'] }">
    <ul>
        <template x-for="nom in noms" :key="nom">
            <li x-text="nom"></li>
        </template>
    </ul>
</div>

Resultat:

<li>Anna</li>
<li>Marc</li>
<li>Pau</li>

Què està passant

  • noms és un array
  • x-for recorre l’array
  • per cada element crea un li
  • x-text mostra el valor

En el tema del DOM havíem de fer això amb:

  • forEach
  • createElement
  • appendChild

Ara el procés queda expressat directament en el HTML.


Per què apareix template

x-for s’aplica habitualment sobre un element <template>.

Això permet a Alpine repetir el contingut de dins tantes vegades com calga.

No és un element que es veja en la pàgina. Servix només com a plantilla interna.


:key en x-for

En els exemples amb x-for apareix:

:key="nom"

La key permet identificar cada element de forma única.

En exemples simples pot semblar un detall menor, però és convenient acostumar-se a posar-la, sobretot quan treballem amb llistes.

Si els elements tenen id, és millor usar l’id.


Treballar amb objectes

També podem guardar objectes dins de x-data.

Exemple

<div x-data="{ alumne: { nom: 'Anna', edat: 17 } }">
    <p x-text="alumne.nom"></p>
    <p x-text="alumne.edat"></p>
</div>

Resultat:

Anna
17

Ací Alpine està accedint a propietats d’un objecte igual que fèiem en JavaScript.


Exemple més complet

<div x-data="{ 
    alumne: { nom: 'Anna', edat: 17 },
    visible: true
}">
    <h2 x-text="alumne.nom"></h2>
    <p x-text="alumne.edat"></p>
    <button @click="visible = !visible">Mostrar / ocultar edat</button>
    <p x-show="visible" x-text="alumne.edat"></p>
</div>

En este exemple combinem:

  • objectes
  • events
  • text dinàmic
  • mostrar i ocultar

x-init

x-init permet executar codi quan el component es carrega.

Això és especialment important quan volem combinar Alpine amb fetch.

Exemple

<div x-data="{ missatge: 'Carregant...' }" x-init="missatge = 'Contingut preparat'">
    <p x-text="missatge"></p>
</div>

Quan el bloc es carrega, s’executa el codi de x-init.


fetch amb Alpine

En el tema anterior utilitzàvem fetch així:

fetch("noms.json")
    .then(res => res.json())
    .then(noms => {
        const llista = document.querySelector("#llista");

        noms.forEach(nom => {
            const li = document.createElement("li");
            li.textContent = nom;
            llista.appendChild(li);
        });
    });

Amb Alpine, la idea és la mateixa, però deixem de manipular el DOM manualment.

Exemple

<div
    x-data="{ noms: [] }"
    x-init="
        fetch('noms.json')
            .then(res => res.json())
            .then(dades => noms = dades)
    "
>
    <ul>
        <template x-for="nom in noms" :key="nom">
            <li x-text="nom"></li>
        </template>
    </ul>
</div>

Procés complet

  1. x-data crea una dada inicial: noms com a array buit
  2. x-init s’executa quan el component es carrega
  3. fetch obté les dades del fitxer
  4. quan arriben, fem noms = dades
  5. Alpine actualitza automàticament el HTML

Açò és molt important:

ja no creem els elements amb createElement() ni els afegim amb appendChild()

Ara simplement actualitzem les dades i Alpine actualitza la vista.


Relació amb el que hem fet abans

El procés general és el mateix que ja coneixem:

  • tenim dades
  • les mostrem en la pàgina

La diferència és la manera de fer-ho.

Amb DOM manual

  • seleccionem
  • creem
  • inserim

Amb Alpine

  • definim dades
  • descrivim el HTML
  • Alpine connecta les dos coses

Què estem deixant de fer

Amb Alpine deixem de necessitar, en molts casos:

  • querySelector
  • textContent
  • createElement
  • appendChild
  • addEventListener

No perquè deixen d’existir, sinó perquè Alpine resol eixes tasques d’una forma més directa.


Exemple comparatiu

DOM manual

const noms = ["Anna", "Marc", "Pau"];
const llista = document.querySelector("#llista");

noms.forEach(nom => {
    const li = document.createElement("li");
    li.textContent = nom;
    llista.appendChild(li);
});

Alpine

<div x-data="{ noms: ['Anna', 'Marc', 'Pau'] }">
    <ul>
        <template x-for="nom in noms" :key="nom">
            <li x-text="nom"></li>
        </template>
    </ul>
</div>

El resultat és equivalent, però la versió amb Alpine és més declarativa i més curta.


Resum

  • Alpine.js permet afegir comportament dinàmic al HTML
  • x-data definix dades
  • x-text mostra text
  • x-on o @event gestiona events
  • x-model connecta inputs amb dades
  • x-show mostra o oculta elements
  • x-bind assigna atributs dinàmics
  • x-for repetix elements a partir d’un array
  • x-init executa codi inicial
  • Alpine permet combinar dades externes (fetch) amb una vista que s’actualitza automàticament

Relació amb el següent pas

Amb tot el que hem fet fins ara, el recorregut queda així:

  • HTML → estructura
  • JavaScript → lògica
  • DOM → manipulació manual de la pàgina
  • fetch → obtenció de dades externes
  • Alpine → connexió directa entre dades i HTML

A partir d’ací, el treball es fa més còmode perquè deixem de centrar-nos en la manipulació manual del DOM i passem a centrar-nos en:

  • les dades
  • l’estat de la interfície
  • la relació entre dades i vista