// BLOG

Le framework AlpineJS

AlpineJS est un micro-framework JavaScript pour réaliser des interactions de base dans des pages web. Le code s'écrit à l'aide de directives que l'on ajoute aux balises HTML et dont la synthaxe s'inspire fortmement de celle du framework Vue.js

Rédigé le 04.04.2020
Par Gilles Vauvarin

JavaScript Framework

Présentation

AlpineJS est un framework javaScript développé par Caleb Porzio permettant de manipuler le DOM et de dynamiser vos pages web. Pour concevoir AlpineJS, Caleb Porzio s'est inspiré de la synthaxe VueJS et du framework CSS TailwindCSS. Le framework met à disposition des directives et des propriétés que l'on utilise directement dans les balises HTML.

C'est un framework léger (6,4kb GZippé et minifié ) qui s'utilise simplement via un lien CDN ou une installation via npm. Pas de DOM virtuel ni d'étape de build.

AlpineJS vient se placer entre le VanillaJS/Jquery et les frameworks comme React/VueJS, il permet de très facilement manipuler le DOM pour par exemple :

Masquer/montrer des noeuds du DOM selon certaines conditions

Manipuler dynamiquement des classes

Modifier l'interface selon les actions de l'utilisateur

Lier des données saisies par l'utilisateur à d'autres éléments

AlpineJS est idéal si vous souhaitez développer des composants dynamiques comme des onglets, des modals, des dropdown menu, des sidebares escamotables etc … dans un projet de type "server-side rendering (Laravel, CMS, Django, Ruby on Rails …) Ce n'est pas, en revanche, un framework adapté pour créer des applications de type single page avec un système de routes pour naviguer dans l'application.

Il peut être utilisé sans avoir besoin d'utiliser/maintenir un fichier js externe ou du javaScript dans une balise <script>. Cependant, selon les cas, cela reste possible et le code peut être organisé en plusieurs fichiers.

En couplant les frameworks TailwindCSS et AlpineJS, on peut par exemple construire sa propre bibliothèque de composants dynamiques sur mesure et l'utiliser dans ses projets.

Installation

AlpineJS s'installe et s'intègre dans un projet existant très facilement.

Depuis un CDN via une balise <script>

<head>
<!-- ... remplacer "2.x.x" par la version souhaitée ! -->
// pour IE11
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine-ie11.js" defer></script>
// pour les autres navigateurs
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.js" defer></script>
</head>

A partir de NPM

Installer le package AlpineJS depuis npm

npm i alpinejs

Puis inclure AlpineJS dans vos scripts

import 'alpinejs'

Les directives

Les directives sont des attributs placés dans les balises html et spécifiques au framework. Elles servent à transformer ou modifier le DOM (Document Object Model).

x-data

Cette directive indique au framework d'initialiser un nouveau composant avec des données au format objet. Le champ d'application des ces données s'applique au niveau du composant dans lequel x-data est déclaré.

// ici le champ d'application s'applique au niveau du <div></div> et donc aussi aux noeuds imbriqués dans ce <div>
<div x-data="{ foo: 'bar' }">
<h1>Hello word!</h1>
</div>

Les datas peuvent être transmises directement, via un objet, dans la directive x-data ou indirectement à l'aide d'une fonction définie soit dans une balise <script> soit dans un fichier .js externe :

<div x-data="helloWord()">
  <button @click="display()">Display message</button>
  <div>Message: <span x-text="messageDisplay"></span></div>
</div>

<script>
function helloWord() {
  return {
    message: 'Hello Word!',
    messageDisplay: '',
    display() {
      this.messageDisplay = this.message;
    }
  }
}
</script>

// quand on clique sur le bouton "Display message" le message "Hello Word!" est affiché dans la balise <span>

On notera que depuis la méthode display(), Alpine.js nous permet d'accéder aux autres propriétés de l'objet via le mot clé this

Il est aussi possible de récupérer des données d'une API et de les transmettre à la directive x-data Pour cela, nous assignerons une fonction à la directive x-data et cette fonction utilisera l'API fetch pour récupérer les données externes.

<div x-data="pokeSearch()">
  <!-- rest of HTML -->
  <button
    type="submit"
    @click="fetchPokemon()"
    :disabled="isLoading"
  >
    Search
  </button>
  <script>
  function pokeSearch() {
    return {
      // other default properties
      isLoading: false,
      pokemon: null,
      fetchPokemon() {
        this.isLoading = true;
        fetch(`https://pokeapi.co/api/v2/pokemon/${this.pokemonSearch}`)
          .then(res => res.json())
          .then(data => {
            this.isLoading = false;
            this.pokemon = data;
          });
      }
    }
  }
 </script>
</div>

x-init

La directive x-init permet d'éxecuter une expression javaScript au moment de l'initialisation du composant.

// initialisation de la propriété "message"
<div x-data="{ message: '' }" x-init="message = 'Hello buddy!'"></div>

