Attrs
El objeto attrs es una de las funcionalidades más útiles de Jx. Recolecta cualquier atributo HTML que pases a un componente que no esté declarado en su sentencia {#def}.
El problema que resuelve Attrs
Imagina un componente botón:
{#def text #}
<button class="btn">{{ text }}</button>
Esto funciona, pero ¿qué pasa si necesitas agregar un atributo id, disabled o data-*? ¿Los agregas todos al {#def}? Eso es poco práctico.
En su lugar, usa attrs:
{#def text #}
<button {{ attrs.render(class="btn") }}>
{{ text }}
</button>
Ahora cualquier atributo extra se recolecta y renderiza automáticamente:
<Button text="Save" id="save-btn" disabled data-action="save" />
Renderiza como:
<button class="btn" id="save-btn" data-action="save" disabled>Save</button>
Cómo funciona
Cuando pasas atributos a un componente:
- Argumentos declarados (de
{#def}) se extraen y están disponibles como variables - Todo lo demás va al objeto
attrs - Llamas a
attrs.render()para mostrarlos como atributos HTML
{#def title #} {# 'title' es un argumento declarado #}
<div {{ attrs.render() }}> {# Todo lo demás se vuelve attrs #}
<h2>{{ title }}</h2>
{{ content }}
</div>
<Card title="Hello" class="card-primary" id="main-card" data-index="1" />
Aquí:
title="Hello"→ Disponible como{{ title }}class="...",id="...",data-index="..."→ Van aattrs
Uso básico
Renderiza todos los attrs
El caso de uso más simple:
<div {{ attrs.render() }}>
...
</div>
Esto muestra todos los atributos extra como HTML.
Agrega atributos por defecto
Puedes proporcionar atributos por defecto:
<button {{ attrs.render(class="btn", type="button") }}>
{{ content }}
</button>
Si el usuario pasa class o type, se fusionarán/sobrescribirán según corresponda.
Fusión de clases
El atributo class es especial; se fusiona en lugar de reemplazar:
{#def text #}
<button {{ attrs.render(class="btn") }}>
{{ text }}
</button>
<Button text="Save" class="btn-primary" />
Renderiza como:
<button class="btn btn-primary">Save</button>
¡Ambas clases están incluidas!
Métodos de Attrs
El objeto attrs tiene varios métodos útiles:
attrs.render(**kwargs)
Renderiza todos los atributos como una cadena HTML.
Puedes pasar atributos adicionales para fusionar:
<div {{ attrs.render(class="card", role="region") }}>
...
</div>
Reglas de fusión:
class: Las clases se agregan (no se reemplazan)- Otros atributos: Los nuevos valores sobrescriben los antiguos
True: Se renderiza como un atributo booleano (por ejemplo,disabled)False: Elimina el atributo- Los guiones bajos se convierten en guiones:
data_id→data-id
attrs.set(**kwargs)
Modifica los atributos antes de renderizar:
{#def title, highlighted=false #}
{% if highlighted %}
{% do attrs.set(class="card card-highlighted", role="alert") %}
{% endif %}
<div {{ attrs.render(class="card") }}>
<h3>{{ title }}</h3>
{{ content }}
</div>
Opciones:
attrs.set(id="new-id")- Establece un atributoattrs.set(disabled=True)- Agrega un atributo booleanoattrs.set(class="extra-class")- Agrega a las clases existentesattrs.set(data_foo="bar")- Los guiones bajos se convierten en guionesattrs.set(hidden=False)- Elimina un atributo
attrs.setdefault(**kwargs)
Establece un atributo solo si no existe ya:
{% do attrs.setdefault(role="button", tabindex=0) %}
<div {{ attrs.render(class="btn") }}>
{{ content }}
</div>
Si el usuario pasó role, no será sobrescrito.
attrs.get(name, default=None)
Obtiene el valor de un atributo:
{%- set btn_type = attrs.get("type", "button") %}
<button {{ attrs.render() }} type="{{ btn_type }}">
{{ content }}
</button>
attrs.add_class(*classes)
Agrega una o más clases al final de la lista de clases:
{% do attrs.add_class("btn", "btn-primary") %}
<button {{ attrs.render() }}>
{{ content }}
</button>
attrs.prepend_class(*classes)
Agrega una o más clases al inicio de la lista de clases:
{% do attrs.prepend_class("btn") %}
<button {{ attrs.render() }}>
{{ content }}
</button>
Esto es útil cuando el orden de las clases importa (por ejemplo, con frameworks CSS utility-first donde la primera clase debe ser el estilo base).
attrs.remove_class(*classes)
Elimina una o más clases:
{% do attrs.remove_class("hidden", "invisible") %}
<div {{ attrs.render() }}>
{{ content }}
</div>
attrs.classes
Devuelve todas las clases HTML como una cadena separada por espacios:
{% if "active" in attrs.classes %}
<span>Este ítem está activo</span>
{% endif %}
attrs.as_dict
Devuelve todos los atributos como un diccionario:
{% set all_attrs = attrs.as_dict %}
{% for key, value in all_attrs.items() %}
<p>{{ key }}: {{ value }}</p>
{% endfor %}
Patrones comunes
Componente botón
{#def text="Click me", variant="primary" #}
<button {{ attrs.render(
class="btn btn-" ~ variant,
type="button"
) }}>
{{ text }}
</button>
<Button text="Save" variant="success" id="save-btn" disabled />
Componente Card
{#def title="" #}
<div {{ attrs.render(class="card") }}>
{% if title %}
<div class="card-header">
<h3>{{ title }}</h3>
</div>
{% endif %}
<div class="card-body">
{{ content }}
</div>
</div>
<Card title="Welcome" class="shadow-lg" data-card-id="123">
<p>Card content here</p>
</Card>
Componente Input
{#def name, label="", required=false #}
{% do attrs.setdefault(type="text", id=name) %}
<div class="form-group">
{% if label %}
<label for="{{ name }}">
{{ label }}
{% if required %}<span class="required">*</span>{% endif %}
</label>
{% endif %}
<input name="{{ name }}" {{ attrs.render(class="form-control") }} />
</div>
<Input
name="email"
label="Email Address"
type="email"
required
placeholder="you@example.com"
autocomplete="email"
/>
Estilo condicional
{#def message, type="info" #}
{% do attrs.add_class("alert", "alert-" ~ type) %}
{% if type == "error" %}
{% do attrs.set(role="alert") %}
{% endif %}
<div {{ attrs.render() }}>
{{ message }}
</div>
Pasa attrs a componentes hijos
Para reenviar attrs a un componente hijo, pásalo explícitamente:
{#import "./button.jx" as Button #}
{#def text #}
<div class="button-wrapper">
<Button text={{ text }} attrs={{ attrs }} />
</div>
Ahora todos los atributos extra se reenvían:
<WrappedButton text="Save" class="primary" disabled />
Importante: No intentes usar {{ attrs.render() }} en una etiqueta de componente. Esto no funcionará:
{# ❌ INCORRECTO - no funcionará #}
<Button {{ attrs.render() }} />
{# ✅ CORRECTO #}
<Button attrs={{ attrs }} />
Las etiquetas de componente se preprocesan antes del renderizado, por lo que debes pasar attrs como un argumento.
Conversión de guion bajo a guion
Los nombres de atributos con guiones bajos se convierten automáticamente a guiones:
<Button
data_user_id="123"
aria_label="Save document"
hx_get="/api/save"
/>
Renderiza como:
<button data-user-id="123" aria-label="Save document" hx-get="/api/save">
...
</button>
Esto es especialmente útil para:
- Atributos
data-*:data_id,data_action - Atributos
aria-*:aria_label,aria_hidden - Atributos de frameworks:
hx_get,x_show,v_if
Casos especiales
Preserva atributos
A veces quieres controlar qué atributos se renderizan:
{#def title #}
{# Obtén attrs específicos antes de renderizar #}
{% set custom_id = attrs.get("id", "default-id") %}
<div id="{{ custom_id }}" class="card">
<h3>{{ title }}</h3>
<div {{ attrs.render() }}>{# Otros attrs van aquí #}
{{ content }}
</div>
</div>
Atributos condicionales
{#def is_active=false #}
{% if is_active %}
{% do attrs.add_class("active") %}
{% do attrs.set(aria_current="true") %}
{% endif %}
<a {{ attrs.render(class="nav-link") }}>
{{ content }}
</a>
Múltiples elementos
Puedes usar attrs en múltiples elementos, pero usualmente quieres atributos diferentes en cada uno:
{#def title #}
<div class="card">
<h3 {{ attrs.render() }}>{# Los attrs van en el título #}
{{ title }}
</h3>
<div class="body">
{{ content }}
</div>
</div>
O sepáralos:
{#def title #}
{% set card_class = attrs.get("card_class", "card") %}
{% do attrs.remove_class(card_class) %}
<div class="{{ card_class }}">
<h3 {{ attrs.render() }}>{# Otros attrs van en el título #}
{{ title }}
</h3>
<div class="body">
{{ content }}
</div>
</div>
Buenas prácticas
1. Siempre proporciona clases por defecto
{# ✅ Bien - asegura el estilo base #}
<button {{ attrs.render(class="btn") }}>
{# ❌ Mal - el usuario siempre debe proporcionar class #}
<button {{ attrs.render() }}>
2. Usa setdefault para atributos semánticos
{% do attrs.setdefault(role="button", tabindex=0) %}
<div {{ attrs.render() }}>...</div>
Esto asegura accesibilidad sin impedir las sobrescrituras del usuario.
3. Documenta los attrs esperados
{#def text #}
{# Attrs comunes: class, id, disabled, data-* #}
<button {{ attrs.render(class="btn") }}>
{{ text }}
</button>
4. No abuses de attrs.set()
{# ❌ Demasiada manipulación #}
{% do attrs.set(class="a") %}
{% do attrs.add_class("b") %}
{% do attrs.set(role="button") %}
{% do attrs.setdefault(tabindex=0) %}
{# ✅ Mejor - hazlo todo de una vez #}
{% do attrs.set(class="a b", role="button") %}
{% do attrs.setdefault(tabindex=0) %}
Próximos pasos
- Assets - Aprende sobre el manejo de CSS y JavaScript
- API: Attrs - Referencia completa de la API para la clase Attrs