将过滤应用于层次结构

Posted

技术标签:

【中文标题】将过滤应用于层次结构【英文标题】:Apply filtering to a hierarchical structure 【发布时间】:2018-01-04 17:51:21 【问题描述】:

我已经构建了以下显示层次结构的 Angular 应用程序:

我正在尝试在此层次结构的顶部插入一个文本框。过滤底部的数据。已经尝试了几个使用过滤器的示例,但到目前为止都没有成功。

我想做的是在用户开始输入文本框时使用角度绑定,动态展开和折叠层次结构并突出显示匹配项。

寻找解决此问题的最佳方法的建议。请注意,层次结构可能会变得很大,并且有大约 3000 条记录。

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 AND ACCESSORIES",
        "ct": "L4-000429",
        "ctNm": "DOLLS GAMES PUZZLES",
        "seg": "L5-000031",
        "segNm": "TOYS",
        "area": "L6-000004",
        "areaNm": "HARDLINES"
    ,
    
        "cls": "L2-006472",
        "clsNm": "FASHION DOLL WITH ACCS",
        "subCt": "L3-001793",
        "subCtNm": "FASHION DOLLS AND 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 AND 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 AND 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 AND 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 AND TOY",
        "subCt": "L3-001793",
        "subCtNm": "FASHION DOLLS AND 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 TO 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 AND 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 AND 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 AND 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 AND 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 AND 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 AND 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 AND 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 AND 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>

编辑: 这是我正在考虑的过滤器,但似乎不适应我构建 html 的方式: how to filter the data from text box in angularjs

如果需要更改 HTML 结构,我愿意接受建议。

【问题讨论】:

听起来是个很有趣的任务。我会编写一个函数,用Array.prototype.filter 过滤列表,然后遍历父母以显示所有内容。不过我对 Angular 一无所知。 这是一种有趣的方法,缺点是我需要存储父路径并以某种方式能够向后重建列表。 你想在嵌套的孩子中过滤掉它吗?还是单独的***? 一直到 L2 级别 【参考方案1】:

首先,您应该创建一个嵌套指令来显示您的树。如果突然显示 7 个级别怎么办?所以首先我会写一个递归指令,这也会减少代码大小。

对于数据过滤部分,您可以将inputng-model-options="debounce: 300"ng-change="filterFunction()" 结合使用,因此过滤仅在用户完成搜索后300 毫秒应用。 filterFunction() 在您的数据以分层形式结构化后非常容易编写,并且可以更改对象状态以向您的指令指示它是否应该显示,以及它的子项是否应该显示。

结果如下:

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 示例:https://plnkr.co/edit/1jiiiwkdUZY4tm7sm79F?p=preview

我将让您编写代码部分以突出显示与搜索匹配的文本,因为转换执行过滤的函数非常容易。提示:在hierarchiesFilter() 函数中,您可以将htmlHighlighted 属性添加到每个节点,您可以在其中将匹配文本包装在&lt;strong&gt;&lt;/strong&gt; 标记之间。

由于这可能不是您要寻找的确切行为,您可以调整过滤器功能以在用户更改搜索时准确显示您想要的内容。

一些代码(递归助手)来自这篇文章:Recursion in Angular directives

【讨论】:

是的,太棒了。玩了一下。有一个问题,如果我不想显示不符合搜索条件的孩子,那如何实现。 @hilnius hierarchiesFilter被调用时添加matchesSearch属性到每个元素的简单方法id,并在hierarchy指令模板中添加条件ng-show以仅在他们@时显示子元素987654339@ 或者如果您需要showChildren。 Forked plunker : plnkr.co/edit/h8vZQlbL47NpZgnaOB9f?p=preview 在这一点上这只是纯粹的算法,我最好的建议是你找一张纸并围绕你想做的事情设计你的代码。

以上是关于将过滤应用于层次结构的主要内容,如果未能解决你的问题,请参考以下文章

图解“管道过滤器模式”应用实例:SOD框架的命令执行管道

如何使用 ICollectionView 过滤 wpf 树视图层次结构?

如何根据 mdx 中的另一个维度层次结构过滤维度层次结构

SSAS MDX WHERE 子句语法 - 从同一层次结构中过滤多个值

如何使用 Stargate REST API 在 HBase 中构建更复杂的过滤器层次结构?

如何在 Maven2 模块之间共享过滤器文件?