Source:search-and-filter.component.js

(function () {
	'use strict';
	// Search and filter
	angular.module('mohistory').component('searchAndFilter', {
		templateUrl: 'app/components/search-and-filter/search-and-filter.component.html',
		controller: searchAndFilter,
		controllerAs: 'searchAndFilter',
		bindings: {
			facets: '<',
			totalResults: '<',
			searchQuery: '<',
			dateToDisplay: '<',
			onSearchSubmit: '<',
			flattenedFacets: '<',
			addFacet: '<',
			removeFacet: '<',
			clearFacets: '<',
			toggleFacet: '<',
			isFacetSelected: '<',
			isAFacetSelected: '<',
			isHelpVisible: '<',
			hasDisclaimer: '<',
			isDisclaimerVisible: '<',
			disclaimerContent: '<',
			onDisclaimerClose: '<',
			isRedirection: '<',
			zeroResultContent: '<',
			layouts: '<',
			curLayout: '<',
			setViewLayout: '<',
			apiError: '<',
			context: '<',
			images: '<',
		}
	});
	searchAndFilterCtrl.$inject = ['$transitions', '$location', '$anchorScroll', '$timeout'];
	/**
	 * Manages the search box and facet tabs that are part of each search interface.
     * @memberof mohistory
     * @name searchAndFilter
     * @ngdoc component
	 * @param {object} $transitions UI-Router service for registering hooks to listen for route changes
	 * @param {object} $location AngularJS wrapper for document.
	 * @param {object} $anchorScroll AngularJS service for scrolling the interface
	 * @param {object} $timeout wrapper for window.setTimeout()
	 */
	function searchAndFilterCtrl($transitions, $location, $anchorScroll, $timeout) {
		var vm = this;
		/* ----- Variables ----- */
		vm.tab = null;
		vm.tabNames = [];
		// UI-Router hook triggered when a route succeeds. Update the local copy of search query and scroll
		// to the search bar.
		var uiOnSuccess = $transitions.onSuccess({}, function ($transition$) {
			var stateName = $transition$.to().name;
			if (stateName === 'main.events' || stateName === 'main.collections' || stateName === 'main.blog' || stateName === 'main.search') {
				// This will only update 'vm.searchQuery' locally not in the parent
				vm.searchQuery = $transition$.params('to')['text'];
				vm.searchQueryToShow = vm.searchQuery;
				// Scroll to the search box
				$timeout(function () {
					if (Object.keys($location.search()).length !== 0) {
						$('html, body').animate({
							scrollTop: $('#search-container').offset().top - $('#site-navigation').outerHeight() - 10,
						}, 1000);
					}
				}, 100);
			}
		});
		/* ----- Function Bindings ----- */
		vm.$onInit = onInit;
		vm.$onDestroy = onDestroy;
		vm.isNumber = isNumber;
		// Displaying text from input
		vm.searchQueryToShow = '';
		vm.placeholderText = 'Enter Keyword(s)';
		// Manipulating tabs
		vm.isTabVisible = isTabVisible;
		vm.setTab = setTab;
		vm.addFacetPlusCloseTab = addFacetPlusCloseTab;
		/* ----- Function Definitions ----- */
		/**
		 * Initial setup for the searchAndFilter component
		 * @memberof searchAndFilter
		 * @function onInit
		 */
		function onInit() {
			// Temporary until audience is figured out
			if (vm.facets.audience) {
				delete vm.facets.audience;
			}
			// Update query to show
			vm.searchQueryToShow = vm.searchQuery;
			// Derive tab names from facets
			vm.tabNames = Object.keys(vm.facets);
			// Technically, this DOM manipulation should be in a link function.
			// However, I didn't think it was worth converting the component to
			// a directive for this one thing.
			if (Object.keys($location.search()).length !== 0) {
				var page = $('html, body');
				// Scroll to search bar, if there are facets selected. Allow the user to interrupt scroll event.
				page.on("scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove", function () {
					page.stop();
				});
				page.animate({
					scrollTop: $('#search-container').offset().top,
				}, 3000, function () {
					page.off("scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove");
				});
			}
		}
		/**
		 * Clean up code run when the component is destroyed
		 * @memberof searchAndFilter
		 * @function onDestroy
		 */
		function onDestroy() {
			uiOnSuccess();
		}
		/**
		 * The search APIs return result counts either as numbers or strings.
		 * This function returns true if item is a number.
		 * @memberof searchAndFilter
		 * @function isNumber
		 * @param {string | number} item
		 * @return {boolean} true if the input is a number, false otherwise
		 */
		function isNumber(item) {
			return typeof item === 'number';
		}
		/**
		 * Returns true if a tab should be visible to the user, false otherwise.
		 * @memberof searchAndFilter
		 * @function isTabVisible
		 * @param {number} tabToCheck Index of the tab to check.
		 * @return {boolean} True if tab should be shown, false otherwise
		 */
		function isTabVisible(tabToCheck) {
			return vm.isFacetTabVisible && vm.tab === tabToCheck;
		}
		/**
		 * Update the current tab. If vm.tab matches the passed parameter, 
		 * then the tab is closed.
		 * @memberof searchAndFilter
		 * @function setTab
		 * @param {number} activeTab Index of tab to make visible.
		 */
		function setTab(activeTab) {
			if ((activeTab !== 0 && !activeTab) || vm.tab === activeTab) {
				vm.tab = null;
				vm.isFacetTabVisible = false;
			} else {
				vm.tab = activeTab;
				vm.isFacetTabVisible = true;
			}
		}
		/**
		 * Add facet to the state and by consequence the URL query string. This will cause the
		 * state to reload, so to reduce confusion close the currently open tab.
		 * @memberof searchAndFilter
		 * @function addFacetPlusCloseTab
		 * @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 addFacetPlusCloseTab(facetType, currentFacet) {
			vm.tab = null;
			vm.isFacetTabVisible = false;
			vm.addFacet(facetType, currentFacet);
		}
	}
})();