// initialisation de la propriété "message" à partir de données externes (API)
<div x-data="{ message: '' }" x-init="message = fetch('https://api.kanye.rest') .then(response => response.json()) .then(data => quote = data.quote)"></div>

// utilisation d'une fonction
<div x-data="{ message: '' }" x-init="message = displayQuote()"></div>
<script>
function displayQuote() {
 return {
  fetch('https://api.kanye.rest') .then(response => response.json()) .then(data => quote = data.quote);
 }
}
</script>

Si vous souhaitez exécuter l'expression javaScript après que Alpinejs ait effectué ses premières mises à jour du DOM, vous pouvez retourner une fonction de callback sur x-init, et elle sera exécuté après.

<div x-data="{ message: '' }" x-init="return () => { // nous avons accès à l'état post mise à jour du DOM ici ... // }"></div>

x-show

Cette directive ajoute ou retire un style display:none sur l'élément ciblé selon que l'expression renvoie true ou false. Cette directive permet donc de masquer ou montrer un élément selon la valeur booléenne renvoyée par l'expression.

x-show met à notre disposition les transitions CSS pour appliquer des effets plus élaborés.

<div x-show.transition="open">
    These contents will be transitioned in and out.
</div>

Liste des transitions disponibles :

Directive Description
x-show.transition A simultanious fade and scale. (opacity, scale: 0.95, timing-function: cubic-bezier(0.4, 0.0, 0.2, 1), duration-in: 150ms, duration-out: 75ms)
x-show.transition.in Ony transition in.
x-show.transition.out Ony transition out.
x-show.transition.opacity Only use the fade.
x-show.transition.scale Only use the scale.
x-show.transition.scale.75 Customize the CSS scale transform
transform: scale(.75). x-show.transition.duration.200ms Sets the "in" transition to 200ms. The out will be set to half that (100ms).
x-show.transition.origin.top.right Customize the CSS transform origin
transform-origin: top right. x-show.transition.in.duration. 200ms.out.duration.50ms Different durations for "in" and "out".

Ces transitions peuvent être utilisées en conjonction les unes avec les autres :

x-show.transition.in.duration.100ms.origin.top.right.opacity.scale.85.out.duration.200ms.origin.bottom.left.opacity.scale.95

x-show attend que les enfants aient terminé leur transition. Si vous voulez contourner ce comportement, ajoutez le modificateur .immediate

<div x-show.immediate="open">
    <div x-show.transition="open">
</div>

x-bind

Cette directive lie la valeur d'un attribut HTML au résultat d'une expression javaScript. L'expression javaScript a accès à toutes les clés de l'objet de données du composant et la valeur de l'attribut est mis à jour chaque fois que les données de l'objet sont modifiés.

// structure
<input x-bind:[attribute]="[expression]">

// écriture longue
<input x-bind:type="inputType">

// écriture courte
<input :type="inputType">

x-bind pour les attributs de classe

Pour les classes, on passe un objet dont la clé est le nom de la classe et la valeur, une expression boolean qui détermine si cette classe est appliquée ou pas à l'élément.

<div x-bind:class="{ 'hidden': foo }"></div>
// la classe "hidden" ne sera appliquée que lorsque "foo" aura la valeur "true"

x-bind pour les attributs de type boolean

x-bind supporte les attributs booléens de la me manière que les attributs ayant une valeur, utilisant une variable ou une expression javaScript retournant true or false.

<button x-bind:disabled="myVar">Click me</button>
// l'attribut "disabled" sera ajouté ou retiré à la balise "button" selon que "myVar" est true ou false respectivement.

Les attributs booléens sont pris en charge conformément à la spécification HTML

x-on

Cette directive attache un "event listener" à l'élément auquel elle est rattachée. Lorsque cet événement est émis, l'expression JavaScript définie comme sa valeur est exécutée.

Si une donnée est modifiée dans l'expression, les attributs des autres éléments "liés" à cette donnée seront mis à jour.

// structure
<button x-on:[event]="[expression]"></button>

// écriture longue
<button x-on:click="foo = 'bar'"></button>

// écriture courte
<button @:click="foo = 'bar'"></button>

keydown modifiers

Il est possible des spécifier des touches à écouter en utilisant les modificateurs de touches annexés à la directive x-on:keydown. Notez que les modificateurs sont des versions kebab des valeurs de Event.key. enter, escape, arrow-up, arrow-down

<input type="text" x-on:keydown.escape="open = false">

Les combinaisons de touches peuvent également être écouté : x-on:keydown.ctl.enter="foo"

.away modifier

Lorsque le modificateur .away est présent, le gestionnaire d'événement ne sera exécuté que si l'événement provient d'une source autre que lui-même ou ses enfants.

Ceci est utile pour masquer les dropdownns et les modals lorsqu'un utilisateur clique à côté des ces éléments.

<div x-on:click.away="showModal = false"></div>

