最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - AngularJS toggle button filters - Stack Overflow

programmeradmin1浏览0评论

So this is a javascript conversion from jQuery app into Angular app. The current jQuery app works but is needed to make into a real app using the Angular framework.

The logic behind the whole app is to select categories and filter OUT and get specific results based on the filter buttons. So lets say you want only to see results that include only where filter 1 AND filter 2 are together, but not (filter1, filter2, and filter1+filter2). see the jquery version: 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');
        }
    });
});

Now I need to convert that javascript magic over to angular, keep the buttons in a toggle stat and show results on the second view. It will essentially be an Angular SPA with 2 views: 1 for filters and 1 for results. Previous app was using jQuery toggle class function, but there is no built in function for Angular in this case. All the examples for toggle button for Angular have only 1 toggle button that hides/shows divs. And other example buttons only show or hide divs separately and are not toggle buttons. And how do I turn filter results into service return and then inject that into View 2 as results and show them?

Need some direction from Angular gods here...

UPDATE 1: thanx to Shaun Scovil, the Angular way of creating this filter groups was found. However the filter group works well on a single page but not in 2 view SPA app: plunkr The filters will break after switching between filters and cases a few times.

UPDATE 2: thanx to Shaun Scovil once more, the filters/cases toggle buttons work now going from page view to page view back to any number of views: plunkr

So this is a javascript conversion from jQuery app into Angular app. The current jQuery app works but is needed to make into a real app using the Angular framework.

The logic behind the whole app is to select categories and filter OUT and get specific results based on the filter buttons. So lets say you want only to see results that include only where filter 1 AND filter 2 are together, but not (filter1, filter2, and filter1+filter2). see the jquery version: 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');
        }
    });
});

Now I need to convert that javascript magic over to angular, keep the buttons in a toggle stat and show results on the second view. It will essentially be an Angular SPA with 2 views: 1 for filters and 1 for results. Previous app was using jQuery toggle class function, but there is no built in function for Angular in this case. All the examples for toggle button for Angular have only 1 toggle button that hides/shows divs. And other example buttons only show or hide divs separately and are not toggle buttons. And how do I turn filter results into service return and then inject that into View 2 as results and show them?

Need some direction from Angular gods here...

UPDATE 1: thanx to Shaun Scovil, the Angular way of creating this filter groups was found. However the filter group works well on a single page but not in 2 view SPA app: plunkr The filters will break after switching between filters and cases a few times.

UPDATE 2: thanx to Shaun Scovil once more, the filters/cases toggle buttons work now going from page view to page view back to any number of views: plunkr

Share Improve this question edited May 23, 2017 at 12:25 CommunityBot 11 silver badge asked Jan 28, 2016 at 19:41 AivoKAivoK 1051 silver badge9 bronze badges 3
  • Suggest you look into some filtering tutorials. Also tutorial on angular docs site has quite a bit of what you need to use in it. – charlietfl Commented Jan 28, 2016 at 20:32
  • Are there any angular filtering tutorials with toggle buttons? – AivoK Commented Jan 28, 2016 at 20:50
  • The buttons are not hard to wire up to scope model that would be used in those filters. Get the general angular filtering concepts figured out. Until you get a good understanding of how the data model drives the view it is very hard to tell you what to do to convert – charlietfl Commented Jan 28, 2016 at 20:59
Add a ment  | 

2 Answers 2

Reset to default 7

Based on your example app and description, here is how I would describe what you need in Angular terms:

  • a controller for your filter toggles view
  • a controller for your cases view
  • a service to store toggled filters
  • a directive for your filter toggle buttons
  • a filter to reduce the list of cases by toggled filters

Working example: JSFiddle (UPDATED to work with ngRoute)

Controllers

The two controllers should serve as view models, providing some well-formed data that can be used in their respective view templates. For example:

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}]}
  ];
}

Service

The purpose of an Angular service is to share data or functionality among controllers, directives, filters and other services. Your service is a data store for the selected filters, so I would use a $cacheFactory cache under the hood. For example:

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;
}

Directive

A directive adds functionality to an HTML element. In your case, I would create a directive with a 'click' event handler that can be added as an attribute to a button or any other element. Our $filterCache service could be used by the event handler to keep track of the group/filter bination that the button represents. For example:

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();
    });
  };
}

Filter

The purpose of the filter is to take the array of case objects defined in CasesController and reduce them based on the filters stored in our $filterCache service. It will reduce the list to an empty array if no filters are toggled. For example:

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;
  };
}

Template

Finally, the HTML template ties it all together. Here is an example of how that would look using all of the other pieces we've built:

<!-- 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>

UPDATE

Based on the ments, I updated the JSFiddle example to work with ngRoute by making the following changes to the toggleFilterDirective:

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');
      }
    }
  };
}

Here is a link to the original example: JSFiddle

<!DOCTYPE html>
<html>
<head>
    <script src="https://ajax.googleapis./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>
发布评论

评论列表(0)

  1. 暂无评论