Warning: We no longer develop features for this version of SKY UX, and we recommend that you use the latest version instead. This site describes the AngularJS (1.x) implementation of the SKY UX framework. We still support this version, but it is in maintenance mode. For more information, see developer.blackbaud.com/skyux.

Filter

The filter module provides components that allow users to select filter criteria. These components create a button to expose filtering options, a modal footer to display buttons that apply and clear filters, and a summary to indicate which filters have been applied.

The filter modal footer component wraps the bb-modal-footer directive to provide filter-specific buttons that apply and clear filters.

  • bb-filter-modal-footer — Specifies the buttons to display in the filter modal's footer.
    • bb-filter-modal-apply — Specifies a function to be called when users click the button to apply filters.
    • bb-filter-modal-clear — Specifies a function to be called when users click the button to clear filters.

Filter button

The filter button component creates a button that exposes filtering options.

  • bb-filter-button — Creates a button that kicks off a function to expose filtering options.
    • bb-filter-button-on-click — Specifies a function to be called when users click the filter button.
    • bb-filter-button-active(Optional.) Specifies whether the filter button should be given active indication. This property should be set when indication of filtering is not visible to the user, such as when a filter summary is not shown. (Default: false)

Filter summary

The filter summary component indicates which filters have been applied.

  • bb-filter-summary — Displays summary items for the filters that users apply.
    • bb-filter-summary-item — Displays an entry in the summary for a filter that has been applied.
      • bb-filter-summary-item-on-click(Optional.) Specifies a function to be called when users click a summary item.
      • bb-filter-summary-item-is-dismissible(Optional.) Indicates whether to include close icons on summary items. (Default: true)
      • bb-filter-summary-item-on-dismiss(Optional.) Specifies a function to be called when users click the close icons on summary items.

Demo

{{item.label}}
{{item.name}}
{{item.description}}

Markup

<div ng-controller="FilterTestController as filterCtrl">
    <div style="padding: 5px;">
        <bb-filter-button 
            bb-filter-button-on-click="filterCtrl.filterButtonClicked()">
        </bb-filter-button>
    </div>
    <bb-filter-summary ng-show="filterCtrl.appliedFilters && filterCtrl.appliedFilters.length > 0">
        <bb-filter-summary-item 
            ng-repeat="item in filterCtrl.appliedFilters" 
            bb-filter-summary-item-on-click="filterCtrl.filterButtonClicked()"
            bb-filter-summary-item-on-dismiss="filterCtrl.onDismiss($index)" >
            {{item.label}}
        </bb-filter-summary-item>
    </bb-filter-summary>

    <div>
        <bb-repeater bb-repeater-expand-mode="none">
            <bb-repeater-item ng-repeat="item in filterCtrl.filteredItems">
            <bb-repeater-item-title>
                {{item.name}}
            </bb-repeater-item-title>
            <bb-repeater-item-content>
                <div>
                {{item.description}}
                </div>
            </bb-repeater-item-content>
            </bb-repeater-item>
        </bb-repeater>
      </div>
</div>
<script type="text/ng-template" id="demo/filter/filters.html">
    <bb-modal>
        <div class="modal-form">
            <bb-modal-header>Food preferences</bb-modal-header>
            <div bb-modal-body>
                <form>
                    <label for="bb-select-type">Fruit type</label>
                    <select id="bb-select-type" ng-model="modalCtrl.filters.fruitType" class="form-control">
                        <option value="any">Any fruit</option>
                        <option value="citrus">Citrus</option>
                        <option value="berry">Berry</option>
                    </select>
                    <label style="margin-top: 15px;">
                        <input type="checkbox" bb-check ng-model="modalCtrl.filters.hideOrange" />
                        Hide orange fruits
                    </label>
                </form>
            </div>
            <bb-filter-modal-footer
                bb-filter-modal-apply="modalCtrl.applyFilters()"
                bb-filter-modal-clear="modalCtrl.clearAllFilters()">
            </bb-filter-modal-footer>
        </div>
    </bb-modal>
</script>

JavaScript