.prevent modifier

L'ajout de .prevent à l'écoute d'un événement appelle preventDefault sur l'événement déclenché.

<input type="checkbox" x-on:click.prevent>

Dans l'exemple ci-dessus, cela signifie que la case à cocher ne sera pas réellement cochée lorsqu'un utilisateur cliquera dessus.

.stop modifier

L'ajout de .stop à l'écoute d'un événement appelle stopPropagation sur l'événement déclenché.

<div x-on:click="foo = 'bar'"><button x-on:click.stop></button></div>

Dans l'exemple ci-dessus, cela signifie que l'événement "click" ne s'étendra pas du bouton jusqu'au <div> extérieur. En d'autres termes, lorsqu'un utilisateur clique sur le bouton, foo ne sera pas réglé sur "bar".

.window modifier

L'ajout de .window à un listener d'événement, installera le listener sur l'objet global window au lieu du noeud DOM sur lequel il est déclaré. Ceci est utile lorsque vous souhaitez modifier l'état d'un composant lorsque quelque chose change avec la fenêtre, comme son redimensionnement.

<div x-on:resize.window="isOpen = window.outerWidth > 768 ? false : open"></div>

Dans l'exemple ci-dessus, lorsque la fenêtre s'agrandit de plus de 768 pixels de large, nous fermons le modal/dropdown, sinon nous maintenons le même état.

Vous pouvez également utiliser le modificateur .document à la place de .window

.once modifier

L'ajout du modificateur .once à un listener d'événement garantira que le listener ne sera traité qu'une seule fois. Cela est utile pour les choses que vous ne voulez faire qu'une seule fois.

<button x-on:mouseenter.once="fetchSomething()"></button>

.debounce modifier

Le modificateur .debounce vous permet de "débloquer" un gestionnaire d'événements. En d'autres termes, le gestionnaire d'événements ne fonctionnera PAS tant qu'un certain temps ne se sera pas écoulé depuis le dernier événement qui s'est déclenché. Lorsque le gestionnaire est prêt à être appelé, le dernier appel du gestionnaire sera exécuté.

<input x-on:input.debounce="fetchSomething()">

Le temps d'attente par défaut pour le déblocage est de 250 millisecondes.

Si vous souhaitez personnaliser ce temps, vous pouvez spécifier un temps d'attente personnalisé de la sorte :

<input x-on:input.debounce.750="fetchSomething()">
<input x-on:input.debounce.750ms="fetchSomething()">

x-model

La diractive x-model ajoute une "liaison de données bidirectionnelle" à un élément. En d'autres termes, la valeur de l'élément d'entrée sera maintenue en synchronisation avec la valeur de l'élément du composant

// structure
<input type="text" x-model="[data item]">

// exemple
<input type="text" x-model="foo">

x-model détecte les changements sur les champs inputs, les cases à cocher, les boutons radio, les textareas, les menus de sélections et les menus de sélections multiples.

.debounce modifier

Le modificateur debounce permet d'ajouter un "débloqueur" à la mise à jour d'une valeur. En d'autres termes, le gestionnaire d'événements ne s'exécutera PAS avant qu'un certain temps ne se soit écoulé depuis le dernier événement qui a déclenché la mise à jour. Lorsque le gestionnaire est prêt à être appelé, le dernier appel du gestionnaire sera exécuté.

Le temps de débloquage par défaut est de 250 milliseconds.

Vous pouvez peronnaliser ce temps d'atente de la façon suivante :

<input x-model.debounce.750="search">
<input x-model.debounce.750ms="search">

x-text

La directive x-text fonctionne de la même manière que la directive x-bind, sauf qu'au lieu de mettre à jour la valeur d'un attribut, elle met à jour le innerText d'un élément.

// structure
<span x-text="[expression]"

// exemple
<span x-text="foo"></span>

x-html

La directive x-html fonctionne de la même manière que la directive x-bind, sauf qu'au lieu de mettre à jour la valeur d'un attribut, elle met à jour le innerHTML d'un élément.

// structure
<span x-html="[expression]"

// exemple
<span x-html="foo"></span>

x-ref

La directive x-ref offre un moyen pratique de récupérer des éléments DOM bruts de votre composant. En définissant un attribut x-ref sur un élément, vous le mettez à la disposition de tous les gestionnaires d'événements à l'intérieur d'un objet appelé $refs.

C'est une alternative utile à la définition d'ids et à l'utilisation de document.querySelector.

// structure
<div x-ref="[ref name]"></div><button x-on:click="$refs.[ref name].innerText = 'bar'"></button>

// exemple
<div x-ref="foo"></div><button x-on:click="$refs.foo.innerText = 'bar'"></button>

Vous pouvez également lier des valeurs dynamiques pour x-ref si besoin.

<span :x-ref="item.id"></span>

x-if

