Hierarchical/Tree Control

for AngularJS

A hierarchical/tree selection control for AngularJS. It can either have the whole data structure (a tree) passed to it or set to asynchronously load each level via a callback. It also allows optional multiple selection and keyboard navigation. Auto-complete type-ahead searching of the tree is also planned.

View on GitHub Latest Release 0.4

Getting started

data - An array of objects that respresent the tree structure

on-selection-changed - Executed when the selected item changes

placeholder - Text to display when there is no selection


Selected item: {{selectedItem1}}
<!-- Include AngularJS (we'll skip this in futher examples) -->
<script id="angularScript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>

<!-- Somewhere on your page -->
<hierarchical-selector placeholder="Select something here please..." data="data" on-selection-changed="selectedItem = items" />

Setting the selected items

selection - An array of objects that respresent the selected items


Selected items: {{selection ? selection[0].name : 'null'}}


Selected items: {{selectedItem6 ? selectedItem6[0].name : 'null'}}

<!-- Set store the selected item from the first control -->
<hierarchical-selector data="data" on-selection-changed="selection = items" />

<!-- Set the selection in the second control -->
<hierarchical-selector data="data" selection="{ {selection }}" />

Multi-select

Also without the button to show/hide the tree pop-down.

multi-select - Enables mult-selection mode. Items will have checkboxes on their left

no-button - Hides the button on the right


Selected items: {{selectedItem2}}
<!-- Turn on multi-select and turn of the button -->
<hierarchical-selector data="data" multi-select="true" on-selection-changed="onSelectionChanged(items)" no-button />

Multi-select leaf selection only

Also works in single selection mode.

select-only-leafs - Only nodes that have no children are selectable


Selected items: {{selectedItem3}}
<!-- Turn on multi-select and limit selection to leaf nodes only -->
<hierarchical-selector data="data" select-only-leafs="true" multi-select="true" on-selection-changed="onSelectionChanged(items)" />

Can select item callback

For custom logic of if an item should be selectable.

can-select-item - If supplied, executed to figure out if an item can be selected. Takes preference over select-only-leafs


Selected items: {{selectedItem4}}
// On your controller - return true if we allow the user to select this node
$scope.selectOnly1Or2 = function(item) {
  if (item)
    return /[12]/.test(item.name);
  return false;
};

<!-- Pass our can-select-item function -->
<hierarchical-selector data="data" can-select-item="selectOnly1Or2(item)" multi-select="true" on-selection-changed="onSelectionChanged(items)" />

Async tree building

Setting the load-child-items callback will change the control to async mode.

load-child-items - Executed to load the child items when the user expands a node. data can be used to set the top level itesm but is ignored after that. The function should return an array of items or a promise that resolved to an array of items

Data model required for async items:
{
  name: "string",
  hasChildren: true|false // item-has-children attribute takes preference.
}


Selected items: {{selectedItem5}}
// Needs to return an array of items or a promise that resolves to an array of items.
$scope.loadAsyncData = function(parent) {
  var defer = $q.defer();
  if (!parent) {
    $http.get('http://jsonplaceholder.typicode.com/users').success(function (data) {
      for (var i = 2; i < data.length; i++) {
        data[i].hasChildren = true;
      }
      defer.resolve(data);
    });
  }
  else {
    if (parent.username) {
      // second level
      $http.get('http://jsonplaceholder.typicode.com/users/' + parent.id + '/posts').success(function (data) {
        // make our 'model'
        for (var i = 0; i < data.length; i++) {
          data[i].name = 'Post: ' + data[i].title;
          if (i === 0) {
            data[i].hasChildren = true;
          }
        }
        defer.resolve(data);
      });
    }
    else if (parent.title) {
      // third level
      $http.get('http://jsonplaceholder.typicode.com/posts/' + parent.id + '/comments').success(function (data) {
        // make our 'model'
        for (var i = 0; i < data.length; i++) {
          data[i].name = 'Comment: ' + data[i].name;
        }
        defer.resolve(data);
      });
    }
  }
  return defer.promise;
};

<!-- Pass our function to load-child-nodes -->
<hierarchical-selector load-child-nodes="loadAsynData" on-selection-changed="selectedItem5 = onSelectionChanged(items)" />