Sparky templates

First, import Sparky:

import '/sparky/module.js';

Sparky registers the is="sparky-template" custom element. Sparky templates in the DOM are automatically replaced with their own rendered content:

<template is="sparky-template">

Sparky templates extend HTML with 3 features: template tags, functions and includes.



Template tags are made of three parts. Values are read from path in the scope object, piped through any number of pipe functions, which (depending on the pipe) may require params, and rendered into the DOM.

<template is="sparky-template" fn="fetch:package.json">
    {[ repository.url|prepend:'Repo: '|lowercase ]}

Paths are a sort-of superset of JS dot notation, where dashes and numbers are allowed in property names:

{[ items.3 ]}
{[ ]}

The root path renders the scope object itself:

{[ . ]}

And a trailing . also renders the tag when the object at the path mutates:

{[ items.3. ]}
{[ ]}

Sparky renders NaN, null and undefined as empty strings, objects as JSON, functions as 'function(params)', and Infinity as '∞'. Other values are converted to strings in the normal way.

Tags placed in an <input :value="{[]}"> set up two-way data binding. Sparky is strict about input types. A number will not render into an input type="text". A string will not render into an input type="number" or type="range".

More importantly, a number typed into an input type="text" will be set as a string in the data, while the same typed into a type="number" will be set as a number. Sparky gaurantees your data types.

The built-in type converter pipes facilitate rendering values into inputs of an unmatched type.



A fn attribute declares one or more functions to run on a template. A function is expected to supply an object that Sparky uses to render template tags:

<template is="sparky-template" fn="fetch:package.json">
    I am {[title]}.
I am Sparky.

The fn attribute may be declared on any element in a sparky template. Here we use the built-in functions fetch, get and each to loop over an array of keywords and generate a list:

<template is="sparky-template" fn="fetch:package.json">
        <li fn="get:keywords each">{[.]}</li>



Sparky extends <template> elements (and <use> elements in SVG) with a src attribute, turning them into includes. The src attribute must reference another template (or sn SVG element). Its rendered content replaces the referring template:

<template id="my-template">
    <li fn="get:keywords each">{[.]}</li>

<template is="sparky-template" fn="fetch:package.json" src="#my-template">

Includes may be used inside templates, too:

<template is="sparky-template" fn="fetch:package.json">
        <template src="#my-template"></template>

The src attribute may contain template tags, making the included template dependent on rendered data. This is how conditionals are written in Sparky. Here, where there is no keywords property in package.json the template with id no-keywords is rendered in place of my-template:

<template is="sparky-template" fn="fetch:package.json">
        <template src="{[.keywords|yesno:#my-template,#no-keywords]}">

built-in functions.


Clones the DOM node and renders a clone for each value in an array.

    <li fn="get:keywords each">{[.]}</li>

Where there are functions declared after each in the attribute, they are run on each clone.


Maps scope to an array of [key, value] arrays.

    <li fn="get:repository entries each">{[0]}: {[1]}</li>
    <li>type: git</li>

fetch: url

Fetches and parses a JSON file and uses it as scope to render the node.

<p fn="fetch:package.json">{[title]}!</p>

get: path

Maps scope to the value at path of the current scope:

<a fn="get:repository" href="{[ url ]}">{[type|capitalise]} repository</a>
<a href="">Git repository</a>


Stops sparkifying the element. No more functions are run, and the contents of the element are not parsed. Use stop to ignore blocks of static HTML inside a Sparky template.

built-in pipes.

is: value

Returns true where value is strictly equal to value, otherwise false.

has: property

Returns true where value is an object with property, otherwise false.

matches: selector

Renders true if value matches selector. Behaviour is overloaded to accept different types of selector. Where selector is a RegExp, value is assumed to be a string and tested against it.

{[ .|matches:/abc/ ]}     // `true` if value contains 'abc'

Where selector is an Object, value is assumed to be an object and its properties are matched against those of selector.

{[ .|matches:{key: 3} ]}  // `true` if value.key is `3`.


Renders the Class – the name of the constructor – of value.



add: n

Adds n to value. Behaviour is overloaded to accept various types of ‘n’. Where n is a number, it is summed with value. So to add 1 to any value:

{[ number|add:1 ]}

Where ‘n’ is a duration string in date-like format, value is expected to be a date and is advanced by the duration. So to advance a date by 18 months:

{[ date|add:'0000-18-00' ]}

Where ‘n’ is a duration string in time-like format, value is expected to be a time and is advanced by the duration. So to put a time back by 1 hour and 20 seconds:

{[ time|add:'-01:00:20' ]}

normalise: curve, min, max

Return a value in the nominal range 0-1 from a value between min and max mapped to a curve, which is one of linear, quadratic, exponential.

denormalise: curve, min, max

Return a value in the range min-max of a value in the range 0-1, reverse mapped to curve, which is one of linear, quadratic, exponential.


Transforms values in the nominal range 0-1 to dB scale, and, when used in two-way binding, transforms them back a number in nominal range.


Transforms a polar coordinate array to cartesian coordinates.


Transforms a polar coordinate array to cartesian coordinates.

floatformat: n

Transforms numbers to strings with n decimal places. Used for two-way binding, gaurantees numbers are set on scope.


add: yyyy-mm-dd

Adds ISO formatted yyyy-mm-dd to a date value, returning a new date.

dateformat: yyyy-mm-dd

Converts an ISO date string, a number (in seconds) or a Date object to a string date formatted with the symbols:

