JSFiddle - React, Tailwind, and code Playground
by xdenser
HTML
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<div class='tree' data-bind='with: tree'>
<ul data-bind='template: {name:"treeNode", foreach: children}'>
</ul>
</div>
<script id='treeNode' type='text/html'>
<li data-bind='css:{closed:isClosed,open:isOpen, leaf: isLeaf, last: isLast}, click: toggleOpen, clickBubble: false'>
<ins></ins>
<span data-bind='text:caption'></span>
<ul data-bind='template: {name:"treeNode", foreach: children}'>
</ul>
</li>
</script>
CSS
.tree li,
.tree ins{ background-image:url("http://habrastorage.org/storage2/0eb/507/98d/0eb50798dca00f5cc8e153e6da9a87f9.png"); background-repeat:no-repeat; background-color:transparent; }
.tree li { background-position:-90px 0; background-repeat:repeat-y; }
.tree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; }
.tree ul, .tree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; }
.tree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; }
.tree > ul > li { margin-left:0px; }
.tree li.last { background:transparent; }
.tree .open > ins { background-position:-72px 0;}
.tree .closed > ins { background-position:-54px 0;}
.tree .leaf > ins { background-position:-36px 0;}
.tree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; }
li.open > ul { display:block; }
li.closed > ul { display:none; }
JavaScript
function TreeView(data, propMap){
this.data = data;
this.children = dataToNodes(data);
setIsLast(this.children);
function dataToNodes(dataArray){
var res = [];
for(var i=0,l=dataArray.length;i<l;i++){
res.push(new TreeViewNode(dataArray[i]));
}
return res;
}
function setIsLast(children){
for(var i=0,l=children.length;i<l;i++){
children[i].isLast = (i==(l-1));
}
}
function TreeViewNode(data){
var self = this;
this.data = data;
var
map = (typeof propMap == 'function') ? propMap(data):propMap;
captionProp = (map && map.caption)||'caption',
childrenProp = (map && map.children)||'children';
this.caption = data[captionProp];
if(data[childrenProp]) this.children = dataToNodes(data[childrenProp]);
else this.children = [];
this.isOpen = ko.observable();
this.isClosed = ko.computed(function(){
return !this.isOpen();
},this);
this.isLeaf = !this.children.length;
this.isLast = false;
setIsLast(this.children);
this.toggleOpen = function(){
self.isOpen(!self.isOpen());
};
}
}
var vm = {
data: [
{
name:'Node 1',
list: [{
name: 'Node 3'
}]
},
{
name:'Node2',
list: [{
name: 'Node 6',
list: [{
name: 'Node 5'
}]
}]
}
]
};
vm.tree = new TreeView(vm.data,function(){return {caption:'name',children:'list'};});
ko.applyBindings(vm);