Knockout JS TreeView
Version: Last Selected, Single Selected.
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap-theme.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"></script>
<h4 style="color: gray; margin-left: 20px;">Selected Document Type:
<!-- ko with: selectedDocType -->
<span data-bind="text: name"
style="color:black;"></span>
<!-- /ko -->
</h4>
<div class="tree">
<ul>
<li>
<a>Scanning</a>
<ul>
<!-- ko foreach: sections -->
<li>
<a data-bind="text: name"></a>
<ul data-bind="foreach: { data: subsections, as: 'subsection' }">
<!-- ko ifnot: subsection.invisible -->
<li>
<a data-bind="text: name"></a>
<ul data-bind="foreach: docTypes">
<li>
<a data-bind="text: name,
click: $root.selectingClick,
css: {selectedItem: $root.checkSelectedDocType($data)}
"></a>
</li>
</ul>
</li>
<!-- /ko -->
<!-- Wanna show docTypes as a subsection for invisible subsections. -->
<!-- ko if: subsection.invisible -->
<!-- ko foreach: docTypes -->
<li>
<a data-bind="text: name,
click: $root.selectingClick,
css: {selectedItem: $root.checkSelectedDocType($data)}
"></a>
</li>
<!-- /ko -->
<!-- /ko -->
</ul>
</li>
<!-- /ko -->
</ul>
</li>
</ul>
</div>
<div style="background-color: #feffc9">
<hr />
<h4 style="margin-left: 20px;">
Static tree for a...
CSS
body {
background-color: white;
}
.tree li {
margin: 0px 0;
list-style-type: none;
position: relative;
padding: 10px 5px 0px 50px;
}
.tree li::before {
content: '';
position: absolute;
top: 0px;
width: 1px;
height: 100%;
right: auto;
left: -20px;
border-left: 1px solid #ccc;
bottom: 50px;
}
.tree li::after {
content: '';
position: absolute;
top: 25px;
width: 70px;
height: 10px;
right: auto;
left: -20px;
border-top: 1px solid #ccc;
}
.tree li a {
display: inline-block;
border: 1px solid #ccc;
padding: 5px 10px;
text-decoration: none;
color: #666;
font-family: arial, verdana, tahoma;
font-size: 12px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
cursor: default;
min-width: 30px;
}
/* Removes connectors for the root. */
.tree > ul > li::before, .tree > ul > li::after {
border: 0;
}
/* Removes long sizes for root elements. */
.tree > ul {
padding-left: 0;
}
.tree > ul > li {
padding-left: 10px;
}
.tree > ul > li::after {
width: 10px;
}
/* Removes connectors for any last child. */
.tree li:last-child::before {
height: 25px;
}
.tree li a:hover, .tree li a:hover + ul li a {
background: #FaFaFa;
color: #000;
border-color: #94a0b4;
}
/*
.tree li a:hover + ul li::after,
.tree li a:hover + ul li::before,
.tree li a:hover + ul::before,
.tree li a:hover + ul ul::before {
*/
.tree li a:hover + ul li::after,
.tree li a:hover + ul li::before {
border-color: #94a0b4;
}
.tree a:last-child {
cursor: pointer;
border-width: 3px;
font-weight: bold;
}
.selectedItem{
background-color: #dae0ea !Important;
}
JavaScript
console.clear();
var viewModel = function(){
"use strict"
var self = this;
self.sections = ko.observableArray([]);
self.selectedDocType = ko.observable();
self.applyTree = function(sections){
self.sections(sections);
};
self.selectingClick = function(docType){
self.selectedDocType(docType);
}
self.checkSelectedDocType = function(docType){
var selected = self.selectedDocType();
if(!selected){
return false;
}
return selected === docType;
}
};
var vm = new viewModel();
ko.applyBindings(vm);
var sections = [
{
name: 'Identity Cards',
subsections:[
{
name: 'Citizen',
docTypes: [
{name: 'Citizen ID 1'},
{name: 'Citizen ID 2'},
{name: 'Citizen ID 3'},
]
},
{
name: 'Foreign',
docTypes: [
{name: 'Foreigner ID 1'},
{name: 'Foreigner ID 2'},
]
},
]
},
{
name: 'Forms',
subsections: [
{
name: 'Property section',
docTypes: [
{name: 'Property Form 1'},
{name: 'Property Form 2'},
]
},
{
name: 'Loyalty invisible section',
invisible: true,
docTypes: [
{name: 'Property Form 1'},
{name: 'Property Form 2'},
]
},
{
name: 'Employment section',
docTypes: [
{name: 'Property Form 1'},
{name: 'Property Form 2'},
]
},
],
}
];
// JSFiddle AJAX emulation.
$.post('/echo/json/',
{
json: JSON.stringify(sections),
delay: 0.1
})
.done(vm.applyTree)
;
//vm.applyTree(sections);