过滤嵌套的ng-repeat:隐藏没有孩子的父母

时间:2014-10-31 23:37:54

标签: angularjs

我想从JSON文件中创建某种项目列表。数据结构(年,月,项目)如下所示:

[{
    "name": "2013",
    "months": [{
        "name": "May 2013",
        "projects": [{
            "name": "2013-05-09 Project A"
        }, {
            "name": "2013-05-14 Project B"
        }, { ... }]
    }, { ... }]
}, { ... }]

我正在使用嵌套的ng-repeat显示所有数据,并使其可以通过从输入框绑定到查询的过滤器进行搜索。

<input type="search" ng-model="query" placeholder="Suchen..." />

<div class="year" ng-repeat="year in data | orderBy:'name':true">
     <h1>{{year.name}}</h1>

    <div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
         <h3>{{month.name}}</h3>

        <div class="project" ng-repeat="project in month.projects | filter:query | orderBy:'name'">
            <p>{{project.name}}</p>
        </div>
    </div>
</div>

如果我现在输入“Project B”,所有空的父元素仍然可见。我该如何隐藏它们?我尝试了一些ng-show技巧,但主要问题似乎是,我无法访问有关父母过滤状态的任何信息。

这是演示我的问题的小提琴:http://jsfiddle.net/stekhn/y3ft0cwn/7/

3 个答案:

答案 0 :(得分:3)

您基本上必须过滤月份以仅保留至少包含一个已过滤项目的月份,并且您还必须过滤年份以仅保留至少有一个过滤月份的月份。

使用以下代码可以轻松实现:

function MainCtrl($scope, $filter) {

    $scope.query = '';
    $scope.monthHasVisibleProject = function(month) {
        return $filter('filter')(month.children, $scope.query).length > 0;
    };

    $scope.yearHasVisibleMonth = function(year) {
        return $filter('filter')(year.children, $scope.monthHasVisibleProject).length > 0;
    };

并在视图中:

<div class="year" ng-repeat="year in data | filter:yearHasVisibleMonth | orderBy:'name':true">
             <h1>{{year.name}}</h1>

            <div class="month" ng-repeat="month in year.children | filter:monthHasVisibleProject | orderBy:sortMonth:true">

这是非常低效的,因为要知道是否接受了一年,您过滤所有月份,并且每个月过滤所有项目。因此,除非性能足以满足您的数据量,否则您应该应用相同的原则,但每次修改查询时都要保持每个对象的接受/拒绝状态(项目,然后是月,然后是年)。

答案 1 :(得分:2)

我认为最好的方法是实现自定义函数,以便在query更改时使用过滤后的数据更新自定义数组。像这样:

$scope.query = '';
$scope.filteredData= angular.copy($scope.data);
$scope.updateFilteredData = function(newVal){
    var filtered = angular.copy($scope.data);
    filtered = filtered.map(function(year){ 
        year.children=year.children.map(function(month){
            month.children = $filter('filter')(month.children,newVal);
            return month;
        });
        return year;
    });
    $scope.filteredData = filtered.filter(function(year){
        year.children= year.children.filter(function(month){
            return month.children.length>0;
        });
        return year.children.length>0;
    });
}

然后您的观点将如下所示:

    <input type="search" ng-model="query" ng-change="updateFilteredData(query)"
           placeholder="Search..." />
    <div class="year" ng-repeat="year in filteredData | orderBy:'name':true">
         <h1>{{year.name}}</h1>
        <div class="month" ng-repeat="month in year.children | orderBy:sortMonth:true">
            <h3>{{month.name}}</h3>
            <div class="project" ng-repeat="project in month.children | orderBy:'name'">
                <p>{{project.name}}</p>
            </div>
        </div>
    </div>

Example


为什么不为此自定义$filter

效率$diggest周期的性质会降低效率。唯一的问题是,这个解决方案不像自定义$filter那样容易重复使用。但是,该自定义$filter也不会非常可重用,因为它的逻辑非常依赖于这个具体的数据结构。


IE8支持

如果您需要在IE8上工作,则必须使用jQuery替换filtermap函数,或者确保定义这些函数,如下所示: (顺便说一下:如果你需要IE8支持,那么使用jQuery完全没有问题。)

过滤

if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun/*, thisArg*/) {
    'use strict';

    if (this === void 0 || this === null) {
      throw new TypeError();
    }

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== 'function') {
      throw new TypeError();
    }

    var res = [];
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++) {
      if (i in t) {
        var val = t[i];
        if (fun.call(thisArg, val, i, t)) {
          res.push(val);
        }
      }
    }
    return res;
  };
}

<强>地图

if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {
    var T, A, k;
    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }
    var O = Object(this);
    var len = O.length >>> 0;
    if (typeof callback !== "function") {
      throw new TypeError(callback + " is not a function");
    }
    if (thisArg) {
      T = thisArg;
    }
    A = new Array(len);
    k = 0;
    while(k < len) {
      var kValue, mappedValue;
      if (k in O) {
        kValue = O[ k ];
        mappedValue = callback.call(T, kValue, k, O);
        A[ k ] = mappedValue;
      }
      k++;
    }
    return A;
  };      
}

确认

我要感谢JB Nizet his feedback

答案 2 :(得分:0)

对于那些感兴趣的人:昨天我找到了解决这个问题的另一种方法,这让我觉得效率很低。在键入查询时,将再次为每个孩子调用这些函数。不如Josep的解决方案好。

function MainCtrl($scope) {

    $scope.query = '';

    $scope.searchString = function () {

        return function (item) {
            var string = JSON.stringify(item).toLowerCase();
            var words = $scope.query.toLowerCase();

            if (words) {
                var filterBy = words.split(/\s+/);

                if (!filterBy.length) {
                    return true;
                }
            } else {
                return true;
            }

            return filterBy.every(function (word) {
                var exists = string.indexOf(word);

                if(exists !== -1){
                    return true;
                }
            });
        };
    };
};

在视图中:     

<div class="year" ng-repeat="year in data | filter:searchString() | orderBy:'name':true">
     <h1>{{year.name}}</h1>

    <div class="month" ng-repeat="month in year.children | filter:searchString() | orderBy:sortMonth:true">
         <h3>{{month.name}}</h3>

        <div class="project" ng-repeat="project in month.children | filter:searchString() | orderBy:'name'">
            <p>{{project.name}}</p>
        </div>
    </div>
</div>

这是小提琴:http://jsfiddle.net/stekhn/stv55sxg/1/