Snippets, composants et layouts
Les Snippets sont des blocs de template réutilisables. Il existe quatre façons de les utiliser – de la simple intégration jusqu’au composant avec slots.
7.1 Intégrer avec include
{% include "header" %}
{% include "teaser" with titel="Neu bei uns" link="/aktuelles" %}
Le Snippet voit toutes les variables de la page, plus les valeurs transmises. Les modifications du Snippet s’appliquent immédiatement à toutes les pages qui l’intègrent. Les noms de Snippets peuvent contenir des barres obliques, p. ex. teile/footer. Un nouveau Snippet portant un tel nom est automatiquement placé dans le dossier teile de la liste. Vous pouvez aussi définir librement le dossier à côté du nom ; il sert uniquement à la vue d’ensemble.
7.2 Composants avec slots
Un composant est un Snippet qui peut englober du contenu – comme les composants de Vue ou de Svelte, mais côté serveur. Le Snippet définit avec {% slot %} où le contenu apparaît ; à l’utilisation, vous remplissez les slots.
Le Snippet card :
<div class="card {{ variante|default:"normal" }}">
<h3>{% slot "titel" %}Ohne Titel{% endslot %}</h3>
<div class="card-body">{% slot %}{% endslot %}</div>
{% if slots.footer %}
<footer>{% slot "footer" %}{% endslot %}</footer>
{% endif %}
</div>
Utilisation :
{% component "card" variante="hervorgehoben" %}
{% fill "titel" %}Unser <em>Angebot</em>{% endfill %}
<p>Alles, was nicht in einem fill steht, landet im Standard-Slot.</p>
{% fill "footer" %}<a href="/kontakt">Jetzt anfragen</a>{% endfill %}
{% endcomponent %}
Les règles :
| Élément | Signification |
|---|---|
{% slot %}…{% endslot %} |
Slot par défaut dans le Snippet. Le contenu entre les balises est la valeur de repli si rien n’est transmis. |
{% slot "name" %}…{% endslot %} |
Slot nommé avec valeur de repli. |
{% fill "name" %}…{% endfill %} |
Remplit un slot nommé lors de l’utilisation. |
Contenu en dehors de fill |
Remplit le slot par défaut. |
slots.name / slots.default |
Dans le Snippet : true si le slot a été rempli – pour des conteneurs facultatifs. |
name=wert après le nom du Snippet |
Paramètres (props), disponibles comme variables dans le Snippet. |
Bon à savoir :
-
Le contenu voit les variables de la page. Ce qui se trouve entre
componentetendcomponentest rendu dans le contexte de la page appelante – les variables de boucle fonctionnent :{% for post in data("blog") %} {% component "card" %} {% fill "titel" %}{{ post.titel }}{% endfill %} {{ post.text|markdown }} {% endcomponent %} {% endfor %} -
Le Snippet ne voit que ses paramètres, plus les variables globales (
page,route,nav(),data()…), mais aucune variable locale de la page. Ce dont le composant a besoin, vous le transmettez explicitement comme paramètre. Cela rend les composants prévisibles et réutilisables. -
Le HTML transmis en paramètre reste du HTML s’il est marqué comme sûr :
text=post.text|markdownouinhalt="<b>x</b>"|safe. Les textes normaux sont échappés comme d’habitude. -
L’imbrication est permise – des composants dans des composants, y compris dans des blocs
fill:{% component "card" %} {% fill "titel" %}Kontakt{% endfill %} {% component "button" href="/kontakt" variante="primaer" %}Schreib uns{% endcomponent %} {% endcomponent %} -
Le nom du Snippet peut aussi provenir d’une variable :
{% component block.typ %}– utile pour des constructeurs de pages à partir de données de Collection. -
Un composant qui s’appelle lui-même à l’infini est arrêté avec un message d’erreur au bout de 30 niveaux.
Dans la liste des Snippets, les Snippets avec slots portent le badge Composant ; dans l’éditeur HTML, Snippet → Composant insère la structure de base avec tous les slots nommés.
7.3 Layouts avec extends et block
Un layout définit la structure d’une page entière ; la page ne remplit que ses blocs. Le Snippet layouts/standard :
{% include "header" %}
<main class="container">
{% block inhalt %}{% endblock %}
</main>
<aside>{% block seitenleiste %}{% include "standard-seitenleiste" %}{% endblock %}</aside>
{% include "footer" %}
Dans le HTML d’une page :
{% extends "layouts/standard" %}
{% block inhalt %}
<h1>{{ page.title }}</h1>
<p>…</p>
{% endblock %}
extendsdoit figurer tout au début. Tout ce qui se trouve en dehors deblockest ignoré.- Les blocs non surchargés conservent le contenu du layout (ici, la barre latérale standard).
- Il y a exactement un layout par page. Pour des éléments répétés au sein d’une page, utilisez des composants.
Mises en page pour les nouvelles pages
Un snippet de mise en page peut se proposer comme modèle pour les nouvelles pages dans l’éditeur de snippets, onglet Mise en page : cochez Proposer comme mise en page, indiquez un nom et une description pour la sélection et, si vous le souhaitez, Présélectionner pour les nouvelles pages. La boîte de dialogue Nouvelle page propose alors les mises en page à côté de Page vide (le comportement habituel) ; une nouvelle page avec mise en page contient seulement
{% extends "layouts/inhaltsseite" %}
Les modifications de la mise en page s’appliquent ainsi à toutes les pages qui l’utilisent, et chaque page peut surcharger des blocs comme ci-dessus. À côté de chaque mise en page, IllusionCMS indique si elle possède une zone de contenu pour les blocs – c’est-à-dire si elle affiche {% blocks page.blocks %}, directement ou via extends, include et des composants. Les mises en page sans cette zone portent la puce contenu statique : la page affiche un contenu fixe issu du template, les blocs ne peuvent pas y être gérés. C’est un cas normal et voulu – par exemple pour une page de contact avec adresse et formulaire.
Les blocs ne sont pas insérés automatiquement, afin que vous gardiez le contrôle total sur le code de la page. Il peut être voulu de choisir des mises en page sans zones de contenu modifiables.
Les intégrations comme IllusionPublish lisent les mises en page via GET /api/v1/page-layouts et créent des pages avec layout_id (documentation de l’API).
7.4 Macros
Pour de petits éléments avec paramètres mais sans contenu, il existe les macros. Dans le Snippet makros (le export est obligatoire) :
{% macro preis(betrag, waehrung="€") export %}<span class="preis">{{ betrag|floatformat:2 }} {{ waehrung }}</span>{% endmacro %}
{% import "makros" preis %}
{{ preis(19.9) }} → 19.90 €
{{ preis(5, "$") }} → 5.00 $
Dans la plupart des cas, les composants sont plus flexibles, car ils peuvent accueillir du contenu HTML.
7.5 CSS et JS dans les Snippets
Dans l’éditeur de Snippets, à droite sous CSS & JS de ce snippet, vous associez des fichiers de la bibliothèque (+ associer) ou en créez de nouveaux (+ nouveau, nom avec .css ou .js). Ils se chargent uniquement sur les pages qui exécutent réellement le Snippet lors du rendu – les styles et scripts d’un slider ne sont donc pas chargés sur chaque page, mais seulement là où se trouve le slider.
- Est pris en compte ce qui est réellement rendu :
{% include %}(même avec un nom issu d’une variable), les composants, les Snippets imbriqués et les layouts viaextends. Dans une branche{% if %}qui ne s’applique pas ou dans une boucle vide, rien n’est chargé. - Les Snippets de macros chargent leurs fichiers dès qu’ils sont intégrés via
{% import %}. - Éviter la récursion infinie : les macros et includes récursifs (p. ex. pour des arborescences) sont autorisés, mais ont toujours besoin d’une condition d’arrêt. Une récursion infinie – y compris via un
includeavec variable qui s’inclut lui-même – termine le processus du serveur, tout comme des valeurs extrêmes pour{% lorem %}et les filtrescenter,ljust,rjustetfloatformat. Pour tester de nouvelles macros, commencez avec de petites valeurs. - Seuls le contenu de la page et le champ Head comptent – un Snippet dans le titre SEO ou dans la description ne charge rien.
- Le CSS est placé dans le
<head>, le JS avant</body>, chaque fois après les fichiers de la page. Chaque fichier n’est chargé qu’une fois – même si le Snippet apparaît dix fois ou si la page possède déjà le même fichier. - L’aperçu dans l’éditeur de pages affiche la version enregistrée du Snippet.
Widgets compilés avec Svelte, Vue, React & Co. : vous compilez le module dans un projet séparé (p. ex. avec Vite) et reprenez le résultat de dist/ :
| Issu du build | Devient dans IllusionCMS |
|---|---|
le balisage du <body> de index.html, le plus souvent juste le point de montage (<div id="app"></div>) |
le contenu du Snippet |
assets/index-….js |
un fichier JS dans le Snippet (+ nouveau → p. ex. wetter.js, coller le contenu) |
assets/index-….css |
un fichier CSS dans le Snippet |
Ensuite, vous intégrez le Snippet ({% include "wetter" %}) – le CSS et le JS ne se chargent que là. Vite génère le JS pour <script type="module">. Si le fichier contient une syntaxe de module (import/export au niveau supérieur ou import.meta), IllusionCMS l’intègre automatiquement ainsi ; sans syntaxe de module, comme <script> normal – dans les deux cas sans réglage.
Pour que le build fonctionne sans retouche :
- Un seul fichier JS : si Vite répartit le code en plusieurs fichiers (
import(…)pour le lazy loading, chunkvendorséparé), les autres manquent – le fichier se trouve sous/_assets/js/<id>.js, les imports relatifs n’y trouvent rien. Renoncez au lazy loading ou définissezbuild: { rollupOptions: { output: { inlineDynamicImports: true } } }dansvite.config.js. - Images et polices du build : soit assez petites pour être intégrées (
build.assetsInlineLimit), soit téléversées sous Fichiers et utilisées avec une adresse absolue (/media/…). - Plusieurs fois sur une page : le script ne se charge qu’une fois. Un build qui démarre dans
#appne démarre donc qu’une fois. Pour plusieurs instances, recherchez tous les emplacements dans le point d’entrée, p. ex. avec Svelte 5 :
// src/main.js
import { mount } from 'svelte'
import Wetter from './Wetter.svelte'
document.querySelectorAll('.wetter-widget').forEach((el) => {
mount(Wetter, { target: el, props: { ort: el.dataset.ort } })
})
Le Snippet fournit alors <div class="wetter-widget" data-ort="{{ ort|default:"Berlin" }}"></div> – les valeurs du template arrivent dans le widget via des attributs data-… (les sorties avec {{ … }} sont automatiquement échappées).
- Les modules ne s’exécutent qu’une fois la page analysée – les emplacements se trouvent alors déjà dans le DOM.
7.6 Constructeur de pages : modules et blocs
Pour les pages que d’autres doivent gérer sans HTML, il existe le constructeur de pages : vous créez une fois des modules (des Snippets dotés d’un schéma), et la page ou une entrée de collection ne stocke plus que des données – une liste de blocs, chacun avec son module, ses valeurs et, le cas échéant, des blocs imbriqués. Le HTML reste dans vos Snippets ; l’éditeur de formulaire ne touche jamais au balisage.
Créer un module
Un module est un Snippet doté d’un schéma. Dans l’éditeur de Snippets, ouvrez l’onglet Module, cliquez sur Définir comme module et renseignez :
- Libellé, description, icône et catégorie – c’est ainsi que le module apparaît dans Ajouter un bloc.
- Paramètres – construits comme les champs d’une collection : nom, libellé, type, obligatoire, valeur par défaut, les options pour Choix, la collection pour Lien. La case Design marque les paramètres d’apparence (variante, espacements, couleurs) ; dans le formulaire, ils figurent dans un groupe Apparence à part.
- Slots – des emplacements où d’autres blocs peuvent figurer, éventuellement limités à certains modules et à un nombre maximal.
Avec la case Utiliser comme module cochée, le module apparaît dans la sélection ; dans la liste des Snippets, il porte le badge Module. Un exemple, le Snippet hero :
<section class="hero hero--{{ variante|default:"hell" }}" id="{{ block.id }}">
{% if bild %}{{ image(bild.id, "100vw") }}{% endif %}
<h1>{{ titel }}</h1>
{{ text }}
{% if slots.buttons %}<div class="hero-actions">{% slot "buttons" %}{% endslot %}</div>{% endif %}
</section>
Avec les paramètres titel (texte, obligatoire), bild (image), text (Markdown) et variante (choix hell, dunkel, design), ainsi que le slot buttons (autorisé : button, 3 au maximum).
Dans le module, les paramètres sont disponibles comme variables, déjà préparés :
| Type de paramètre | Dans le module |
|---|---|
| Texte, texte multiligne, URL, choix, date, nombre, oui/non | la valeur, échappée comme d’habitude |
| Markdown | du HTML prêt – simplement {{ text }} |
| HTML | le HTML tel quel |
| Image, fichier | objet fichier comme file(id) avec url, srcset, width, height, alt, name … – p. ex. {{ image(bild.id) }} ou {{ bild.url }} |
| Lien, liens | l’entrée liée ou la liste (seulement les entrées publiées pour les visiteurs) |
S’y ajoute block avec block.id et block.type. Il n’existe pas de paramètre de type Blocs – l’imbrication passe par les slots. Les noms de slots doivent correspondre à {% slot %} dans le Snippet (default est le slot par défaut) ; l’onglet signale un slot présent uniquement dans le schéma ou uniquement dans le code, et reprend en un clic les slots du code.
Des blocs sur une page
Dans le HTML de la page, vous décidez où les blocs apparaissent :
{% include "header" %}
<main>
{% blocks page.blocks %}
</main>
{% include "footer" %}
Si la balise figure dans le HTML ou si la page a déjà des blocs, l’éditeur de pages affiche l’onglet Blocs. Sinon, le bouton Blocs de la barre d’outils de l’onglet HTML insère la balise (au curseur, sinon à la fin) et ouvre l’onglet.
Travailler dans l’éditeur de formulaire :
- Ajouter un bloc ouvre la sélection par catégorie, avec recherche. Dans un slot, seuls les modules autorisés y sont proposés.
- Un clic sur la barre de titre déplie le bloc : le formulaire selon le schéma, puis les slots avec leurs propres blocs. La barre de titre affiche le premier texte renseigné en résumé.
- Monter/descendre, dupliquer et supprimer via les boutons à droite. Faire glisser la barre de titre déplace les blocs au sein d’une liste et entre les slots, tant que le module y est autorisé.
- L’aperçu montre l’état non enregistré. Tant que l’onglet Blocs est ouvert, un clic sur un bloc dans l’aperçu l’ouvre dans le formulaire ; le bloc sélectionné est entouré dans l’aperçu.
L’enregistrement se fait avec la page (Enregistrer, Ctrl+S) – avec historique des versions et contrôle des conflits, comme pour le HTML.
Collections avec blocs
Le type de champ Blocs transforme un champ de collection en constructeur de pages, par exemple le contenu d’un article de blog. Le formulaire d’entrée affiche le même éditeur de formulaire ; si la collection a une page de détail, un clic dans l’aperçu sélectionne ici aussi le bloc. Le champ s’affiche avec la même balise :
{% with post=item("blog", "slug", route.slug) %}
<h1>{{ post.titel }}</h1>
{% blocks post.inhalt %}
{% endwith %}
Dans l’export CSV, l’arbre de blocs est écrit en texte JSON ; l’import CSV ignore les valeurs invalides avec un avertissement.
Blocs globaux
Certains blocs ont leur place sur de nombreuses pages – un encart newsletter, un teaser de contact. Créez-les sous Snippets → Blocs globaux : nom, description, dossier et un arbre de blocs dans le même éditeur. Vous les insérez via Ajouter un bloc → Blocs globaux ; la page ne contient alors qu’une référence, et les modifications du bloc global s’appliquent partout.
- Intégrer remplace la référence par une copie de ses blocs. Vous ne les modifiez ensuite plus qu’à cet endroit.
- À côté de l’éditeur figure la liste des endroits où le bloc global est utilisé (pages, entrées, autres blocs globaux), avec des liens.
- La suppression n’est possible que tant qu’il n’est utilisé nulle part.
- Un bloc global ne doit pas se contenir lui-même, même indirectement – l’enregistrement le refuse déjà.
- Dans les slots qui n’autorisent que certains modules, les blocs globaux ne sont pas proposés.
- Adapter des paramètres : sous la référence, vous pouvez remplacer certains paramètres des blocs du bloc global pour cette seule utilisation – par exemple le titre du bloc newsletter sur cette page uniquement. Ouvrez Adapter les paramètres de cette utilisation, cochez adapter devant le paramètre et saisissez la nouvelle valeur ; sans coche, la valeur du bloc global s’applique (et change avec lui). Seuls les paramètres peuvent être adaptés, jamais la structure, l’ordre ou les slots. Si le bloc ou le paramètre est retiré plus tard du bloc global, l’adaptation est ignorée.
Limites et remarques
- L’enregistrement vérifie : module inconnu, paramètres obligatoires manquants, valeurs du mauvais type, slots inconnus, modules non autorisés ou trop de blocs dans un slot. Le message indique la position dans l’arbre.
- 10 niveaux au maximum et 1 Mo par arbre de blocs. Les paramètres que le schéma ne connaît pas (ou plus) sont supprimés à l’enregistrement.
- Un bloc dont le module a été supprimé reste enregistré mais n’affiche rien sur le site en ligne ; l’aperçu affiche un avertissement. Les champs vides n’affichent rien.
- Les images et fichiers des blocs comptent comme utilisés pour Nettoyer. Le CSS/JS rattaché au Snippet d’un module est chargé sur les pages qui utilisent ce bloc.
- Les marqueurs pour la sélection dans l’aperçu (
<!--ic-block:…-->) n’existent que dans l’aperçu, jamais sur le site en ligne. - Pour les éditeurs visuels (IllusionPublish), l’aperçu indique en outre quel élément affiche quel paramètre (
data-ic-field). Cela fonctionne automatiquement lorsqu’un paramètre texte, zone de texte ou Markdown se trouve seul et inchangé dans un élément – donc<h2>{{ ueberschrift }}</h2>et<div class="text">{{ inhalt }}</div>plutôt que titre et Markdown côte à côte dans une même<section>; les images viaimage(bild.id)ou{{ bild.url }}. Là où cela ne convient pas (p. ex.{{ titel|upper }}), marquez l’élément avec{{ block_field("titel") }}:<p{{ block_field("titel") }}>{{ titel|upper }}</p>. Aucun de ces marqueurs n’existe sur le site en ligne. Détails dans la documentation de l’API. - Pour les intégrations :
blockssur les pages, le type de champblocks,GET /api/v1/blocks/typeset/api/v1/global-blocks– détails dans la documentation de l’API.
Quel outil pour quel usage ?
| Situation | Outil |
|---|---|
| Pages que d’autres assemblent sans HTML à partir de modules prêts à l’emploi | Modules + {% blocks page.blocks %} |
| Même en-tête/pied de page sur toutes les pages | include (ou un layout) |
| Structure de page pour de nombreuses pages similaires | extends + block |
| Élément répété avec son propre contenu (carte, encadré, accordéon, bouton) | component + slot |
| Petit formateur sans contenu | Macro |
| Texte qui évoluera ensuite indépendamment | Snippet → Contenu (copie) |