@ivi/tpl
v3.0.1
Published
ivi Template Language.
Downloads
3
Readme
ivi Template Language
@ivi/tpl
module provides an interface for creating ivi templates with templates literals:
htm
creates a template with HTMLElement nodes.svg
creates a template with SVGElement nodes.
htm`
div :id='app'
h1 'Template Example'
div.content
${condition ? "true" : "false"}
`
In this example we are creating a root element <div id="app">
with two children: <h1>TemplateExample</h1>
and <div class="content">{..}</div>
. In an HTML it could look something like that:
<div id="app">
<h1>Template Example</h1>
<div class="content">
{condition ? "true" : "false"}
</div>
</div>
As we can see from this example, indentation level is used for children nesting. Also, children node can be nested by declaring them on the same line as their parent. E.g.
htm`div a ${expr}`
HTML:
<div><a>{expr}</a></div>
Or in a mixed form:
htm`
div a ${0}
${1}
`
HTML:
<div><a>{0}</a>{1}</div>
Inline nodes that can't have any children will be rendered as siblings:
htm`div 'prefix' ${expr} 'suffix'`
HTML:
<div>prefix{expr}suffix</div>
Text Nodes
Static text nodes are declared either with 'text'
, "text"
or #'text'#
syntax.
Text nodes are escaped automatically:
htm`div 'escape & <'`
HTML:
<div>escape & <</div>
Multiple Root Nodes
htm`
div 'a'
${expr}
text'
div 'b'
`
Element Properties Syntax
div.classA.classB
- Static class names<div class="classA classB">
div${expr}
- Dynamic class nameselement.className = expr
div :name='value'
- Static attribute with a value<div name="value">
.div :name
- Static attribute without a value<div name>
.div :name=${expr}
- Dynamic attributeelement.setAttribute(name, expr)
.div .name=${expr}
- Propertyelement[name] = expr
.div *name=${expr}
- Propertyelement[name] = expr
, diffs against a DOM value.div ~name='value'
- Static style<div style="value">
.div ~name=${expr}
- Dynamic styleelement.style.setProperty(name, expr)
.div @name=${expr}
- Eventelement.addEventListener(name, expr)
.div =${expr}
- Text Contentelement.textContent = expr
.div &=${directive}
- Client-Side Element Directivedirective(element)
.div &:ssr=${directive}
- Element Directive that works during Client-Side and Server-Side Renderingdirective(element, hydrate)
.
Element properties can be declared on the same line as element or with an indentation level.
htm`
div :inline-attr1 :inline-attr2
:indented-attr
:can-be-indented-with-any-amount-of-spaces
child-element
`
Class Names
Static class names are declared with a .
character immediately after a tag name:
htm`div.class-one.class-two ${expr}`
HTML:
<div class="class-one class-two"></div>
Dynamic class names are declared with an expression immediately after a tag name:
htm`div${condition ? "class-one" : "class-two"}`
HTML:
<div class={condition ? "class-one" : "class-two"}></div>
Static and dynamic class names cannot be mixed together.
Attributes
div :name='value'
- Static attribute with a value<div name="value">
.div :name
- Static attribute without a value<div name>
.div :name=${expr}
- Dynamic attributeelement.setAttribute(name, expr)
.
DOM attributes are assigned with Element.setAttribute(..)
.
When dynamic attribute has an undefined
, null
or false
value, it will be removed from the DOM element with Element.removeAttribute(..)
method.
Attribute values are escaped automatically:
htm`div :name='escape & "'`
HTML:
<div name="escape & ""></div>
Properties
div .name=${expr}
- Propertyelement[name] = expr
.div *name=${expr}
- Propertyelement[name] = expr
, diffs against a DOM value.
Properties are assigned with an assignment operator Element.name = value
.
Diffing with a DOM value is useful in use cases when we use <input>
values to avoid triggering unnecessary input
events.
Styles
div ~name='value'
- Static style<div style="value">
.div ~name=${expr}
- Dynamic styleelement.style.setProperty(name, expr)
.
Static styles are automatically merged with :style="value"
attribute.
Dynamic styles are assigned with a CSSStyleDeclaration.setProperty(..)
method.
When style has an undefined
, null
or false
value, it will be removed with CSSStyleDeclaration.removeProperty(..)
method.
Events
div @name=${expr}
- Eventelement.addEventListener(name, expr)
.
Events are assigned with an EventTarget.addEventListener(..)
method.
When event has an undefined
, null
or false
value, it will be removed with EventTarget.removeEventListener(..)
method.
Text Content
div =${expr}
- Text Contentelement.textContent = expr
.
Text content property can be used as an optimization that slightly reduces memory consumption for elements with a text child. It will create a text node with a Node.textContent
property and won't have any stateful nodes associated with a text node.
Text content value should have an undefined
, null
, false
, string
or a number
type.
Directives
div &=${directive}
- Client-Side Element Directivedirective(element)
.div &:ssr=${directive}
- Element Directive that works during Client-Side and Server-Side Renderingdirective(element, hydrate)
.
Directive is a function that is invoked each time template is updated and receives a DOM element associated with a directive:
type ElementDirective = <E extends Element>(
element: E,
hydrate?: boolean,
) => void | string | { a?: string, c?: string; };
Directive function is invoked only when template is created with a different function, so if we are going to reuse the same function, it can be used as a DOM element created callback:
const Example = component((c) => {
const onCreated = (innerElement) => {
// ..
};
return () => htm`
div.outer
div.inner &=${onCreated}
`;
});
Directives can be used not just as a simple DOM created callbacks, but also as stateful directives. E.g.
function createStatefulDirective() {
// Internal state that stores previous value.
let prev;
// Returns a factory that creates directive functions.
return (next) => (element) => {
// Check if previous value has been changed.
if (prev !== next) {
prev = next;
// Updates textContent only when input value is changed.
element.textContent = next;
}
};
}
const Example = component((c) => {
const directive = createStatefulDirective();
return (i) => htm`
div &=${directive(i)}
`;
});