- `'YYYY'` years
- `'YY'`   2-digit year
- `'MM'`   month, 2-digit
- `'MMM'`  month, 3-letter
- `'MMMM'` month, full name
- `'D'`    day of week
- `'DD'`   day of week, two-digit
- `'ddd'`  weekday, 3-letter
- `'dddd'` weekday, full name
- `'hh'`   hours
- `'mm'`   minutes
- `'ss'`   seconds
- `'sss'`  seconds with decimals


add: duration

Adds duration, an ISO-like time string, to a time value, and returns a number in seconds.

Add 12 hours:

{[ time|add:'12:00' ]}

Durations may be negative. Subtract an hour and a half:

{[ time|add:'-01:30' ]}

Numbers in a `duration` string are not limited by the cycles of the clock.
Add 212 seconds:

{[ time|add:'00:00:212' ]}

Use `timeformat` to transform the result to a readable format:

{[ time|add:'12:00'|timeformat:'h hours, mm minutes' ]}

Not that `duration` must be quoted because it contains ':' characters.
May be used for two-way binding.

timeformat: format

Formats value, which must be an ISO time string or a number in seconds, to match format, a string that may contain the tokens:

- `'±'`   Sign, renders '-' if time is negative, otherwise nothing
- `'Y'`   Years, approx.
- `'M'`   Months, approx.
- `'MM'`  Months, remainder from years (max 12), approx.
- `'w'`   Weeks
- `'ww'`  Weeks, remainder from months (max 4)
- `'d'`   Days
- `'dd'`  Days, remainder from weeks (max 7)
- `'h'`   Hours
- `'hh'`  Hours, remainder from days (max 24), 2-digit format
- `'m'`   Minutes
- `'mm'`  Minutes, remainder from hours (max 60), 2-digit format
- `'s'`   Seconds
- `'ss'`  Seconds, remainder from minutes (max 60), 2-digit format
- `'sss'` Seconds, remainder from minutes (max 60), fractional
- `'ms'`  Milliseconds, remainder from seconds (max 1000), 3-digit format

{[ .|timeformat:'±hh:mm' ]}

Type converters


Transforms booleans to strings and vice versa. May by used for two-way binding.


Transforms numbers to float strings, and, used for two-way binding, gaurantees numbers are set on scope.

floats-string: separator

Transforms an array of numbers to a string using separator, and, used for two-way binding, gaurantees an array of numbers is set on scope.


Transforms numbers to integer strings, and, used for two-way binding, gaurantees integer numbers are set on scope.

ints-string: separator

Transforms an array of numbers to a string of integers seperated with separator, and, used for two-way binding, gaurantees an array of integer numbers is set on scope.


Transforms strings to numbers, and, used for two-way binding, gaurantees float strings are set on scope.


Transforms strings to integer numbers, and, used for two-way binding, gaurantees integer strings are set on scope.


Transforms objects to json, and used in two-way binding, sets parsed objects on scope.


is-in: array

Returns true if value is contained in array, otherwise false.

{[ path|is-in:[0,1] ]}

Write a function


Sparky ‘functions’ are view-controllers with access to the node where they are declared and control over the flow of objects being sent to the renderer.

import Sparky from './sparky/build/module.js';

// Define fn="my-function:params"
Sparky.fn('my-function', function(input, node, params) {
    // input is a mappable, filterable, consumable,
    // stoppable stream of scope objects
    return {
        // Map scope...

Functions are called before a node is mounted. They receive a stream of scopes and the DOM node, and may return the same stream, or a new stream, or they may block mounting and rendering altogether. Types of return value are interpreted as follows:

The stream returned by the last function declared in the fn attribute is piped to the renderer. Values in that stream are rendered, and the life of the renderer is controlled by the state of that stream. Sparky’s streams come from



Push a single scope object to the renderer:

import { register, Stream } from './sparky/module.js';

register('my-scope', function(node, params) {
    // Return a stream of one object
    return Stream.of({
        text: 'Hello, Sparky!'

Return a promise to push a scope when it is ready:

register('my-package', function(node, params) {
    // Return a promise
    return fetch('package.json')
    .then((response) => response.json());

Push a new scope object to the renderer every second:

register('my-clock', function(node, params) {
    const output = Stream.of();

    // Push a new scope to the renderer once per second
    const timer = setInterval(() => {
    }, 1000);

    // Listen to the input stream, stop the interval
    // timer when it is stopped
    this.done(() => clearInterval(timer));

    // Return the stream
    return output;

Function helpers


Create an event delegation controller fn, where types is an object of objects mapping event types and selectors to listener functions:

import { delegate, register } from './sparky/module.js';

register('fn-name', delegate({
    'click': {
        'button': function(button, e) {
            // Button was clicked...

To further delegate by the name of the button you could use get and overload from Fn:

import { delegate, register } from './sparky/module.js';
import { get, overload } from './fn/module.js';

register('fn-name', delegate({
    'click': {
        'button[name]': overload(get('name'), {
            'button-name': function() {
                // Button name="button-name" was clicked...

(This is a little more efficient than listing all possible button names as selectors).

Write a pipe


Define a pipe using Sparky.pipe(name, fn).

import Sparky from './sparky/build/module.js';

// Define {[.|my-pipe:param]}
Sparky.pipe('my-pipe', function(param, value) {
    // return something from param and value

A pipe may take any number of params; the final parameter is the value being piped frmo the tag scope.

Pipes are called during the render process on the next animation frame after the value of a tag has changed. It is best not to do anything too expensive in a pipe to keep the render process fast.



import Sparky from './sparky/build/module.js';
const tax = 1.175;

// Define {[number|add-tax]}
Sparky.pipe('add-tax', function(value) {
    return value * tax;