13 KiB
Builder Fields — Campos editables del index-base.tpl
Este documento define los campos editables que el usuario rellena desde el panel del builder de Acai. Cubre el atributo data-field-type con todos sus tipos (textfield, headfield, textbox, wysiwyg, link, upload, uploadMulti, list, multiv2, checkbox, colorpicker), la regla data-field-label → nombre de variable, los atributos Acai (c-if, c-else, c-for, c-class, c-hidden, c-required), el tag <set>, la inclusión de módulos, los formularios c-form y los componentes built-in. Léelo antes de crear o modificar cualquier index-base.tpl.
Reglas de nomenclatura de variables
El atributo data-field-label se convierte automáticamente en el nombre de variable Twig: se ponen minúsculas y se eliminan espacios y caracteres especiales.
| Label | Variable resultante |
|---|---|
Categoría Noticia |
categoranoticia |
Color Principal |
colorprincipal |
Título Producto |
ttuloproducto |
Reglas obligatorias:
- Todo elemento con
data-field-typeDEBE incluir tambiéndata-field-label. - Sin
data-field-label, el builder genera variables temporales o incorrectas y el módulo queda mal configurado. - Usa labels descriptivos y estables; no dejes labels vacíos ni genéricos como "Campo" o "Texto".
- En
index-base.tplevita clases Tailwind con valores arbitrarios (text-[44px],font-['Cinzel'],leading-[1.1]) — pueden romper el parseo. Muévelas astyle.css.
Tipos de campo (data-field-type)
| Tipo | Elemento HTML | Devuelve |
|---|---|---|
textfield |
<p> |
String |
headfield |
<h1>–<h6> |
String + variable extra _tag con la etiqueta elegida |
textbox |
<div> |
String multilínea |
wysiwyg |
<div class="wysiwyg"> |
String HTML |
link |
<a> |
URL string (ya incluye barras) |
upload |
<img> |
Array de {urlPath, info1, info2, info3, info4} |
uploadMulti |
<li> |
Itera sobre archivos subidos |
list (fijo) |
<div data-list-options="..."> |
Valor seleccionado |
list (tabla) |
<div data-list-table="..."> |
num del registro |
multiv2 |
<li> wrapper |
Array de objetos repetibles |
checkbox |
<div> o <input> |
1 o 0 (número) |
colorpicker |
<div> |
Hex color string |
textfield
<p data-field-type="textfield" data-field-label="Título">
Elemento editable
</p>
headfield
Genera 2 variables: la estándar y _tag con la etiqueta elegida (h1…h6).
<{{ titulo_tag | default('h2') }}
data-field-type="headfield"
data-field-label="Título Sección"
class="text-3xl font-bold">
Título de la sección
</{{ titulo_tag | default('h2') }}>
textbox
<div data-field-type="textbox" data-field-label="Descripción">
Texto largo editable
</div>
wysiwyg
Editor de texto enriquecido. Acceder con | raw para no escapar el HTML.
<div class="wysiwyg" data-field-type="wysiwyg" data-field-label="Contenido Enriquecido">
<p>Texto con <strong>estilos</strong> editables</p>
</div>
link
El campo enlace de Acai ya incluye las barras necesarias — nunca añadas barras extra.
<a data-field-type="link" data-field-label="Enlace Principal" href="#">
Haz clic aquí
</a>
upload
Devuelve un array. Acceso en Twig: {{ imagen[0].urlPath }}.
<div class="p-1/6 relative">
<img class="absolute top-0 left-0 w-full h-full object-cover lazyload"
data-field-type="upload"
data-field-label="Imagen Principal"
data-lazy="true"
data-field-info1="titulo"
data-field-width="1400"
alt="">
</div>
Atributos disponibles:
data-lazy="true"— carga perezosadata-field-width="1400"— ancho máximo sugeridodata-field-info1="titulo"— campo de información adicional (típicamente alt)
uploadMulti
Itera sobre todas las imágenes subidas. Variable iteradora: uploadMulti.
<li data-field-type="uploadMulti" data-field-label="Galería" data-field-info1="titulo">
<div class="relative min-h-screen">
<img class="absolute top-0 left-0 w-full h-full object-cover lazyload"
data-src="{{ uploadMulti.urlPath | imagec(2100) }}"
alt="{{ uploadMulti.info1 }}">
</div>
</li>
list (opciones fijas)
<div data-field-type="list"
data-field-label="Color Producto"
data-list-options="Rojo,Azul,|Verde,3|Amarillo">
</div>
Formato data-list-options:
opcion1,opcion2→ la opción es etiqueta y valor a la vez|valor3,etiqueta3→ separa valor de etiqueta con|
list (tabla)
Selecciona un registro de otra tabla. Devuelve el num.
<div data-field-type="list"
data-field-label="Noticia Destacada"
data-list-table="noticias"
data-list-value="num"
data-list-label="titulo">
{{ record.titulo }}
</div>
data-list-table— nombre de tabla sin prefijocms_data-list-value— campo a usar como valor (normalmentenum)data-list-label— campo a mostrar como label
multiv2 — Campos repetibles
Crea grupos de campos repetibles. La variable resultante es un array de objetos.
<ul>
<li data-field-type="multiv2" data-field-label="Productos">
<div data-field-type="textfield" data-field-label="Nombre">
Nombre del producto
</div>
<div data-field-type="textbox" data-field-label="Descripción">
Descripción del producto
</div>
<div class="p-1/6 relative">
<img class="absolute top-0 left-0 w-full h-full object-cover lazyload"
data-field-type="upload"
data-field-label="Imagen"
data-lazy="true"
data-field-width="800"
alt="">
</div>
</li>
</ul>
Uso en Twig:
{% for record in productos %}
<div class="producto">
<h3>{{ record.nombre }}</h3>
<p>{{ record.descripcion }}</p>
<img src="{{ record.imagen[0].urlPath }}" alt="">
</div>
{% endfor %}
checkbox
Devuelve 1 o 0 (número), nunca true/false.
colorpicker
Devuelve un string hexadecimal (#ff0000). Almacenado en config-vars (no en builder_custom).
Atributos Acai
c-if — Renderizado condicional
Usa = (un solo igual) para comparaciones, no ==.
<div c-if="subtitle">{{ subtitle }}</div>
<div c-if="layout = 'grid'">Grid layout</div>
c-else
Va inmediatamente después del elemento c-if.
<div c-if="image">
<img src="{{ image[0].urlPath }}">
</div>
<div c-else>
<p>No image available</p>
</div>
c-for — Iteración sobre array
<div c-for="item in record.features">
<h3>{{ item.title }}</h3>
</div>
c-for — Iteración sobre tabla de BD
<ul>
<li c-for="producto in productos"
c-where="'visible=1'"
c-order="'num desc'"
c-limit="10">
{{ producto.title }}
</li>
</ul>
Parámetros opcionales: c-where (string SQL), c-order (string de orden), c-limit (entero).
Equivalente Twig:
{% for producto in 'productos' | get('visible=1','num desc',10) %}
<li>{{ producto.title }}</li>
{% endfor %}
Variables del loop: loop.index (1-based), loop.index is odd, loop.index is even.
c-class — Clases CSS condicionales
<!-- Simple -->
<div c-class="{ 'text-center': alineacion == '1', 'text-right': alineacion == '2' }">
<!-- Múltiples condiciones -->
<div c-class="{
'flex-row-reverse': orden == '1',
'cursor-pointer click-a-child': record.enlace_anchor,
'rounded-xl': radioborde == '4'
}">
<!-- Con loop -->
<div c-class="{
'md:order-1': loop.index is odd,
'md:pl-6': loop.index is even
}">
<!-- Combinado con clases estáticas -->
<div class="flex items-center" c-class="{ 'justify-center': centrado }">
c-hidden — Variables ocultas
Elemento que NO se renderiza pero SÍ declara variables builder. Patrón típico para colores y opciones de configuración.
<div c-hidden="true">
<input data-field-type="textfield" data-field-label="Color de fondo" value="">
<div data-field-type="list"
data-field-label="Color titulo resaltado"
data-list-options="|Main color,1|Main color light,2|Main color dark"></div>
</div>
c-required — Validación condicional
<input type="text" name="telefono"
c-required="'2' not in camposquitar"
placeholder="Teléfono">
Tag <set> — Definir variables
<!-- Obtener configuración de la BD -->
<set :tienda="'configuracion_tienda' | get('num != 0')[0]"></set>
<!-- Construir URLs dinámicas -->
<set :logo="tienda.logo.0.urlPath
? 'https://' ~ server.HTTP_HOST ~ tienda.logo.0.urlPath
: 'https://' ~ server.HTTP_HOST ~ '/template/estandar/images/logo.png'">
</set>
<!-- Twig set para expresiones complejas -->
{% set gracias = 'apartados' | get('num = 20').0 %}
Incluir módulos
Para incluir un módulo dentro de otro módulo o dentro de una sección general, usa el moduleId como etiqueta HTML:
<module_id :param1="value1" :param2="'string value'"></module_id>
Ejemplos:
<header_menu :showLogo="true" :menuItems="items"></header_menu>
<product_card :product="selectedProduct" :showPrice="true"></product_card>
El módulo hijo recibe los parámetros como variables en su contexto.
Formularios — c-form
Maneja automáticamente validación, almacenamiento en BD y envío de emails.
<c-form
class="max-w-2xl mx-auto p-6 bg-white rounded-lg shadow"
tableName="'solicitudes'"
mailRecord="['correos', 'CONTACTO']"
sendTo="'contacto@empresa.com'"
sendToClient="'email'"
captcha="true"
honeypot="true"
messageOK="'¡Gracias! Te contactaremos pronto'"
messageKO="'Por favor, completa todos los campos'"
redirect="'/gracias'"
attachFiles="true">
<input name="nombre" type="text" required class="w-full p-2 border rounded">
<input name="email" type="email" required class="w-full p-2 border rounded">
<textarea name="mensaje" required class="w-full p-2 border rounded" rows="5"></textarea>
<label class="flex items-center">
<input name="acepto_politica" type="checkbox" class="mr-2" required>
<span>Acepto la política de privacidad</span>
</label>
<button type="submit" class="bg-teal-500 text-white px-6 py-2 rounded">Enviar</button>
<captcha/>
</c-form>
Atributos c-form
| Atributo | Descripción |
|---|---|
tableName="'tabla'" |
Tabla destino (sin cms_) |
mailRecord="['correos', 'ID']" |
Template de email en tabla correos |
sendTo="'email@dominio.com'" |
Destinatarios (separados por coma) |
sendToClient="'campo_email'" |
Campo del formulario con email del cliente para auto-reply |
captcha="true" |
Activa Google reCAPTCHA |
honeypot="true" |
Campo oculto anti-spam |
messageOK="'texto'" |
Mensaje al enviar correctamente |
messageKO="'texto'" |
Mensaje al fallar validación |
redirect="'/ruta/'" |
Redirección tras envío correcto |
attachFiles="true" |
Adjuntar archivos al email |
showImages="true" |
Mostrar thumbnails en email |
emailMode="'twig'" |
Email en formato Twig |
header="'<div>...'" |
HTML cabecera del email |
footer="'<div>...'" |
HTML footer del email |
styles="'body { ... }'" |
CSS del email |
Para formularios estándar (contacto, postulación), prefiere c-form antes que crear lógica custom de POST/hook. Solo crea una tabla propia si necesitas gestionar esos registros desde el admin.
Componentes built-in
Carousel — c-tns-wrapper
<div class="c-tns-wrapper"
data-responsive='{"0":1,"768":2,"1024":3}'
data-speed="400"
data-nav="true"
data-autoplay-timeout="3000">
<div c-for="slide in record.slides">
<img src="{{ slide.image[0].urlPath }}">
</div>
</div>
Lightbox
<a href="{{ image[0].urlPath }}" class="glightbox" data-gallery="gallery1">
<img src="{{ image[0].urlPath | imagec(400) }}">
</a>
Breadcrumb
<breadCrumb/>
<breadCrumb class="bg-gray-200 p-3 rounded" c-prevlinks="null"></breadCrumb>
Animate On Scroll (AOS)
<div data-aos="fade-up" data-aos-delay="200" data-aos-duration="800">
Contenido animado
</div>
Valores comunes: fade-up, fade-down, fade-left, fade-right, zoom-in, zoom-in-up, fade-up-right, fade-up-left. Tras cambios dinámicos en JS: AOS.refresh().
Lazy loading
<img class="lazyload" data-src="{{ image[0].urlPath }}">
<!-- O en builder field: -->
<img data-field-type="upload" data-field-label="Imagen" data-lazy="true">
Reglas críticas
- Todo
data-field-typeexigedata-field-label. data-field-labelse transforma a variable: minúsculas, sin espacios ni caracteres especiales.- Campos
uploadretornan arrays — usaimagen[0].urlPath, nuncaimagen. - Variables dentro de
multiv2son propiedades del objeto iterado (record.nombre). c-ifusa=(un igual).{% if %}usa==(doble igual).c-forcon tabla: nombre sin prefijocms_.enlaceya incluye las barras — no añadas slashes extra.- Checkbox guarda
1o0(número), nuncatrue/false. - Evita Tailwind arbitrary-value en
index-base.tpl— muévelos astyle.css. script.jsystyle.cssson estáticos: NO uses sintaxis Twig dentro. Pasa valores dinámicos víadata-*.