Snippets, componentes y layouts
Los Snippets son piezas de template reutilizables. Hay cuatro formas de usarlos, desde la inclusión sencilla hasta el componente con slots.
7.1 Incluir con include
{% include "header" %}
{% include "teaser" with titel="Novedad" link="/aktuelles" %}
El Snippet ve todas las variables de la página más los valores que se le pasan. Los cambios en el Snippet se aplican de inmediato en todas las páginas que lo incluyen. Los nombres de Snippet pueden contener barras, p. ej. teile/footer. Un Snippet nuevo con un nombre así se coloca automáticamente en la lista dentro de la carpeta teile. La carpeta también la puedes definir libremente junto al nombre; solo sirve para organizar.
7.2 Componentes con slots
Un componente es un Snippet que puede envolver contenido, como los componentes de Vue o Svelte, pero en el servidor. El Snippet define con {% slot %} dónde aparece el contenido; al usarlo, rellenas los slots.
El Snippet card:
<div class="card {{ variante|default:"normal" }}">
<h3>{% slot "titel" %}Sin título{% endslot %}</h3>
<div class="card-body">{% slot %}{% endslot %}</div>
{% if slots.footer %}
<footer>{% slot "footer" %}{% endslot %}</footer>
{% endif %}
</div>
Uso:
{% component "card" variante="hervorgehoben" %}
{% fill "titel" %}Nuestra <em>oferta</em>{% endfill %}
<p>Todo lo que no está dentro de un fill acaba en el slot predeterminado.</p>
{% fill "footer" %}<a href="/kontakt">Consultar ahora</a>{% endfill %}
{% endcomponent %}
Las reglas:
| Elemento | Significado |
|---|---|
{% slot %}…{% endslot %} |
Slot predeterminado en el Snippet. El contenido entre las etiquetas es el valor de reserva si no se pasa nada. |
{% slot "name" %}…{% endslot %} |
Slot con nombre, con valor de reserva. |
{% fill "name" %}…{% endfill %} |
Al usar el componente, rellena un slot con nombre. |
Contenido fuera de fill |
Rellena el slot predeterminado. |
slots.name / slots.default |
En el Snippet: true si el slot se ha rellenado; para envoltorios opcionales. |
name=wert tras el nombre del Snippet |
Parámetro (props), disponible como variable en el Snippet. |
Conviene saber:
-
El contenido ve las variables de la página. Lo que hay entre
componentyendcomponentse renderiza en el contexto de la página que lo llama, así que las variables de bucle funcionan:{% for post in data("blog") %} {% component "card" %} {% fill "titel" %}{{ post.titel }}{% endfill %} {{ post.text|markdown }} {% endcomponent %} {% endfor %} -
El Snippet solo ve sus parámetros más las variables globales (
page,route,nav(),data()…), pero no las variables locales de la página. Lo que necesite el componente se lo pasas explícitamente como parámetro. Eso hace que los componentes sean predecibles y reutilizables. -
El HTML como parámetro sigue siendo HTML si está marcado como seguro:
text=post.text|markdownoinhalt="<b>x</b>"|safe. Los textos normales se escapan como siempre. -
Anidar está permitido: componentes dentro de componentes, también en bloques
fill:{% component "card" %} {% fill "titel" %}Contacto{% endfill %} {% component "button" href="/kontakt" variante="primaer" %}Escríbenos{% endcomponent %} {% endcomponent %} -
El nombre del Snippet también puede venir de una variable:
{% component block.typ %}, útil para constructores de páginas a partir de datos de Collections. -
Un componente que se llama a sí mismo sin fin se detiene tras 30 niveles con un mensaje de error.
Los Snippets con slots llevan en la lista de Snippets la insignia Componente; en el editor HTML, Snippet → Componente inserta la estructura básica con todos los slots con nombre.
7.3 Layouts con extends y block
Un layout define el esqueleto de una página entera; la página solo rellena sus bloques. El Snippet layouts/standard:
{% include "header" %}
<main class="container">
{% block inhalt %}{% endblock %}
</main>
<aside>{% block seitenleiste %}{% include "standard-seitenleiste" %}{% endblock %}</aside>
{% include "footer" %}
En el HTML de una página:
{% extends "layouts/standard" %}
{% block inhalt %}
<h1>{{ page.title }}</h1>
<p>…</p>
{% endblock %}
extendsdebe estar al principio del todo. Todo lo que quede fuera deblockse ignora.- Los bloques no sobrescritos conservan el contenido del layout (aquí, la barra lateral estándar).
- Cada página tiene exactamente un layout. Para piezas repetidas dentro de una página, usa componentes.
Diseños de página para páginas nuevas
Un snippet de diseño puede ofrecerse como plantilla para páginas nuevas en el editor de snippets, pestaña Diseño de página: marca Ofrecer como diseño de página, añade nombre y descripción para la selección y, si quieres, Preseleccionar para páginas nuevas. El diálogo Página nueva ofrece entonces los diseños junto a Página vacía (el comportamiento de siempre); una página nueva con diseño solo contiene
{% extends "layouts/inhaltsseite" %}
Así, los cambios en el diseño afectan a todas las páginas que lo usan, y cada página puede sobrescribir bloques como arriba. Junto a cada diseño, IllusionCMS muestra si tiene un área de contenido para bloques – es decir, si produce {% blocks page.blocks %}, directamente o mediante extends, include y componentes. Los diseños sin esa área llevan el chip contenido estático: la página muestra contenido fijo del template y allí no se pueden gestionar bloques. Es un caso normal y deseado – por ejemplo, una página de contacto con dirección y formulario.
Los bloques no se insertan automáticamente, para que mantengas el control total sobre el código de la página. Puede ser deseado elegir diseños sin áreas de contenido editables.
Integraciones como IllusionPublish leen los diseños con GET /api/v1/page-layouts y crean páginas con layout_id (documentación de la API).
7.4 Macros
Para piezas pequeñas con parámetros y sin contenido existen las macros. En el Snippet makros (el export es obligatorio):
{% 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 $
En la mayoría de los casos, los componentes son más flexibles, porque pueden recibir contenido HTML.
7.5 CSS y JS en Snippets
En el editor de Snippets, a la derecha, en CSS y JS de este snippet, asignas archivos de la biblioteca (+ asociar) o creas archivos nuevos (+ nuevo, nombre con .css o .js). Solo se cargan en las páginas que realmente ejecutan el Snippet al renderizar: los estilos y scripts de un slider, por tanto, no en todas las páginas, sino solo donde está el slider.
- Cuenta lo que realmente se renderiza:
{% include %}(también con nombre tomado de una variable), componentes, Snippets anidados y layouts medianteextends. En una rama{% if %}que no se cumple o en un bucle vacío no se carga nada. - Los Snippets de macros cargan sus archivos en cuanto se incluyen mediante
{% import %}. - Evitar la recursión infinita: las macros e includes recursivos (p. ej. para estructuras en árbol) están permitidos, pero siempre necesitan una condición de parada. Una recursión infinita – también mediante un
includecon variable que se incluye a sí mismo – termina el proceso del servidor, igual que valores extremos en{% lorem %}y en los filtroscenter,ljust,rjustyfloatformat. Al probar macros nuevas, empieza con valores pequeños. - Solo cuentan el contenido de la página y el campo Head: un Snippet en el título SEO o en la descripción no carga nada.
- El CSS va en el
<head>y el JS antes de</body>, en ambos casos detrás de los archivos de la página. Cada archivo se carga una sola vez, aunque el Snippet aparezca diez veces o la página ya tenga ese mismo archivo. - La vista previa del editor de páginas muestra el estado guardado del Snippet.
Widgets ya compilados de Svelte, Vue, React y compañía: compilas el módulo en un proyecto propio (p. ej. con Vite) y tomas el resultado de dist/:
| Del build | Se convierte en IllusionCMS en |
|---|---|
el marcado del <body> del index.html, normalmente solo el punto de montaje (<div id="app"></div>) |
el contenido del Snippet |
assets/index-….js |
un archivo JS en el Snippet (+ nuevo → p. ej. wetter.js, pegar el contenido) |
assets/index-….css |
un archivo CSS en el Snippet |
Después incluyes el Snippet ({% include "wetter" %}): el CSS y el JS solo se cargan allí. Vite genera el JS para <script type="module">. Si el archivo contiene sintaxis de módulo (import/export en el nivel superior o import.meta), IllusionCMS lo incluye automáticamente de esa forma; sin sintaxis de módulo, como <script> normal; ambas cosas sin ningún ajuste.
Para que el build funcione sin retoques:
- Un solo archivo JS: si Vite divide el código en varios archivos (
import(…)para carga diferida, chunkvendorpropio), faltan los demás: el archivo está en/_assets/js/<id>.jsy las importaciones relativas no encuentran nada allí. Prescinde de la carga diferida o configura envite.config.jsbuild: { rollupOptions: { output: { inlineDynamicImports: true } } }. - Imágenes y fuentes del build: o bien lo bastante pequeñas para incrustarlas (
build.assetsInlineLimit), o bien súbelas en Archivos y úsalas con una dirección absoluta (/media/…). - Varias veces en una página: el script se carga una sola vez. Un build que arranca en
#app, por tanto, arranca una sola vez. Para varias instancias, busca todos los marcadores de posición en el punto de entrada, p. ej. con 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 } })
})
El Snippet devuelve entonces <div class="wetter-widget" data-ort="{{ ort|default:"Berlin" }}"></div>: los valores del template llegan al widget mediante atributos data-… (las salidas con {{ … }} se escapan automáticamente).
- Los módulos se ejecutan cuando la página ya está analizada: los marcadores de posición ya están entonces en el DOM.
7.6 Constructor de páginas: módulos y bloques
Para las páginas que otras personas deben mantener sin HTML existe el constructor de páginas: creas una vez los módulos (Snippets con esquema), y la página o una entrada de collection solo guarda datos: una lista de bloques, cada uno con su módulo, sus valores y, si hace falta, bloques anidados. El HTML se queda en tus Snippets; el editor de formularios nunca toca el marcado.
Crear un módulo
Un módulo es un Snippet con esquema. En el editor de Snippets abre la pestaña Módulo, haz clic en Definir como módulo y establece:
- Etiqueta, descripción, icono y categoría: así aparece el módulo en Añadir bloque.
- Parámetros: se definen como los campos de una collection: nombre, etiqueta, tipo, obligatorio, valor predeterminado, las opciones para Selección y la collection para Vínculo. La casilla Diseño marca los parámetros de apariencia (variante, espaciado, colores); en el formulario están en un grupo Apariencia propio.
- Slots: lugares donde pueden ir otros bloques, opcionalmente limitados a ciertos módulos y a un número máximo.
Con la casilla Usar como módulo marcada, el módulo aparece en la selección; en la lista de Snippets lleva la insignia Módulo. Un ejemplo, el 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>
Con los parámetros titel (texto, obligatorio), bild (imagen), text (Markdown) y variante (selección hell, dunkel, diseño), y el slot buttons (permitido: button, como máximo 3).
En el módulo, los parámetros están disponibles como variables, ya preparados:
| Tipo de parámetro | En el módulo |
|---|---|
| Texto, texto multilínea, URL, selección, fecha, número, sí/no | el valor, escapado como siempre |
| Markdown | HTML listo: simplemente {{ text }} |
| HTML | el HTML sin cambios |
| Imagen, archivo | objeto de archivo como file(id) con url, srcset, width, height, alt, name …, p. ej. {{ image(bild.id) }} o {{ bild.url }} |
| Vínculo, vínculos | la entrada vinculada o la lista (para los visitantes, solo las publicadas) |
Además está block con block.id y block.type. No hay parámetros de tipo Bloques: el anidamiento se hace con slots. Los nombres de los slots deben coincidir con {% slot %} en el Snippet (default es el slot predeterminado); la pestaña avisa cuando un slot solo está en el esquema o solo en el código, y toma los slots del código con un clic.
Bloques en una página
En el HTML de la página decides dónde aparecen los bloques:
{% include "header" %}
<main>
{% blocks page.blocks %}
</main>
{% include "footer" %}
Si la etiqueta está en el HTML o la página ya tiene bloques, el editor de páginas muestra la pestaña Bloques. Si no, el botón Bloques de la barra de herramientas de la pestaña HTML inserta la etiqueta (en el cursor o, si no, al final) y abre la pestaña.
Así se trabaja en el editor de formularios:
- Añadir bloque abre la selección por categorías, con búsqueda. Dentro de un slot solo se ofrecen los módulos permitidos allí.
- Un clic en la barra de título despliega el bloque: el formulario según el esquema y, debajo, los slots con sus propios bloques. La barra de título muestra como resumen el primer texto rellenado.
- Subir/bajar, duplicar y eliminar con los botones de la derecha. Arrastrando la barra de título mueves bloques dentro de una lista y entre slots, siempre que el módulo esté permitido allí.
- La vista previa muestra el estado sin guardar. Mientras la pestaña Bloques está abierta, un clic en un bloque de la vista previa lo abre en el formulario; el bloque elegido aparece enmarcado en la vista previa.
Se guarda junto con la página (Guardar, Ctrl+S), con historial de versiones y control de conflictos como en el HTML.
Collections con bloques
El tipo de campo Bloques convierte un campo de collection en un constructor de páginas, por ejemplo el contenido de un artículo del blog. El formulario de la entrada muestra el mismo editor; si la collection tiene una página de detalle, también aquí un clic en la vista previa selecciona el bloque. El campo se muestra con la misma etiqueta:
{% with post=item("blog", "slug", route.slug) %}
<h1>{{ post.titel }}</h1>
{% blocks post.inhalt %}
{% endwith %}
En la exportación CSV, el árbol de bloques se escribe como texto JSON; la importación CSV omite los valores no válidos con un aviso.
Bloques globales
Algunos bloques van en muchas páginas: una caja de newsletter, un teaser de contacto. Créalos en Snippets → Bloques globales: nombre, descripción, carpeta y un árbol de bloques en el mismo editor. Los insertas con Añadir bloque → Bloques globales; en la página solo queda una referencia, y los cambios en el bloque global se aplican en todas partes.
- Incrustar sustituye la referencia por una copia de sus bloques. A partir de ahí solo los editas en ese lugar.
- Junto al editor ves dónde se usa el bloque global (páginas, entradas, otros bloques globales), con enlaces.
- Eliminar solo es posible mientras no se use en ningún sitio.
- Un bloque global no puede contenerse a sí mismo, ni siquiera de forma indirecta: ya el guardado lo rechaza.
- En los slots que solo permiten ciertos módulos no se ofrecen bloques globales.
- Ajustar parámetros: bajo la referencia puedes sobrescribir parámetros concretos de los bloques del bloque global para este uso concreto – por ejemplo el título del recuadro de boletín solo en esta página. Abre Ajustar los parámetros de este uso, marca ajustar junto al parámetro e introduce el nuevo valor; sin marca vale el valor del bloque global (y cambia con él). Solo se pueden ajustar parámetros, nunca estructura, orden ni slots. Si el bloque o el parámetro se elimina después del bloque global, el ajuste se ignora.
Límites y notas
- Al guardar se comprueba: módulo desconocido, parámetros obligatorios que faltan, valores del tipo incorrecto, slots desconocidos, módulos no permitidos o demasiados bloques en un slot. El mensaje indica la posición en el árbol.
- Como máximo 10 niveles y 1 MB por árbol de bloques. Los parámetros que el esquema no conoce (o ya no conoce) se descartan al guardar.
- Un bloque cuyo módulo se eliminó se conserva, pero no muestra nada en el sitio en vivo; la vista previa muestra un aviso. Los campos vacíos no muestran nada.
- Las imágenes y archivos de los bloques cuentan como usados al Limpiar. El CSS/JS asociado al Snippet de un módulo se carga en las páginas que usan ese bloque.
- Las marcas para seleccionar en la vista previa (
<!--ic-block:…-->) solo existen en la vista previa, nunca en el sitio en vivo. - Para editores visuales (IllusionPublish), la vista previa marca además qué elemento muestra qué parámetro (
data-ic-field). Funciona automáticamente cuando un parámetro de texto, área de texto o Markdown está solo y sin cambios en un elemento – es decir,<h2>{{ ueberschrift }}</h2>y<div class="text">{{ inhalt }}</div>en lugar de título y Markdown uno junto al otro en la misma<section>; las imágenes medianteimage(bild.id)o{{ bild.url }}. Donde no encaje (p. ej.{{ titel|upper }}), marca el elemento con{{ block_field("titel") }}:<p{{ block_field("titel") }}>{{ titel|upper }}</p>. Ninguna de estas marcas existe en el sitio en vivo. Detalles en la documentación de la API. - Para integraciones:
blocksen las páginas, el tipo de campoblocks,GET /api/v1/blocks/typesy/api/v1/global-blocks; detalles en la documentación de la API.
¿Qué uso para qué?
| Situación | Herramienta |
|---|---|
| Páginas que otras personas montan sin HTML a partir de módulos listos | Módulos + {% blocks page.blocks %} |
| La misma cabecera/pie en todas las páginas | include (o un layout) |
| Esqueleto para muchas páginas parecidas | extends + block |
| Pieza repetida con contenido propio (tarjeta, caja, acordeón, botón) | component + slot |
| Pequeño formateador sin contenido | Macro |
| Texto que después evolucionará de forma independiente | Snippet → Contenido (copia) |