【问题标题】:AngularJS toggle button filtersAngularJS 切换按钮过滤器
【发布时间】:2016-02-22 08:21:56
【问题描述】:

所以这是从 jQuery 应用程序到 Angular 应用程序的 javascript 转换。当前的 jQuery 应用程序可以工作,但需要使用 Angular 框架制作成真正的应用程序。

整个应用程序背后的逻辑是选择类别并过滤OUT并根据过滤器按钮获得特定结果。因此,假设您只想查看仅包含过滤器 1 和过滤器 2 在一起的结果,而不是(过滤器 1、过滤器 2 和过滤器 1+过滤器2)。查看 jquery 版本:demo

$(document).ready(function(){
    $('.filter-selector').click(function(){

        /* Filter 1 Categories */
        if($(this).attr("value")==".filter1_group1"){
            $(".filter1_group1-show").toggle();
            $(this).toggleClass('active');
        }
        if($(this).attr("value")==".filter1_group2"){
            $(".filter1_group2-show").toggle();
            $(this).toggleClass('active');
        }
    });
});

现在我需要将 javascript 魔法转换为 angular,将按钮保持在切换状态中并在第二个视图上显示结果。它本质上是一个Angular SPA,有 2 个视图:1 个用于过滤器,1 个用于结果。以前的应用程序使用 jQuery 切换类函数,但在这种情况下,Angular 没有内置函数。 Angular 切换按钮的所有示例只有 1 个切换按钮,用于隐藏/显示 div。其他示例按钮仅单独显示或隐藏 div,而不是切换按钮。以及如何将过滤结果转换为服务返回,然后将其作为结果注入 View 2 并显示出来?

这里需要 Angular 大神的指导...

更新 1: 感谢Shaun Scovil,找到了创建此过滤器组的 Angular 方法。但是过滤器组在单个页面上运行良好,但在 2 视图 SPA 应用程序中效果不佳:plunkr 在过滤器和案例之间切换几次后,过滤器将中断。

更新 2: 再次感谢 Shaun Scovil,过滤器/案例切换按钮现在可以从页面视图到页面视图再到任意数量的视图:plunkr

【问题讨论】:

  • 建议你看看一些过滤教程。 Angular 文档网站上的教程也有很多你需要在其中使用的东西。
  • 有没有带切换按钮的角度过滤教程?
  • 这些按钮不难连接到将在这些过滤器中使用的范围模型。弄清楚一般的角度过滤概念。在您充分了解数据模型如何驱动视图之前,很难告诉您如何进行转换

标签: javascript jquery html angularjs


【解决方案1】:

根据您的示例应用程序和描述,我将用 Angular 术语描述您的需求:

  • 过滤器切换视图的控制器
  • 案例视图的控制器
  • 存储切换过滤器的服务
  • 过滤器切换按钮的指令
  • 通过切换过滤器减少案例列表的过滤器

工作示例:JSFiddle (已更新为使用 ngRoute)

控制器

这两个控制器应该作为视图模型,提供一些可以在各自的视图模板中使用的格式良好的数据。例如:

angular.module('myApp')
  .controller('FilterToggleController', FilterToggleController)
  .controller('CasesController', CasesController)
;

function FilterToggleController() {
  var vm = this;
  vm.filterGroups = {
    1: [1,2],
    2: [1,2]
  };
}

function CasesController() {
  var vm = this;
  vm.cases = [
    {label:'Case 1,2', filters:[{group:1, filter:1}, {group:1, filter: 2}]},
    {label:'Case 1',   filters:[{group:1, filter:1}]},
    {label:'Case 2',   filters:[{group:1, filter:2}]},
    {label:'Case 1,3', filters:[{group:1, filter:1}, {group:2, filter:1}]},
    {label:'Case 4',   filters:[{group:2, filter:2}]}
  ];
}

服务

Angular 服务的目的是在控制器、指令、过滤器和其他服务之间共享数据或功能。您的服务是所选过滤器的数据存储,因此我将在后台使用$cacheFactory 缓存。例如:

