Skip to content
This repository has been archived by the owner on Jan 25, 2023. It is now read-only.

dy/templize

Repository files navigation

templize

Native HTML templating based on template parts.

Templize provides elegant native templating for any DOM elements with expressions and reactivity. Based on Template Instantiation and DOM-parts spec.

Features

  • Works with any elements;
  • Expression processor;
  • Reactive props support;
  • Loops, conditions directives;
  • Directives API;
  • Vanilla ESM, no tooling.

Usage

<div id="foo" class="foo {{y}}">{{x}} world</div>
<script type="importmap">{ "imports": { "templize": "parth/to/templize.js" }}</script>

<script type="module">
import templize from 'templize'

const [params, update] = templize(document.getElementById('foo'), { x: 'Hello', y: 'bar'})
// <div id="foo" class="foo bar">Hello world</div>

params.x = 'Goodbye' // === update({x: 'Goodbye'})
// <div id="foo" class="foo bar">Goodbye world</div>
</script>

params is proxy reflecting current state. Changing any of its props updates fields. update can be used for bulk-updating multiple props.

Expressions

Templize enables expressions via default expression processor:

<header id="title">
  <h1>{{ user.name }}</h1>
  Email: <a href="mailto:{{ user.email }}">{{ user.email }}</a>
</header>

<script>
  import templize from 'templize'
  const titleParams = templize(
    document.querySelector('#title'),
    { user: { name: 'Hare Krishna', email: '[email protected]' }}
  )
  titleParams.user.name = 'Hare Rama'
</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) }}

Attributes

Processor makes assumptions regarding how attribute parts set values.

  • hidden="{{ boolean }}" boolean values set or remove attribute.
  • onClick="{{ function }}" assigns onclick handler function (no need to call it).
  • class="{{ classes }}" can take either an array or a string.
  • style="{{ styles }}" can take either an object or a string.

Other attributes are handled as strings.

Reactivity

Initial state can define async/reactive values: Promise/Thenable, AsyncIterable, Observable/Subject.

Update happens when any param changes:

<div id="done">{{ done || '...' }}</div>

<script type="module">
  import templize from 'templize'

  const done = new Promise(ok => setTimeout(() => ok('Done!'), 1000))

  templize(document.querySelector('#done'), { done })

  // <div id="done">...</div>

  // ... 1s after
  // <div id="done">done</div>
</script>

This way, for example, rxjs can be streamed directly to element attribute or content.

Note: observers don't require disposal, since they're connected in weak fashion.

Directives

Templize recognizes shortcut directives via :attr (similar to vue).

Loops

Iterating over set of items can be done with each directive:

<ul>
  <li :each="{{ item in items }}" id="item-{{item.id}}" data-value="{{item.value}}">{{item.label}}</li>
</ul>

Conditions

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>

Adding directives

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))
)

Interop

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)

Dependencies

  • template-parts − compact template parts ponyfill.
  • subscript − fast and tiny expressions parser.
  • sube − subscribe to any reactive source.
  • element-props − normalized element properties setter.

Buddies

  • 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.

Neighbors

  • stampino − small HTML template system based on lit-html.

🕉