Saltearse al contenido

Plantillas

Las plantillas Owl se describen utilizando la especificación QWeb. Se basa en el formato XML y se utiliza principalmente para generar HTML. En OWL, las plantillas QWeb se compilan en funciones que generan una representación DOM virtual del HTML. Además, dado que Owl es un sistema de componentes en vivo, existen directivas adicionales específicas de Owl (como t-on).

<div>
<span t-if="somecondition">Some string</span>
<ul t-else="">
<li t-foreach="messages" t-as="message">
<t t-esc="message"/>
</li>
</ul>
</div>

Las directivas de plantilla se especifican como atributos XML con el prefijo t-, por ejemplo t-if para condicionales, donde los elementos y otros atributos se representan directamente.

Para evitar la representación de elementos, también está disponible un elemento marcador <t>, que ejecuta su directiva pero no genera ninguna salida por sí mismo.

Presentamos en esta sección el lenguaje de plantillas, incluidas sus extensiones específicas de Owl.

Directivas

Como referencia, aquí hay una lista de todas las directivas estándar de QWeb:

NombreDescripción
t-escGenerar un valor de forma segura
t-outSalida de valor, posiblemente sin escape
t-set, t-valueConfiguración de variables
t-if, t-elif, t-else,condicionales
t-foreach, t-asBucles
t-att, t-attf-*, t-att-*Atributos dinámicos
t-callRenderizado de subplantillas
t-debug, t-logDepuración
t-translationDeshabilitar la traducción de un nodo

The component system in Owl requires additional directives, to express various needs. Here is a list of all Owl specific directives:

NombreDescripción
t-component, t-propsDefinición de un subcomponente
t-refEstablecer una referencia a un nodo DOM o un subcomponente
t-keyDefinición de una clave (para facilitar la conciliación del DOM virtual)
t-on-*Manejo de eventos
t-portalPortal
t-slot, t-set-slot, t-slot-scopeRenderizado de ranuras
t-modelEnlaces de entrada de formulario
t-tagRepresentación de nodos con nombre de etiqueta dinámico
t-custom-*Representación de nodos con directivas personalizadas

Referencia de plantilla de QWeb

Espacios en blanco

Los espacios en blanco en una plantilla se manejan de una manera especial:

  • Los espacios en blanco consecutivos siempre se condensan en un solo espacio en blanco.
  • Si un nodo de texto que solo contiene espacios en blanco contiene un salto de línea, se ignora.
  • Las reglas anteriores no se aplican si estamos en una etiqueta <pre>

Evaluación de expresión

Las expresiones de QWeb son cadenas que se procesarán en el momento de la compilación. Cada variable de la expresión de JavaScript se reemplazará con una búsqueda en el contexto (es decir, el componente). Por ejemplo, a + b.c(d) se convertirá en:

context["a"] + context["b"].c(context["d"]);

Es útil explicar las distintas reglas que se aplican a estas expresiones:

  1. Debe ser una expresión simple que devuelva un valor. No puede ser una declaración.

    <div><p t-if="1 + 2 === 3">ok</p></div>

    es válido, pero lo siguiente no es válido:

    <div><p t-if="console.log(1)">NOT valid</p></div>
  2. Puede utilizar cualquier cosa en el contexto de representación (que normalmente contiene las propiedades del componente):

    <p t-if="user.birthday === today()">Happy bithday!</p>

    es válido y leerá el objeto user del contexto y llamará a la función today.

  3. Puede utilizar algunos operadores especiales para evitar el uso de símbolos como <, >, & o |. Esto resulta útil para asegurarnos de que seguimos escribiendo XML válido.

    Palabrareemplazado con
    and&&
    or||
    gt>
    gte>=
    lt<
    lte<=

Entonces, uno puede escribir esto:

<div><p t-if="10 + 2 gt 5">ok</p></div>

Nodos HTML estáticos

Los nodos HTML normales y regulares se representan en sí mismos:

<div>hello</div> <!–– rendered as itself ––>

Salida de datos

La directiva t-esc es necesaria siempre que se desee agregar una expresión de texto dinámica en una plantilla. El texto se escapa para evitar problemas de seguridad.

<p><t t-esc="value"/></p>

