A tree menu with Angular-Treeview

Maria Cristina Di Termine
22-12-2016

In this post I’d like to show you how to make a tree menu using Angular-treeview.
Angular-treeview is a module based on pure AngularJS. It uses some attributes that we need to specify between the view and the controller. The attributes it uses on default are these:

  • angular-treeview: is the treeview directive and we set it on `true` or `false` if we want to turn it on or off
  • tree-id : each tree has an unique id
  • tree-model : is where we set the name of the tree model in the $scope
  • node-id : each node has also an id
  • node-label : each node has a label which is the title we will see in the view for each node
  • node-children: if a node has a children it will be set under this attribute

This is an example of our directive in the view:

  <div>
    <div>

      <div>       
      </div>

    </div>
  </div>

But the real heart of our tree menu is in the Controller because is there where we set up the structure of the menu. Here a simple example:

myApp.controller('myController', function($scope){

$scope.treeMenu = [
{
"roleName": "Menu", "roleId": "menu", "collapsed": true, "children": [
  { "roleName": "Group 3", "roleId": "group3", "collapsed": true, "children": [] },
  { "roleName": "Group 4", "roleId": "group4", "collapsed": true, "children": [] },
  { "roleName": "Group 5", "roleId": "group5", "collapsed": true, "children": [] },
{
"roleName": "Group 6", "roleId": "group6", "collapsed": true, "children": [
  { "roleName": "Block 1", "roleId": "block1", "collapsed": true, "children": [] },
  {
"roleName": "Block 2", "roleId": "block2", "collapsed": true, "children": [
{
"roleName": "Child 1", "roleId": "child1", "collapsed": true, "children": [
  { "roleName": "Livel 1", "roleId": "livel1", "collapsed": true, "children": [] },
  { "roleName": "Livel 2", "roleId": "livel2", "collapsed": true, "children": [] },
  { "roleName": "Livel 3", "roleId": "livel3", "collapsed": true, "children": [] }
]
},
  { "roleName": "Child 2", "roleId": "child2", "children": [] },
  { "roleName": "Child 3", "roleId": "child3", "children": [] },
  { "roleName": "Child 4", "roleId": "child4", "children": [] }
]
},
  { "roleName": "Block 3", "roleId": "block3", "collapsed": true, "children": [] }
]
},
  { "roleName": "Group 7", "roleId": "group7", "collapsed": true, "children": [] },
  { "roleName": "Group 8", "roleId": "group8", "collapsed": true, "children": [] }
]
}

];

});

})();

As you have noticed we have an property more in our object : collapsed. This property is where we can set a boolean value to make the node be expanded or collapsed already on load. For example if we want the menu already open on the first node, then we should set collapsed of node Menu on false.

To see how that works, let’s have a look at the module. Of course the module can be modified and we could add new attributes or change the structure of the output we will get in the view if we like.

(function ( angular ) {
'use strict';

angular.module( 'angularTreeview', [] ).directive( 'treeModel', ['$compile', function( $compile ) {
return {
restrict: 'A',
link: function ( scope, element, attrs ) {
//tree id
var treeId = attrs.treeId;

//tree model
var treeModel = attrs.treeModel;

//node id
var nodeId = attrs.nodeId || 'id';

//node label
var nodeLabel = attrs.nodeLabel || 'label';

//children
var nodeChildren = attrs.nodeChildren || 'children';

//tree template
var template =
'<ul>' +
'<li>' +
'<i class="collapsed"></i>' +
'<i class="expanded"></i>' +
'<i class="normal"></i> ' +
'<span>{{node.' + nodeLabel + '}}</span>' +
'<div></div>' +
'</li>' +
'</ul>';


//check tree id, tree model
if( treeId && treeModel ) {

//root node
if( attrs.angularTreeview ) {

//create tree object if not exists
scope[treeId] = scope[treeId] || {};

//if node head clicks,
scope[treeId].selectNodeHead = scope[treeId].selectNodeHead || function( selectedNode ){

  //Collapse or Expand
  selectedNode.collapsed = !selectedNode.collapsed;
  };

  //if node label clicks,
  scope[treeId].selectNodeLabel = scope[treeId].selectNodeLabel || function( selectedNode ){

 //remove highlight from previous node
 if( scope[treeId].currentNode && scope[treeId].currentNode.selected ) {
   scope[treeId].currentNode.selected = undefined;
 }

 //set highlight to selected node
 selectedNode.selected = 'selected';

 //set currentNode
    scope[treeId].currentNode = selectedNode;
 };
}

//Rendering template.
element.html('').append( $compile( template )( scope ) );
}
}
};
}]);
})( angular );

To make it working properly you also need to add some style and Angular-treeview provides also that usign [data-tree-model] as selector:

div[data-angular-treeview] {
  /* prevent user selection */
  -moz-user-select: -moz-none;
  -khtml-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;

  /* default */
  font-family: Tahoma;
  font-size:13px;
  color: #555;
  text-decoration: none;
}

div[data-tree-model] ul {
  margin: 0;
  padding: 0;
  list-style: none; 
  border: none;
  overflow: hidden;
}

div[data-tree-model] li {
  position: relative;
  padding: 0 0 0 20px;
  line-height: 20px;
}

div[data-tree-model] li .expanded {
  padding: 1px 10px;
  background-image: url("../img/folder.png"); // Icon of an open folder
  background-repeat: no-repeat;
}

div[data-tree-model] li .collapsed {
  padding: 1px 10px;
  background-image: url("../img/folder-closed.png"); // Icon of a folder
  background-repeat: no-repeat;
}

div[data-tree-model] li .normal { // Icon of a normal text file
  padding: 1px 10px;
  background-image: url("../img/file.png");
  background-repeat: no-repeat;
}

div[data-tree-model] li i, div[data-tree-model] li span {
   cursor: pointer;
}

div[data-tree-model] li .selected {
  background-color: #aaddff;
  font-weight: bold;
  padding: 1px 5px;
}

One last thing we could do is to make the code detect which node we selected and maybe get all the information we can have from it. For example, if we want to get the title form the menu to an imaginary header, we should know in which level of the tree-menu we are and which node we selected. To do this we can use a $watch statement like this:

$scope.$watch( 'mytree.currentNode', function( newObj, oldObj ) {
   if( $scope.mytree && angular.isObject($scope.mytree.currentNode) ) {
      console.log( 'Here you are!' );
      console.log( $scope.mytree.currentNode );
   }
}, false);

Here you can check my DEMO
Here the original GitHub code

LEAVE A REPLY

you might also like