twig macros

Macros en Twig

Definición y estructura

Las macros en Twig son como funciones reutilizables que te permiten encapsular fragmentos de código HTML/Twig para usarlos múltiples veces. Son especialmente útiles en Grav para crear componentes repetitivos.

Estructura básica:

{% verbatim %}
{% macro nombreMacro(parametro1, parametro2) %}
    {# Código de la macro #}
    <div>{{ parametro1 }} - {{ parametro2 }}</div>
{% endmacro %}
{% endverbatim %}

Para usar una macro, primero debes importarla:

{% verbatim %}
{% import _self as macros %}
{{ macros.nombreMacro('valor1', 'valor2') }}
{% endverbatim %}

O importar desde otro archivo:

{% verbatim %}
{% import 'partials/macros.html.twig' as macros %}
{% endverbatim %}

Ejemplos básicos

Ejemplo 1: Botón reutilizable

{% verbatim %}
{# En tu plantilla o en partials/macros.html.twig #}
{% macro boton(texto, url, clase) %}
    <a href="{{ url }}" class="btn {{ clase|default('btn-primary') }}">
        {{ texto }}
    </a>
{% endmacro %}

{# Uso #}
{% import _self as ui %}
{{ ui.boton('Leer más', page.url, 'btn-success') }}
{{ ui.boton('Inicio', base_url_absolute, 'btn-secondary') }}
{% endverbatim %}

Ejemplo 2: Breadcrumbs en Grav

{% verbatim %}
{% macro breadcrumbs(page) %}
    <nav aria-label="breadcrumb">
        <ol class="breadcrumb">
            {% for crumb in page.parent().children() %}
                <li class="breadcrumb-item">
                    <a href="{{ crumb.url }}">{{ crumb.title }}</a>
                </li>
            {% endfor %}
            <li class="breadcrumb-item active">{{ page.title }}</li>
        </ol>
    </nav>
{% endmacro %}

{# Uso #}
{% import _self as nav %}
{{ nav.breadcrumbs(page) }}
{% endverbatim %}

Ejemplo 3: Imagen responsive

{% verbatim %}
{% macro imagen_responsive(media, alt, clases) %}
    {% if media %}
        <img src="{{ media.url }}" 
             alt="{{ alt }}" 
             class="{{ clases|default('img-fluid') }}"
             loading="lazy">
    {% endif %}
{% endmacro %}

{# Uso con imágenes de Grav #}
{% import _self as img %}
{{ img.imagen_responsive(page.media['foto.jpg'], 'Descripción', 'rounded') }}
{% endverbatim %}

Ejemplos complejos

Ejemplo 1: Sistema de tarjetas (cards) con múltiples opciones

{% verbatim %}
{% macro card(titulo, contenido, imagen, opciones) %}
    {% set defaults = {
        'clase_card': 'card mb-4',
        'clase_img': 'card-img-top',
        'mostrar_fecha': true,
        'mostrar_autor': false,
        'boton_texto': 'Leer más',
        'boton_url': '#'
    } %}
    {% set config = defaults|merge(opciones|default({})) %}

    <div class="{{ config.clase_card }}">
        {% if imagen %}
            <img src="{{ imagen }}" class="{{ config.clase_img }}" alt="{{ titulo }}">
        {% endif %}
        <div class="card-body">
            <h5 class="card-title">{{ titulo }}</h5>
            <p class="card-text">{{ contenido }}</p>

            {% if config.mostrar_fecha or config.mostrar_autor %}
                <div class="card-meta">
                    {% if config.mostrar_fecha %}
                        <small class="text-muted">{{ config.fecha|date('d/m/Y') }}</small>
                    {% endif %}
                    {% if config.mostrar_autor %}
                        <small class="text-muted">Por {{ config.autor }}</small>
                    {% endif %}
                </div>
            {% endif %}

            <a href="{{ config.boton_url }}" class="btn btn-primary">
                {{ config.boton_texto }}
            </a>
        </div>
    </div>
{% endmacro %}

{# Uso en Grav #}
{% import _self as componentes %}

{% for child in page.children.published %}
    {{ componentes.card(
        child.title,
        child.summary(200),
        child.media.images|first.url,
        {
            'mostrar_fecha': true,
            'mostrar_autor': true,
            'fecha': child.date,
            'autor': child.header.author,
            'boton_url': child.url,
            'boton_texto': 'Ver artículo'
        }
    ) }}
{% endfor %}
{% endverbatim %}

Ejemplo 2: Menú multinivel recursivo

{% verbatim %}
{% macro menu_recursivo(paginas, nivel) %}
    {% set nivel = nivel|default(0) %}
    <ul class="menu-nivel-{{ nivel }}">
        {% for pagina in paginas if pagina.visible %}
            <li class="{{ pagina.active or pagina.activeChild ? 'active' : '' }}">
                <a href="{{ pagina.url }}">
                    {{ pagina.menu|default(pagina.title) }}
                </a>

                {% if pagina.children.count > 0 %}
                    {{ _self.menu_recursivo(pagina.children, nivel + 1) }}
                {% endif %}
            </li>
        {% endfor %}
    </ul>
{% endmacro %}

{# Uso #}
{% import _self as nav %}
{{ nav.menu_recursivo(pages.children) }}
{% endverbatim %}

Ejemplo 3: Grid de imágenes con lightbox

{% verbatim %}
{% macro galeria(imagenes, columnas, id_galeria) %}
    {% set columnas = columnas|default(3) %}
    {% set clase_columna = 'col-md-' ~ (12 / columnas) %}

    <div class="row galeria" id="{{ id_galeria|default('galeria') }}">
        {% for imagen in imagenes %}
            <div class="{{ clase_columna }} mb-4">
                <div class="imagen-container">
                    <a href="{{ imagen.url }}" 
                       data-lightbox="{{ id_galeria }}"
                       data-title="{{ imagen.meta.alt_text|default('') }}">
                        <img src="{{ imagen.cropResize(400, 300).url }}" 
                             alt="{{ imagen.meta.alt_text|default('Imagen ' ~ loop.index) }}"
                             class="img-fluid">
                    </a>
                    {% if imagen.meta.caption %}
                        <p class="caption">{{ imagen.meta.caption }}</p>
                    {% endif %}
                </div>
            </div>
        {% endfor %}
    </div>
{% endmacro %}

{# Uso con las imágenes de una página en Grav #}
{% import _self as media %}
{{ media.galeria(page.media.images, 4, 'galeria-principal') }}
{% endverbatim %}