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">
    Hello!
</template>
Hello!

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

tags.

{[path|pipe:params]}

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 ]}
</template>
repo: https://github.com/cruncher/sparky.git

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

{[ items.3 ]}
{[ items.my-object ]}

The root path renders the scope object itself:

{[ . ]}

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

{[ items.3. ]}
{[ items.my-object. ]}

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.

functions.

fn="name:params"

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]}.
</template>
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">
    <ul>
        <li fn="get:keywords each">{[.]}</li>
    </ul>
</template>
<ul>
    <li>javascript</li>
    <li>browser</li>
</ul>

includes.

src="#id"

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>

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

Includes may be used inside templates, too:

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

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">
    <ul>
        <template src="{[.keywords|yesno:#my-template,#no-keywords]}">
        </template>
    </ul>
</template>

built-in functions.

each

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

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

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

entries

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

<ul>
    <li fn="get:repository entries each">{[0]}: {[1]}</li>
</ul>
<ul>
    <li>type: git</li>
    <li>url: https://github.com/cruncher/sparky.git</li>
</ul>

fetch: url

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

<p fn="fetch:package.json">{[title]}!</p>
<p>Sparky!</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="https://github.com/cruncher/sparky.git">Git repository</a>

stop

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

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

class

Returns the Class of value.

type

Returns the typeof value.

Booleans

Numbers

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.

to-db

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.

to-cartesian

Transforms a polar coordinate array to cartesian coordinates.

to-polar

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.

Dates

add: yyyy-mm-dd

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

Times

add: 'hh:mm:ss'

Adds an ISO time in the form 'hh:mm:ss' to a time value. May be used for two-way binding. (Note the string must be quoted because it contains ‘:’ characters.)

timeformat: 'hh:mm:ss'

Converts ISO time string, a number (in seconds) or the UTC time values of a Date object to a formatted string.

Type converters

boolean-string

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

float-string

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.

int-string

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.

string-float

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

string-int

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

json

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

Strings

Write a function

Sparky.fn

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 input.map(function(scope) {
        // 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 stephen.band/fn/#stream.

Examples

Examples

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(() => {
        output.push({
            time: window.performance.now()
        });
    }, 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

delegate(types)

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

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

Examples

Examples

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

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

API