Pour les cas où la directive x-show n'est pas suffisante (x-show définit un élément display: none aucun s'il est faux), x-if peut être utilisé pour retirer effectivement un élément complètement du DOM.

Il est important que la directive x-if soit utilisée sur une balise <template></template> car AlpineJS n'utilise pas de DOM virtuel.

// structure
<template x-if="[expression]"><div>Some Element</div></template>

// exemple
<template x-if="true"><div>Some Element</div></template>

La directive x-if doit avoir un élément racine unique (ici <div> )à l'intérieur de la balise <template></template>

x-for

La directive x- for est disponible dans les cas où vous souhaitez créer de nouveaux nœuds DOM pour chaque élément d'un tableau.

// exemple
<template x-for="item in items" :key="item">
    <div x-text="item"></div>
</template>

La clé :key est optionnelle mais HAUTEMENT recommandée.

Si vous voulez accéder à l'index actuel de l'itération, utilisez la syntaxe suivante :

<template x-for="(item, index) in items" :key="index">
    <!-- You can also reference "index" inside the iteration if you need. -->
    <div x-text="index"></div>
</template>

Il est possible d'imbriquer des directives x-for mais vous DEVEZ "envelopper" chaque boucle dans un élément (ici un élément <div>)

<template x-for="item in items">
    <div>
        <template x-for="subItem in item.subItems">
            <div x-text="subItem"></div>
        </template>
    </div>
</template>

x-transition

AlpineJS propose 6 directives différentes pour gérer la transition entre les états "cachés" et "montrés" d'un élément. Ces directives fonctionnent à la fois avec la directive x-show ET la directive x-if.

Directive Description
:enter Appliqué pendant toute la phase d'entrée.
:enter-start Ajouté avant l'insertion de l'élément, retiré un cadre après l'insertion de l'élément.
:enter-end Ajout d'un cadre après l'insertion d'un élément (en même temps que la suppression de enter-start), suppression lorsque la transition/animation se termine.
:leave Appliqué pendant toute la phase de départ.
:leave-start Ajouté immédiatement lorsqu'une transition de départ est déclenchée, supprimé après un cadre.
:leave-end Ajout d'un cadre après le déclenchement d'une transition de départ (en même temps que le départ est supprimé), suppression lorsque la transition/animation se termine.
<div
    x-show="open"
    x-transition:enter="transition ease-out duration-300"
    x-transition:enter-start="opacity-0 transform scale-90"
    x-transition:enter-end="opacity-100 transform scale-100"
    x-transition:leave="transition ease-in duration-300"
    x-transition:leave-start="opacity-100 transform scale-100"
    x-transition:leave-end="opacity-0 transform scale-90"
>...</div>
<template x-if="open">
    <div
        x-transition:enter="transition ease-out duration-300"
        x-transition:enter-start="opacity-0 transform scale-90"
        x-transition:enter-end="opacity-100 transform scale-100"
        x-transition:leave="transition ease-in duration-300"
        x-transition:leave-start="opacity-100 transform scale-100"
        x-transition:leave-end="opacity-0 transform scale-90"
    >...</div>
</template>

x-cloak

Les attributs x-cloak sont retirés des éléments lorsque AlpineJS s'initialise. Ceci est utile pour cacher les DOM pré-initialisés. Typiquement utilisé pour ajouter le style global suivant :

<style>
    [x-cloak] { display: none; }
</style>

Les propriétés magiques

$el

$el est une propriété magique qui peut être utilisée pour récupérer le nœud DOM du composant racine.

// exemple
<div x-data>
    <button @click="$el.innerHTML = 'foo'">Replace me with "foo"</button>
</div>

$refs

$refs est une propriété magique qui peut être utilisée pour récupérer les éléments DOM marqués d'un x-ref à l'intérieur du composant. Ceci est utile lorsque vous devez manipuler manuellement des éléments DOM.

// exemple
<span x-ref="foo"></span>
<button x-on:click="$refs.foo.innerText = 'bar'"></button>

$event

$event est une propriété magique qui peut être utilisée dans un auditeur d'événement pour récupérer l'objet "Event" du navigateur natif.

// exemple
<input x-on:input="alert($event.target.value)">

$dispatch

$dispatch est un raccourci pour créer un CustomEvent et l'expédier en utilisant .dispatchEvent() en interne. Il existe de nombreux cas d'utilisation pour la transmission de données entre les composants et autour de ceux-ci à l'aide d'événements personnalisés. Lisez ici pour plus d'informations sur le système CustomEvent sous-jacent dans les navigateurs.

// exemple
<div @custom-event="console.log($event.detail.foo)">
    <button @click="$dispatch('custom-event', { foo: 'bar' })">
    <!-- When clicked, will console.log "bar" -->
</div>

Vous remarquerez que toute donnée passée comme deuxième paramètre à $dispatch('some-event', { some : 'data' }), devient disponible grâce à la nouvelle propriété "detail" des événements : $event.detail.some. L'ajout de données d'événements personnalisés à la propriété .detail est une pratique courante pour les CustomEvents dans les navigateurs. Pour plus d'informations, cliquez ici.

