Source: fedramp.components/grid-search.component.js

(function () {
    'use strict';

    angular
        .module('fedramp.components')
        .component('gridSearch', {
            templateUrl: 'templates/components/grid-search.html',
            controller: GridSearch,
            controllerAs: 'controller',
            require: {
                gridController: '^grid'
            },
            bindings: {
                // Property expression. Tells search which property to search on. 
                property: '@',
                // Placeholder text to display on input field
                placeholder: '@',
                id: '@',
                filterFunc: '<'
            }
        });

    GridSearch.$inject = ['Searcher'];

    /**
     * Allows a dataset to be searched using free text searches. This component utilizes a property 
     * expression to determine which field to search on. See {@link Services.Searcher Searcher} example for 
     * property expression samples. 
     *
     * If no property is specified, a custom filterFunc can be passed in to include custom
     * filtering logic.
     *
     * Requires to be nested within a [Grid]{@link Components.Grid} component.
     *
     * @example
     * // Using property expression
     * <grid-search property="product.name in products" placeholder="Search by Product Name"></grid-search>
     *
     * // Using custom filter function
     * <grid-search filter-func="myCustomFilterFunc" placeholder="Search by Product Name or Provider Name"></grid-search>
     * @constructor
     * @memberof Components
     */
    function GridSearch (Searcher) {
        /**
         * Reference to current GridSearch
         */
        var self = this;        

        /**
         * Used to traverse an object to search for values on a given property
         */
        var searcher = new Searcher();

        // Exposed functions
        self.$onInit = $onInit;
        self.search = search;
        self.clear = clear;
        self.restoreState = restoreState;

        /**
         * Contains array of filtered items
         * @member {array}
         * @memberof Components.GridSearch
         */
        self.filtered = [];

        /**
         * Initializes the component and validates the existence of specific attributes
         * @memberof Components.GridSearch
         */
        function $onInit () {
            if (!self.id) {
                throw 'Id is required for grid search filter';
            }
            if (!self.property && !self.filterFunc) {
                throw 'If property is not specified, filterFunc must be provided';
            }
            
            self.filterFunc = (self.filterFunc ? wrapFilterFunc(self.filterFunc) : filterFunc);
            self.gridController.addFilter(self);
            restoreState();
            search();
        }

        /**
         * Filter applied to dataset when user searches
         *
         * @public
         * @memberof Components.GridSearch
         *
         * @param {object} obj
         * The current object instance within a dataset being filtered
         * @param {int} index
         * The index of the current object
         * @param {array} arr
         * Reference to the dataset being filtered
         */
        function filterFunc (obj, index, arr) {
            if (self.searchTerm) {
                var found = searcher.prop(self.property).contains(obj, self.searchTerm);
                if (found.length > 0) {
                    return obj;
                } else {
                    return null;
                }
            } else {
                return obj;
            }
        }

        /**
         * Every time a user searches, we filter based on the entire dataset and then internally 
         * store the results and call doFilter() on the parent dataset to appropriately merge results from
         * all relevant fitlers.
         *
         * @public
         * @memberof Components.GridSearch
         */
        function search () {
            saveState();
            self.filtered = self.gridController.rawItems.filter(self.filterFunc);
            self.gridController.doFilter();
        }

        /**
         * Stores the current search parameters to state.
         *
         * @public
         * @memberof Components.GridSearch
         */
        function saveState () {
            if (self.searchTerm) {
                self.gridController.state[self.id] = self.searchTerm;
            } else {
                delete self.gridController.state[self.id];
            }
        }

        /**
         * Extracts state informatin to restore search filter
         *
         * @public
         * @memberof Components.GridSearch
         */
        function restoreState () {
            var params = self.gridController.state;
            if (!(self.id in params)) {
                return null;
            }
            self.searchTerm = params[self.id];
        }

        /**
         * When a custom filterFunc is provided, we wrap it with this function in order to properly
         * pass the searchTerm to the passed in function.
         *
         * @public
         * @memberof Components.GridSearch
         * @param {function} func
         * Custom filterFunc to be wrapped
         *
         * @returns
         * A function that has been wrapped to contain an additonal parameters
         */
        function wrapFilterFunc (func) {
            return function (obj, index, arr) {
                return func(obj, index, arr, self.searchTerm);
            };
        }

        /**
         * Clears the search text
         *
         * @public
         * @memberof Components.GridSearch
         */
        function clear () {
            self.searchTerm = '';
            search();
        }
    }
})();