#Trackira

Virtual DOM libraries can be designed in many ways depending on the problems it is trying to solve, and it can be a challenging task to pick the right library to use.
Trackira try to solve this with being a boilerplate you can build upon. It's goal is not to be the fastest or the best, but possessing the best code structure and also be compatible with the DOM standards today.
Trackira output an Observable of "Virtual DOM Elements", to keep performance fast by patching the actual DOM with only the minimum necessary changes.
You can find a benchmark here
Supported features
- Custom elements,
- SVG and MathML support
- xlink and xml namespace support
- Type extensions
- Server side rendering
- mounting / unmounting
- updates
- patching
- DOM level O events
- lifeCycle hooks
###Example
var tree = new Trackira.Tree();
var count = 0;
function render(count) {
return Trackira.h({tagName: "div",
attrs: {
style: {
"text-align": 'center',
"line-height": (100 + count) + 'px',
border: '1px solid red',
width: (100 + count) + 'px',
height: (100 + count) + 'px'
}
}
}, [count]);
}
var vnode = render(count);
var mountId = tree.mount(document.body, vnode);
setInterval(function () {
count++;
vnode = render(count);
tree.update(mountId, vnode);
}, 1000);
View on JSFiddle
The above example can be compared with the example of virtual-dom.
Virtual nodes
###Trackira.Element()
Trackira.Element()
create a real DOM node, and accepts an optional data object and an optional string or array of children.
E.g. new new Trackira.Element("div", {}, [new Text("Heeey!")])
. Other virtual nodes are available as well - both text and comment node.
The optional data object contains this configurable values:
{
key: String|Integer
props {}
attrs: {}
events: {}
data:()
hooks: {}
}
###Example:
var h = Trackira.h
var vnode = h({ tagName: "div", attrs: {style: {color: 'red'}}}, [
h({tagName: "h1"}, ["Headline"]),
h({tagName: "p"}, ["A paragraph"])
]);
View on JSFiddle
###Attributes
Element attributes can be set with the attrs{}
property. The attribute name are always the same as in HTML. If you provide a boolean for the attribute value. The attribute will be added with an empty string if it's a true value, and removed otherwise.
var h = Trackira.h;
h({tagName:"img",attrs: {src: 'http://...', alt: 'Image ...'}});
h({tagName:"input", attrs: {id: "name-field", "class":"'important-field"});
h({tagName:"div", attrs: {style: 'border-bottom': '1px solid black', color: 'gray'}});
Non-ASCII symbols are supported and can be used example in class names.
h({tagName:"div", {attrs: {"class": "ΑΒΓΔΕΖ"}});
###Inline style (css)
For inline style, you have to set the unit ( e.g. 'px') yourself. Values that need an unit, but don't have one, will not be set.
var h = Trackira.h;
h({tagName:"div", attrs: {style: { height:"200px", width:"200px" }}});
h({tagName:"div", attrs: {style: 'border-bottom': '1px solid black', color: 'gray'}});
###Keys
Keys are unique and are attached to a virtual node, to ensure that only the elements which were really changed are updated. Even if elements were added/removed before or moved to another position. While common operations like appending and prepending nodes are optimized, it is also very fast at updating random changes like sorting or filtering.
var mountId,
h = Trackira.h,
tree = new Trackira.Tree();
var from = [
h({tagName: "input", key: 0, attrs: { id: "input_1", placeholder: "input_#1"}}),
h({tagName: "input", key: 1, attrs: { id: "input_2", placeholder: "input_#2"}})
];
var to = [
h({tagName: "input", key: 1, attrs: { id: "input_2", placeholder: "input_#2"}}),
h({tagName: "input", key: 0, attrs: { id: "input_1", placeholder: "input_#1"}})
];
var active = false,
updateFunc = function() {
active = !active;
return active ? from : to;
},
mountId = tree.mount(document.body, updateFunc);
setInterval(function() {
tree.update(mountId);
}, 1100);
View on JSFiddle
###Children
var h = Trackira.h;
h({tagName:"span"}, ["Hello World!]")
h({tagName:"span"]}, ["Hello ", "World!"])
Virtual DOM node methods
Each virtual DOM node has it's own methods:
- .render() - render the virtual node, and return a single DOM element
- .toHTML() - create HTML markup server-side
- .patch() - patch a virtual node with a real DOM node
- .detach() - Remove a real DOM element from where it was inserted
- .equalTo() - Checks if two virtual nodes are equal to each other, and they can be updated
Examples
node.render()
node.patch(vnode)
node.detach()
node.equalTo(vnode)
##Virtual tree
The API provides all necessary functions to create, update and remove virtual DOM nodes to/from the real DOM.
And many of the API features are the same as for other Virtual DOM libraries.
###.mount()
Mounting to the document.body
.
var tree = new Trackira.Tree(),
foo = new Trackira.Element("h1", {}, ["Foo visited Bar"]);
tree.mount(document.body, foo);
Mounting to element with an id - #mount-point
.
tree.mount("#mount-point", foo);
Mounting to element with a class - .mount-point
.
tree.mount(".mount-point", foo);
Mounting to element with DOM element - document.getElementById("test")
.
tree.mount(document.getElementById("test"), foo);
You have probably realized that it supports CSS Selectors.
You can also mount with a factory
- function.
var h = Trackira.h,
tree = new Trackira.Tree(),
render = (function() {
var children = ["Hello, ", "World!!"];
return h("div", children);
}())
tree.mount("#mount-point", render);
With Trackira you also got more advanced options such as mounting with a unique ID identifer.
var bookedId = tree.guid();
var mountId = tree.mount("#mount-point1", h({tagName:"div"}, ["#1", "#2", "#3"]), {mountId: bookedId})
###.unmount()
When you unmount a virtual tree, you can choose to unmount them all, or only one tree. Note that the unmount()
function needs to be called with the mount identifier.
tree.unmount(mountID);
tree.unmount();
###.update()
Once a virtual tree is mounted, you can update it. This API method takes one or two arguments. If no virtual
node are set - as the second argument - it will only update already mounted node. E.g. changing / updating it's focus, or diff / patch
the child nodes.
tree.mount( uid);
With two arguments, you can update the existing node with another virtual node as shown in this example:
var tree = new Trackira.Tree();
var h = Trackira.h;
var foo = h({tagName:"h1"}, ["Foo visited Bar"])
var mountId = tree.mount(document.body, foo);
var newFoo = h({tagName:"h1"}, ["Bar was eating Foo"]);
tree.update(mountId, newFoo);
View on JSFiddle
If want to update all mounted virtual trees, you can do it like this:
tree.update();
###.mountPoint()
This method needs one argument - the unique number created when you mount a virtual tree
.children( uid )
###.children()
This method needs one argument - the unique number created when you mount a virtual tree
.children( uid )
###.mounted()
.mounted()
returns true if the virtual DOM node are mounted into the DOM, false otherwise. You can use this method to guard asynchronous calls.
.mounted( uid )
.mounted()
Detach
Trackira.detach()
let you remove virtual nodes.
Example on detaching / removing an element node:
var container = document.createElement("div");
var node = new Trackira.Element("div");
var element = node.render();
container.appendChild(element);
node.detach();
View on JSFiddle
Patching operations
There are one main API methods for patching / diffing.
For patching children of a real DOM node, you use Trackira.patch()
.
This method takes a DOM root node, and a set of DOM patches. The patching will only happen if the children are different.
Example patching a text node:
var h = Trackira.h;
var from = h({tagName:"div"}, ["hello", "to"]);
var to = h({tagName:"div"}, ["hello"]);
from.patch(to);
Another example:
var h = Trackira.h;
var from = h({tagName:"div", props: { title: "hello" } });
var to = h({tagName:"div", props: { title: "world" } });
from.patch(to);
Patch children on a real DOM node
var h = Trackira.h,
oldChildren = h({tagName:"div"}, [h({tagName:"div"}]));
newChildren = h({tagName:"div"});
Trackira.patch(node, oldChildren, newChildren);
##Server rendring
Trackira supports server rendring. Use .toHTML()
to turn virtual DOM nodes into HTML server-side. Properties get converted into attribute values.
var element = Trackira.h({tagName:"input", props: {
autofocus: true,
disabled: false
}
});
html = element.toHTML();
View on JSFiddle
var element = Trackira.h({tagName:"form", props:{
className: "login",
acceptCharset: "ISO-8859-1",
accessKey: "h"
}
});
html = element.toHTML();
View on JSFiddle
var svg = Trackira.h({tagName:"circle", attrs: {
cx: "60",
cy: "60",
r: "50"
}
});
html = svg.toHTML();
View on JSFiddle
var comment = Trackira.h("Foo like Bar");
html = comment.toHTML();
View on JSFiddle
var node = Trackira.h({tagName:"div", props: { innerHTML: "<span>hello, terrible world!!</span>" } });
html = node.toHTML();
var node = Trackira.h({tagName:"span"}, ["测试&\"\'<>"]);
html = node.toHTML();
##Event system
Trackira's is a cross-browser wrapper around the browser's native event, and each event are managed in a delegated way. The event handlers will be passed instances of
SyntheticEvent
, a cross-browser wrapper around the browser's native event. It has the same interface as the browser's native event, including
stopPropagation()
and preventDefault()
, except the events work identically across all browsers.
To activate
the event management, you wouldt need to initialize it with Trackira.initEvent() first
. Like this:
Trackira.initEvent();
new Trackira.Element('div', {
events: {
onclick: function() {
alert("Hello, world!");
}
}
});
After you have initialized the events, a set of common events are automatically bound to document body, and ready to be used.
Note!! You can use the events with or without the on
prefix. E.g. onclick` or click
.
####Common event types
Trackira normalizes events so that they have consistent properties across different browsers and attach them to the current document.
You controll this events with the bind()
and unbind()
API methods.
Supported common events:
- blur
- change
- click
- contextmenu
- copy
- cut
- dblclick
- drag
- dragend
- dragenter
- dragexit
- dragleave
- dragover
- dragstart
- drop
- focus,
- input
- keydown
- keyup
- keypress
- mousedown
- mousemove
- mouseout
- mouseover
- mouseup
- paste
- scroll
- submit
- touchcancel
- touchend
- touchmove
- touchstart
- wheel
####.bind() and .unbind()
This methods let you control the use of the common events. Here is an example:
var tree = new Trackira.Tree();
var Evt = Trackira.initEvent();
Evt.bind("click");
var vnode = new Trackira.Element("button", {
events: {
onclick: function () {
alert("Hello, world!");
}
}
}, [new Trackira.Text("Click me!")]);
tree.mount(document.body, vnode);
View on JSFiddle
Show a set of events you are listening to.
var Evt = Trackira.initEvent();
console.log(Evt.listeners());
##Lifecycle Methods
Various methods are executed at specific points in a virtual node's lifecycle. This hooks are executed
at different steps. Following hooks are provided:
updated
- called every time an update occur on a virtual nodecreated
- called once a virtual node has been createddetach
- called when a virtual node is going to be removeddestroy
- called on element removal
Animated transitions can be supported through "created" & "destroy" hooks.
var params,
tag = new Trackira.Element("div", {
hooks: {
created: function() {
params = Array.prototype.slice.call(arguments);
}
}
});
var element = tag.render();
Example on detach / destroy hooks when you are detaching a node
var destroyHook;
var h = Trackira.h;
var params = [];
var tag = h({tagName:"div", hooks: {
detach : function() {
params.push(Array.prototype.slice.call(arguments));
},
destroy : function(element, factory) {
destroyHook = factory;
params.push([element, destroyHook]);
}
});
var mountId = tree.mount(document.body, tag);
tree.unmount(mountId);
destroyHook();
##Shadow DOM
Trackira supports Shadow DOM, and should work right out of the box with it. Shadow root is found and automatically dealt with.
Sources:
Many of the ideas for this virtual DOM are comming from the open source community. Many clever brains, with many clever ideas.
Trackira is mainly inspired by
- ReactiveJS ( text, comment and element prototype)
- JSBlocks - ( virtual text and virtual comment nodes)
- Virtual DOM ( the virtual DOM structure itself)
- Kivi - Patching / diffing algorithm
- citoJS - performance tips
- REACT - mounting / unmounting and HTML markup server-side
- vdom-to-html - for server side rendring
- dom-layer - ideas about structure
##Installing
Download the repo and run:
npm install
###Commands:
Trackira uses gulp and Babel, and the commands are pretty straight forward:
$ npm gulp
$ npm gulp build
$ npm gulp browserify
$ npm gulp coverage
$ npm gulp test
$ npm gulp watch
$ npm gulp test-browser
##Community
- Ask "how do I...?" questions on Slack:
- Propose and discuss significant changes as a GitHub issues
##Testing
All unit tests in the Travis CI are server-side. However. If you run this:
HTML file in your browser after you have cloned the repo, will you see unit tests running client-side.
Contribution
Want to contribute? Just send a pull request. All major development are in the dev branch. Master contains more or less a stable release.
##LICENSE
The MIT License (MIT)