<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <title>AngularJS UI Tree demo</title>
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" />
    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/angular-ui-tree/2.22.6/angular-ui-tree.min.css" />
    <link rel="stylesheet" href="demo.css" />
  </head>

  <body ng-app="demoApp">
    <div class="container">
      <div class="row">
        <div class="col-md-12">
          <div class="row" ng-controller="MainCtrl">
            <div class="col-lg-6">
              <h1>Hierarchy Selector</h1>
              <hr>
              <hierarchy-search dataset="list"></hierarchy-search>
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- JavaScript -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-tree/2.22.6/angular-ui-tree.min.js"></script>
    <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.0.js"></script>
    <script src="demolist.js"></script>
    <script src="HierarchyNodeService.js"></script>
    <script src="hierarchySearch.js"></script>
    <script src="main.js"></script>
  </body>

</html>
* {
  -webkit-font-smoothing: antialiased;
}
.noselect {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

.past-users-filter {
    cursor:pointer;
    margin-left:20px;
}
.no-results {
  padding-left:20px;
  padding-bottom:10px;
}

/*matched result from search*/
.matched {
    font-weight:700 !important;
}

/*wraps entire selector*/
.hierarchy-wrap {
    background: #FFFFFF;
    border:1px solid #ccc;
    border-radius:5px;
}
/* past users + search */
.hierarchy-header {
    padding:10px;
}

.hierarchy-header .search-wrap {
    margin-bottom:5px;
}

.angular-ui-tree-handle {
    color: black;
    padding: 5px;
    font-weight:normal;
    cursor:pointer;
}

.angular-ui-tree-nodes .angular-ui-tree-nodes { padding-left:0; }
.angular-ui-tree-nodes .angular-ui-tree-nodes .node-embed {
    padding-left:10px;
}
.angular-ui-tree-nodes .angular-ui-tree-nodes .angular-ui-tree-nodes .node-embed {
    padding-left:20px;
}

.angular-ui-tree-nodes .angular-ui-tree-nodes .angular-ui-tree-nodes .angular-ui-tree-nodes .node-embed {
    padding-left:30px;
}


.angular-ui-tree-handle:hover {
    color: black;
    background: #f5f5f5;
}

.node-header {
    padding-right:5px;
}

.nodeActive {
    background:#deeaf9;
}

.nodeActive:hover {
    background: #428bca;
    color:#FEFEFA;
}

.angular-ui-tree-handle i {
    color:black;
}


<div class="node-embed" ui-tree-handle ng-click="itemSelect(item,this)" ng-class="{nodeActive:item.isSelected,matched:item.match}">
    <a class="btn btn-clear btn-xs" data-ng-show="item.items.length" ng-click="expandNode(this,$event)">
        <i class="fa" ng-class="{'fa-chevron-right': collapsed, 'fa-chevron-down': !collapsed}"></i>
    </a>
    <span class="node-header">
        <input type="checkbox" ng-checked="item.isSelected" indeterminate-checkbox node="item" />
    </span>
    {{item.title}}
</div>

<ol ui-tree-nodes="options" ng-model="item.items" ng-class="{hidden: collapsed,displayed:!collapsed}">
  <li ng-repeat="item in item.items" ui-tree-node collapsed="true" ng-include="'items_renderer.html'">
  </li>
</ol>
(function() {
angular.module('demoApp.list',[])
.value('MyList',[
{
  "id": 1,
  "title": "ASD Headquarters",
  "items": [
    {
      "id": 11,
      "title": "San Jose",
      "items": [
        {
         "id":13,
         "title":"Jensen Chapman's Team",
         "items": [
            {
              "id":14,
              "title":"Jimmy John"
            },
            {
              "id":15,
              "title":"Daniel Mills"
            }
            ,
            {
              "id":16,
              "title":"Chris Boden"
            }
           ]
        }
        ],
    },
    {
      "id": 12,
      "title": "Irvine",
      "items": [
        {
         "id":23,
         "title":"Tracey Chapman's Team",
         "items": [
            {
              "id":24,
              "title":"San Jesus"
            },
            {
              "id":25,
              "title":"Fat Albert"
            }
            ,
            {
              "id":26,
              "title":"Connor McDavid"
            }
           ]
        }
        ]
    },
    {
      "id":30,
      "title":"San Diego",
      "items": [{
        "id":31,
        "title":"Duran Duran's Team",
        "items":[
             {
              "id":32,
              "title":"Amberlynn Pinkerton"
            },
            {
              "id":33,
              "title":"Tony Mejia"
            }
            ,
            {
              "id":34,
              "title":"Richard Partridge"
            }
            ,
            {
              "id":35,
              "title":"Elliot Stabler"
            }
          ]
        },
        {
        "id":40,
        "title":"Steely Dan's Team",
        "items":[
             {
              "id":36,
              "title":"Tony Stark"
            },
            {
              "id":37,
              "title":"Totally Rad"
            }
            ,
            {
              "id":38,
              "title":"Matt Murdock"
            }
            ,
            {
              "id":39,
              "title":"Stan Lee"
            }
          ]
        }
      ]
    }
  ]
}
]);

})()
<div>
    <div class="hierarchy-wrap">
        <div class="hierarchy-header">
            <div class="noselect">
                <label for="pastUsersFilter" class=" past-users-filter">
                    <input type="checkbox"  ng-model="pastUsersFilter" id="pastUsersFilter"/>
                    <span style="margin-left:5px">Include Past Users</span>
                </label>
                <div class="pull-right">
                  <strong>{{numSelected}} selected</strong>
                </div>
            </div>
            <div class="input-group search-wrap">
                <div class="input-group-addon"><i class="fa fa-search"></i></div>
                <input type="text" class="form-control" ng-model="searchValue" placeholder="Search..." />
            </div>
        </div>
        <div class="no-results" ng-show="emptyData"><strong>No Results Found</strong></div>
        <div ng-show="!emptyData" ui-tree="options" data-drag-enabled="false">
            <ol id="demoTree" ui-tree-nodes="" ng-model="list">
              <li ng-repeat="item in list" ui-tree-node collapsed="true" ng-include="'items_renderer.html'"></li>
            </ol>
        </div>
    </div>
    <hr>
    <h2>Selected Ids</h2>
    <pre class="code">{{ selected }}</pre>
    <h2>Search Results</h2>
    <pre class="code">{{ list | json }}</pre>
</div>
(function(){
    angular.module('demoApp.directives',[])

    .directive('indeterminateCheckbox',function(HierarchyNodeService) {
        return {
            restrict:'A',
            scope: {
            node:'='  
            },
            link: function(scope, element, attr) {
                
                scope.$watch('node',function(nv) {
                    
                    var flattenedTree = HierarchyNodeService.getAllChildren(scope.node,[]);
                    flattenedTree = flattenedTree.map(function(n){ return n.isSelected });
                    var initalLength = flattenedTree.length;
                    var compactedTree = _.compact(flattenedTree);
                    
                    var r = compactedTree.length > 0 && compactedTree.length < flattenedTree.length;
                    element.prop('indeterminate', r);
                    
                },true);
                
            }
        }
    })

    .directive('hierarchySearch',function(HierarchyNodeService,$timeout) {
    
    return {
        restrict:'E',
        templateUrl:'hierarchySearch.tpl.html',
        scope: {
            dataset:'='
        },
        controller:function($scope) {
            $scope.numSelected = 0;
            //$scope.list is used by ng-tree, dataset should never be modified
            $scope.list = angular.copy($scope.dataset);
            
            $scope.options = {};
            
            $scope.expandNode = function(n,$event) {
                $event.stopPropagation();
                n.toggle();
            }
            
            
            $scope.itemSelect = function(item) {
                var rootVal = !item.isSelected;
                HierarchyNodeService.selectChildren(item,rootVal)
                
                HierarchyNodeService.findParent($scope.list[0],null,item,selectParent);
                var s = _.compact(HierarchyNodeService.getAllChildren($scope.list[0],[]).map(function(c){ return c.isSelected && !c.items;}));
                $scope.numSelected = s.length;
            }   
            
            function selectParent(parent) {
                var children = HierarchyNodeService.getAllChildren(parent,[]);
                
                if(!children) return;
                children = children.slice(1).map(function(c){ return c.isSelected;});
                
                parent.isSelected =  children.length === _.compact(children).length;
                HierarchyNodeService.findParent($scope.list[0],null,parent,selectParent)
            }       

            $scope.nodeStatus = function(node) {
                var flattenedTree = getAllChildren(node,[]);
                flattenedTree = flattenedTree.map(function(n){ return n.isSelected });
    
                return flattenedTree.length === _.compact(flattenedTree);
            }  
 
        },
        link:function(scope,el,attr) {
            
            scope.$watch('pastUsersFilter',function(nv){
               if(_.isUndefined(nv)) return;
               
               if(nv) {
                   HierarchyNodeService.trimLeafs(scope.list[0]);
               } else {
                   scope.list = angular.copy(scope.dataset);
               }
               
            });
            
          
            var inputTimeout;
            var time = 300;
            scope.$watch('searchValue',function(nv) {
                if(!nv && nv !== '') {
                    return;
                }
                var previousDataset = angular.copy(scope.list);
                var newData = (scope.searchValue === '') ? angular.copy(scope.dataset) : [HierarchyNodeService.treeSearch(angular.copy(scope.dataset[0]),scope.searchValue)];
                
                if(newData.length === 1 && _.isEmpty(newData[0]) ) {
                  scope.emptyData = true;
                  return;
                }
                
                scope.emptyData = false;
                if(_.isEqual(previousDataset,newData)) {
                  clearTimeout(inputTimeout);
                  return;
                } 
                
                scope.list = newData;
                
                
                $timeout.cancel(inputTimeout);
                inputTimeout = $timeout(function() {
                    
                    var els = document.querySelectorAll('[ui-tree-node]');
                    
                    Array.prototype.forEach.call(els,function(el) {
                        el = angular.element(el);
                        var elScope = el.scope();
                        if(elScope.$modelValue.match) {
                            
                            elScope.expand();
                            
                            //loop through all parents and keep expanding until no more parents are found
                            var p = elScope.$parentNodeScope;
                            while(p) {
                              p.expand();
                              p = p.$parentNodeScope;
                              
                            }
                        }
                    });
                },500);
            });
            
            
          
            
            scope.$watch('list',function(nv,ov) {
                if(!nv) return;
                if(nv && !ov) { scope.$apply();}
                
                
                //UPDATE SELECTED IDs FOR QUERY
                //get the root node
                var rootNode = nv[0];
                
                //get all elements where isSelected == true
                var a = HierarchyNodeService.getSelected(rootNode,[]);
                
                //get the ids of each element
                a = _.pluck(a,'id');
                
                scope.selected = a;
        
            },true);
        }
    }
})
}).call(this);
(function(){

angular.module('demoApp.services',[])
.service('HierarchyNodeService',function() {

    function lowerCase(str) {
        return str.split(' ').map(function(e){
            return e.toString().toLowerCase();  
        }).join(' ');
    }
    
    function treeSearch(tree, query) {
        if (!tree) {
            return {};
        }

        if (lowerCase(tree.title).indexOf(lowerCase(query)) > -1) {
            tree.match = true;
            return tree;
        }

        var branches = _.reduce(tree.items, function(acc, leaf) {
            var newLeaf = treeSearch(leaf, query);

            if (!_.isEmpty(newLeaf)) {
                acc.push(newLeaf);
            }

            return acc;
        }, []);

        if (_.size(branches) > 0) {
            var trunk = _.omit(tree, 'items');
            trunk.items = branches;
        
            return trunk;
        }
    
        return {};
    }   
    
   function getAllChildren(node,arr) {
       if(!node) return;
        arr.push(node);

        if(node.items) {
            //if the node has children call getSelected for each and concat to array
            node.items.forEach(function(childNode) {
                arr = arr.concat(getAllChildren(childNode,[]))  
            })
        }
        return arr;   
    }    
    

    
    function findParent(node,parent,targetNode,cb) {
        if(_.isEqual(node,targetNode)) {
            cb(parent);
            return;
        }
        
        if(node.items) {
            node.items.forEach(function(item){
                findParent(item,node,targetNode,cb);
            });
        }
    }
            
    
    function getSelected(node,arr) {
        //if(!node) return [];
        //if this node is selected add to array
        if(node.isSelected) {
            arr.push(node);
            return arr;
        }
        
        if(node.items) {
            //if the node has children call getSelected for each and concat to array
            node.items.forEach(function(childNode) {
                arr = arr.concat(getSelected(childNode,[]))  
            })
        }
        return arr;    
    }
    
    function selectChildren(children,val) {

        //set as selected
        children.isSelected = val;
        if(children.items) {
            //recursve to set all children as selected
            children.items.forEach(function(el) {
                selectChildren(el,val);  
            })
        }
    }
    
    function trimLeafs(node,parent) {
        
            if(!node.items) {
                //da end of the road
                delete parent.items;
            } else {
                node.items.forEach(function(item){
                    trimLeafs(item,node);
                })
            }
        
    }
    
    
   return {
       getAllChildren:getAllChildren,
       getSelected:getSelected,
       selectChildren:selectChildren,
       trimLeafs:trimLeafs,
       treeSearch:treeSearch,
       findParent:findParent
   };
   
})
    
}).call(this);
(function() {
    console.clear();
'use strict';
angular.module('demoApp', ['ui.tree', 'ui.bootstrap','demoApp.list','demoApp.directives','demoApp.services'])

.controller('MainCtrl', function($scope,$timeout,MyList,HierarchyNodeService) {
    $scope.list = MyList; 

    $scope.getParentId = function(item) {
        alert(JSON.stringify(item));  
    };
})

})();