Literal 0.8.3

Template scope

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 renders strings or other primitives, arrays of values, promises or streams, DOM nodes and fragments, and even other renderers.

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

Objects

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 in a template unless you want to print information about the renderer of the current text or attribute.

Renderer render count: ${ this.renderCount }
Renderer id:           ${ this.id }

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.

Streams

clock(interval)

If interval is a number, returns a stream of DOM timestamps at interval seconds apart.

${ clock(1).map(floor) }

If duration is "frame", returns a stream of DOM timestamps of animation frames.

${ 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 emit a mutated inputs object instead, 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, but emitted objects are only valid to be consumed synchronously, as the next emitted object is actually the same object.

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 an array (or array-like), a promise, a function, a producer (an object with .pipe() and .stop() methods), or an object of streams, promises or values.

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

Functions

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

getValue(element)

TODO

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

navigate(url)

Navigate to a url, alerting the history API where pertinent. Takes optional parameters:

navigate(url, state, scroll)

TODO: better description!

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)

Prints an object or objects to the DOM as a debug message.

<template is="literal-html" data="../../package.json">
    ${ print(data) }
</template>

Renders as:

rect(element)

A shortcut for element.getBoundingClientRectangle(). Returns a DOMRect object with left, top, width and height properties.

remove(array, value)

Remove value from array. Where value is not in array, does nothing.

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

Extending the template scope

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';

Template expressions

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 0undefined, 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 }
null ${ null }
NaN ${ NaN }
String ${ 'Hello' }
Boolean ${ true }, ${ false }
Number ${ 123.4 }
Infinity ${ Infinity }, ${ -Infinity }
Function ${ function name(param) {} }
Arrow ${ (param) => {} }
RegExp ${ /^regexp/ }
Symbol ${ Symbol('name') }
Array ${ [0, 1, 2, 3] }
Object ${ { property: 'value' } }
Node ${ document.createTextNode('Hello') }
Promise ${ Promise.resolve('yoohoo') }
Stream ${ 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.