HTML reactive template parts for any DOM elements with expressions and directives.
Based on Template Instantiation and DOM-parts specs.
Extends template parts with the following:
- Works with any elements, not just
<template>
; - Supports reactive fields;
- Provides expression processor;
- Enables loops, conditions;
- Exposes directives API;
- Plugs as vanilla ESM, doesn't require tooling.
It can be used as module via npm i templize
or in HTML directly:
<script type="importmap">{ "imports": { "templize": "path/to/templize.js" }}</script>
<div id="foo" class="foo {{y}}">{{x}} world</div>
<script type="module">
import templize from 'templize'
templize(document.getElementById('foo'), { x: 'Hello', y: 'bar'})
// <div id="foo" class="foo bar">Hello world</div>
</script>
const [params, update] = templize(element, init?);
params
is proxy reflecting template fields values. Changing any of its props updates / rerenders fields.
update
can be used for bulk-updating multiple props.
init
is the initial state to render the template. It can include reactive values, see reactivity.
Template fields support the following async/reactive values:
- Promise/Thenable
- AsyncIterable
- Observable/Subject
Update happens when any param changes:
<div id="done">{{ loading ? '...' : result }}</div>
<script type="module">
import templize from 'templize'
import { signal } from '@preact/signals'
const loading = signal(false), result = signal(false)
templize(document.querySelector('#done'), { loading, result })
setTimeout(() => (loading.value = true, result.value = 'done'), 1000)
// <div id="done">...</div>
// ... 1s after
// <div id="done">done</div>
</script>
This way, for example, @preact/signals or rxjs can be streamed directly to element attribute or content.
Note: observers don't require disposal, since they're connected in weak fashion. Once element is disposed, observables are disconnected.
Templize enables expressions via default expression processor:
<header id="title">
<h1>{{ user.name }}</h1>
Email: <a href="mailto:{{ user.email }}" onclick="{{ e => { event.preventDefault(); await sendEmail(user.email); } }}">{{ user.email }}</a>
</header>
<script>
import templize from 'templize';
templize(
document.querySelector('#title'),
{ user: { name: 'Hare Krishna', email: 'krishn@hari.om' }}
)
</script>
It supports the following field expressions with common syntax:
Part | Expression |
---|---|
Value | {{ foo }} |
Property | {{ foo.bar?.baz }} , {{ foo[bar] }} |
Call | {{ foo.bar(baz, qux) }} |
Boolean | {{ !foo && bar || baz }} |
Ternary | {{ foo ? bar : baz }} |
Primitives | {{ "foo" }} , {{ true }} , {{ 0.1 }} |
Comparison | {{ foo == 1 }} , {{ bar >= 2 }} |
Math | {{ a * 2 + b / 3 }} |
Pipe | {{ bar | foo }} → {{ foo(bar) }} |
Processor makes assumptions regarding how attribute parts set values.
hidden="{{ boolean }}"
boolean values set or remove attribute.onClick="{{ function }}"
assignsonclick
handler function (no need to call it, unlike in html).class="{{ classes }}"
can take either an array or a string.style="{{ styles }}"
can take either an object or a string.
Other attribute values cast to strings.
Templize recognizes shortcut directives via :<attr>
(similar to vue).
Iterating over set of items can be done with each
directive:
<ul>
<li :each="{{ item, index in items }}" id="item-{{item.id}}" data-value="{{item.value}}">{{item.label}}</li>
</ul>
To optionally display an element, there are if
, else-if
, else
directives.
<span :if="{{ status == 0 }}">Inactive</span>
<span :else-if="{{ status == 1 }}">Active</span>
<span :else>Finished</span>
Note: text conditions can be organized via ternary operator:
<span>Status: {{ status === 0 ? 'Active' : 'Inactive' }}</span>
To register a directive, directive(name, onCreate)
function can be used:
import templize, { directive } from 'templize'
directive('inline', (instance, innerTplPart, state) =>
innerTplPart.replaceWith(innerTplPart.template.createInstance(state))
)
Templize supports any standard template parts processor:
const params = templize(element, initState, {
createCallback(element, parts, state) {
// ... init parts / parse expressions
},
processCallback(element, parts, state) {
// ... update parts / evaluate expressions
}
})
Any external processor can be used with templize, eg. @github/template-parts:
import templize from 'templize'
import { propertyIdentityOrBooleanAttribute } from '@github/template-parts'
const params = templize(
document.getElementById('foo'),
{ x: 'Hello', hidden: false },
propertyIdentityOrBooleanAttribute
)
params.hidden = true
Templize expression processor can also be used with other template instancing libraries as:
import { TemplateInstance } from '@github/template-parts'
import { processor } from 'templize'
const instance = new TemplateInstance(document.querySelector('my-template'), {}, processor)
Or it can be used with proposal polyfill:
import 'templize-instantiation-polyfill'
import { processor } from 'templize'
document.defineTemplateType('my-template-type', processor)
- template-parts − compact template parts ponyfill.
- subscript − fast and tiny expressions parser.
- sube − subscribe to any reactive source.
- element-props − normalized element properties setter.
- spect − selector observer, perfect match for organizing flexible native DOM templates.
- value-ref − reactive value container with reactivity, useful for state management.
- subscribable-things − reactive wrappers for various APIs.
- stampino − small HTML template system based on lit-html.
🕉