﻿#Trackira

[![Build Status](https://travis-ci.org/trackira/trackira.svg?branch=master)](https://travis-ci.org/trackira/trackira)
[![devDependency Status](https://david-dm.org/trackira/trackira/dev-status.svg)](https://david-dm.org/trackira/trackira#info=devDependencies)
[![npm version](https://badge.fury.io/js/trackira.svg)](http://badge.fury.io/js/trackira)
[![Coverage Status](https://coveralls.io/repos/trackira/trackira/badge.svg?branch=master)](https://coveralls.io/r/trackira/trackira?branch=master)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/trackira/trackira/master/LICENSE)
[![](http://trackira.herokuapp.com/badge.svg)](https://trackira.herokuapp.com/)

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](http://vdom-benchmark.github.io/vdom-benchmark/)

## 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
```javascript
// 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);
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/93/)

The above example can be compared with the [example](https://github.com/Matt-Esch/virtual-dom#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:

```javascript
{
 key: String|Integer
 props {}
 attrs: {}
 events: {}
 data:()
 hooks: {}
}
```
###Example:

```javascript
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](http://jsfiddle.net/jpyr2ksL/94/)

###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.

 ```javascript
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.
```javascript
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.

 ```javascript
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.

```javascript
// 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);
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/95/)

###Children
```javascript
 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

```javascript
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`.
 ```javascript
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`.
 ```javascript
tree.mount("#mount-point",  foo);
```
Mounting to element with a class - `.mount-point`.
 ```javascript
tree.mount(".mount-point",  foo);
```
Mounting to element with DOM element - `document.getElementById("test")`.
 ```javascript
tree.mount(document.getElementById("test"),  foo);
```
You have probably realized that it supports [CSS Selectors](http://www.w3schools.com/cssref/css_selectors.asp).

You can also mount with a `factory` - function.

```javascript
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.
 ```javascript
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.

```javascript
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.

```javascript
 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:

```javascript
// 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);
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/96/)


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

```javascript
.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

```javascript
.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.

```javascript
.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:

 ```javascript
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
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/97/)

## 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:

 ```javascript
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:

 ```javascript
// 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

 ```javascript
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.

```javascript
// toHTML boolean properties
var element = Trackira.h({tagName:"input", props: {
    autofocus: true,
    disabled: false
    }
});
    html = element.toHTML();

// result:  <input autofocus>
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/98)

```javascript
// 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>
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/99)

```javascript
// .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>
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/100)

```javascript
// .toHTML a comment
var comment = Trackira.h("Foo like Bar");
    html = comment.toHTML();

// result:  <!-- Foo like Bar -->
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/101)

```javascript
var node = Trackira.h({tagName:"div", props: { innerHTML: "<span>hello, terrible world!!</span>" } });
    html = node.toHTML();

// result: <div><span>hello, terrible world!!</span></div>"
```

```javascript
// preserve UTF-8 entities and escape special html characters

var node = Trackira.h({tagName:"span"}, ["测试&\"\'<>"]);
    html = node.toHTML();

// result: <span>测试&amp;&quot;&#x27;&lt;&gt;</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:

```javascript
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:

```javascript
// 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);
```
[View on JSFiddle](http://jsfiddle.net/jpyr2ksL/102)


Show a set of events you are listening to.

```javascript
// 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 node*
- `created` - *called once a virtual node has been created*
- `detach`  - *called when a virtual node is going to be removed*
- `destroy` - *called on element removal*

Animated transitions can be supported through "created" & "destroy" hooks.

```javascript
// 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

```javascript
 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:

```javascript
npm install
```

###Commands:

Trackira uses **gulp** and Babel, and the commands are pretty straight forward:

```javascript
// 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](https://github.com/trackira/trackira/blob/master/test/runner.html) 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)](https://github.com/trackira/trackira/blob/master/LICENSE)
