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);