Skip to content

Instantly share code, notes, and snippets.

@bringking
Created January 12, 2014 19:19
Show Gist options
  • Save bringking/8389136 to your computer and use it in GitHub Desktop.
Save bringking/8389136 to your computer and use it in GitHub Desktop.
Knockout TreeView- A nice binding handler that accepts a dynamic tree of data, and displays a searchable and selectable tree-type list.
* {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
user-select: none;
font-family: "Segoe UI", "Helvetica", Arial, sans-serif;
}
.navbar{
margin-bottom:10px;
}
.ko-treeview-container {
max-height: 300px;
overflow-y: auto;
overflow-x: hidden;
border: 1px solid #eee;
background-color: white;
border-radius: 3px;
padding-right: 7px;
padding-top: 5px;
}
.ko-treeview-container .navbar {
margin-right: 0px;
}
.ko-treeview-container > .ko-treeview-list {
padding: 0;
margin-left:0px;
}
.ko-treeview-list {
list-style: none;
}
.ko-treeview-listitem {
line-height: 2em;
margin-top: 0px;
}
.ko-treeview-label {
-webkit-transition: all 250ms;
-moz-transition: all 250ms;
-ms-transition: all 250ms;
-o-transition: all 250ms;
cursor: pointer;
display: block;
margin-left: 5px;
width: 100%;
background-color: white;
color: #2980b9;
border-bottom: 1px solid #bdc3c7;
}
.ko-treeview-label:hover {
border-bottom-color: #2980b9;
}
.ko-treeview-cb {
-webkit-transition: all 250ms;
-moz-transition: all 250ms;
-ms-transition: all 250ms;
-o-transition: all 250ms;
float: right;
clear: both;
margin-top: 1em;
margin-left: 5px;
}
.ko-treeview-cb:checked {
margin-top: 1.5em;
}
.ko-treeview-cb:checked + label {
line-height: 50px;
font-weight: bold;
border-bottom-color: #27ae60;
font-size: 1.1em;
color: #27ae60;
}
<!DOCTYPE html>
<html>
<head>
<link href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css" rel="stylesheet" type="text/css" />
<link href="http://getbootstrap.com/2.3.2/assets/css/bootstrap-responsive.css" rel="stylesheet" type="text/css" />
<link href="treeview.css" rel="stylesheet" type="text/css" />
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
<script src="treeview.js"></script>
<meta charset=utf-8 />
<title>Knockout TreeView</title>
</head>
<body>
<div data-bind="treeView:{title:'Some Title',selected:selectedNodes, data:data}">
</div>
<h4>Selected Nodes</h4>
<ul data-bind="foreach: selectedNodes">
<li data-bind="text: $data"></li>
</ul>
</body>
</html>
ko.bindingHandlers.treeView = {
createNodes: function(rootElement, options){
var rootTmpl = '<script id="ko-treeview-root-tmpl"><div class="navbar"><p class="brand" data-bind="text:$data.title">Title</p><div class="container"><form class="navbar-form pull-right col-sm-4"><div class="input-append"><input class="span4" type="text" placeholder="Search" data-bind="value:$data.search, valueUpdate: \'afterkeydown\'"/><span class="add-on"><i class="icon-search"></i></span></div></form></div></div><ul class="ko-treeview-list" data-bind="template:{foreach:$data.data,name:\'ko-treeview-node-tmpl\'}"></ul></script>';
var nodeTmpl = '<script id="ko-treeview-node-tmpl"><li class="ko-treeview-listitem"><div data-bind="template:{name:\'ko-treeview-item-tmpl\',data:$data}"></div><ul class="ko-treeview-list" data-bind="template:{name:\'ko-treeview-node-tmpl\',foreach:$data[$root.childNode]}"></div></li></script>';
var itemTmpl ='<script id="ko-treeview-item-tmpl"><div data-bind="visible:$data[$root.label].indexOf($root.search()) > -1"><input type="checkbox" class="ko-treeview-cb" data-bind="checked: $root.selected, attr:{value:$data[$root.label], id:$data[$root.label]}" /><label class="ko-treeview-label" data-bind="text:$data[$root.label], attr:{for:$data[$root.label]}"></label></div></script>'
//append templates
document.body.insertAdjacentHTML('beforeend', rootTmpl);
document.body.insertAdjacentHTML('beforeend', nodeTmpl);
document.body.insertAdjacentHTML('beforeend', itemTmpl);
//apply first binding
ko.applyBindingsToNode(rootElement, {template:{name:"ko-treeview-root-tmpl"}},options);
},
init: function(element, valueAccessor) {
//style element
element.className = "ko-treeview-container";
//extend options with search
var options = valueAccessor();
options.search = ko.observable("");
//set default data values
if(!options.label) options.label = 'id';
if(!options.childNode) options.childNode = 'children';
//create the tree
ko.bindingHandlers.treeView.createNodes(element,options);
valueAccessor().data.subscribe(function(){
ko.bindingHandlers.treeView.createNodes(element,options);
});
//let this handler control its descendants.
return { controlsDescendantBindings: true };
}
};
function vm(){
this.selectedNodes = ko.observableArray([]);
this.data = ko.observableArray([
{
id:"Level 1",
children:[
{id:"Level 1-1",children:[
{id:"Level 1-1-1",children:[
{id:"Level 1-1-1-1"}
]}
]},
{id:"Level 1-2"},
]
},
{
id:"Level 2",
children:[
{id:"Level 2-1",children:[
{id:"Level 2-1-1"}
]},
{id:"Level 2-2"},
]
},
]);
}
var myVM = new vm();
ko.applyBindings(myVM);
@jotosmurf
Copy link

@bringking Very nice! Is it possible to give the data a more friendly title attribute along with the id?

@niceboomer
Copy link

Very nice one. Still useable on 2020 ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment