javascript

Potion 1.0.0

Potion est un moteur de templates (template engine) implémenté en JavaScript pour ForumActif ou pour tout autre projet.

  • Ce plugin est un outil. Il n'est pas vraiment exploitable en l'état et demande de bonnes notions en JavaScript pour être utilisé à son plein potentiel.

Installer le script

Sur forum

Le plugin est compilé en un seul fichier et doit être installé en script externe. Ouvrez votre template overall_footer_end (fin du bas de page) et juste avant la fermeture de la balise </body>, insérez le code suivant :

<!-- Importation de Potion.js -->
<script src="https://cdn.jsdelivr.net/npm/@poumon/potion@1/potion.min.js"></script>

<!-- Importation de Potion.js Diète -->
<script src="https://cdn.jsdelivr.net/npm/@poumon/potion-diet@1/potion.min.js"></script>

Via npm

Pour la toute première fois 🥳 un de mes plugins peut être utilisé tel quel dans n'importe quel projet et n'est pas dépendant de l'environnement ForumActif pour fonctionner.

Pour utiliser Potion dans vos projets, vous pouvez utiliser la commande npm install @poumon/potion ou npm install @poumon/potion-diet et l'importer comme module :

import potion from "@poumon/potion"
// OU
import potion from "@poumon/potion-diet"

Utilisation du plugin

Potion et Potion Diet

Il existe deux versions de Potion : la principale et la plus puissante (tout inclus) et "Diète", une version simplifiée et plus compacte. La fonction principale des deux versions est identique et permet d'imiter un moteur de template en JavaScript :

const template = "Bonjour, je m'appelle [user.name] et j'ai [user.cats.length] chats."
const data = {
    user: {
        name: "Poumon",
        cats: ["Boudin", "Babushka"],
    }
};

const output = potion(template, data);
// "Bonjour, je m'appelle Poumon et j'ai 2 chats."

Il est également possible, avec les fonctionnalités principales, de travailler avec des templates imbriqués, de cette façon :

const data = { catItems: ['Croquettes', 'Herbe (à chat)', 'Jouet ressort'] };

let ul = "<ul>[list]</ul>",
    li = "<li>[item]</li>",
    body = "";
    
for (let i = 0; i < 2; i++){
    body += potion(li, { contents: data.catItems[i] });
}

const output = potion(ul, { list: body });

// "<ul><li><a>Croquettes</a></li><li><a>Herbe (à chat)</a></li><li><a>Jouet ressort</a></li></ul>"

Ou d'utiliser des tableaux de cette façon :

potion("Hello [0] and [1]!", ["Boudin", "Babushka"]);
// retourne "Hello Boudin and Babushka!"

potion("Hello [cats.0] and [cats.1]!", { cats: ["Boudin", "Babushka"] });
// retourne "Hello Boudin and Babushka!"

En soi, Potion permet de remplacer ce qu'on appelle des "jetons" (tokens) dans une chaîne de texte par des données. L'appel de la fonction potion(template, data) retourne la valeur du template une chaîne de charactères une fois les jetons remplacés.

Fonctionnalités avancées

Prenez note que la suite des fonctionnalités ne concernent pas Potion Diète.

Options

Potion est pensé en prenant en compte les restrictions d'écriture rencontrées dans les templates de ForumActif. Il est toutefois possible de changer les délimiteurs en accolades { } au besoin.

// potion(template, data, options)

potion(template, data, {
    start: "{",
    end: "}"
});
  • start | Par défaut, [
  • end | Par défaut, ]

Si vous décidez de modifier ces délimiteurs, assurez-vous de ne pas utiliser d'accolades dans un script à l'intérieur d'un template. ForumActif n'aime pas qu'on touche à ses accolades et risquerait de les interpréter comme les siennes.

Micro-template dans le DOM

L'élément HTML <template> permet de stocker du HTML qui ne sera pas affiché lors du chargement initial de la page, mais qui peut être instancié et affiché par la suite grâce à du JavaScript.

Potion est capable de récupérer ces éléments <template>, à condition qu'ils soient identifiés pour lui. Il existe deux façons de faire :

  1. En créant un <template type="template/potion"> anonyme.
  2. <template type="template/potion" data-name="babushka"> nommé, grâce à l'attribut data-name.

