Incremental DOM is a library for expressing and applying updates to DOM trees. JavaScript can be used to extract, iterate over and transform data into calls generating HTMLElements and Text nodes. It differs from Virtual DOM approaches in that a diff operation is performed incrementally (that is one node at a time) against the DOM, rather than on a virtual DOM tree.
Rather than targeting direct usage, Incremental DOM aims to provide a platform for higher level libraries or frameworks. As you might notice from the examples, Incremental DOM-style markup can be somewhat challenging to write and read. See Why Incremental DOM for an explanation.
The DOM to be rendered is described with the incremental node functions, elementOpen, elementClose and text. For example, the following function:
function renderPart() {
elementOpen('div');
text('Hello world');
elementClose('div');
}
would correspond to
<div>
Hello world
</div>
Using the renderPart function from above, the patch function can be used to render the desired structure into an existing Element or Document (which includes Shadow DOM). Calling the patch function again will patch the DOM tree with any changes, updating attributes, and creating/removing DOM nodes as needed.
In addition to creating DOM nodes, you can also add/update attributes and properties on Elements. They are specified as variable arguments, alternating between attribute/property name and value. Values that are Objects or Functions are set as properties, with all others being set as attributes.
One use for setting a property could be to store callbacks for use with an event delegation library. Since you can assign to any property on the DOM node, you can even assign to on* handlers, like onclick.
Often times, you know that some properties on a DOM node will not change. One example would be the type attribute in <input type="text">. Incremental DOM provides a shortcut to avoid comparing attributes/properties you know will not change. The third argument to elementOpen is an array of unchanging attributes. To avoid allocating an array on each pass, you will want to declare the array in a scope that is only executed once.
If the statics array is provided, you must also provide a key. This ensures that an Element with the same tag but different statics array is never re-used by Incremental DOM.
Styles for an element can be set either using a string or an object. When setting styles using an object, the names should be camelCase as they are set on the Element's style property.
As you can mix node declarations and JavaScript, rendering conditional branches is fairly straightforward. Simply place the node declarations inside a branch. This works with switch statements too!
You can use your favorite way to render an array (or any sort of iterable) of items. When rendering an array of items, you will want to specify a 'key' as the second argument to the elementOpen function. Incremental DOM uses the key in order to:
Prevent the treating of newly added or moved items as a diff that needs to be reconciled.
Correctly maintain focus on any input fields, buttons or other items that may receive focus that have moved.
As Incremental DOM does not know when you are rendering an array of items, there is no warning generated when a key should be specified but is not present. If you are compiling from a template or transpiling, it might be a good idea to statically check to make sure a key is specified.
The incremental node functions are evaluated when they are called. If you do not want to have them appear in the current location (e.g. to pass them to another function), simply wrap the statements in a function which can be called later.
function renderStatement(content, isStrong) {
if (isStrong) {
elementOpen('strong');
content();
elementClose('strong');
} else {
content();
}
}
function renderGreeting(name) {
function content() {
text('Hello ');
text(name);
}
elementOpen('div');
renderStatement(content, true);
elementClose('div');
}
API
elementOpen
Description
Declares an Element with zero or more attributes/properties that should be present at the current location in the document tree.
Parameters
string tagname
The name of the tag, e.g. 'div' or 'span'. This could also be the tag of a custom element.
string key
The key that identifies Element for reuse. See Arrays of Items
Array staticPropertyValuePairs
Pairs of property names and values. Depending on the type of the value, these will be set as either attributes or properties on the Element. These are only set on the Element once during creation. These will not be updated during subsequent passes. See Statics Array.
vargs propertyValuePairs
Pairs of property names and values. Depending on the type of the value, these will be set as either attributes or properties on the Element.
Usage
var elementOpen = IncrementalDOM.elementOpen;
var somefunction = function() { … };
…
elementOpen('div', item.key, ['staticAttr', 'staticValue'],
'someAttr', 'someValue',
'someFunctionAttr', somefunction);
elementClose
Description
Signifies the end of the element opened with elementOpen, correspoding to a closing tag (e.g. </div> in HTML). Any childNodes of the currently open Element that are in the DOM that have not been encountered in the current render pass are removed by the call to elementClose.
Parameters
string tagname
The name of the tag, e.g. 'div' or 'span'. This could also be the tag of a custom element.
Usage
var elementClose = IncrementalDOM.elementClose;
…
elementClose('div');
The name of the tag, e.g. 'div' or 'span'. This could also be the tag of a custom element.
string key
The key that identifies Element for reuse. See Arrays of Items
Array staticPropertyValuePairs
Pairs of property names and values. Depending on the type of the value, these will be set as either attributes or properties on the Element. These are only set on the Element once during creation. These will not be updated during subsequent passes. See Statics Array.
vargs propertyValuePairs
Pairs of property names and values. Depending on the type of the value, these will be set as either attributes or properties on the Element.
Declares a Text node, with the specified text, should appear at the current location in the document tree.
Parameters
string text
The text for the Text node.
Usage
var text = IncrementalDOM.text;
…
text('hello world');
patch
Description
Updates the provided Node with a function containing zero or more calls to elementOpen, text and elementClose. The provided callback function may call other such functions. The patch function may be called with a new Node while a call to patch is already executing.
Parameters
Node node
The Node to patch. Typically, this will be an HTMLElement or DocumentFragment.
function description
The description of the DOM tree underneath node.
Usage
var patch = IncrementalDOM.patch;
function render() {
elementOpen('div');
elementClose('div');
…
}
var myElement = document.getElementById(…);
patch(myElement, render);
Demos
Using Keys
The section on arrays of items mentions why using a key is important when iterating over an item. This demo shows how using a key prevents DOM nodes corresponding to separate items from being seen as a diff. In this case, a newly added item at the head of an array causes a new element by be created rather than all the items being updated.
Incremental DOM itself only renders Elements and Text nodes, but you may want to use components when building an application. One way this chould be solved is by using the emerging web components standards. The following demo shows one way you could create components.
Incremental DOM has two main strengths compared to virtual DOM based approaches:
The incremental nature allows for significantly reduced memory allocation during render passes, allowing for more predictable perfromance.
It easily maps to template based approaches. Control statements and loops can be mixed freely with element and attribute declarations.
Incremental DOM is a small (2.6kB min+gzip), standalone and unopinionated library. It renders DOM nodes and allows setting attributes/properties, but leaves the rest, including how to organize views, up to you. For example, an existing Backbone application could use Incremental DOM for rendering and updating DOM in place of a traditional template and manual update approach.