angular.module('myApp')
  .factory('$filterCache', filterCacheFactory)
;

function filterCacheFactory($cacheFactory) {
  var cache = $cacheFactory('filterCache');
  var $filterCache = {};

  $filterCache.has = function(group, filter) {
    return cache.get(concat(group, filter)) === true;
  };

  $filterCache.put = function(group, filter) {
    cache.put(concat(group, filter), true);
  }

  $filterCache.remove = function(group, filter) {
    cache.remove(concat(group, filter));
  }

  $filterCache.count = function() {
    return cache.info().size;
  }

  function concat(group, filter) {
    return group + ':' + filter;
  }

  return $filterCache;
}

指令

指令向 HTML 元素添加功能。在您的情况下,我将创建一个带有“click”事件处理程序的指令,该处理程序可以作为属性添加到按钮或任何其他元素。事件处理程序可以使用我们的$filterCache 服务来跟踪按钮所代表的组/过滤器组合。例如:

angular.module('myApp')
  .directive('toggleFilter', toggleFilterDirective)
;

function toggleFilterDirective($filterCache) {
  return function(scope, iElement, iAttrs) {
    var toggled = false;

    iElement.on('click', function() {
      var group = scope.$eval(iAttrs.group);
      var filter = scope.$eval(iAttrs.filter);

      toggled = !toggled;

      if (toggled) {
        $filterCache.put(group, filter);
        iElement.addClass('toggled');
      } else {
        $filterCache.remove(group, filter);
        iElement.removeClass('toggled');
      }

      scope.$apply();
    });
  };
}

过滤器

过滤器的目的是获取CasesController 中定义的案例对象数组,并根据存储在我们的$filterCache 服务中的过滤器来减少它们。如果没有切换过滤器,它会将列表减少为空数组。例如:

angular.module('myApp')
  .filter('filterCases', filterCasesFactory)
;

function filterCasesFactory($filterCache) {
  return function(items) {
    var filteredItems = [];
    var filterCount = $filterCache.count();

    if (filterCount) {
      angular.forEach(items, function(item) {
        if (angular.isArray(item.filters) && item.filters.length >= filterCount) {
          for (var matches = 0, i = 0; i < item.filters.length; i++) {
            var group = item.filters[i].group;
            var filter = item.filters[i].filter;

            if ($filterCache.has(group, filter))
              matches++;

            if (matches === filterCount) {
              filteredItems.push(item);
              break;
            }
          }
        }
      });
    }

    return filteredItems;
  };
}

模板

最后,HTML 模板将它们联系在一起。以下是使用我们构建的所有其他部分的外观示例:

<!-- Filter Toggles View -->
<div ng-controller="FilterToggleController as vm">
  <div ng-repeat="(group, filters) in vm.filterGroups">
    <h2>
      Group {{group}}
    </h2>
    <div ng-repeat="filter in filters">
      <button toggle-filter group="group" filter="filter">
        Filter {{filter}}
      </button>
    </div>
  </div>
</div>

<!-- Cases View -->
<div ng-controller="CasesController as vm">
  <h2>
    Your Cases
  </h2>
  <ol>
    <li ng-repeat="case in vm.cases | filterCases">
      {{case.label}}
    </li>
  </ol>
</div>

更新

基于 cmets,我通过对 toggleFilterDirective 进行以下更改,更新了 JSFiddle 示例以使用 ngRoute

function toggleFilterDirective($filterCache) {
  return function(scope, iElement, iAttrs) {
    var group, filter, toggled;
    sync();
    update();
    iElement.on('click', onClick);
    scope.$on('$destroy', offClick);

    function onClick() {
      sync();
      toggle();
      update();
      scope.$apply();
    }

    function offClick() {
      iElement.off('click', onClick);
    }

    function sync() {
      group = scope.$eval(iAttrs.group);
      filter = scope.$eval(iAttrs.filter);
      toggled = $filterCache.has(group, filter);
    }

    function toggle() {
      toggled = !toggled;
      if (toggled) {
        $filterCache.put(group, filter);
      } else {
        $filterCache.remove(group, filter);
      }
    }

    function update() {
      if (toggled) {
        iElement.addClass('toggled');
      } else {
        iElement.removeClass('toggled');
      }
    }
  };
}

