(function () {
'use strict';
// Search Container
angular
.module('mohistory')
.component('searchContainer', {
templateUrl: 'app/components/search-container/search-container.component.html',
controller: searchContainerCtrl,
controllerAs: 'searchContainer',
bindings: {
searchResults: '<',
context: '<',
calendar: '<',
setFooterHidden: '<',
}
});
searchContainerCtrl.$inject = ['$transitions', '$scope', 'searchFacets', 'keywordSearcher', '$state'];
/**
* Wrapper for the search interfaces, i.e. general, blog, event, and collections. Contains logic
* to handle search facets, display data in various views, and determine what to do with faulty
* feedback from the server.
* @memberof mohistory
* @name searchContainer
* @ngdoc component
* @param {object} $transitions$ UI-router object
* @param {object} searchFacets Service that handles manipulating parameters in the state.
* @param {object} keywordSearcher Service that wraps calls to the search APIs
*/
function searchContainerCtrl($transitions, $scope, searchFacets, keywordSearcher, $state) {
var vm = this;
/* ----- Variables ----- */
/* --- Controls how each search interface behaves, e.g. number/type of layouts available. --- */
vm.searchConfig = {
'search-collections': {
layouts: [
'grid',
'thumb',
'detail',
'list',
],
defaultLayout: 'detail',
hasDisclaimer: true,
disclaimerContent: '<p>Many research materials are only available in person. If you do not find what you are looking for online <a href="http://mohistory.org/research/in-person-research">contact the reading room staff</a>.</p>',
zeroResultContent: 'If you cannot find what you are looking for online, please consider visiting the <a class="theme-text" href="http://mohistory.org/research/in-person-research">Library & Research Center</a> in person or contacting staff for assistance.',
errorSearchResults: {
facets: {
department: [{
count: '',
email: 'archives@mohistory.org',
label: 'Archival Collections',
phone: '314-746-4510',
url: 'http://mohistory.org/research/documents-archives/',
}, {
count: '',
email: 'photo@mohistory.org',
label: 'Photos and Prints Collections',
phone: '314-746-4511',
url: 'http://mohistory.org/research/photographs-prints/',
}, {
count: '',
email: 'objects@mohistory.org',
label: 'Object Collections',
phone: '314-746-4441',
url: 'http://mohistory.org/research/objects-artifacts',
}, {
count: '',
email: 'library@mohistory.org',
label: 'Library Collections',
phone: '314-746-4500',
url: 'http://mohistory.org/research/library/',
}, {
count: '',
email: 'movingimages@mohistory.org',
label: 'Moving Image and Sound Collections',
phone: '314-746-4585',
url: 'http://mohistory.org/research/multimedia/',
}],
collection: [],
decade: {},
creator: [],
type: [],
subject: [],
place: [],
},
items: [],
total: 0,
},
isHelpVisible: true,
},
'search-blog': {
layouts: [
'grid',
'detail',
],
defaultLayout: 'grid',
hasDisclaimer: false,
disclaimerContent: '',
zeroResultContent: 'Currently there are no blog posts that match what you are looking for. Please modify your search and try again.',
errorSearchResults: {
facets: {
author: [],
category: [],
subject: [],
exhibit: [],
},
items: [],
total: 0,
},
isHelpVisible: false,
},
'search-events': {
layouts: [
'detail',
],
defaultLayout: 'detail',
hasDisclaimer: false,
disclaimerContent: '',
zeroResultContent: 'Currently no events match what you are looking for. Please try a new search or check back later.',
errorSearchResults: {
facets: {
exhibit: [],
location: [],
series: [],
type: [],
},
items: [],
total: 0,
},
isHelpVisible: false,
},
'search-all': {
layouts: [
'detail',
'list',
],
defaultLayout: 'detail',
hasDisclaimer: false,
disclaimerContent: '',
zeroResultContent: 'No search results match what you are looking for. Please modify your search and try again.',
errorSearchResults: {
facets: {
subject: [],
audience: [],
type: [],
category: [],
location: [],
author: [],
},
items: [],
total: 0,
},
isHelpVisible: false,
}
};
vm.curSearchConfig = {};
/* --- Information for child components --- */
vm.searchQuery = '';
vm.curLayout = 'detail';
vm.flattenedFacets = [];
vm.isDisclaimerVisible = true;
vm.isRedirection = false;
vm.apiError = false;
vm.images = false;
/* --- Events --- */
vm.today = null;
vm.dateToDisplay = null;
vm.eventIcons = [{
label: "Free program",
count: 0
}, {
label: "Registration required",
count: 0
}, {
label: "Paid program",
count: 0
}, {
label: "Program with member benefits",
count: 0
}, {
label: "Accessible program",
count: 0
}];
vm.eventIconsToShow = [];
// Date
vm.monthNumToName = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December'
];
vm.monthNameToNum = {
'January': 0,
'February': 1,
'March': 2,
'April': 3,
'May': 4,
'June': 5,
'July': 6,
'August': 7,
'September': 8,
'October': 9,
'November': 10,
'December': 11,
};
// Information used to populate the drop down menus
vm.yearsToShow = [];
// By storing an index we don't have to keep copying the vm.monthNumToName array
vm.monthsToShowStart = null;
vm.daysToShow = [];
// Selected date in the drop down menu
vm.selectedYear = 'year';
vm.selectedMonth = 'month';
vm.selectedDay = 'day';
// Information used for calendar display
vm.displayYear = null;
vm.displayMonth = null;
/* --- Pagination --- */
vm.setPage = setPage;
vm.nextPage = nextPage;
vm.prevPage = prevPage;
/* --- Infinite Scroll --- */
vm.infinList = [];
vm.infinListToShow = [];
vm.infinListStep = 4;
vm.ISBusy = false;
vm.endOfList = false;
vm.infinTotalResults = 0;
// UI-Router hook triggered when a transition is started
var uiOnStart = $transitions.onStart({}, function ($transition$) {
var stateName = $transition$.to().name;
var prevStateName = $transition$.from().name;
// This transition hook is triggered when transitioning from a search state to another state, so
// we need to verify that we are transition to a search state.
if ((stateName === prevStateName) && (stateName === 'main.collections' || stateName === 'main.blog' || stateName === 'main.events' || stateName === 'main.search')) {
if (vm.curLayout !== 'thumb') {
vm.setFooterHidden(false); // Clean up if we're transitioning from thumb view
return keywordSearcher.getResultsForPage(vm.context, $transition$.params()).then(function (data) {
if (data === 'error') {
// If there is an error use the fallback data so the UI doesn't break and show an error message
vm.searchResults = vm.curSearchConfig.errorSearchResults;
vm.apiError = true;
} else {
vm.searchResults = data;
vm.apiError = false;
}
});
} else {
vm.setFooterHidden(true); // Transitioning to thumb view so hide footer, until infinite scroll is done
vm.ISBusy = true;
return keywordSearcher.getInfinList(vm.context, $transition$.params(), 0).then(function (data) {
if (data === 'error') {
// If there is an error use the fallback data so the UI doesn't break and show an error message
vm.searchResults = vm.curSearchConfig.errorSearchResults;
vm.infinList = vm.searchResults.items;
vm.infinListToShow = vm.infinList;
vm.apiError = true;
vm.setFooterHidden(false);
} else {
vm.searchResults = data;
vm.apiError = false;
if (vm.searchResults.items.length === 0) {
vm.endOfList = true;
vm.setFooterHidden(false);
} else {
vm.endOfList = false;
vm.infinList = vm.searchResults.items;
vm.infinTotalResults = vm.searchResults.total;
vm.infinListToShow = vm.infinList;
}
}
vm.ISBusy = false;
});
}
}
});
// UI-Router hook triggered when a transition succeeds
var uiOnSuccess = $transitions.onSuccess({}, function ($transition$) {
var stateName = $transition$.to().name;
var prevStateName = $transition$.from().name;
// This transition hook is triggered when transitioning from a search state to another state, so
// we need to verify that we are transitioning to a search state.
if ((stateName === prevStateName) && (stateName === 'main.collections' || stateName === 'main.blog' || stateName === 'main.events' || stateName === 'main.search')) {
// Update variables based on any changes
vm.setupData();
}
});
/* ----- Function Bindings ----- */
vm.$onInit = onInit;
vm.$onDestroy = onDestroy;
vm.setupData = setupData;
// Submit keyword search
vm.onSearchSubmit = onSearchSubmit;
/* --- Manipulating the search facet list --- */
vm.addDateFacet = addDateFacet;
vm.addFacet = addFacet;
vm.removeFacet = removeFacet;
vm.clearFacets = clearFacets;
vm.toggleFacet = toggleFacet;
/* --- Manipulating information in facet panels --- */
vm.isAFacetSelected = isAFacetSelected;
vm.isFacetSelected = isFacetSelected;
/* --- Routing to layout views --- */
vm.setViewLayout = setViewLayout;
/* --- Handle Disclaimer --- */
vm.onDisclaimerClose = onDisclaimerClose;
/* --- Events --- */
vm.setupCalendar = setupCalendar;
vm.setupEventIcons = setupEventIcons;
vm.setYearsToShow = setYearsToShow;
vm.setDaysToShow = setDaysToShow;
vm.setMonthsToShowStart = setMonthsToShowStart;
vm.verifyDate = verifyDate;
vm.nextMonth = nextMonth;
vm.prevMonth = prevMonth;
vm.isMonthDisabled = isMonthDisabled;
vm.isDateDisabled = isDateDisabled;
/* --- Infinite Scroll --- */
vm.getInfiniteNextPage = getInfiniteNextPage;
/* ----- Function Definitions ----- */
/**
* Initialization code run every time the component is created, used to setup variables
* and data.
* @function onInit
* @memberof searchContainer
*/
function onInit() {
vm.curSearchConfig = vm.searchConfig[vm.context];
if (vm.searchResults === 'error' || vm.searchResults === null || vm.searchResults === undefined) {
// If error use the errorSearchResults for the given context
vm.searchResults = vm.curSearchConfig.errorSearchResults;
vm.apiError = true;
} else {
vm.apiError = false;
vm.setupData();
}
}
/**
* Clean up function called when a component is removed.
* @function onDestroy
* @memberof searchContainer
*/
function onDestroy() {
// Deregister UI-Router transition hooks
uiOnStart();
uiOnSuccess();
// Make sure the footer is visible
vm.setFooterHidden(false);
}
/**
* Process data before displaying it. Some of the setup is based on search type,
* where as, there are pieces that apply to all types.
* @function setupData
* @memberof searchContainer
*/
function setupData() {
// Retrieve information from state / URL query parameters
vm.searchQuery = searchFacets.getSearchFacets('text');
vm.flattenedFacets = searchFacets.getFlattenedFacets();
vm.isRedirection = searchFacets.getSearchFacets('redirect');
if (vm.context === 'search-collections') {
if (vm.searchResults.facets.department.length === 0) {
// if API does not return any department information, use hard coded values
vm.searchResults.facets.department = vm.curSearchConfig.errorSearchResults.facets.department;
}
// Results with images checkbox
vm.images = searchFacets.getSearchFacets('images');
}
if (vm.context === 'search-events' && vm.calendar !== null && vm.calendar !== undefined) {
vm.setupCalendar();
vm.setupEventIcons();
}
if (vm.isDisclaimerVisible && vm.curSearchConfig['hasDisclaimer']) {
// Any of the searches can have a disclaimer
vm.isDisclaimerVisible = true;
} else {
vm.isDisclaimerVisible = false;
}
var layoutInHash = searchFacets.getSearchFacets('layout');
if (vm.curSearchConfig['layouts'].indexOf(layoutInHash) >= 0) {
// Layout in hash must be valid for the given context
vm.curLayout = layoutInHash;
} else {
vm.setViewLayout(vm.curSearchConfig['defaultLayout']);
}
if (vm.curLayout === 'thumb') {
// Infinite scroll
vm.infinList = vm.searchResults.items;
vm.infinListToShow = vm.infinList;
vm.infinTotalResults = vm.searchResults.total;
}
// Ensure the facet names are always in the same order and if the API doesn't return
// a facet it still shows up for the user
var apiSearchFacets = vm.searchResults.facets;
vm.searchResults.facets = {};
for (var facet in vm.curSearchConfig.errorSearchResults.facets) {
if (vm.curSearchConfig.errorSearchResults.facets.hasOwnProperty(facet)) {
if (apiSearchFacets[facet] !== undefined && apiSearchFacets[facet] !== null) {
vm.searchResults.facets[facet] = apiSearchFacets[facet];
} else {
vm.searchResults.facets[facet] = vm.curSearchConfig.errorSearchResults.facets[facet];
}
}
}
}
/**
* Unlike other search facets, all event icons need to be visible
* regardless of if they were returned as viable facets from the API.
* This function generates a list of icons and counts to display based
* on the hard coded list of all possible icons and the icons returned
* from the API.
* @function setupEventIcons
* @memberof searchContainer
*/
function setupEventIcons() {
var iconsFromAPI = vm.searchResults.facets['icon'];
var label = null;
var count = null;
vm.eventIconsToShow = [];
// Loop through the hard coded event icons
for (var i = 0; i < vm.eventIcons.length; i++) {
label = vm.eventIcons[i].label;
count = vm.eventIcons[i].count;
if (vm.isFacetSelected('icon', '-' + label)) {
label = '-' + label;
}
// Loop through the icons returned from the API
// and update the count if necessary
for (var j = 0; j < iconsFromAPI.length; j++) {
if (label === iconsFromAPI[j].label) {
count = iconsFromAPI[j].count;
}
}
vm.eventIconsToShow.push({
label: label,
count: count,
});
}
// Remove the icon property on the facets object to prevent
// it showing up in the tabbed area where most facets are displayed
delete vm.searchResults.facets['icon'];
}
/**
* Handle the setup for the event search calendar. This means setting the
* variables used to display the calendar and validating the date facet.
* @function setupCalendar
* @memberof searchContainer
*/
function setupCalendar() {
if (vm.today === null) {
// If first load, initialize date with server time.
vm.today = new Date(vm.searchResults.serverDate);
}
var dateFacet = searchFacets.getSearchFacets('date')[0];
if (/^\d{4}(-\d{2}(-\d{2}(T.*)?)?)?$/.test(dateFacet)) {
// If date is a facet, then verify it and set local variables
var dateArr = dateFacet.split('T')[0].split('-');
vm.selectedYear = dateArr[0]; // A year must be selected in order to pass the above regex
vm.selectedMonth = vm.monthNumToName[Number(dateArr[1]) - 1] || 'month'; // set month from facet or use default
vm.selectedDay = dateArr[2] || 'day'; // set day from facet or use default
// Verify entered date.
var hasChanged = verifyDate();
if (hasChanged) {
// If the date changed, then update the information stored in state
vm.addFacet('date');
}
} else {
// No date selected, set everything to defaults
vm.selectedYear = 'year';
vm.selectedMonth = 'month';
vm.selectedDay = 'day';
}
// Based on vm.selectedYear, vm.selectedMonth, and vm.selectedDay set
// the values displayed in the dropdown selection menus
setYearsToShow();
setMonthsToShowStart();
setDaysToShow();
// Display values are used for the calendar
vm.displayYear = vm.selectedYear === 'year' ? vm.today.getFullYear() : vm.selectedYear;
vm.displayMonth = vm.selectedMonth === 'month' ? vm.monthNumToName[vm.today.getMonth()] : vm.selectedMonth;
// vm.dateToDisplay is passed to search-and-facet so it can be displayed under the result count
vm.dateToDisplay = null;
if (vm.selectedYear !== 'year' && vm.selectedMonth !== 'month' && vm.selectedDay !== 'day') {
vm.dateToDisplay = vm.selectedMonth + ' ' + vm.selectedDay + ', ' + vm.selectedYear;
} else if (vm.selectedYear !== 'year' && vm.selectedMonth !== 'month' && vm.selectedDay === 'day') {
vm.dateToDisplay = vm.selectedMonth + ' ' + vm.selectedYear;
} else if (vm.selectedYear !== 'year' && vm.selectedMonth === 'month' && vm.selectedDay === 'day') {
vm.dateToDisplay = vm.selectedYear;
}
}
/**
* Fill the array used to populate the year dropdown menu.
* @function setYearsToShow
* @memberof searchContainer
*/
function setYearsToShow() {
if (vm.yearsToShow.length === 0) {
vm.yearsToShow = Object.keys(vm.calendar);
}
}
/**
* Determine if the months in the dropdown should start with January or
* the current month.
* @function setMonthsToShowStart
* @memberof searchContainer
*/
function setMonthsToShowStart() {
// If current year is selected, then the monthsToShow needs to be
// adjusted so nothing before the current month is accessible.
// Using double equals because getFullYear returns a number and
// selectedYear is stored as a string
if (vm.selectedYear == vm.today.getFullYear()) {
vm.monthsToShowStart = vm.today.getMonth();
} else {
vm.monthsToShowStart = 0;
}
}
/**
* Populate the daysToShow array which is used to generate the day selector
* and logic for determining the accuracy of a date facet.
* MUST BE CALLED AFTER updating selectedYear and selectedMonth
* @function setDaysToShow
* @memberof searchContainer
*/
function setDaysToShow() {
vm.daysToShow = []; // Clear out any old data
if (vm.selectedYear !== 'year' && vm.selectedMonth !== 'month') {
var monthData = vm.calendar[vm.selectedYear][vm.selectedMonth];
// Loop over the weeks and days stored in the calendar for the selected year and month
for (var week = 0; week < monthData.length; week++) {
for (var day = 0; day < monthData[week].length; day++) {
if (monthData[week][day] !== '00') {
// Skip all blank days
if (vm.selectedYear == vm.today.getFullYear() && vm.selectedMonth === vm.monthNumToName[vm.monthsToShowStart]) {
if (monthData[week][day] >= vm.today.getDate()) {
// If traversing current year and month only add today and preceding days.
vm.daysToShow.push(monthData[week][day]);
}
} else {
// If not current year and month, add all days
vm.daysToShow.push(monthData[week][day]);
}
}
}
}
} else {
// There are a max of 31 days in any month
for (var i = 1; i <= 31; i++) {
vm.daysToShow.push(('0' + i).slice(-2));
}
}
}
/**
* Verify selected date. A date is valid if the selected values are in
* allowed ranges and the selected values can be combined to form one of
* the following date combinations: yyyy-mm-dd, yyyy-mm, or yyyy-mm-dd.
* @function verifyDate
* @memberof searchContainer
* @return {boolean} true if a change was made, false otherwise
*/
function verifyDate() {
var hasChanged = false;
if (vm.selectedYear !== 'year') {
// A year has been selected
vm.setYearsToShow();
if (vm.yearsToShow[0] > vm.selectedYear) {
// selected year is already past
vm.selectedYear = vm.yearsToShow[0];
hasChanged = true;
} else if (vm.yearsToShow[vm.yearsToShow.length - 1] < vm.selectedYear) {
// selected year is more than five years in the future
vm.selectedYear = vm.yearsToShow[vm.yearsToShow.length - 1];
hasChanged = true;
}
}
if (vm.selectedMonth !== 'month') {
// A month has been selected
if (vm.selectedYear === 'year') {
// A year must be set to alter the month
vm.selectedYear = String(vm.today.getFullYear());
vm.setYearsToShow();
hasChanged = true;
}
vm.setMonthsToShowStart();
if (vm.monthNameToNum[vm.selectedMonth] < vm.monthsToShowStart) {
vm.selectedMonth = vm.monthNumToName[vm.monthsToShowStart];
hasChanged = true;
} else if (vm.monthNameToNum[vm.selectedMonth] > vm.monthNumToName.length - 1) {
vm.selectedMonth = vm.monthNumToName[vm.monthNumToName.length - 1];
hasChanged = true;
}
}
if (vm.selectedDay !== 'day') {
// A day has been selected
if (vm.selectedYear === 'year') {
// A year must be set to alter the day
vm.selectedYear = String(vm.today.getFullYear());
vm.setYearsToShow();
vm.setMonthsToShowStart();
hasChanged = true;
}
if (vm.selectedMonth === 'month') {
// A month must be set to alter the day
vm.selectedMonth = vm.monthNumToName[vm.today.getMonth()];
hasChanged = true;
}
vm.setDaysToShow();
if (vm.selectedDay < vm.daysToShow[0]) {
vm.selectedDay = vm.daysToShow[0];
hasChanged = true;
} else if (vm.selectedDay > vm.daysToShow[vm.daysToShow.length - 1]) {
vm.selectedDay = vm.daysToShow[vm.daysToShow.length - 1];
hasChanged = true;
}
}
return hasChanged;
}
/**
* Navigate to the next month. If current month is December move to January
* of the next year. This function has no impact on the selected date.
* @function nextMonth
* @memberof searchContainer
*/
function nextMonth() {
if (vm.displayMonth === vm.monthNumToName[vm.monthNumToName.length - 1]) {
// Moving from December of the current year to January of the next year
vm.displayYear = '' + (Number(vm.displayYear) + 1); // convert to a number, then back to string
vm.displayMonth = vm.monthNumToName[0];
} else {
var monthIdx = vm.monthNameToNum[vm.displayMonth];
vm.displayMonth = vm.monthNumToName[monthIdx + 1];
}
}
/**
* Navigate to the previous month. If current month is January move to
* December of the previous year. This function has no impact on the
* selected date.
* @function prevMonth
* @memberof searchContainer
*/
function prevMonth() {
if (vm.displayMonth === vm.monthNumToName[0]) {
// Moving from January of current year to December of the previous year
vm.displayYear = '' + (Number(vm.displayYear) - 1); // convert to a number, then back to string
vm.displayMonth = vm.monthNumToName[vm.monthNumToName.length - 1];
} else {
var monthIdx = vm.monthNameToNum[vm.displayMonth];
vm.displayMonth = vm.monthNumToName[monthIdx - 1];
}
}
/**
* Determine if a navigational arrows should be disabled for a given direction.
* By disabling those buttons, we can prevent navigation to months outside the
* allowed range.
* @function isMonthDisabled
* @memberof searchContainer
* @param {string} direction the direction to check
* @return {boolean} True if the month is not in the allowed range, false otherwise
*/
function isMonthDisabled(direction) {
var monthIdx = vm.monthNameToNum[vm.displayMonth];
if (direction === 'PREV') {
if (monthIdx === 0) {
// Is the previous month in the allowed range
var prevYear = Number(vm.displayYear) - 1;
return vm.calendar[prevYear] === undefined;
} else {
// Previous month has already past
var prevMonth = monthIdx - 1;
return vm.displayYear == vm.today.getFullYear() && prevMonth < vm.today.getMonth();
}
} else if (direction === 'NEXT') {
if (monthIdx === (vm.monthNumToName.length) - 1) {
// Is the next year in the allowed range
var nextYear = Number(vm.displayYear) + 1;
return vm.calendar[nextYear] === undefined;
} else {
// Any month less than 12 is in the allowed range for moving forward
return false;
}
}
}
/**
* Determine if a day is a valid choice for selection. By disabling calendar
* day that have already past, we can prevent users from selecting a past date.
* @function isDateDisabled
* @memberof searchContainer
* @param {string} day The day to check
* @return {boolean} True if the day has already past, false otherwise
*/
function isDateDisabled(day) {
if (day === '00') {
return true;
}
if (vm.displayYear == vm.today.getFullYear()) {
if (vm.monthNameToNum[vm.displayMonth] <= vm.today.getMonth()) {
return day < vm.today.getDate();
}
}
return false;
}
/**
* Called when a new keyword is entered. Updates search text and page
* number in the state parameters. By updating the parameters, a transition
* is triggered which runs setupData.
* @memberof searchContainer
* @function onSearchSubmit
* @param {string} q String representing the search parameter
*/
function onSearchSubmit(q) {
searchFacets.addFacetsForKeywordSearch(q);
}
/**
* Update the selected values of year, month, or day. The new value is
* verified, then the new facet is added to the state.
* @function addDateFacet
* @memberof searchContainer
* @param {string} datePart Piece of the date that should be changed
* (i.e. year, month, or day)
* @param {string} value The new value for the provided datePart
*/
function addDateFacet(datePart, value) {
if (datePart === 'day') {
vm.selectedDay = value;
} else if (datePart === 'month') {
vm.selectedMonth = value;
} else if (datePart === 'year') {
vm.selectedYear = value;
}
var hasChanged = verifyDate();
vm.addFacet('date');
}
/**
* Adds a search facet (a.k.a. filter) to the state, which triggers a transition
* which in turn triggers the `onStart` listener. Inside the `onStart` transition
* listener, an API call is made with the updated state parameters.
* @function addFacet
* @memberof searchContainer
* @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 addFacet(facetType, currentFacet) {
if (facetType === 'date' && !currentFacet) {
if (vm.selectedYear !== 'year' && vm.selectedMonth !== 'month' && vm.selectedDay !== 'day') {
searchFacets.addFacetToHash('date', vm.selectedYear + '-' + ('0' + (vm.monthNameToNum[vm.selectedMonth] + 1)).slice(-2) + '-' + vm.selectedDay);
} else if (vm.selectedYear !== 'year' && vm.selectedMonth === 'month' && vm.selectedDay === 'day') {
searchFacets.addFacetToHash('date', vm.selectedYear);
} else if (vm.selectedYear !== 'year' && vm.selectedMonth !== 'month' && vm.selectedDay === 'day') {
searchFacets.addFacetToHash('date', vm.selectedYear + '-' + ('0' + (vm.monthNameToNum[vm.selectedMonth] + 1)).slice(-2));
} else {
searchFacets.removeFacetFromHash('date');
}
} else {
searchFacets.addFacetToHash(facetType, currentFacet);
}
}
/**
* Removes a search facet (a.k.a. filter) from the state, which triggers a transition
* which in turn triggers the `onStart` listener. Inside the `onStart` transition
* listener, an API call is made with the updated state parameters.
* @memberof searchContainer
* @function removeFacet
* @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 removeFacet(facetType, currentFacet) {
searchFacets.removeFacetFromHash(facetType, currentFacet);
}
/**
* If the facet's first character is '-' remove it, else add a minus to the front
* of the string. Then, remove the old facet and add the new one.
* @memberof searchContainer
* @function toggleFacet
* @param {string} facetType Major category of facet, e.g. 'dept' or 'collection'
* @param {string} currentFacet Specific facet to filter by, e.g. 'Archival Collections'
*/
function toggleFacet(facetType, currentFacet) {
searchFacets.toggleFacetInHash(facetType, currentFacet);
}
/**
* Remove all facets for the state and by consequence the URL hash.
* @function clearFacets
* @memberof searchContainer
*/
function clearFacets() {
searchFacets.removeAllFacetsFromHash();
}
/**
* Returns true if the provided facet type has at least one selected facet in the
* url hash.
* @memberof searchContainer
* @function isAFacetSelected
* @param {string} facetType Major category of facet, e.g. 'dept' 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) {
return searchFacets.isAFacetSelected(facetType);
}
/**
* Returns true if the provided facet is in the url hash.
* @memberof searchContainer
* @function isFacetSelected
* @param {string} facetType Major category of facet, e.g. 'dept' 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) {
return searchFacets.isFacetSelected(facetType, currentFacet);
}
/**
* Switch to given view layout
* @memberof searchContainer
* @function setViewLayout
* @param {string} layout
*/
function setViewLayout(layout) {
vm.curLayout = layout;
searchFacets.addFacetToHash('layout', layout);
}
/**
* Hide the disclaimer while this component is alive. If the user
* navigates away from the search interface then comes back, the
* disclaimer will be visible again.
* @function onDisclaimerClose
* @memberof searchContainer
*/
function onDisclaimerClose() {
vm.isDisclaimerVisible = false;
}
/**
* Navigate to the specified page by transitioning to a new state.
* @function setPage
* @memberof searchContainer
* @param {number} num
*/
function setPage(num) {
$state.go('.', {
page: num
});
}
/**
* Navigate to the next page by transitioning to a new state.
* @function nextPage
* @memberof searchContainer
*/
function nextPage() {
$state.go('.', {
page: vm.searchResults.curPage + 1
});
}
/**
* Navigate tot the previous page by transitioning to a new state.
* @function nextPage
* @memberof searchContainer
*/
function prevPage() {
$state.go('.', {
page: vm.searchResults.curPage - 1
});
}
/**
* Manage infinite scrolling for the thumbnail view. Every API call retrieves 20 items. Every time
* this function is called it adds four elements to those shown. If there are no more elements
* in memory, then call the API again.
* @function getInfiniteNextPage
* @memberof searchContainer
*/
function getInfiniteNextPage() {
if (vm.ISBusy) {
return;
}
if (vm.endOfList) {
vm.setFooterHidden(false);
vm.ISBusy = false;
return;
}
vm.ISBusy = true;
vm.setFooterHidden(true); // Hide the footer, because it interferes with the infinite scroll process
if ((vm.infinListToShow.length + vm.infinListStep) < vm.infinList.length || vm.infinList.length === vm.infinTotalResults) {
// Add more elements from those stored in memory
vm.infinListToShow = vm.infinListToShow.concat(vm.infinList.slice(vm.infinListToShow.length, vm.infinListToShow.length + vm.infinListStep));
if (vm.infinList.length === vm.infinTotalResults && vm.infinList.length === vm.infinListToShow.length) {
// All elements have been scrolled through, so show the footer
vm.endOfList = true;
vm.setFooterHidden(false);
}
vm.ISBusy = false;
} else if (vm.infinList.length !== vm.infinTotalResults) {
// Add more elements after querying the API for another batch
var diff = vm.infinList.length - vm.infinListToShow.length;
if (diff > 0) {
// Just in case the shown list isn't the same as the list stored in memory, add the last few elements
vm.infinListToShow = vm.infinListToShow.concat(vm.infinList.slice(vm.infinListToShow.length));
}
keywordSearcher.getInfinList(vm.context, searchFacets.getSearchFacets(), vm.infinList.length - 1).then(function (response) {
if (response.items.length === 0) {
// API has confirmed the end of the list
vm.endOfList = true;
vm.setFooterHidden(false);
} else {
// Add items from the API to those stored in memory
vm.infinList = vm.infinList.concat(response.items);
if (typeof response.total !== 'number') {
// Remove comma if the total is returned as a string
response.total = Number(response.total.replace(/,/g, ""));
}
vm.infinTotalResults = response.total;
vm.infinListToShow = vm.infinListToShow.concat(vm.infinList.slice(vm.infinListToShow.length, diff))
}
vm.ISBusy = false;
});
} else {
// @note: not sure if this is necessary
// Fall back
vm.endOfList = true;
vm.setFooterHidden(false);
vm.ISBusy = false;
}
}
}
})();