shown in:
address unknown


data-graft.js is a animation-friendly differential DOM template engine, self-contained and framework-agnostic.

Instead of stringing up bits of HTML markup, it works from a DOM template, operates on DOM nodes and never touches markup.

data-graft.js updates differentially - minimizing the number of changes to the presentation required to match the new data - and provides hooks to animate those changes.

Check out the catsagram demo and get the code for release 0.1.2: plain or minified


Keep your template in the page - make it invisible with css:

Germinate the presentation graft with initial data and place its output:

graft = data-graft.germ({color:red}, document.getElementById('tpl_about'), {});

The 3rd argument to germ() is the context, more on that later.

Update the graft as data changes, this will differentially update its presentation in place:



Templates are annotated DOM element trees:

  • data-graft attributes describe how data is grafted with nodes
  • id attributes are simply ignored and filtered out
  • everything else is copied as is, including all other attributes, text nodes and comments

Templates are accessed in every update, so they need to stay around.

In the following examples d will refer to the data object matched with the element being discussed.

Grafting text


Use it on an element to place inside a node with text from key of d.

null or undefined will result in no text node at all.


The value of key of d is often the value of the property key in d (d.key), but there are other possibilities:

  • if key is the empty string, then the value will be d itself. this is particularly useful if d is already of a simple type
  • composition is allowed: e.g.: the key could be a.b with value d.a.b


It's possible to evaluate a javascript function by prefixing the function name - which can be composite - with a colon (:).

data-graft.js will look for functions in the context object, that's one of the uses for the 3rd argument to germ().

The function to evaluate takes a single argument with d and is expected to return a string.

Currently it's not possible to compose functions with other functions or with property access (.).

Function example

With the following function defined inside context:

context = { f: function(d) { return d.a+ '_'+ d.b; } };

Grafting attribute values


Use it on an element to set the value of the attribute name to key of d. Note the double dash.

null or undefined will result in the attribute not being set at all.

Grafting conditionally on a property


data-graft-if allows for conditional presentation of part of a template.

The elements inside will be grafted if and only if key exists in d and d.key is not null or undefined.

d.key will be used as data to graft those elements.

Grafting each property


Will graft a copy of the child template element with each (own) property of d.

Inside, prop refers to each property ID.

The copies are ordered by increasing value of the property ID.

data-graft-each can only handle a single child element though it can be arbitrarily complex.

Grafting an array


Will graft a copy of the child template with each element of the array d.

The copies are identified and ordered by increasing value of key of d, which must be unique for data-graft-for to work correctly.

data-graft-for can only handle a single child element though it can be arbitrarily complex.

Grafting simple array

You can sort and graft an array of a simple type by using the simple values themselves as keys.

But will only work correctly if there are no duplicates.

Grafting by index

You can use the _idx_ special key to graft by index.

The elements of the array will appear in the same order they are in the array, and replace whatever elements were there, rather than move up or down to accommodate changes.

Grafting where else


data-graft-else is complementary to data-graft-if, data-graft-each and data-graft-for.

The elements inside will be grafted if nothing was grafted in the preceding data-graft-if, data-graft-each or data-graft-for.


data-graft.js operates differentially by design, and will insert, remove and change the minimum amount of elements it can in order to match changed data.

It will not animate any of these changes by itself, but rather provide hooks for application developers to implement their animations.

You can set animation hooks by mapping change handlers inside the context object. These can be set for elements of a specific class:

context = {'item': { preUpdateText: function(e, finished) { finished(); } } };

Or for all elements:

context = {'_all_': { preUpdateText: function(e, finished) { finished(); } } };

All change handlers have the same signature:

  • e is the element being changed or its parent
  • f is a function to chain back to (with no args) after completing the animation

Change handlers

All change handlers come in pairs. A pre- and a post-operation. And with the exception of the text change handlers also in Insert- and Remove- pairs.

  • preInsert- handlers are called with a newly created element, before it is inserted into the DOM. That doesn't happen until the handler has called back
  • postInsert- handlers are called after a new element has been inserted into the DOM
  • preRemove- handlers are called before an existing element is removed from the DOM. That doesn't happen until the handler has called back
  • postRemove- handlers are called after the element has been removed from the DOM

These 4 handlers are defined for different kinds of elements:

  • -Text for data-graft-text, e is the parent data-graft-text element for convenience rather than the text node itself
  • -If for data-graft-if, again e is the parent data-graft-if element rather than the elements just inserted/removed under it
  • -Sequence for data-graft-each and data-graft-for, in this case e is the the child element
  • -Else for data-graft-else, again e is the parent data-graft-else element rather the elements just inserted/removed under it

Finally there are 2 handlers for in-place changes to a text node, in both e is the parent data-graft-text element:

  • preUpdateText is called before the text node is changed. That doesn't happen until the handler has called back
  • postUpdateText is called after the text node has been changed

Change handlers table

Text (parent)If (parent)Sequence (child)Else (parent)
preInsertText postInsertText preInsertIf postInsertIf preInsertSequence postInsertSequence preInsertElse postInsertElse
preRemoveText postRemoveText preRemoveIf postRemoveIf preRemoveSequence postRemoveSequence preRemoveElse postRemoveElse
preUpdateText postUpdateText

Animation start/finish

Each graft update() can set off a cascade of change handler calls and resulting (asynchronous) animations.

Strictly following the convention to call finished() upon the completion of the animation(s) for a change allows update() to orchestrate these callbacks harmoniously.

Until everything settles down, you must not touch the DOM graft either directly or through another call to update() since it may interfere with changes still queued up or with the animations themselves, often with showstopper errors.

To keep tabs with the animation progress you can set two hooks in context:

  • start() will be called with no arguments before the animations start
  • finish() will be called with no arguments after the animations have finished

Initializing an element

Sometimes, it can be handy to initialize a newly created element.

Change handlers are meant for animation and are not called on initial germination, or for elements internal to an insertion.

context = {'item': { init: function(e) { ... } } };

init is called for each element e created during germination or update. It must perform its function synchronously and without delay

Fork me on GitHub