Representado con el valor value establecido en 42 en el contexto de representación, da como resultado:

<p>42</p>

La directiva t-out es casi la misma que t-esc, pero posiblemente sin el escape. La diferencia es que el valor recibido por la directiva t-out solo no será escapado si ha sido marcado como tal, utilizando la función de utilidad markup:

Por ejemplo, en el siguiente componente:

const { markup, Component, xml } = owl;
class SomeComponent extends Component {
static template = xml`
<t t-out="value1"/>
<t t-out="value2"/>`;
value1 = "<div>some text 1</div>";
value2 = markup("<div>some text 2</div>");
}

El primer t-out actuará como una directiva t-esc, lo que significa que se escapará el contenido de value1. Sin embargo, dado que value2 se ha etiquetado como un marcado, se inyectará como html.

Configuración de variables

QWeb permite crear variables desde dentro de la plantilla, para memorizar un cálculo (para usarlo varias veces), darle a un dato un nombre más claro, …

Esto se hace mediante la directiva t-set, que toma el nombre de la variable a crear. El valor a establecer se puede proporcionar de dos maneras:

  1. un atributo t-value que contiene una expresión, y se establecerá el resultado de su evaluación:

    <t t-set="foo" t-value="2 + 1"/>
    <t t-esc="foo"/>

    imprimirá 3. Tenga en cuenta que la evaluación se realiza en el momento de la representación, no en el momento de la compilación.

  2. Si no hay ningún atributo t-value, se guarda el cuerpo del nodo y su valor se establece como el valor de la variable:

    <t t-set="foo">
    <li>ok</li>
    </t>
    <t t-esc="foo"/>

    generará &lt;li&gt;ok&lt;/li&gt; (el contenido se escapa ya que usamos la directiva t-esc)

La directiva t-set actúa como una variable regular en la mayoría de los lenguajes de programación. Tiene un alcance léxico (los nodos internos son subámbitos), se puede ocultar…

Condicionales

La directiva t-if es útil para representar algo de manera condicional. Evalúa la expresión dada como valor de atributo y luego actúa en consecuencia.

<div>
<t t-if="condition">
<p>ok</p>
</t>
</div>

El elemento se representa si la condición (evaluada con el contexto de representación actual) es verdadera:

<div>
<p>ok</p>
</div>

pero si la condición es falsa se elimina del resultado:

<div>
</div>

La representación condicional se aplica al portador de la directiva, que no tiene por qué ser <t>:

<div>
<p t-if="condition">ok</p>
</div>

Dará los mismos resultados que el ejemplo anterior.

Las directivas de ramificación condicional adicionales t-elif y t-else también están disponibles:

<div>
<p t-if="user.birthday == today()">Happy bithday!</p>
<p t-elif="user.login == 'root'">Welcome master!</p>
<p t-else="">Welcome!</p>
</div>

Atributos dinámicos

Se puede utilizar la directiva t-att- para agregar atributos dinámicos. Su uso principal es evaluar una expresión (en el momento de la representación) y vincular un atributo a su resultado:

Por ejemplo, si tenemos id establecido en 32 en el contexto de representación,

<div t-att-data-action-id="id"/> <!-- result: <div data-action-id="32"></div> -->

Si una expresión se evalúa como un valor falso, no se establecerá en absoluto:

<div t-att-foo="false"/> <!-- result: <div></div> -->

A veces resulta conveniente formatear un atributo con interpolación de cadenas. En ese caso, se puede utilizar la directiva t-attf-. Es útil cuando necesitamos mezclar elementos literales y dinámicos, como las clases CSS. Los elementos dinámicos se pueden especificar con {{...}} o #{...}:

<div t-attf-foo="a {{value1}} is #{value2} of {{value3}} ]"/>
<!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> -->

Si necesitamos nombres de atributos completamente dinámicos, existe una directiva adicional: t-att, que acepta un objeto (con claves asignadas a sus valores) o un par [clave, valor]. Por ejemplo:

<div t-att="{'a': 1, 'b': 2}"/> <!-- result: <div a="1" b="2"></div> -->
<div t-att="['a', 'b']"/> <!-- <div a="b"></div> -->

Atributos de clase dinámicos