Vous pouvez également utiliser $dispatch() pour déclencher des mises à jour de données pour les directive de binding x-model.

// exemple
<div x-data="{ foo: 'bar' }">
    <span x-model="foo">
        <button @click="$dispatch('input', 'baz')">
        <!-- After the button is clicked, `x-model` will catch the bubbling "input" event, and update foo to "baz". -->
    </span>
</div>

$nextTick

$nextTick est une propriété magique qui vous permet de n'exécuter une expression donnée qu'APRÈS que AlpineJS ait fait ses mises à jour réactives de DOM. Ceci est utile pour les fois où vous voulez interagir avec l'état DOM APRÈS qu'il ait reflété les mises à jour de données que vous avez faites.

// exemple
<div x-data="{ fruit: 'apple' }">
    <button
        x-on:click="
            fruit = 'pear';
            $nextTick(() => { console.log($event.target.innerText) });
        "
        x-text="fruit"
    >
    </button>
</div>

$watch

Vous pouvez "surveiller" la propriété d'un composant avec la méthode magique $watch. Dans l'exemple ci-dessus, lorsque l'on clique sur le bouton et qu'on le modifie, le callback fourni se déclenche et console.log la nouvelle valeur.

// exemple
<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
    <button @click="open = ! open">Toggle Open</button>
</div>

Passons à la pratique

Après avoir parcouru la documentation d'AlpineJS (traduite de la documentation officielle), voyons comment utiliser ce framework avec quelques cas concrets issus de l'excellent site scrimba.com qui propose des vidéos de présentation d'AlpineJS. Dans les exemples suivants, le framework TailwindCSS est utilisé pour styler les élements. AlpineJS et TailwindCSS sont importés depuis un CDN, c'est pratique pour les exemples mais pour un projet en production, ces fichiers doivent être minifiés et purgés pour des raisons de performances.

Exemple 1 : Création d'une modal

Dans cette exemple, nous allons créer un bouton pour ouvrir la modal, celle-ci étant masquée par défaut. Dans la modal, un autre bouton sera créé pour la fermer.

Initialisation du composant principal avec un objet

Nous commençons par initialiser un composant dans une balise <div> avec un objet à l'aide de la directive x-data L'objet possède une propriété que nous appelons modalOpen et nous initialisons sa valeur à false. La propriété modalOpen aura sa valeur à false lorsque la modale sera fermée et à true lorsqu'elle sera ouverte. Par défaut, la modal est fermée, c'est pourquoi nous initialisons modalOpen à false.

<div 
   x-data="{ modalOpen: false }"
>
...
</div>

Création du bouton d'ouverture de la modal

Sur ce bouton, nous ajoutons une directive x-on pour attacher un event-listener "click". Nous utilisons ici la notation courte @click Lorsque l'utilisateur clique sur ce bouton d'ouverture, nous passons la valeur de la propriété modalOpen à true

Nous utilisons également sur cet event-listener la propriété magique $nexTick pour executer une fonction fléchée après la mise à jour des données. Cette fonction donne le focus sur l'élément DOM modalCloseButton identifiée ainsi à l'aide d'une directive x-ref Cet élément n'est autre que le bouton de fermeture de la modal.

<div 
   x-data="{ modalOpen: false }"
>
<button 
   class="bg-blue-700 text-white px-4 py-3 mt-4 text-sm rounded" 
   @click="modalOpen = true
   $nextTick(() => $refs.modalCloseButton.focus())"
>
Open this modal
</button>
...
</div>

Creation de la modal et de son bouton de fermeture

La modal est positionée en absolute via des classes TailwindCSS, je ne détaillerai pas cette partie qui concerne le framework CSS.

La balise <div> qui définie la modal contient une directive x-show qui masque la modal lorsque la valeur de la propriété modalOpen est à false ou la montre lorsqu'elle est à true.

Le bouton de fermeture contient une directive x-on qui écoute sur le clique de l'utilisateur. Nous utilisons ici la notation courte @click Lorsque l'utilisateur clique sur le bouton de fermeture, la valeur de la propriété modalOpen est passé à false La directve x-show branchée sur la valeur de propriété modalOpen reçoit alors le signal de masquer la modal.

<div
                class="mx-auto absolute top-0 left-0 w-full h-full flex items-center shadow-lg overflow-y-auto"
                x-show="modalOpen"
            >
                <div class="bg-gray-400 container mx-auto rounded p-4 mt-2 overflow-y-auto">
                    <div class="bg-white rounded px-8 py-8">
                        <h1 class="font-bold text-2xl mb-3">Modal</h1>
                        <div class="modal-body">
                            <p>I am open, isn't it!
                            </p>
                        </div>
                        <div class="mt-4">
                            <button 
                                class="bg-blue-700 text-white px-4 py-3 mt-4 text-sm rounded"
                                @click="modalOpen = false" 
                                x-ref="modalCloseButton"
                            >
                                Close this modal
                            </button>
                        </div>
                    </div>
                </div>
            </div>

