Literal templates are compiled in a scope that contains a number of objects and functions designed for writing concise template expressions.
Expressions are made powerful by Literal’s renderer, which accepts expressions that evaluate to a string or other primitive, a DOM node or fragment, an array of values, another renderer, or even an asynchronous value in a promise or a stream.
The data
object carries data for rendering.
${ data }
The events()
function returns a stream of DOM events.
<!-- Listen to events -->
${ events('click', element).each((e) => { ... }) }
<!-- Map a stream of events to text -->
${ events('change', element).map((e) => e.target.value) }
The include()
function returns a template renderer.
<!-- Include another template -->
${ include('#template-id', data) }
<!-- Include another template and render it when JSON data is fetched -->
${ include('#template-id', '../documentation/modules/package.json') }
<!-- Include another template for each object in an array -->
${ data.array.map(include('#template-id')) }
data
The main object passed into the template carrying data. This object is special. When it mutates, the DOM re-renders.
this
The current renderer. Normally you wouldn’t reference this unless you want to print information about the template renderer itself.
body
An alias of document.body
.
element
The element enclosing the current template tag.
host
Where this literal template renders the shadow DOM of a custom element, host
refers to the custom element.
shadow
Where this literal template renders the shadow DOM of a custom element, shadow
refers to the custom element’s shadow root.
nothing
A frozen array-like and stream-like object that contains no value.
clock(interval)
If interval
is a number, returns a stream of DOM timestamps at interval
seconds apart.
${ clock(1).map(floor) }
${ clock(1).map(floor) }
If duration
is "frame"
, returns a stream of DOM timestamps of animation
frames.
${ clock('frame').map((time) => time.toFixed(2)) }
${ clock('frame').map((time) => time.toFixed(2)) }
events(type, element)
Returns a mappable stream of events heard on element
.
${ events('click', element).map((e) => e.target.id) }
The first parameter may alternatively be a select object. It must have a
.type
property.
${ events({ type: 'click' }, element).map((e) => e.target.id) }
The object may contain a number of other properties that select the events received. It supports the standard addEventListener options, for passive and capture phase event binding.
${ events({ type: 'scroll', passive: true, capture true }, window)
.map((e) => window.scrollTop) }
And a .select
property, a CSS selector, that filters events to those with
targets that match or have a closest()
ancestor that matches the selector.
${ events({ type: 'click', select: '[name="button"]' }, element)
.map((e) => e.target.id) }
However, if you need to delegate events it is recommended to use the
delegate()
function, which has the added benefit of direct access to the
delegated target.
${ events('click', element).each(delegate({
'[name="button"]': (target, e) => console.log(target.id),
'[name="remove"]': (target, e) => document.getElementById(target.value).remove(),
...
})) }
Stopping an event stream removes event listeners.
${ events('click', element).stop() }
But streams returned from template expressions, like this one, are stopped automatically when the renderer is stopped, so normally there is no need to worry about managing them.
observe(path, object)
Returns a stream of values at path
in object
. Values are sent whenever
the Data()
proxy of object
is mutated.
${ observe('path.to.value', data).each(() => {...}) }
TODO: warning, this function is probably going to be renamed as mutations()
,
updates()
or changes()
.
Stream.combine(inputs)
Creates a stream of objects containing the latest values of all streams and
promises in inputs
as they resolve:
Stream.combine({ a: stream, b: promise }).each((object) => {
// object.a and object.b are values
});
If inputs
contains properties that are not streams or promises, those are
also set on streamed objects:
Stream.combine({ a: stream, c: 5 }).each((object) => {
// object.c is 5
});
By default immutable – the stream emits new objects – it can be made to
mutate and emit the inputs
object by passing an options object with
mutable
set to true
as a second parameter. This can help minimise
garbage collection when dealing with large streams.
Stream
.combine({ a: stream, b: promise }, { mutable: true })
.each((object) => {
// object is the input object, properties a and b are now values
});
Output objects are created using the constructor of the input object
,
making it possible to use an array or other object as input and expect an
array or other object as output.
Stream.combine([promise, stream]).each((object) => {
// object is an Array
});
Stream.from(source)
Creates a stream from source
, which may be a pipeable (an object with
.pipe()
and .stop()
methods), an array (or array-like), or a promise.
Stream.merge(stream1, stream2, …)
Creates a stream by merging values from any number of input streams into a single output stream. Values are emitted in the time order they are received from inputs.
Stream.merge(stream1, stream2).each((value) => {
// value is from stream1 or stream 2
});
Stream.of(value1, value2, …)
Creates a pushable BufferStream from the given parameters.
assign(a, b, …)
Alias of Object.assign()
.
by(fn, a, b)
For sorting arrays. Compares fn(a)
against fn(b)
and returns -1
, 0
or
1
. Partially applicable. To sort an array of objects by their ids:
array.sort(by(get('id')))
ceil(n)
Alias of Math.ceil()
.
clamp(min, max, n)
Clamps number n
to the limits min
to max
. Values of n
lower than min
return min
, and those higher than max
return max
.
Data(object)
Returns the data proxy of object
. Use this proxy to set properties in a
way that can be observed with observe(path, object)
.
Normally this is not needed. It’s for advanced use. The data
object in the
scope of the template is already a data proxy and mutations to it are
observed by the template renderer.
delegate(map)
Takes an object map of functions keyed to selectors, and returns a function that handles event objects, delegating them to the first function whose selector matches the event target. Functions are passed the target node and the event object, plus any other arguments passed to the handler.
delegate({
'button': (button, event) => {}
})
denormalise(min, max, value)
entries(object)
Alias of Object.entries()
.
equals(a, b)
Compares a
and b
for deep equality and returns true
where they are equal,
otherwise false.
floor(n)
Alias of Math.floor()
.
frame(fn)
Alias of window.requestAnimationFrame(fn)
. Aliased for brevity inside
templates.
get(path, object)
Gets the value of path
in object
, where path
is a string in JS
dot-notation. Where a path does not lead to a value, returns undefined
:
${ get('path.to.value', data) }
Numbers are accepted as path components:
${ get('array.0', data) }
id(value)
Returns value
.
include(src, data)
Includes a template identified by src
, passing in an object to render in that
template as data
. In production it is recommended that src
is a fragment
identifier referring to the id of a template already in the DOM:
${ include('#another-template', data) }
The include
function is partially applicable, making it easy to use for
looping over an array to return an array of rendered templates:
${ data.array.map(include('#list-item')) }
isDefined(value)
Check for defined value
. where value
is undefined
, NaN
or null
,
returns false
, otherwise true
.
keys(object)
Alias of Object.keys()
.
last(array)
Returns the last value in an array.
matches(selector, object)
For filtering and pattern matching. Returns true where all the properties
of selector
object are strictly equal to the same properties of object
.
Note that object
may have more properties than selector
.
const vegetarian = menu.filter(matches({ vegetable: true }));
noop()
Does nothing, returns undefined.
normalise(min, max, value)
overload(fn, object)
Returns an overloaded.
Takes a fn
that returns a string key, and an object
of key:function
pairs. The returned function calls fn
with all arguments to get a key,
then calls the function at object[key]
with all arguments.
Where fn
returns undefined
, object.default
is called if it is defined
in object
, otherwise overload
throws a ‘no function defined for key’ error.
var handleEvent = overload(get('type'), {
click: (e) => {...},
input: (e) => {...},
default: (e) => {...}
});
paramify(object)
Turns an object with enumerable properties into a native URL search parameters object. Rejects undefined properties and flattens out array values.
print(data)
Where window.DEBUG
was falsy at the time Literal is imported, print()
does
nothing.
Prints an object or objects to the DOM as a debug message.
<template is="literal-html" data="../../package.json">
${ print(data) }
</template>
Renders as:
${ print(data) }rect(element)
A shortcut for element.getBoundingClientRectangle()
. Returns a DOMRect
object with left
, top
, width
and height
properties.
request(method, url)
Uses fetch()
to send a request to url
. Returns a promise.
${ request('get', '../documentation/modules/package.json').then(get('author')) }
To send data with the request:
${ request('post', url, data).then(...) }
(Where type
is "GET"
, data
is serialised and appended to the URL,
otherwise it is sent as a request body.)
A 4th parameter may be a content type string or a headers object
(in which case it must have a 'Content-Type'
property).
${ request('post', url, data, {...}).then(...) }
round(value)
Round value
to the nearest integer.
round(value, n)
Round value
to the nearest multiple of n
.
slugify(string)
Replaces any series of non-word characters with a '-'
and lowercases the rest.
slugify('Party on #mydudes!') // 'party-on-mydudes'
sum(a, b)
Returns the sum of b + a
.
translate(key)
Looks up an alternative value stored by key
in a window.translations
object, if it exists. A super simple translation mechanism, but requires
window.translations
to be an object.
${ translate('Go to homepage') }
trigger(type, node)
Triggers event of type
on node
. Returns false
if the event default was
prevented, otherwise true
.
trigger('activate', node);
Alternatively the first argument may be an object with a type
property, and
optionally detail
, which must be an object, and bubbles
, cancelable
and
composed
options, which determine the behaviour of the event.
trigger({
type: 'activate',
detail: {...},
bubbles: true,
cancelable: true,
composed: false
}, node);
px(value)
Takes a number in pixels or a string of the form '10px'
, '10em'
, '10rem'
,
'100vw'
, '100vh'
, '100vmin'
or '100vmax'
, and returns a numeric value
in pixels.
em(value)
Takes numeric value in px, or CSS length of the form '10px'
, and returns
a numeric value in em
, eg. 0.625
. Depends on the user defined browser
font-size
.
rem(value)
Takes numeric value in px, or CSS length of the form '10px'
, and returns
a numeric value in rem
, eg. 0.625
. Depends on the font-size
of the
documentElement.
vw(value)
Takes number in pixels or CSS length of the form '10em'
and returns a
numeric value in vw
, eg. 120
. Depends on the width of the viewport at
render time.
vh(value)
Takes number in pixels or CSS length of the form '10em'
and returns a
numeric value in vh
, eg. 120
. Depends on the height of the viewport at
render time.
To make a function or other object available in the scope of all Literal
templates, import Literal’s scope
object and assign to it.
import { scope } from './literal/module.js';
scope.myFunction = function() {};
Note that this must be done before literal-html
or literal-element
have been
imported. Those imports declare and compile templates, at which point their
scopes cannot be changed. Put the above code into a setup script and import that
before importing the templates.
import './my-literal-setup.js';
import './literal/literal-html/module.js';
import './literal/literal-element/module.js';
Expressions may evaluate to a string or other primitive, a DOM node or fragment, an array of values, another renderer, or even an asynchronous value in a promise or a stream.
Falsy values other than false
and 0
– undefined
, null
or NaN
– don’t
render at all.
Arrays are flattened and joined (without spaces or commas).
Promises and streams are rendered asynchronously when they emit values.
Literal flattens nested collections. A stream of arrays of strings will render text whenever the stream emits an array of strings.
Type | Expression | Renders as |
---|---|---|
undefined | ${ undefined } |
${ undefined } |
null | ${ null } |
${ null } |
NaN | ${ NaN } |
${ NaN } |
String | ${ 'Hello' } |
${ 'Hello' } |
Boolean | ${ true }, ${ false } |
${ true }, ${ false } |
Number | ${ 123.4 } |
${ 123.4 } |
Infinity | ${ Infinity }, ${ -Infinity } |
${ Infinity }, ${ -Infinity } |
Function | ${ function name(param) {} } |
${ function name(param) {} } |
Arrow | ${ (param) => {} } |
${ (param) => {} } |
RegExp | ${ /^regexp/ } |
${ /^regexp/ } |
Symbol | ${ Symbol('name') } |
${ Symbol('name') } |
Array | ${ [0, 1, 2, 3] } |
${ [0, 1, 2, 3] } |
Object | ${ { property: 'value' } } |
${ { property: 'value' } } |
Node | ${ document.createTextNode('Hello') } |
${ document.createTextNode('Hello') } |
Promise | ${ Promise.resolve('yoohoo') } |
${ Promise.resolve('yoohoo') } |
Stream | ${ events('pointermove', body) |
${ events('pointermove', body).map((e) => round(e.pageX)) } |
Built by Stephen Band for
Cruncher.
Documentation set in Euclid
and Martian Mono. Fonts
used for documentation not included as part of the license covering the
use of Literal
.