Sparky is a live data binding templating engine that enhances the DOM with declarative tags and composeable templates, updating tags and rendering changes in batches at the browser frame rate for performance.
If you just want to clone the repo to use Sparky in a project:
git clone https://github.com/cruncher/sparky.git
If you're going to develop it, Sparky has submodules. Clone the repo recursively:
git clone https://github.com/cruncher/sparky.git --recursive
Install node modules for building and testing:
npm install
var sparky = Sparky(node, scope, fn)
To bind a node call Sparky with a node or id, a scope
object and/or a function.
node node | document fragment | selector
Required parameter. If it is a DOM node or document fragment, Sparky parses it
for tags. If it is a selector string Sparky selects a node (only #id
selectors currently supported) and parses that for tags.
scope object | string | undefined
An object who's properties are used to render tags in the node. (Sparky's scope
may also be replaced at a later time via sparky.scope(object).) If
the scope parameter is a string it defines path to an object in the Sparky.data.
fn function | string | undefined
A function to run upon instantiating the node, or a string defining a name or
names of function(s) stored in sparky.fn or in
Sparky.fn. Log Sparky.fn in the console to see the
default functions available.
sparky
A sparky object is an array-like object of DOM nodes that have been bound to data models. It emits lifecycle events and exposes a few methods for interacting with the template.
Create a new sparky as a dependent of the current sparky.
var child = sparky.create(node, scope, fn);
Parameters are the same as for the Sparky() constructor. Child
sparkies inherit data and fn objects, and are updated
and destroyed along with their parents.
The nuke option. Destroys the data bindings, removes nodes from the DOM and removes any event handlers. Also destroys any child sparkies.
Force all tags to update with current values. This should rarely be needed, as Sparky handles render updates automatically, but it can be useful in cases where unobserved data changes and you need to give Sparky a nudge to display it.
If no path arguments are passed in then all tags are updated.
Swap the scope being used by this sparky for a new object. Sparky simply updates it's DOM with data from the new scope.
Stops Sparky from running functions and parsing it's nodes.
Typically called when content is being replaced,
for eaxmple, the built-in function Sparky.fn.each calls interrupt
before cloning a node for each item in a collection.
Returns a function that calls all functions in the data-fn list
that have not yet been called.
Where jQuery is available, returns sparky's element nodes (but not text nodes) wrapped as a jQuery object.
Sparky templates are reasonably tolerant to being manipulated in the DOM. Nodes in a template will stay bound to data when they are moved around or removed from the DOM, or when other nodes are inserted between them.
You should be aware, though, that changing text content or attributes of nodes
that had Sparky tags in them when they were bound will likely cause problems –
Sparky will overwrite your changes the next time it's data is updated. The
exception is the class attribute: you can add and remove your own
classes as much as you like without fear of upsetting Sparky.
Listen to event type with fn.
Stop listening to event type with fn.
All parameters are optional.
scope: triggered when scope is initialised or changeddestroy: triggered when data bindings have been destroyed and the node removed from the DOM.
Where template is a string, replaces the Sparky tags in the string
with matching properties of object.
Sparky.render('{{ bossname }} loves wooo!', {
bossname: "Sparky"
});
// Returns: 'Sparky loves wooo!'
Where template is a regular expression, composes the regexp with
regexp properties of object.
Sparky.render(/{{ropen}}\s*(\w+)\s*{{rclose}}/g, {
ropen: /\{{2,3}/,
rclose: /\}{2,3}/
});
// Returns: /\{{2,3}\s*(\w+)\s*\}{2,3}/g
Where template is a function, and that function contains a single
JS comment, the contents of the comment are whitespace-cropped and treated as
a template string.
Sparky.render(function(){/*
{{ boss }} loves wooo!
*/}, {
boss: "Sparky"
});
// -> 'Sparky loves wooo!'
This is a nice hacky technique for writing multiline templates in JS, although now superseded by ES6 multiline strings.
Note that Sparky.render is not used by Sparky to update the DOM.
Sparky does not treat the DOM as strings, it treats the DOM as the DOM, keeping
an internal map of node attributes and text node content bound directly to data
changes.
Change the opening and closing template tag brackets. ropen and
rclose must be regular expressions.
Sparky.tags(/\{{2,3}/, /\{{2,3}/)
The regular expressions used to test for tags (Sparky.rtags,
Sparky.rsimpletags) are updated with the new opening and closing
tags. Sparky.rtags and Sparky.rsimpletags are
read-only properties.
Given the id of a template tag in the DOM, Sparky.template(id)
returns the cloned contents of that template as a document fragment.
Supports older browsers where <template> does not have the
associated JavaScript property template.content.
A small library of DOM helper functions.
.query(node, selector)-.tag(node)- Returns the element's tag name.create(type, text)- Creates a 'text', 'comment', 'fragment' or element node.append(parent, node || collection)- Append node to parent.after(node1, node2)-.before(node1, node2)-.empty(node)-.remove(node || collection)-.closest(node, selector [, root])- Finds closest ancestor matching selector.matches(node, selector)-.classes(node)- Returns a classList object.style(node, name)- Returns the computed style for named CSS property.fragmentFromTemplate(id)- Returns cloned fragment from a template's content.fragmentFromContent(node)- Returns a fragment containing a node's content or children
Nodes
.isElementNode(node)-.isTextNode(node)-.isCommentNode(node)-.isFragmentNode(node)-
Events
.on(node, type, fn)-.off(node, type, fn)-.trigger(node, type)-.delegate(selector, fn)-.isPrimaryButton(e)-
An array of attributes where Sparky looks for template tags.
classhreftitleidstylesrcalt
For labels, inputs, selects and textareas Sparky also looks in:
fornamevaluemaxmin
Boolean properties are set on an element according to the truthiness of a single Sparky tag found in their attributes:
required="{{object.required}}"disabled="{{object.enabled|yesno:false,true}}"
A Regular expression matching tags of the form {{ path.to.property }}.
The path is stored in capturing group 1.
A Regular expression matching tags of the form
{{ path.to.property|filter:'param' }}. The opening brackets, the path
and the filter string are stored in capturing groups 1,2 and 3.
An object containing template filters.
Display the date, formatted:
<h1 class="language-{{lang}}" data-scope="text">
{{title}}
<time>{{date|date:'d M Y'}}</time>
</h1>
Sparky has a number of template filters for modifying and formatting data. You can also create your own. Sparky template filter syntax is similar to Django template filter syntax:
<p>{{ date|date:'d M Y' }}</p>
- add
- capfirst
- cut: string – cuts matching string from a value
- date
- decibels – Takes a number as a ratio of powers and performs 20log10(number) to render it on the decibel scale.
- decimals: number – Alias of floatformat.
- divide: number – Divides by number.
- escape
- find-in:
path- finds an object in a collection atpathby it's indexed key (usuallyid). - first –
- floatformat: number –
- floor
- get: string – Takes an object and renders the named property.
- greater-than: value, stringTrue, stringFalse
- invert – Returns 1/property.
- is: value – Strictly compares property to value, returns a boolean.
- equals: value - Deeply compares property to value, returns a boolean.
- join
- json
- last
- length
- less-than: value, stringTrue, stringFalse
- lower
- lowercase – Alias of lower.
- mod: number – Performs value % number.
- multiply: number
- parseint
- percent: number – Takes a number and multiplies by 100 to render it as a percentage.
- pluralize: stringSingular, stringPlural, lang –
- postpad: number, string –
- prepad: number, string –
- random
- replace
- round
- slice
- slugify
- striptags
- switch: string, … – Takes a number and returns the string at that index.
- symbolise – Converts common values to symbolic equivalents: JavaScript's number Infinity becomes '∞'.
- truncatechars
- type – Returns type of value
- uppercase –
- yesno
The data-scope attribute is a path to an object to be used as
scope to render the tags in this elements. Paths are written in dot notation.
<div data-scope="path.to.object">
<h1>{{title}}</h1>
</div>
This will look for the object in the current sparky's sparky.data
object, or in the global data object Sparky.data. A tagged path
makes Sparky look for an object in the current scope.
<div data-scope="{{path.to.object}}">
<h1>{{title}}</h1>
</div>
If no object is found at path.to.object, the
<div> is removed from the DOM. Sparky puts it back as soon
as the path can be resolved to an object. Sparky even updates the DOM if any of
the objects in the path are swapped for new objects.
Object names can contain - characters, or be numbers.
{{path.0.my-object}}
The data-fn attribute tells Sparky to run one or more functions
when it wires up this element.
<form data-fn="submit-validate">...</form>
Sparky looks for functions in the current sparky's sparky.fn store,
or in the global function store Sparky.fn. Functions are powerful.
They can modify or replace the scope, change and listen to the DOM, define new
data and fn stores and so on. Sparky deliberately
permits anything in a function so that you may organise your app as you please.
More than one fn can be defined. They are run in order.
<form data-fn="my-app-scope validate-on-submit">...</form>
The return value of each function is passed along the chain as the
scope argument of the next.
Sparky replaces template tags with data, and updates them when the data changes.
<h1 data-scope="object">{{ first-page.title }}</h1>
The text in the <h1> is now updated whenever
object.first-page.title changes.
Sparky will find tags in text nodes, class, href, title, id, style, src,
alt, for, value, min, max and name attributes. This list can be modified
by pushing to Sparky.attributes.
Sparky treats tags in the class attribute as individual tokens, so
it is safe to modify the class attribute outside of Sparky. Sparky
avoids overwriting any new classes that are added.
Modify scope values with filters:
<h1 data-scope="my-model" class="{{selected|yesno:'active','inactive'}}">
{{title|uppercase}}
</h1>
More about filters.
A triple bracket tag updates from the scope once only.
<h1 data-scope="my-model">{{{ title }}}</h1>
These tags are updated once from the scope (in this case my-model), but they don't live bind to changes. If you know where you can do it, this can be good for performance.
By putting a Sparky tag in the name attribute, inputs, selects and
textareas are 2-way bound to a scope property. When the scope changes, the
input's values is updated, and when the input is changed the scope property is
updated.
<form class="user-form" data-scope="{{user}}">
<input type="text" name="{{username}}" placeholder="Sparky" />
<input type="number" name="{{age}}" />
</form>
By default Sparky is strict about type in form elements. The first input above
is type="text" and it will only display the username
property if that property is a string. Other types will display as an empty value.
scope.username = 'Sparky'; // input.value === 'Sparky'
scope.username = 3; // input.value === ''
The second input is type="number". It will only display the
age property if that property is a number.
scope.age = 'Fourteen'; // input.value === ''
scope.age = 32; // input.value === '32'
Similarly, type="range" only gets and sets numbers, and
type="checkbox" only gets and sets booleans, unless the value
attribute contains a string (in which case the property must be a string
matching the value for the input to be checked).
Other types get and set strings by default. To force the input to get and set a
different type use one of Sparky's value-xxx functions&hellips;
To data-bind a specific type, give the element one of Sparky's value functions:
value-stringsets a stringvalue-floatsets a floatvalue-float-logsets a float with alog xtransformvalue-float-pow-2sets a float with ax2transformvalue-float-pow-3sets a float with ax3transformvalue-intsets an integer, rounding where necessaryvalue-int-logsets an integer with a log transformvalue-boolsetstrueorfalsevalue-anygets any type, sets strings
Here are some examples. Radio inputs that sets scope.property to integers
1 or 2:
<input type="radio" data-fn="value-int" name="{{property}}" value="1" />
<input type="radio" data-fn="value-int" name="{{property}}" value="2" />
A select that sets scope.property to true or false:
<select data-fn="value-bool" name="{{property}}">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
A checkbox that is checked when scope.property === 3:
<input type="checkbox" data-fn="value-int" name="{{property}}" value="3" />
A range slider that sets scope.property as a string:
<input type="range" data-fn="value-string" name="{{property}}" min="0" max="1" step="any" />
A range slider that sets scope.property as a float, with a logarithmic transform across it's range from 1 to 10:
<input type="range" data-fn="value-string" name="{{property}}" min="1" max="10" step="any" />
The each function loops over a scope that is a collection or array, cloning a new node for each item in the collection. This...
<ul id="list">
<li data-scope="{{contributors}}" data-fn="each">
<a href="{{url}}">{{name}}</a>
</li>
</ul>
Sparky('list', {
contributors: Collection([
{ name: "Sparky", url: "http://github.com/cruncher/sparky" },
{ name: "Cruncher", url: "http://cruncher.ch" }
])
});
...results in a DOM that looks like this:
<ul id="list">
<li>
<a href="http://github.com/cruncher/sparky">Sparky</a>
</li>
<li>
<a href="http://cruncher.ch">Cruncher</a>
</li>
</ul>
to Mariana Alt (www.alt-design.ch) for drawing me for my logo.