这里是原始示例的链接:JSFiddle

【讨论】:

  • 哇,肖恩,你这个男人。我没想到我的问题会得到如此全面的答案。几个月来我一直在尝试学习 Angular,我认为我不接近那种 javascript/Angular 专业知识。由于我还在学习更多关于该语言的知识,您是如何得出 2 个控制器、指令、过滤器和服务的结论的?我的理解是只有 2 个控制器,工厂就可以了......
  • 很高兴我能帮上忙。这真的归结为使用正确的工具来完成这项工作。 Angular 中的控制器用于向模板公开数据和功能,因此它们实际上更像视图模型。服务是可注入的,并为控制器、指令、过滤器等提供功能。如果您有一个想要根据某些用户输入减少(或过滤)的数组,那么过滤器就是要走的路。如果您需要与 DOM 元素交互,例如添加事件侦听器时,创建自定义指令是合适的策略。
  • 代码在单页上按预期工作,但是即使使用 2 个控制器,在 SPA 中的过滤器/案例视图之间切换时,过滤器组和案例机制也会崩溃。点击第一个过滤器,然后转到案例视图,查看结果,返回第一个视图,点击另一个过滤器,然后转到案例视图,您会看到不正确的结果。过滤器切换状态是否应保留为“已单击”?我不确定为什么第一次复飞后视图模型逻辑会崩溃。它是指令的简单重新格式化吗?还是过滤器?
  • @AivoK 我明白你在说什么。这是一个使用 ngRoute 的工作示例:jsfiddle.net/sscovil/syh8qchf
  • Shaun 再次使用扩展解决方案!所以是指令需要从一个页面到另一个页面中删除点击事件监听器?由于我仍在学习 Angular,学习和试验自定义指令是否是正确的方法?我为这些解决方案欠了一轮啤酒!谢谢
