trackira
v0.2.3-a
Published
Virtual DOM boilerplate
Downloads
74
Maintainers
Readme
#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
// creating the virtual tree
var tree = new Trackira.Tree();
// setting start count
var count = 0;
// creating a virtual node
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);
// mount the virtual node
var mountId = tree.mount(document.body, vnode);
// render a new node and update the tree each second
setInterval(function () {
count++;
vnode = render(count);
tree.update(mountId, vnode);
}, 1000);
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"])
]);
###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;
// img element with src and alt attributes
h({tagName:"img",attrs: {src: 'http://...', alt: 'Image ...'}});
// input element with an id and class
h({tagName:"input", attrs: {id: "name-field", "class":"'important-field"});
// div element with a style object
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;
// Element with style object
h({tagName:"div", attrs: {style: { height:"200px", width:"200px" }}});
// Element with style string
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.
// ensuring right focus
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() {
// negative value
active = !active;
return active ? from : to;
},
// mount
mountId = tree.mount(document.body, updateFunc);
setInterval(function() {
tree.update(mountId);
}, 1100);
###Children
var h = Trackira.h;
// span element with one child
h({tagName:"span"}, ["Hello World!]")
// span element with children
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() // render the virtual node
node.patch(vnode) // patch the virtual node with a real DOM node
node.detach() // remove the node
node.equalTo(vnode) // returns boolean - true / false
##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);
}())
// Mount the tree
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); // unmount a virtual tree with the mount identifier
tree.unmount(); // unmount all virtual trees
###.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); // update focus on already mounted node
With two arguments, you can update the existing node with another virtual node as shown in this example:
// create and mount a virtual node
var tree = new Trackira.Tree();
var h = Trackira.h;
var foo = h({tagName:"h1"}, ["Foo visited Bar"])
var mountId = tree.mount(document.body, foo);
// new element to update
var newFoo = h({tagName:"h1"}, ["Bar was eating Foo"]);
// update the tree with the mount identifier
tree.update(mountId, newFoo);
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 ) // returns a real DOM node where the virtual tree are mounted
###.children()
This method needs one argument - the unique number created when you mount a virtual tree
.children( uid ) // return a overview over all children to the mounted tree
###.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 ) // return a boolean - true / false - if current tree are mounted
.mounted() // returns a list over all mounted virtual trees
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);
// container.childNodes.length equal to 1
node.detach();
// container.childNodes.length equal to 0
Patching operations
There are one main API methods for patching / diffing.
Trackira.patch()
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"]);
// patch the node
from.patch(to);
Another example:
// patching a HTML property
var h = Trackira.h;
var from = h({tagName:"div", props: { title: "hello" } });
var to = h({tagName:"div", props: { title: "world" } });
// patch the node
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' are only used for patching / diffing the children
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.
// toHTML boolean properties
var element = Trackira.h({tagName:"input", props: {
autofocus: true,
disabled: false
}
});
html = element.toHTML();
// result: <input autofocus>
// convert properties to attributes
var element = Trackira.h({tagName:"form", props:{
className: "login",
acceptCharset: "ISO-8859-1",
accessKey: "h"
}
});
html = element.toHTML();
// result: <form class="login" accept-charset="ISO-8859-1" accesskey="h"></form>
// .toHTML SVG attributes
var svg = Trackira.h({tagName:"circle", attrs: {
cx: "60",
cy: "60",
r: "50"
}
});
html = svg.toHTML();
// result: <circle cx="60" cy="60" r="50"></circle>
// .toHTML a comment
var comment = Trackira.h("Foo like Bar");
html = comment.toHTML();
// result: <!-- Foo like Bar -->
var node = Trackira.h({tagName:"div", props: { innerHTML: "<span>hello, terrible world!!</span>" } });
html = node.toHTML();
// result: <div><span>hello, terrible world!!</span></div>"
// preserve UTF-8 entities and escape special html characters
var node = Trackira.h({tagName:"span"}, ["测试&\"\'<>"]);
html = node.toHTML();
// result: <span>测试&"'<></span
##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:
// create virtual tree
var tree = new Trackira.Tree();
// initialize the events
var Evt = Trackira.initEvent();
// stop listen to the 'click' event
Evt.bind("click"); // try 'unbind()' to unbind event
// the event will not work because you unbinded it
var vnode = new Trackira.Element("button", {
events: {
onclick: function () {
alert("Hello, world!");
}
}
}, [new Trackira.Text("Click me!")]);
// mount element
tree.mount(document.body, vnode);
Show a set of events you are listening to.
// initialize the events
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.
// using 'created' hook on create
var params,
tag = new Trackira.Element("div", {
hooks: {
created: function() {
params = Array.prototype.slice.call(arguments);
}
}
});
var element = tag.render(); // params are now equal to [tag, element]
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);
// Console.log info:
// mountPoint.innerHTML => <div></div>
// params => [[tag, tag.node], [tag.node, destroyHook]]
// Call the callback
destroyHook();
// mountPoint.innerHTML => ""
##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:
// runs the unit tests
$ npm gulp
// build the library
$ npm gulp build
// bundle the library for our unit tests
$ npm gulp browserify
// show a istanbul coverage statistic on cmd line
$ npm gulp coverage
// runs unit tests
$ npm gulp test
// run the headless unit tests as you make changes
$ npm gulp watch
// set up a livereload environment for our spec runner
$ 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)