/*global angular */
(function () {
    'use strict';

    function FilterModalController($uibModalInstance, existingFilters) {
        var self = this;

        function clearAllFilters() {
            self.filters = {
                fruitType: 'any'
            };
        }
        
        function transformFiltersToArray(filters) {
            var result = [];

            if (filters.fruitType && filters.fruitType !== 'any') {
                result.push({name: 'fruitType', value: filters.fruitType, label: filters.fruitType});
            }

            if (filters.hideOrange) {
                result.push({name: 'hideOrange', value: true, label: 'hide orange fruits'});
            }

            return result;
        }

        function transformArrayToFilters(array) {
            var i,
                filters = {};

            for (i = 0; i < array.length; i++) {
                if (array[i].name === 'fruitType') {
                    filters.fruitType = array[i].value;
                }

                if (array[i].name === 'hideOrange') {
                    filters.hideOrange = array[i].value;
                }
            }

            return filters;
        }

        function applyFilters() {
            var result = transformFiltersToArray(self.filters);
            $uibModalInstance.close(result);
        }


        if (!existingFilters) {
            clearAllFilters();
        } else {
            self.filters = transformArrayToFilters(existingFilters);
        }

        if (angular.isUndefined(self.filters.fruitType)) {
            self.filters.fruitType = 'any';
        }

        self.clearAllFilters = clearAllFilters;
        self.applyFilters = applyFilters;

    }

    FilterModalController.$inject = ['$uibModalInstance', 'existingFilters'];
    
    function FilterTestController(bbModal) {
        var self = this,
            items = [
                {
                    name: 'Orange',
                    description: 'A round, orange fruit.',
                    type: 'citrus',
                    color: 'orange'
                },
                {
                    name: 'Mango',
                    description: 'Delicious in smoothies, but don\'t eat the skin.',
                    type: 'other',
                    color: 'orange'
                },
                {
                    name: 'Lime',
                    description: 'A sour, green fruit used in many drinks.',
                    type: 'citrus',
                    color: 'green'
                },
                {
                    name: 'Strawberry',
                    description: 'A red fruit that goes well with shortcake.',
                    type: 'berry',
                    color: 'red'
                },
                {
                    name: 'Blueberry',
                    description: 'A small, blue fruit often found in muffins.',
                    type: 'berry',
                    color: 'blue'
                }
            
            ];

        function orangeFilterFailed(filter, item) {
            return filter.name === 'hideOrange' && filter.value && item.color === 'orange';
        }

        function fruitTypeFilterFailed(filter, item) {
            return filter.name === 'fruitType' && filter.value !== 'any' && filter.value !== item.type;
        }

        function itemIsShown(filters, item) {
            var passesFilter = true,
                j;

            for (j = 0; j < filters.length; j++) {
                if (orangeFilterFailed(filters[j], item)) {
                    passesFilter = false
                } else if (fruitTypeFilterFailed(filters[j], item)) {
                    passesFilter = false
                }
            }

            return passesFilter;
        }

        function filterItems(items, filters) {
            var i,
                passesFilter,
                result = [];

            for (i = 0; i < items.length; i++) {
                passesFilter = itemIsShown(filters, items[i]);
                if (passesFilter) {
                    result.push(items[i]);
                }
            }

            return result;
        }

        self.filteredItems = items;

        self.filterButtonClicked = function () {
            bbModal
                .open({
                    controller: 'FilterModalController as modalCtrl',
                    templateUrl: 'demo/filter/filters.html',
                    resolve: {
                        existingFilters: function () {
                            return angular.copy(self.appliedFilters);
                        }
                    }
                })
                .result
                .then(function (result) {

                    self.appliedFilters = angular.copy(result);

                    self.filteredItems = filterItems(items, self.appliedFilters);

                },
                    angular.noop
                );
        };

        self.onDismiss = function (index) {
            self.appliedFilters.splice(index, 1);
            self.filteredItems = filterItems(items, self.appliedFilters);
        }
       
    }

    FilterTestController.$inject = ['bbModal'];
    
    angular
        .module('stache')
        .controller('FilterTestController', FilterTestController)
        .controller('FilterModalController', FilterModalController);
})();