Apply filtering to a hierarchical structure
First, you should create a nested directive to display your tree. What if there is suddenly 7 levels to display ? So first I would write a recursive directive, which will also reduce the code size.
For the data filtering part, you can use an input
with a ng-model-options="{debounce: 300}"
combined with ng-change="filterFunction()"
so the filtering applies only 300ms after the user has finished writing its search.The filterFunction()
is pretty easy to write once your data is structured in a hierarchical form, and can change the object state to indicate to your directive wether it should be displayed, and wether its children should be displayed.
The result looks like this:
MainController.js
var app = angular.module('app', []);app.controller('MainController', [function () { var ctrl = this; ctrl.search = ''; initHierarchies(); // function that transforms the data in hierarchical form // filterHierarchies is called everytime the user changed the search input ctrl.filterHierarchies = function () { ctrl.filteredHierarchies = hierarchiesFilter(ctrl.hierarchies, ctrl.search).hierarchies; } ctrl.filterHierarchies(); // init the filteredHierarchies data. // function that filters the hierarchy. It is a recursive function function hierarchiesFilter(hierarchies, search) { if (!hierarchies || !hierarchies.length) { return { hierarchies: [], hasExpandedChildren: false}; } console.log(hierarchies, search); var oneIsExpanded = false; for (var i = 0; i < hierarchies.length; i++) { hierarchies[i].showChildren = false; if (search.length) { var rx = new RegExp(search, 'i'); if (hierarchies[i].name.match(rx)) { oneIsExpanded = true; } } // if the node has children which are expanded, we need to display it so its children that // should be highlighted are visible var hasExpandedChildren = hierarchiesFilter(hierarchies[i].children, search).hasExpandedChildren; if (hasExpandedChildren) { hierarchies[i].showChildren = true; oneIsExpanded = true; } } return { hierarchies: hierarchies, hasExpandedChildren: oneIsExpanded }; }; // function to transform the array data to a hierarchical structure function initHierarchies() { var data = getData(); var mp6Data = {}; var mp6Root = []; angular.forEach(data, function (value, key) { if (mp6Root.indexOf(value.area) === -1) { mp6Root.push(value.area); } addToMap(value.cls, value.clsNm, ""); addToMap(value.subCt, value.subCtNm, value.cls); addToMap(value.ct, value.ctNm, value.subCt); addToMap(value.seg, value.segNm, value.ct); addToMap(value.area, value.areaNm, value.seg); }); function addToMap(pKey, pName, pChild) { if (!mp6Data[pKey]) { mp6Data[pKey] = { name: pName, childrenKeys: [] }; } else { if (mp6Data[pKey].childrenKeys.indexOf(pChild) === -1) { mp6Data[pKey].childrenKeys.push(pChild); } } } function buildHierarchicalStructure(childrenKeys) { var builtChildren = []; for (var i = 0; i < childrenKeys.length; i++) { builtChildren.push({ name: mp6Data[childrenKeys[i]].name, children: buildHierarchicalStructure(mp6Data[childrenKeys[i]].childrenKeys) }); } return builtChildren; } for (var i = 0; i < mp6Data.length; i++) { mp6Data[i].showChildren = true; } ctrl.hierarchies = buildHierarchicalStructure(mp6Root); }}]);
hierarchy.directive.js
app.directive('hierarchy', ['RecursionHelper', function (RecursionHelper) { return { template: '<div><div ng-click="hierarchyCtrl.ngModel.showChildren = !hierarchyCtrl.ngModel.showChildren">{{ hierarchyCtrl.ngModel.name }}</div><ul ng-if="hierarchyCtrl.ngModel.children && (hierarchyCtrl.ngModel.showChildren)"><li ng-repeat="element in hierarchyCtrl.ngModel.children"><hierarchy ng-model="element"></hierarchy></li></ul></div>', restrict: 'E', scope: { ngModel: '=' }, controller: ['$scope', function($scope) { this.ngModel = $scope.ngModel; }], controllerAs: 'hierarchyCtrl', compile: function (element) { return RecursionHelper.compile(element); }, };}]);
index.html
<body><h1>Hello Plunker!</h1><div ng-controller="MainController as mainCtrl"> <input type="text" ng-model="mainCtrl.search" ng-model-options="{debounce: 300}" ng-change="mainCtrl.filterHierarchies()" /> <ul> <li ng-repeat="hierarchy in mainCtrl.filteredHierarchies"><hierarchy ng-model="hierarchy"></hierarchy></li> </ul></div></body>
Plunker example: https://plnkr.co/edit/1jiiiwkdUZY4tm7sm79F?p=preview
I'll let you write the code part to highlight the text that matches the search, as it's pretty easy to transform the function that does the filtering. Hint: in the hierarchiesFilter()
function, you can add a htmlHighlighted
property to each node where you wrap the matching text between <strong>
and </strong>
tags.
As this is probably not the exact behavior you were looking for, you can tweak the filter function to display exactly what you want when the user changes his search.
Some code (the recursion helper) comes from this post: Recursion in Angular directives