Code complet :
Copiez/collez ce code dans un fichier modal.html puis ouvrez le dans une fenêtre de votre navigateur.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AlpineJS - Modal</title>
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.2.5/dist/alpine.js" defer></script>

</head>

<body class="bg-gray-200">
    <div class="container mx-auto px-4 xl:px-64 mt-12 mb-12">
        <h2 class="text-2xl font-bold">AlpinJS - Modal</h2>

        <div 
            x-data="{ modalOpen: false }"
        >
            <button 
                class="bg-blue-700 text-white px-4 py-3 mt-4 text-sm rounded" 
                @click="modalOpen = true
                $nextTick(() => $refs.modalCloseButton.focus())
            ">
                Open this modal
            </button>

            <div
                class="mx-auto absolute top-0 left-0 w-full h-full flex items-center shadow-lg overflow-y-auto"
                x-show="modalOpen"
            >
                <div class="bg-gray-400 container mx-auto rounded p-4 mt-2 overflow-y-auto">
                    <div class="bg-white rounded px-8 py-8">
                        <h1 class="font-bold text-2xl mb-3">Modal</h1>
                        <div class="modal-body">
                            <p>I am open, isn't it!
                            </p>
                        </div>
                        <div class="mt-4">
                            <button 
                                class="bg-blue-700 text-white px-4 py-3 mt-4 text-sm rounded"
                                @click="modalOpen = false" 
                                x-ref="modalCloseButton"
                            >
                                Close this modal
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>

Exemple 2 : Création d'une sidebar escamotable

Dans cet exemple, une icone représentant un menu hamburger permet d'ouvrir une sidebar contenant un menu d'items. Cette sidebar dispose d'une icone "X" pour la masquer. Cet exemple dans son fonctionnement est assez proche de celui de la modal. Un élément est masqué par défaut et positionné en "absolute". Un clique de l'utilisateur à un endroit de l'interface ouvre cet élément, un clique à un autre endroit ferme l'élément.

Initialisation du composant principal avec un objet

Le composant principal est une balise <nav> dans laquelle on initialise un objet contenant une propriété sidebarOpen dont la valeur est à false. Cette propriété nous servira à indiquer si la sidebar est masquée (valeur à false) ou pas (valeur à true).

<nav 
   class="container mx-auto px-4 flex items-center justify-between py-4" 
   x-data="{ sidebarOpen: false }"
>
...
</nav>

Création du bouton d'ouverture de la sidebar

Une icone hamburger permettra à l'utilisateur d'ouvrir la sidebar à l'aide d'une directive x-on qui écoute sur l'événement clique. Celle-ci est écrite dans sa version courte @click et modifie à true la valeur de la propriété sidebarOpen lorsque l'utilisateur clique sur l'icone.

<button
   @click="sidebarOpen = true"
>
<svg fill="currentColor" viewBox="0 0 24 24" width="24" height="24">
<path class="heroicon-ui" d="M4 5h16a1 1 0 010 2H4a1 1 0 110-2zm0 6h16a1 1 0 010 2H4a1 1 0 010-2zm0 6h16a1 1 0 010 2H4a1 1 0 010-2z"/>
</svg>
</button>

Création de l'élément sidebar

L'élément sidebar est définie dans une balise <div> positionnée de façon absolute via des classes TailwindCSS. Une directive x-show masque ou montre la sidebar selon la valeur de la propriété sidebarOpen

Une directive x-on (ici en notation courte @click) écoute un événement "clique" en dehors de l'élément à l'aide du "modifier" .away Ainsi lorsque l'utilisateur clique en dehors de l'élément sidebar, la valeur de la propriété sidebarOpen est passé à false et la directive x-show masque l'élément sidebar.

<div 
   class="absolute top-0 left-0 bg-white border border-right h-full w-48" 
   x-show="sidebarOpen" 
   @click.away="sidebarOpen = false"
>
...
</div>

Code complet :

Copiez/collez ce code dans un fichier sidebar.html puis ouvrez le dans une fenêtre de votre navigateur.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AlpineJS - Sidebar</title>
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.2.5/dist/alpine.js" defer></script>