Para mayor comodidad, Owl admite un caso especial para el caso t-att-class: se puede utilizar un objeto con claves que describen las clases y valores booleanos que indican si la clase está o no presente:

<div t-att-class="{'a': true, 'b': true}"/> <!-- result: <div class="a b"></div> -->
<div t-att-class="{'a b': true, 'c': true}"/> <!-- result: <div class="a b c"></div> -->

Tenga en cuenta que se puede combinar con el atributo de clase normal:

<div class="a" t-att-class="{'b': true}"/> <!-- result: <div class="a b"></div> -->

Nombre de etiquetas dinámicos

Al escribir componentes o plantillas genéricas, todavía no se conoce la etiqueta concreta específica para un elemento HTML. En esas situaciones, la directiva t-tag resulta útil. Simplemente evalúa dinámicamente una expresión para usarla como nombre de etiqueta. La plantilla:

<t t-tag="tag">
<span>content</span>
</t>

se representará como <div><span>content</span></div> si la clave de contexto tag se establece en div.

Bucles

QWeb tiene una directiva de iteración t-foreach que toma una expresión que devuelve la colección sobre la cual iterar, y un segundo parámetro t-as que proporciona el nombre a usar para el elemento actual de la iteración:

<t t-foreach="[1, 2, 3]" t-as="i" t-key="i">
<p><t t-esc="i"/></p>
</t>

Se representará como:

<p>1</p>
<p>2</p>
<p>3</p>

Al igual que las condiciones, t-foreach se aplica al elemento que lleva el atributo de la directiva, y

<p t-foreach="[1, 2, 3]" t-as="i" t-key="i">
<t t-esc="i"/>
</p>

es equivalente al ejemplo anterior.

Se debe hacer una diferencia importante con el comportamiento habitual de QWeb: Owl requiere la presencia de una directiva t-key para poder conciliar correctamente las representaciones.

t-foreach puede iterar sobre cualquier iterable, y también tiene soporte especial para objetos y mapas, expondrá la clave de la iteración actual como el contenido de t-as, y el valor correspondiente con el mismo nombre y el sufijo _value.

Además del nombre pasado a través de t-as, t-foreach proporciona algunas otras variables útiles (nota: $as será reemplazado con el nombre pasado a t-as):

  • $as_value: el valor de iteración actual, idéntico a $as para matrices y otros iterables, pero para objetos y mapas, proporciona el valor (donde $as proporciona la clave)
  • $as_index: el índice de iteración actual (el primer elemento de la iteración tiene índice 0)
  • $as_first: si el elemento actual es el primero de la iteración (equivalente a $as_index == 0)
  • $as_last: si el elemento actual es el último de la iteración (equivalente a $as_index + 1 == $as_size), requiere que el tamaño del iterador esté disponible

Estas variables adicionales proporcionadas y todas las nuevas variables creadas en t-foreach solo están disponibles en el ámbito de t-foreach. Si la variable existe fuera del contexto de t-foreach, el valor se copia al final de foreach en el contexto global.

<t t-set="existing_variable" t-value="false"/>
<!-- existing_variable now False -->
<p t-foreach="Array(3)" t-as="i" t-key="i">
<t t-set="existing_variable" t-value="true"/>
<t t-set="new_variable" t-value="true"/>
<!-- existing_variable and new_variable now true -->
</p>
<!-- existing_variable always true -->
<!-- new_variable undefined -->

Aunque Owl intenta ser lo más declarativo posible, el DOM no expone completamente su estado de forma declarativa en el árbol DOM. Por ejemplo, el estado de desplazamiento, la selección actual del usuario, el elemento enfocado o el estado de una entrada no se establecen como atributos en el árbol DOM. Por eso, utilizamos un algoritmo DOM virtual para asegurarnos de mantener el nodo DOM real en lugar de reemplazarlo por uno nuevo.

Consideremos la siguiente situación: tenemos una lista de dos elementos [{text: "a"}, {text: "b"}] y los representamos en esta plantilla:

<p t-foreach="items" t-as="item" t-key="item_index"><t t-esc="item.text"/></p>

El resultado serán dos etiquetas <p> con los textos a y b. Ahora, si las intercambiamos y volvemos a renderizar la plantilla, Owl necesita saber cuál es la intención:

  • ¿Debería Owl realmente intercambiar los nodos DOM?
  • ¿O debería mantener los nodos DOM, pero con un contenido de texto actualizado?