Un avantage non négligeable d'utiliser <template> est de conserver le HTML dans un fichier HTML et la logique des données dans un fichier JavaScript à part, ce qui permet de ne pas travailler le HTML en chaîne de caractères (sans tabulation, sans structure, sans beauté) dans une variable JavaScript.

De plus, au chargement du Document, Potion place immédiatement tous les <template> qu'il reconnaît dans son cache.

<!-- Le plus collant, Boudin -->
<template type="template/potion">
    <div class="[cat.boudin.bgcolor]">
        <img src="[cat.boudin.img]" alt="" />
    </div>
</template>

<!-- La plus patate, Babushka -->
<template type="template/potion" data-name="babushka">
    <div class="[cat.babushka.bgcolor]">
        <img src="[cat.babushka.img]" alt="" />
    </div>
</template>

La seule différence entre un <template> anonyme et un nommé, c'est la façon de l'appeler.

Les templates anonymes sont enregistrés selon l'ordre dans lequel ils sont rencontrés dans le DOM, en prenant en compte tous les <template type="template/potion"> selon la convention suivante [data-name] OU `potion-${i}`;

const data = {
    cat: {
        boudin: {/* ... */},
        babushka: {/* ... */},
    }
};

// Le plus collant, Boudin
const boudin = potion('potion-0', data);

// La plus patate, Babushka
const babushka = potion('babushka', data);

Puisque Potion met en cache le contenu des balises <template> qu'il reconnaît, vous pouvez les récupérer en indiquant leur nom comme premier argument de la fonction potion(template, data).

Syntaxe

Utiliser des conditions de rendu

Potion supporte l'utilisation de conditions booléennes permettant l'affichage d'un rendu en fonction d'une valeur true ou false dans les données.

const template = "[isLoggedIn][secret][/isLoggedIn]";
const data = { isLoggedIn: user.loggedIn(), secret: "Poumon aime beaucoup ses chats." };

potion(template, data);

D'après l'exemple ci-dessus, la valeur de secret ne sera révélée que si l'utilisateur est connecté.

Parcourir des tableaux

Comme précédemment présenté, Potion peut traitées des données en tableaux.

<template type="template/potion" data-name="cats">
    Les plus beaux chats du monde :
    [cats]
        [name] est [color].
    [/cats]
</template>
potion("cats", {
    cats: [
        { name: "Boudin", color: "noir" },
        { name: "Babushka", color: "écaille de tortue" }
    ]
});

Comme le template ci-dessus est nommé cats, nous pouvons le récupérer en passant simplement son nom comme premier argument de la fonction.

Utiliser des fonctions

Potion est capable de détecter une fonction et de l'exécuter. À l'intérieur de celle-ci, le mot-clef this fait référence à l'objet passé en données.

potion("boudin", {
    name: "Boudin",
    famousQuote: function() {
        return `REGARDE-MOOOEEEEEW —${this.name}` 
    }
});

Pour des raisons de sécurité, les fonctions ne sont malheureusement pas autorisées à l'intérieur d'un <template>. Par exemple [name.toUpperCase()] ne sera pas exécuté. Il est préférable de transformer vos données directement depuis l'objet data.

Parcourir des objets

Il est possible de traverser des objets. Lorsque potion détecte un objet, il mettra à disposition deux nouveaux "jetons" pour accéder aux propriétés de l'objet.

<template type="template/potion" data-name="cats">
    Les plus beaux chats du monde :
    [cats]
        [_key] est [_value].
    [/cats]
</template>
potion("cats", {
    cats: { 
        boudin: "noir"
        babushka: "écaille de tortue"
    }
});

Interprétation d'un template

Il est également possible de placer un <template> quelque part dans le DOM et de le transformer en véritable HTML une fois les données reçues. Tous les <template> doivent être placés avant l'import du script.

Rendering