</head>
<body class="bg-gray-300 text-gray-800">
    <header class="border-t-4 border-blue-700 bg-white shadow-md">
        <nav 
            class="container mx-auto px-4 flex items-center justify-between py-4" 
            x-data="{ sidebarOpen: false }"
        >
            <div class="flex">                    
                <button
                    @click="sidebarOpen = true"
                >
                <svg fill="currentColor" viewBox="0 0 24 24" width="24" height="24"><path class="heroicon-ui" d="M4 5h16a1 1 0 010 2H4a1 1 0 110-2zm0 6h16a1 1 0 010 2H4a1 1 0 010-2zm0 6h16a1 1 0 010 2H4a1 1 0 010-2z"/></svg>
                </button>
            </div>
            <div 
                class="absolute top-0 left-0 bg-white border border-right h-full w-48" 
                x-show="sidebarOpen" 
                @click.away="sidebarOpen = false"
            >
                <div class="flex justify-end">
                    <button 
                        class="text-2xl mr-3 mt-1" 
                        @click="sidebarOpen = false"
                    >&times;</button>
                </div>
                <ul>
                    <li>
                        <a href="#" class="flex items-center px-3 py-3 hover:bg-gray-200">
                            <svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="text-gray-600"><path d="M12 12a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm0-2a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm9 11a1 1 0 0 1-2 0v-2a3 3 0 0 0-3-3H8a3 3 0 0 0-3 3v2a1 1 0 0 1-2 0v-2a5 5 0 0 1 5-5h8a5 5 0 0 1 5 5v2z" class="heroicon-ui"></path></svg>
                            <span class="ml-2">Account</span>
                        </a>
                    </li>
                    <li class="">
                        <a href="#" class="flex items-center px-3 py-3 hover:bg-gray-200">
                            <svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="text-gray-600"><path d="M9 4.58V4c0-1.1.9-2 2-2h2a2 2 0 0 1 2 2v.58a8 8 0 0 1 1.92 1.11l.5-.29a2 2 0 0 1 2.74.73l1 1.74a2 2 0 0 1-.73 2.73l-.5.29a8.06 8.06 0 0 1 0 2.22l.5.3a2 2 0 0 1 .73 2.72l-1 1.74a2 2 0 0 1-2.73.73l-.5-.3A8 8 0 0 1 15 19.43V20a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-.58a8 8 0 0 1-1.92-1.11l-.5.29a2 2 0 0 1-2.74-.73l-1-1.74a2 2 0 0 1 .73-2.73l.5-.29a8.06 8.06 0 0 1 0-2.22l-.5-.3a2 2 0 0 1-.73-2.72l1-1.74a2 2 0 0 1 2.73-.73l.5.3A8 8 0 0 1 9 4.57zM7.88 7.64l-.54.51-1.77-1.02-1 1.74 1.76 1.01-.17.73a6.02 6.02 0 0 0 0 2.78l.17.73-1.76 1.01 1 1.74 1.77-1.02.54.51a6 6 0 0 0 2.4 1.4l.72.2V20h2v-2.04l.71-.2a6 6 0 0 0 2.41-1.4l.54-.51 1.77 1.02 1-1.74-1.76-1.01.17-.73a6.02 6.02 0 0 0 0-2.78l-.17-.73 1.76-1.01-1-1.74-1.77 1.02-.54-.51a6 6 0 0 0-2.4-1.4l-.72-.2V4h-2v2.04l-.71.2a6 6 0 0 0-2.41 1.4zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" class="heroicon-ui"></path></svg>
                            <span class="ml-2">Settings</span>
                        </a>
                    </li>
                    <li>
                        <a href="#" class="flex items-center px-3 py-3 hover:bg-gray-200">
                            <svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="text-gray-600"><path d="M0 0h24v24H0z" fill="none"></path><path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></svg><span class="ml-2">Logout</span>
                        </a>
                    </li>
                </ul>
            </div>
            
        </nav>
    </header>
</body>
</html>

Exemple 3 : Création d'onglets

Dans ce dernier exemple commenté, nous allons construire une liste <ul><li>, chaque item <li> représentant un onglet. Le contenu lié à l'onglet est inséré dans une balise <div> en dehors de la liste. Par défaut c'est l'onglet 1 qui est montré et actif. Lorsque l'utilisateur clique sur un des deux autres onglets, son contenu remplace le contenu de l'onglet précédent et l'onglet cliqué devient actif.

Initialisation du composant principal avec un objet

Le composant principal est une balise <div> contenant une directive x-data qui initialise un objet comportant une propriété tab initialisé avec la valeur tab1

<div 
  class="container mx-auto p-4 mb-4" 
  x-data="{ tab: 'tab1' }"
>

Onglet actif et inactif

L'onglet est représenté par une balise <li> qui contient deux directives.

Une directive x-bind lié à un attribut de class (noté avec sa version courte ici :class). Cette directive permet d'appliquer des classes à l'élément selon la valeur booléenne renvoyée par une expression javaScript. Ici, dans l'onglet 1, cette expression teste si la propriété tab a la valeur tab1, si oui, les classes de la directive sont appliquées à l'élément pour visuellement montrer que l'onglet est actif (fond blanc, bordure, texte en bleu …).

Une seconde directive x-on écoute l'évènement click de l'utilisateur (en notation courte ici: @click). Si l'onglet est cliqué, la valeur de la propriété tab est passé à tab1

