Apply filtering to a hierarchical structure Apply filtering to a hierarchical structure json json

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