javascript - Apply filtering to a hierarchical structure -
i have built following angular app shows hierarchy:
and trying insert text box on top of hierarchy. filter data @ bottom. have tried couple of examples filters have not had luck far.
what want utilize angular binding when user starts typing text box, dynamically expand , collapse hierarchy , highlight matches.
looking advise on best way tackle this. note hierarchy can big , have around 3000 records.
angular.module('helloworldapp', []) .controller('helloworldcontroller', function($scope) { $scope.mp6root = []; $scope.mp6data = []; var data = [ { "cls": "l2-013551", "clsnm": "fashion dolls", "subct": "l3-001793", "subctnm": "fashion dolls , accessories", "ct": "l4-000429", "ctnm": "dolls games puzzles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006472", "clsnm": "fashion doll accs", "subct": "l3-001793", "subctnm": "fashion dolls , accessories", "ct": "l4-000429", "ctnm": "dolls games puzzles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-014668", "clsnm": "activities", "subct": "l3-001793", "subctnm": "fashion dolls , accessories", "ct": "l4-000429", "ctnm": "dolls games puzzles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-014667", "clsnm": "storage", "subct": "l3-001793", "subctnm": "fashion dolls , accessories", "ct": "l4-000429", "ctnm": "dolls games puzzles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-014675", "clsnm": "fashion doll playset", "subct": "l3-001793", "subctnm": "fashion dolls , accessories", "ct": "l4-000429", "ctnm": "dolls games puzzles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006476", "clsnm": "role play fashion , toy", "subct": "l3-001793", "subctnm": "fashion dolls , accessories", "ct": "l4-000429", "ctnm": "dolls games puzzles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-014677", "clsnm": "core ps figure w playset", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006508", "clsnm": "core ps musical instrument", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-014788", "clsnm": "wagons toys", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006536", "clsnm": "riding toys foot floor", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-014678", "clsnm": "core ps puzzle", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006506", "clsnm": "core ps figure playset", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006509", "clsnm": "core ps other toys", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006511", "clsnm": "core ps talking sound", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006507", "clsnm": "core ps learning toy", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006510", "clsnm": "core ps roleplay", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006512", "clsnm": "core ps vehicles", "subct": "l3-001798", "subctnm": "core preschool toys", "ct": "l4-000428", "ctnm": "preschool", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006585", "clsnm": "diecast med lg scale vehicles", "subct": "l3-001818", "subctnm": "diecast , playsets", "ct": "l4-000425", "ctnm": "act figs construction vehicles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006587", "clsnm": "diecast playsets", "subct": "l3-001818", "subctnm": "diecast , playsets", "ct": "l4-000425", "ctnm": "act figs construction vehicles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006586", "clsnm": "diecast mini vehicles", "subct": "l3-001818", "subctnm": "diecast , playsets", "ct": "l4-000425", "ctnm": "act figs construction vehicles", "seg": "l5-000031", "segnm": "toys", "area": "l6-000004", "areanm": "hardlines" }, { "cls": "l2-006798", "clsnm": "vacuums upright bagless", "subct": "l3-001851", "subctnm": "floor cleaning", "ct": "l4-000449", "ctnm": "home electrics", "seg": "l5-000054", "segnm": "hard home", "area": "l6-000012", "areanm": "in , outdoor home" }, { "cls": "l2-006795", "clsnm": "vacuums hand", "subct": "l3-001851", "subctnm": "floor cleaning", "ct": "l4-000449", "ctnm": "home electrics", "seg": "l5-000054", "segnm": "hard home", "area": "l6-000012", "areanm": "in , outdoor home" }, { "cls": "l2-006791", "clsnm": "floor deep cleaner chemicals", "subct": "l3-001851", "subctnm": "floor cleaning", "ct": "l4-000449", "ctnm": "home electrics", "seg": "l5-000054", "segnm": "hard home", "area": "l6-000012", "areanm": "in , outdoor home" }, { "cls": "l2-006796", "clsnm": "vacuums stick", "subct": "l3-001851", "subctnm": "floor cleaning", "ct": "l4-000449", "ctnm": "home electrics", "seg": "l5-000054", "segnm": "hard home", "area": "l6-000012", "areanm": "in , outdoor home" }, { "cls": "l2-012895", "clsnm": "floor steam mops", "subct": "l3-001851", "subctnm": "floor cleaning", "ct": "l4-000449", "ctnm": "home electrics", "seg": "l5-000054", "segnm": "hard home", "area": "l6-000012", "areanm": "in , outdoor home" }] ; $scope.loadmp6datatomemory = function(data) { angular.foreach(data, function (value, key) { if ($.inarray(value.area, $scope.mp6root) === -1) { $scope.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); }); } addtomap = function (pkey, pname, pchild) { if (!$scope.mp6data[pkey]) { cset = []; $scope.mp6data[pkey] = { name: pname, children: cset }; } else { if ($.inarray(pchild, $scope.mp6data[pkey].children) === -1) { $scope.mp6data[pkey].children.push(pchild); } } } $scope.expandmp6 = function (pkey) { if (pkey) { mp = $scope.mp6data[pkey]; return { name: mp.name, children: mp.children, visible: false } } } $scope.loadmp6datatomemory(data); $scope.l5visible = false; $scope.l4visible = false; $scope.l3visible = false; $scope.l2visible = false; }); <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script> <div class="container" ng-app="helloworldapp" ng-controller="helloworldcontroller"> <div class="md-grid"> <ul class="md-list"> <li class="md-list-item-text" ng:repeat="l6 in mp6root" ng-click="l5visible = !l5visible; $event.stoppropagation();"> l6 {{expandmp6(l6).name}} <ul class="md-list" ng-show="l5visible"> <li class="md-list-item-text" ng:repeat="l5 in expandmp6(l6).children" ng-click="l4visible = !l4visible; $event.stoppropagation();"> l5 {{expandmp6(l5).name}} <ul class="md-list" ng-show="l4visible"> <li class="md-list-item-text" ng:repeat="l4 in expandmp6(l5).children" ng-click="l3visible = !l3visible; $event.stoppropagation();"> l4 {{expandmp6(l4).name}} <ul class="md-list" ng-show="l3visible"> <li class="md-list-item-text" ng:repeat="l3 in expandmp6(l4).children" ng-click="l2visible = !l2visible; $event.stoppropagation();"> l3 {{expandmp6(l3).name}} <ul class="md-list" ng-show="l2visible"> <li class="md-list-item-text" ng:repeat="l2 in expandmp6(l3).children"> l2 {{expandmp6(l2).name}} </li> </ul> </li> </ul> </li> </ul> </li> </ul> </li> </ul> </div> </div> edit: filter thinking of not seem adapt on way ive structured html: how filter data text box in angularjs
if html structure needs changed i'm open suggestions.
first, should create nested directive display tree. if there 7 levels display ? first write recursive directive, reduce code size.
for data filtering part, can use input ng-model-options="{debounce: 300}" combined ng-change="filterfunction()" filtering applies 300ms after user has finished writing search. filterfunction() pretty easy write once data structured in hierarchical form, , can change object state indicate directive wether should displayed, , wether children should displayed.
the result looks this:
maincontroller.js
var app = angular.module('app', []); app.controller('maincontroller', [function () { var ctrl = this; ctrl.search = ''; inithierarchies(); // function transforms data in hierarchical form // filterhierarchies called everytime user changed search input ctrl.filterhierarchies = function () { ctrl.filteredhierarchies = hierarchiesfilter(ctrl.hierarchies, ctrl.search).hierarchies; } ctrl.filterhierarchies(); // init filteredhierarchies data. // function filters hierarchy. recursive function function hierarchiesfilter(hierarchies, search) { if (!hierarchies || !hierarchies.length) { return { hierarchies: [], hasexpandedchildren: false}; } console.log(hierarchies, search); var oneisexpanded = false; (var = 0; < 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 node has children expanded, need display children // should highlighted visible var hasexpandedchildren = hierarchiesfilter(hierarchies[i].children, search).hasexpandedchildren; if (hasexpandedchildren) { hierarchies[i].showchildren = true; oneisexpanded = true; } } return { hierarchies: hierarchies, hasexpandedchildren: oneisexpanded }; }; // function transform array data 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 = []; (var = 0; < childrenkeys.length; i++) { builtchildren.push({ name: mp6data[childrenkeys[i]].name, children: buildhierarchicalstructure(mp6data[childrenkeys[i]].childrenkeys) }); } return builtchildren; } (var = 0; < 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 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 write code part highlight text matches search, it's pretty easy transform function filtering. hint: in hierarchiesfilter() function, can add htmlhighlighted property each node wrap matching text between <strong> , </strong> tags.
as not exact behavior looking for, can tweak filter function display want when user changes search.
some code (the recursion helper) comes post: recursion in angular directives
Comments
Post a Comment