<li class="-mb-px mr-1">
  <a class="inline-block rounded-t py-2 px-4 font-semibold hover:text-blue-800"
  :class="{ 'bg-white text-blue-700 border-l border-t border-r' : tab === 'tab1' }" href="#"
  @click.prevent="tab = 'tab1'"
  >Tab 1</a>
</li>

Contenu de l'onglet, visible ou masqué

Le contenu des onglets est inséré dans une balise <div> juste en dessous de la liste <ul><li> contenant les onglets. Leur apparition ou leur masquage est déterminé via une directive x-show qui évalue la valeur booléenne de l'expression javaScript tab === 'tab1'. Si l'expression renvoie true, le contenu de l'élément <div> est montré.

<div 
   x-show="tab === 'tab1'"
>
  <strong>Tab I content.</strong> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nesciunt sunt, consectetur eos
  quod perferendis mollitia consequuntur natus, porro molestiae qui iusto deserunt rerum tempore
  voluptatum itaque. Ad, nisi esse cum quidem consequuntur ullam obcaecati.
</div>

Code complet :

Copiez/collez ce code dans un fichier tabs.html puis ouvrez le dans une fenêtre de votre navigateur.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AlpineJS - Tabs</title>
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.2.5/dist/alpine.js" defer></script>

</head>
    <body class="text-gray-800 bg-gray-200">
        <div 
          class="container mx-auto p-4 mb-4" 
          x-data="{ tab: 'tab1' }"
        >
            <h2 class="text-2xl font-bold">Tabs</h2>
            <ul class="flex border-b mt-6">
                <li class="-mb-px mr-1">
                    <a class="inline-block rounded-t py-2 px-4 font-semibold hover:text-blue-800"
                        :class="{ 'bg-white text-blue-700 border-l border-t border-r' : tab === 'tab1' }" href="#"
                        @click.prevent="tab = 'tab1'"
                    >Tab 1</a>
                </li>
                <li class="-mb-px mr-1">
                    <a class="inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold"
                        :class="{ 'bg-white text-blue-700 border-l border-t border-r' : tab === 'tab2' }" href="#"
                        @click.prevent="tab = 'tab2'"
                >Tab 2</a>
                </li>
                <li class="-mb-px mr-1">
                    <a class="inline-block py-2 px-4 text-blue-500 hover:text-blue-800 font-semibold"
                        :class="{ 'bg-white text-blue-700 border-l border-t border-r' : tab === 'tab3' }" href="#"
                        @click.prevent="tab = 'tab3'"
                >Tab 3</a>
                </li>
            </ul>
            <div class="content bg-white px-4 py-4 border-l border-r border-b pt-4">
                <div 
                   x-show="tab === 'tab1'"
                >
                    <strong>Tab I content.</strong> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nesciunt sunt, consectetur eos
                    quod perferendis mollitia consequuntur natus, porro molestiae qui iusto deserunt rerum tempore
                    voluptatum itaque. Ad, nisi esse cum quidem consequuntur ullam obcaecati.
                </div>
                <div 
                   x-show="tab === 'tab2'"
                >
                    <strong>Tab II content.</strong> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nesciunt sunt, consectetur eos
                    quod perferendis mollitia consequuntur natus, porro molestiae qui iusto deserunt rerum tempore
                    voluptatum itaque. Ad, nisi esse cum quidem consequuntur ullam obcaecati.
                </div>
                <div 
                    x-show="tab === 'tab3'"
                >
                    <strong>Tab III content.</strong> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nesciunt sunt, consectetur eos
                    quod perferendis mollitia consequuntur natus, porro molestiae qui iusto deserunt rerum tempore
                    voluptatum itaque. Ad, nisi esse cum quidem consequuntur ullam obcaecati.
                </div>
            </div>
        </div>
    </body>
    </html>

Quelques exemples plus élaborés

On peut aussi faire des choses plus élaborées avec AlpineJS et un peu de JavaScript comme par exemple :

Un générateur de factures.

Un formulaire multi étapes.

Un formulaire de newsletter avec validation

Conclusion

Ce petit framework léger combiné à un framework CSS comme TailwindCSS peut être une alternative viable aux frameworks plus complexes comme React ou VueJS pour concevoir des composnats fonctionnels qui viendront s'intégrer et dynamiser vos sites web ou vos applications basés sur une technologie "server side rendering" (Laravel, Rails, Django, CMS …).

Tant que vous n'avez pas besoin de gérer des routes et des états comme dans les "Single Page Application", AlpineJS devient le compagnon idéal pour manipuler le DOM et construire des interactions utilisateur dans vos pages web.

Les possibilités sont nombreuses avec AlpineJS, il suffit de laissez libre court à son imagination.

Sources :

La partie documentation de cet article est une traduction de la documentation officielle de AlpineJS

Les exemples concrets sont issues des tutoriels vidéos du site Scrimba

A voir aussi :

https://www.alpinetoolbox.com/