La fonction potion.render(template, data, options) transformera la balise <template> en une <div> (ou tout autre balise spécifiée dans les options durant l'appel).

Contrairement à potion(), potion.render() ne fonctionne qu'en ciblant un template par son nom, puisque celui-ci sera remplacé dynamiquement.

const div = potion.render('cats', data, {
    tag: 'section',
    class: 'visible' 
});
  • tag | Par défaut, le <template> sera transformé en <div> et conservera tous les attributs dont il disposait (incluant son [data-name]). Il est toutefois possible de le transformer en autre chose en spécifiant la balise d'un élément HTML valide.
  • class | Comme tous les attributs sont transférés à l'élément transformé, cette option permet d'ajouter des classes supplémentaires une fois le <template> transformé.

{1.2.0} Syncing

La fonction potion.sync(template, data, options) transformera la balise <template> en une <div> (ou tout autre balise spécifiée dans les options durant l'appel).

Contrairement à potion(), potion.sync() ne fonctionne qu'en ciblant un template par son nom, puisque celui-ci sera remplacé dynamiquement.

  • tag | Par défaut, le <template> sera transformé en <div> et conservera tous les attributs dont il disposait (incluant son [data-name]). Il est toutefois possible de le transformer en autre chose en spécifiant la balise d'un élément HTML valide.
  • class | Comme tous les attributs sont transférés à l'élément transformé, cette option permet d'ajouter des classes supplémentaires une fois le <template> transformé.
const data = {
    cats: { 
        boudin: "noir"
        babushka: "écaille de tortue"
    }
};

const syncedData = potion.sync('colors', data, {
    tag: 'section',
    class: 'visible'
});

En revanche, potion.sync() retourne directement le clonde de l'objet passé en data sous la forme d'un proxy. Dans l'exemple ci-dessus, toutes les modifications effectuées aux valeurs de syncedData seront reflétées directement dans le DOM.

  • syncedData.cats.boudin = "blanc" serait faux (parce que boudin est noir) fonctionnerait de la même façon que si vous étiez allé sélectionner le div dans lequel le texte est affiché, et que vous aviez utilisé div.textContent = 'blanc' (même si c'est faux).

{1.2.0} Gestion des événements

En plus des jetons, il est maintenant possible d'ajouter des événements directement dans les <template>.

Les événements sont ajoutés comme attribut d'un élément et sont précédés d'un @ comme dans <div @click="">. Il s'agit d'un "event listener". Vous trouverez une liste complète des événements du DOM en suivant ce lien .

Il existe deux façons d'utiliser les événements dans un <template>.

Événements intégrés

Cette façon de faire permet de modifier la valeur d'un jeton sans devoir passer par l'exécution d'une fonction. Seules les méthodes primitives relatives au type de jeton sont acceptées.

@click="catsNum++" et @click="cats.push('Babushka')"

Événements déclarés

Les événements déclarés permettent d'appeler une fonction présente dans l'objet data passé en argument lors de l'initialisation du <template>. Tous les arguments de type "variable" doivent aussi existés dans l'objet data.

@click="petCat('Boudin', cats)

{1.2.0} Modificateurs

Les modificateurs sont des suffixes appliqués aux directives d'un événement qui permettent de changer le comportement d’un événement sans écrire de code supplémentaire.

Exemples concrets :

  • @click.stopEmpêche la propagation de l’événement comme event.stopPropagation().
  • @submit.preventEmpêche le rechargement de la page lors de l’envoi d’un formulaire comme event.preventDefault().
  • @keyup.enterDéclenche l’action uniquement quand on appuie sur "Entrée".

Événement

  • .stop
  • .prevent

Clavier

Les événements claviers supportés sont @keyup (appui d'une touche), @keydown (au relâchement d'une touche appuyée). Potion supporte ces touches comme modificateurs.

  • .enter
  • .tab
  • .delete (fonctionne à la fois pour "Delete" et "Backspace")
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

Souris

  • .left
  • .right
  • .middle

Ce qu'il me reste à faire

  • Dans un monde idéal, j'aimerais m'inspirer de React et permettre une façon de transformer les <template> en "DOM réactif" de sorte à ce que les données restent connectées à leur noeuds HTML (et donc, modifiable en temps réel si l'objet data est modifié). Dans un premier temps, il me faudra proposer une façon de parser un <template> et le transformer en shadowdom ouvert, peut-être grâce à une fonction déconstruite potion.templates('name', data).render().
  • Optimiser la fonction .sync().
  • Vérifier le support async/await des fonctions dans les données.

Remerciements