【解决方案2】:
<!DOCTYPE html>
<html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
    <script>
        var app = angular.module('App', []);

        app.service('serviceFilters', function() {
            var filters = [
                    {
                        name: 'Filter 1',
                        groups: [
                            {
                                name: 'Group 1',
                                selected: false
                            },
                            {
                                name: 'Group 2',
                                selected: false
                            }
                        ]
                    },
                    {
                        name: 'Filter 2',
                        groups: [
                            {
                                name: 'Group 1',
                                selected: false
                            },
                            {
                                name: 'Group 2',
                                selected: false
                            }
                        ]
                    }
                ],
                getFilters = function () {
                    return filters;
                },
                isCase12Selected = function () {
                    return filters[0].groups[0].selected && filters[0].groups[1].selected && !filters[1].groups[0].selected && !filters[1].groups[1].selected;
                },
                isCase1Selected = function () {
                    return filters[0].groups[0].selected && !filters[0].groups[1].selected && !filters[1].groups[0].selected && !filters[1].groups[1].selected;
                },
                isCase2Selected = function () {
                    return !filters[0].groups[0].selected && filters[0].groups[1].selected && !filters[1].groups[0].selected && !filters[1].groups[1].selected;
                },
                isCase13Selected = function () {
                    return filters[0].groups[0].selected && !filters[0].groups[1].selected && filters[1].groups[0].selected && filters[1].groups[1].selected;
                },
                isCase4Selected = function () {
                    return !filters[0].groups[0].selected && !filters[0].groups[1].selected && !filters[1].groups[0].selected && filters[1].groups[1].selected;
                };

                return {
                    getFilters: getFilters,
                    isCase12Selected: isCase12Selected,
                    isCase1Selected: isCase1Selected,
                    isCase2Selected: isCase2Selected,
                    isCase13Selected: isCase13Selected,
                    isCase4Selected: isCase4Selected
                };
        });

        app.filter('selectedGroups', function() {
            return function(groups) {
                return groups.filter(function (group) {
                    return group.selected;
                });
            };
        });

        app.directive(
            'viewApplication',
            [
                'serviceFilters',
                function (serviceFilters) {
                    'use strict';

                    return {
                        restrict: 'E',
                        template:
                            '<div>' +
                                '<view-filters></view-filters>' +
                                '<view-selected></view-selected>' +
                                '<view-cases></view-selected>' +
                            '</div>',
                        controller: ['$scope', function ($scope) {
                            $scope.serviceFilters = serviceFilters;
                        }]
                    };
                }
            ]
        );

        app.directive(
            'viewFilters',
            [
                'serviceFilters',
                function (serviceFilters) {
                    'use strict';

                    return {
                        restrict: 'E',
                        scope: {},
                        template:
                            '<div>' +
                                '<h1>Filters</h1>' +
                                '<div ng-repeat="filter in serviceFilters.getFilters()">' +
                                    '<h2>{{::filter.name}}</h2>' +
                                    '<div ng-repeat="group in filter.groups">' +
                                        '<span>{{::group.name}}&nbsp;</span>' +
                                        '<button ng-click="group.selected=!group.selected">{{group.selected ? \'Unselect\' : \'Select\'}}</button>' +
                                    '</div>' +
                               '</div>' +
                            '</div>',
                        controller: ['$scope', function ($scope) {
                            $scope.serviceFilters = serviceFilters;
                        }]
                    };
                }
            ]
        );

        app.directive(
            'viewSelected',
            [
                'serviceFilters',
                function (serviceFilters) {
                    'use strict';

                    return {
                        restrict: 'E',
                        scope: {},
                        template:
                            '<div>' +
                                '<h1>Selected</h1>' +
                                '<div ng-repeat="filter in serviceFilters.getFilters()">' +
                                    '<div ng-repeat="group in filter.groups | selectedGroups">' +
                                        '{{filter.name}} | {{group.name}}' +
                                    '</div>' +
                                '</div>' +
                            '</div>',
                        controller: ['$scope', function ($scope) {
                            $scope.serviceFilters = serviceFilters;
                        }]
                    };
                }
            ]
        );

        app.directive(
            'viewCases',
            [
                'serviceFilters',
                function (serviceFilters) {
                    'use strict';

                    return {
                        restrict: 'E',
                        scope: {
                            filters: '='
                        },
                        template:
                            '<div>' +
                                '<h1>Cases</h1>' +
                                '<span ng-if="serviceFilters.isCase12Selected()">Case 1,2</span>' +
                                '<span ng-if="serviceFilters.isCase1Selected()">Case 1</span>' +
                                '<span ng-if="serviceFilters.isCase2Selected()">Case 2</span>' +
                                '<span ng-if="serviceFilters.isCase13Selected()">Case 1,3</span>' +
                                '<span ng-if="serviceFilters.isCase14Selected()">Case 4</span>' +
                            '</div>',
                        controller: ['$scope', function ($scope) {
                            $scope.serviceFilters = serviceFilters;
                        }]
                    };
                }
            ]
        );
    </script>
</head>
<body ng-app="App">
    <view-application></view-application>
</body>
</html>

【讨论】:

  • 嗯。过滤器阵列是控制器的一部分吗?如果 Angular 切换按钮使用布尔值,我如何将切换按钮与数组和对象一起使用?
  • 是的,你是对的。过滤为模型(服务) - 更好。我忘记了“案例”部分。查看更新的代码。
  • 这是不可扩展的。每次添加过滤器时,所需的serviceFilters.isCaseNNSelected 函数的数量都会成倍增加。
猜你喜欢
  • 1970-01-01
  • 2016-10-02
  • 1970-01-01
  • 1970-01-01
  • 2020-08-30
  • 1970-01-01
  • 2018-08-16
  • 1970-01-01
  • 2020-02-24
相关资源
最近更新 更多