Puede parecer trivial, pero en realidad es importante. Estas dos posibilidades conducen a resultados diferentes en algunos casos. Por ejemplo, si el usuario seleccionó el texto de la primera p, al intercambiarlos se conservará la selección, pero al actualizar el contenido del texto no.

Hay muchos otros casos en los que esto es importante: etiquetas input con su valor, clases y animaciones CSS, posición de desplazamiento…

Por lo tanto, la directiva t-key se utiliza para dar una identidad a un elemento. Permite que Owl comprenda si los distintos elementos de una lista son realmente diferentes o no.

El ejemplo anterior se puede modificar agregando un ID: [{id: 1, text: "a"}, {id: 2, text: "b"}]. Entonces, la plantilla podría verse así:

<p t-foreach="items" t-as="item" t-key="item.id"><t t-esc="item.text"/></p>

La directiva t-key es útil para listas (t-foreach). Una clave debe ser un número o cadena única (los objetos no funcionarán: se convertirán a la cadena "[object Object]", que obviamente no es única).

Además, la clave se puede configurar en una etiqueta t o en sus elementos secundarios. Las siguientes variaciones son todas equivalentes:

<p t-foreach="items" t-as="item" t-key="item.id">
<t t-esc="item.text"/>
</p>
<t t-foreach="items" t-as="item" t-key="item.id">
<p t-esc="item.text"/>
</t>
<t t-foreach="items" t-as="item">
<p t-key="item.id" t-esc="item.text"/>
</t>

Si no hay una directiva t-key, Owl utilizará el índice como clave predeterminada.

nota: la directiva t-foreach solo acepta matrices (listas) u objetos. No funciona con otros iterables, como Set. Sin embargo, solo es cuesti%C3%B3n de usar el operador de javascript .... Por ejemplo:

<t t-foreach="[...items]" t-as="item">...</t>

El operador ... convertirá el Conjunto (o cualquier otro iterable) en una lista, que funcionará con Owl QWeb.

Subplantillas

Las plantillas QWeb se pueden usar para la representación de nivel superior, pero también se pueden usar desde otra plantilla (para evitar duplicación o dar nombres a partes de las plantillas), utilizando la directiva t-call:

<div t-name="other-template">
<p><t t-value="var"/></p>
</div>
<div t-name="main-template">
<t t-set="var" t-value="owl"/>
<t t-call="other-template"/>
</div>

se representará como <div><p>owl</p></div>. Este ejemplo muestra que la subplantilla se representa con el contexto de ejecución del padre. La subplantilla está realmente incorporada en la plantilla principal, pero en un subámbito: las variables definidas en la subplantilla no se escapan.

A veces, es posible que desees pasar información a la subplantilla. En ese caso, el contenido del cuerpo de la directiva t-call está disponible como una variable mágica especial 0:

<t t-name="other-template">
This template was called with content:
<t t-raw="0"/>
</t>
<div t-name="main-template">
<t t-call="other-template">
<em>content</em>
</t>
</div>

dará como resultado:

<div>
This template was called with content:
<em>content</em>
</div>

Esto se puede utilizar para definir variables limitadas a una subplantilla:

<t t-call="other-template">
<t t-set="var" t-value="1"/>
</t>
<!-- "var" does not exist here -->

nota: de forma predeterminada, el contexto de representaci%C3%B3n de una subplantilla es simplemente el contexto de representaci%C3%B3n actual. Sin embargo, puede resultar %C3%BAtil poder especificar un objeto espec%C3%ADfico como contexto. Esto se puede hacer utilizando la directiva t-call-context:

<t t-call="other-template" t-call-context="obj"/>

Subplantillas dinámicas

La directiva t-call también se puede utilizar para llamar dinámicamente a una subplantilla, mediante interpolación de cadenas. Por ejemplo:

<div t-name="main-template">
<t t-call="{{template}}">
<em>content</em>
</t>
</div>

Aquí, el nombre de la plantilla se obtiene del valor plantilla en el contexto de representación de la plantilla.

Debugging

La implementación de QWeb en JavaScript proporciona dos directivas de depuración útiles:

t-debug agrega una declaración de depuración durante la representación de la plantilla:

<t t-if="a_test">
<t t-debug=""/>
</t>

detendrá la ejecución si las herramientas de desarrollo del navegador están abiertas.

t-log toma un parámetro de expresión, evalúa la expresión durante la representación y registra su resultado con console.log:

<t t-set="foo" t-value="42"/>
<t t-log="foo"/>

imprimirá 42 en la consola.

Directivas personalizadas

Owl 2 admite la declaración de directivas personalizadas. Para utilizarlas, es necesario configurar un objeto de funciones en la APP Owl:

new App(..., {
customDirectives: {
test_directive: function (el, value) {
el.setAttribute("t-on-click", value);
}
}
});

Las funciones se llamarán cuando se encuentre una directiva personalizada con el nombre de la función. El elemento original se reemplazará por el modificado por la función. Esto:

<div t-custom-test_directive="click" />

será reemplazado por:

<div t-on-click="value"/>

Fragmentos

Owl 2 admite plantillas con una cantidad arbitraria de elementos raíz o incluso un solo nodo de texto. Por lo tanto, las siguientes plantillas son todas válidas:

hello owl. This is just a text node!
<div>hello</div>
<div>hello</div>
<div>ola</div>
<div t-if="someCondition"><SomeChildComponent/></div>
<t t-if="someCondition"><SomeChildComponent/></t>

Plantillas en línea

La mayoría de las aplicaciones reales definirán sus plantillas en un archivo XML para aprovechar el ecosistema XML y realizar algún procesamiento adicional, como traducirlas. Sin embargo, en algunos casos, es conveniente poder definir una plantilla en línea. Para ello, se puede utilizar la función auxiliar xml:

const { Component, xml } = owl;
class MyComponent extends Component {
static template = xml`
<div>
<span t-if="somecondition">text</span>
<button t-on-click="someMethod">Click</button>
</div>
`;
...
}
mount(MyComponent, document.body);

Esta función simplemente genera una cadena de identificación única y registra la plantilla bajo esa identificación en el interior de Owl, luego devuelve la identificación.

Renderizado de svg

Los componentes de Owl se pueden utilizar para generar gráficos SVG dinámicos:

class Node extends Component {
static template = xml`
<g>
<circle t-att-cx="props.x" t-att-cy="props.y" r="4" fill="black"/>
<text t-att-x="props.x - 5" t-att-y="props.y + 18"><t t-esc="props.node.label"/></text>
<t t-set="childx" t-value="props.x + 100"/>
<t t-set="height" t-value="props.height/(props.node.children || []).length"/>
<t t-foreach="props.node.children || []" t-as="child">
<t t-set="childy" t-value="props.y + child_index*height"/>
<line t-att-x1="props.x" t-att-y1="props.y" t-att-x2="childx" t-att-y2="childy" stroke="black" />
<Node x="childx" y="childy" node="child" height="height"/>
</t>
</g>
`;
static components = { Node };
}
class RootNode extends Component {
static template = xml`
<svg height="180">
<Node node="graph" x="10" y="20" height="180"/>
</svg>
`;
static components = { Node };
graph = {
label: "a",
children: [
{ label: "b" },
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
{ label: "f", children: [{ label: "g" }] },
],
};
}

Este componente RootNode mostrará una representación SVG en vivo del gráfico descrito por la propiedad graph. Tenga en cuenta que aquí hay una estructura recursiva: el componente Node se utiliza a sí mismo como subcomponente.

Nota importante: Owl necesita configurar correctamente el espacio de nombres para cada elemento svg. Dado que Owl compila cada plantilla por separado, no puede determinar fácilmente si una plantilla debe incluirse en un espacio de nombres svg o no. Por lo tanto, Owl depende de una heurística: si una etiqueta es svg, g o path, entonces se considerará como svg. En la práctica, esto significa que cada componente o cada subplantilla (incluida con t-call) debe tener una de estas etiquetas como etiqueta raíz.

Restricciones

Tenga en cuenta que las plantillas Owl prohíben el uso de etiquetas o atributos que comiencen con la cadena block-. Esta restricción evita la colisión de nombres con el código interno de Owl.

<div><block-1>this will not be accepted by Owl</block-1></div>