Source:search-facets.service.js

(function () {
    'use strict';
    // Search Facets
    angular
        .module('mohistory')
        .service('searchFacets', searchFacets);
    searchFacets.$inject = ['$state', '$uiRouterGlobals'];
    /**
     * An interface for adding, deleting, and querying search facets stored
     * in UI-Router State. Router state and URL parameters are synced on state
     * traversal.
     * @memberof mohistory
     * @name searchFacets
     * @ngdoc service
     * @param {object} $state UI-Router state
     * @param {object} $uiRouterGlobals Stores the current state's parameters
     */
    function searchFacets($state, $uiRouterGlobals) {
        var service = this;
        /* ----- Variables ----- */
        // These facets are stored in the hash as strings not arrays.
        // They are also not displayed in the tabbed panels.
        var STRING_FACETS = ['text', 'layout', '#', 'redirect', 'sort', 'fullscreen', 'view', 'page', 'hasImage'];
        /* ----- Function Bindings ----- */
        // Retrieve a flattened, aka one level deep version, of the state parameters
        // stored as arrays, this is used to populate the selected facets section of
        // the view
        service.getFlattenedFacets = getFlattenedFacets;
        // Manipulating the search facet list
        service.addFacetsForKeywordSearch = addFacetsForKeywordSearch;
        service.addFacetToHash = addFacetToHash;
        service.removeFacetFromHash = removeFacetFromHash;
        service.removeAllFacetsFromHash = removeAllFacetsFromHash;
        service.toggleFacetInHash = toggleFacetInHash;
        // Manipulating information in facet panels
        service.isAFacetSelected = isAFacetSelected;
        service.isFacetSelected = isFacetSelected;
        // Helper which wraps a lookup in $uiRouterGlobals
        service.getSearchFacets = getSearchFacets;
        /* ----- Function Definitions ----- */
        /**
         * Helper function to prep the facets to be displayed in the view. 
         * This function also removes several facets that are displayed in 
         * other ways in the view.
         * @memberof searchFacets
         * @function getFlattenedFacets
         */
        function getFlattenedFacets() {
            var flattenObj = [];
            for (var param in $uiRouterGlobals.params) {
                if ($uiRouterGlobals.params.hasOwnProperty(param) && Array.isArray($uiRouterGlobals.params[param]) && param !== 'date' && param !== 'icon') {
                    for (var i = 0; i < $uiRouterGlobals.params[param].length; i++) {
                        flattenObj.push({
                            type: param,
                            name: $uiRouterGlobals.params[param][i],
                        });
                    }
                }
            }
            return flattenObj;
        }
        /**
         * Update the state to reflect new keyword search.
         * @memberof searchFacets
         * @function addFacetsForKeywordSearch
         * @param {string} query Text entered into the search box
         */
        function addFacetsForKeywordSearch(query) {
            $state.go('.', {
                page: 1,
                text: query,
                redirect: '',
            }, {
                location: 'replace'
            });
        }
        /**
         * Adds a search facet (a.k.a. filter) to the state. If the facetType is stored
         * as an array, the new facet is added to the end. If the facetType is stored as
         * a string, it is replaced by the new value. The calls to $state.go cause an update.
         * Any update to the facets, resets the page to 1.
         * @memberof searchFacets
         * @function addFacetToHash
         * @param {string} facetType Major category of facet, e.g. 'department' or 'collection'
         * @param {string} currentFacet Specific facet to filter by, e.g. 'Archival Collections' 
         */
        function addFacetToHash(facetType, currentFacet) {
            var searchFacets = service.getSearchFacets(facetType);
            var tempObj = {
                page: 1,
            };
            if (Array.isArray(searchFacets) && facetType !== 'date') {
                // UI-Router determines a need for a transition by object storage location
                // not content, so we need to make a copy of the current facets, modify it,
                // then give the copy to UI-router.
                searchFacets = searchFacets.slice();
                if (searchFacets.indexOf(currentFacet) < 0) {
                    tempObj[facetType] = searchFacets.concat(currentFacet);
                    $state.go('.', tempObj, {
                        location: 'replace'
                    });
                }
            } else if (facetType === 'date') {
                // Edge case: date is stored as an array, but currently the search
                // only supports selecting one date value at a time. So, replace
                // the current facet with the value passed into this function.
                tempObj[facetType] = [currentFacet];
                $state.go('.', tempObj, {
                    location: 'replace',
                });
            } else {
                tempObj[facetType] = currentFacet;
                $state.go('.', tempObj, {
                    location: 'replace',
                });
            }
        }
        /**
         * Removes a search facet (a.k.a. filter) from the state. If the facetType is stored
         * as an array and the currentFacet is provided, the currentFacet is removed from the array,
         * else the entire array is emptied. If the facetType is stored as
         * a string, it is replaced by an empty string. The calls to $state.go cause an update.
         * Any update to the facets, resets the page to 1.
         * @memberof searchFacets
         * @function removeFacetFromHash
         * @param {string} facetType Major category of facet, e.g. 'department' or 'collection'
         * @param {string} currentFacet Specific facet to filter by, e.g. 'Archival Collections' 
         */
        function removeFacetFromHash(facetType, currentFacet) {
            // UI-Router determines a need for a transition by object storage location
            // not content, so we need to make a copy of the current facets, modify it,
            // then give the copy UI-router.
            var searchFacets = service.getSearchFacets(facetType).slice();
            var tempObj = {
                page: 1,
            };
            if (Array.isArray(searchFacets)) {
                if (currentFacet) {
                    searchFacets.splice(searchFacets.indexOf(currentFacet), 1);
                    tempObj[facetType] = searchFacets;
                } else {
                    tempObj[facetType] = [];
                }
                $state.go('.', tempObj, {
                    location: 'replace'
                });
            } else {
                tempObj[facetType] = '';
                $state.go('.', tempObj, {
                    location: 'replace'
                });
            }
        }
        /**
         * Resets state to the defaults stored in the Router config
         * @memberof searchFacets
         * @function removeAllFacetsFromHash
         */
        function removeAllFacetsFromHash() {
            $state.go('.', {}, {
                inherit: false,
                location: 'replace',
            });
        }
        /**
         * If the facet's first character is '-' remove it, else add a minus to the front 
         * of the string, then replace the old facet with the updated one.
         * Any update to the facets, resets the page to 1.
         * @memberof searchFacets
         * @function toggleFacetInHash
         * @param {string} facetType Major category of facet, e.g. 'department' or 'collection'
         * @param {string} currentFacet Specific facet to filter by, e.g. 'Archival Collections'
         */
        function toggleFacetInHash(facetType, currentFacet) {
            var tempObj = {
                page: 1,
            };
            // User proofing: this function only works for facets stored in arrays
            if (STRING_FACETS.indexOf(facetType) < 0) {
                // UI-Router determines a need for a transition by object storage location
                // not content, so we need to make a copy of the current facets, modify it,
                // ad then give the copy UI-router.
                var searchFacets = service.getSearchFacets(facetType).slice();
                var indx = searchFacets.indexOf(currentFacet);
                if (indx >= 0) {
                    var toggledFacet = currentFacet.charAt(0) === '-' ? currentFacet.slice(1) : '-' + currentFacet;
                    searchFacets[indx] = toggledFacet;
                    tempObj[facetType] = searchFacets;
                    $state.go('.', tempObj, {
                        location: 'replace'
                    });
                }
            }
            if (facetType === 'sort') {
                // exception for the collection list sort
                var toggledFacet = currentFacet.charAt(0) === '-' ? currentFacet.slice(1) : '-' + currentFacet;
                tempObj[facetType] = toggledFacet;
                $state.go('.', tempObj, {
                    location: 'replace'
                });
            } else {
                return false;
            }
        }
        /**
         * Returns true if the provided facet type has at least one selected facet in the
         * state.
         * @memberof searchFacets
         * @function isAFacetSelected
         * @param {string} facetType Major category of facet, e.g. 'department' or 'collection'
         * @return {boolean} true if there is at least one facet of the given type in the 
         * url hash, false otherwise.
         */
        function isAFacetSelected(facetType) {
            var searchFacets = service.getSearchFacets(facetType);
            return searchFacets && searchFacets.length > 0;
        }
        /**
         * Returns true if the provided facet is in the state.
         * @memberof searchFacets
         * @function isFacetSelected
         * @param {string} facetType Major category of facet, e.g. 'department' or 'collection'
         * @param {string} currentFacet Specific facet to filter by, e.g. 'Archival Collections'
         * @return {boolean} True if the given facet is in the url hash, false otherwise
         */
        function isFacetSelected(facetType, currentFacet) {
            var searchFacets = service.getSearchFacets(facetType);
            // Type of facet has not yet been selected or all facets of given type have been removed
            if (!searchFacets || searchFacets.length === 0) {
                return false;
            }
            return searchFacets.includes(currentFacet) || searchFacets.includes('-' + currentFacet);
        }
        /**
         * Wrapper for a $uiRouterGlobals lookup. If a parameter is provided, the function will
         * return all of the facets associated with that type. By default, the function
         * returns an object containing all facets currently stored in state.
         * @memberof searchFacets
         * @function getSearchFacets
         * @param {string} facetType Major category of facet, e.g. 'department' or 'collection'
         * @return {object | array | string} Object containing all search facets, an array of facets,
         * or a string facet for a particular type
         */
        function getSearchFacets(facetType) {
            if (facetType) {
                return $uiRouterGlobals.params[facetType];
            }
            return $uiRouterGlobals.params;
        }
    }
})();