/********************************************* * Copyright (c) 2014 AnsibleWorks, Inc. */ /** * @ngdoc function * @name helpers.function:search * @description * SearchHelper * * All the parts for controlling the search widget on * related collections. * * SearchInit({ * scope: , * set: * url: * list: * }); * */ 'use strict'; angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper']) .factory('SearchInit', ['Alert', 'Rest', 'Refresh', '$location', 'GetBasePath', 'Empty', '$timeout', 'Wait', 'Store', function (Alert, Rest, Refresh, $location, GetBasePath, Empty, $timeout, Wait, Store) { return function (params) { var scope = params.scope, set = params.set, defaultUrl = params.url, list = params.list, iterator = (params.iterator) ? params.iterator : list.iterator, setWidgets = (params.setWidgets === false) ? false : true, sort_order = params.sort_order || '', widgets, i, modifier; // add 'selected' class to the selected li element function setSelectedItem(iterator, label, modifier) { // add 'selected' class to the selected li element $('#' + iterator + 'SearchDropdown' + modifier + ' li').each(function() { $(this).removeClass('selected'); var link = $(this).find('a'); if (label === link.text()) { $(this).addClass('selected'); } }); } function setDefaults(widget) { // Set default values var f, fld, fka, modifier; modifier = (widget === undefined || widget === 1) ? '' : widget; scope[iterator + 'SearchField' + modifier] = ''; scope[iterator + 'SearchFieldLabel' + modifier] = ''; for (fld in list.fields) { if (list.fields[fld].searchWidget === undefined && widget === 1 || list.fields[fld].searchWidget === widget) { if (list.fields[fld].key) { if (list.fields[fld].sourceModel) { fka = list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField; sort_order = (list.fields[fld].desc) ? '-' + fka : fka; } else { sort_order = (list.fields[fld].desc) ? '-' + fld : fld; } if (list.fields[fld].searchable === undefined || list.fields[fld].searchable === true) { scope[iterator + 'SearchField' + modifier] = fld; scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; } break; } } } // Default the search field to 'defaultSearchField', if one exists for (fld in list.fields) { if (list.fields[fld].searchWidget === undefined && widget === 1 || list.fields[fld].searchWidget === widget) { if (list.fields[fld].defaultSearchField) { scope[iterator + 'SearchField' + modifier] = fld; scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; } } } // A field marked as key may not be 'searchable', and there might not be a 'defaultSearchField', // so find the first searchable field. if (Empty(scope[iterator + 'SearchField' + modifier])) { for (fld in list.fields) { if (list.fields[fld].searchWidget === undefined && widget === 1 || list.fields[fld].searchWidget === widget) { if (list.fields[fld].searchable === undefined || list.fields[fld].searchable === true) { scope[iterator + 'SearchField' + modifier] = fld; scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label; break; } } } } scope[iterator + 'SearchType' + modifier] = 'icontains'; scope[iterator + 'SearchTypeLabel' + modifier] = 'Contains'; scope[iterator + 'SearchParams' + modifier] = ''; scope[iterator + 'SearchValue' + modifier] = ''; scope[iterator + 'SelectShow' + modifier] = false; // show/hide the Select scope[iterator + 'HideSearchType' + modifier] = false; scope[iterator + 'InputDisable' + modifier] = false; scope[iterator + 'ExtraParms' + modifier] = ''; scope[iterator + 'ShowStartBtn' + modifier] = true; scope[iterator + 'HideAllStartBtn' + modifier] = false; if (list.fields[scope[iterator + 'SearchField' + modifier]] && list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) { if (scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]) { // if set to a scope variable scope[iterator + 'SearchPlaceholder' + modifier] = scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]; } else { // Set to a string value in the list definition scope[iterator + 'SearchPlaceholder' + modifier] = list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder; } } else { // Default value scope[iterator + 'SearchPlaceholder' + modifier] = 'Search'; } scope[iterator + 'InputDisable' + modifier] = (list.fields[scope[iterator + 'SearchField' + modifier]] && list.fields[scope[iterator + 'SearchField' + modifier]].searchObject === 'all') ? true : false; f = scope[iterator + 'SearchField' + modifier]; if (list.fields[f]) { if (list.fields[f].searchType && (list.fields[f].searchType === 'boolean' || list.fields[f].searchType === 'select')) { scope[iterator + 'SelectShow' + modifier] = true; scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[f].searchOptions; } if (list.fields[f].searchType && list.fields[f].searchType === 'int') { scope[iterator + 'HideSearchType' + modifier] = true; } if (list.fields[f].searchType && list.fields[f].searchType === 'gtzero') { scope[iterator + 'InputHide' + modifier] = true; } } setSelectedItem(iterator, scope[iterator + 'SearchFieldLabel' + modifier], modifier); } if (setWidgets) { // Set default values for each search widget on the page widgets = (list.searchWidgets) ? list.searchWidgets : 1; for (i = 1; i <= widgets; i++) { modifier = (i === 1) ? '' : i; if ($('#search-widget-container' + modifier)) { setDefaults(i); } } } scope[iterator + '_current_search_params'] = { set: set, defaultUrl: defaultUrl, list: list, iterator: iterator, sort_order: sort_order }; Store(iterator + '_current_search_params', scope[iterator + '_current_search_params']); Store('CurrentSearchParams', scope[iterator + '_current_search_params']); // Keeping this around for activity stream // Functions to handle search widget changes scope.setSearchField = function (iterator, fld, label, widget) { var modifier = (widget === undefined || widget === 1) ? '' : widget; scope[iterator + 'SearchFieldLabel' + modifier] = label; scope[iterator + 'SearchField' + modifier] = fld; scope[iterator + 'SearchValue' + modifier] = ''; scope[iterator + 'SelectShow' + modifier] = false; scope[iterator + 'HideSearchType' + modifier] = false; scope[iterator + 'InputHide' + modifier] = false; scope[iterator + 'SearchType' + modifier] = 'icontains'; scope[iterator + 'InputDisable' + modifier] = (list.fields[fld].searchObject === 'all') ? true : false; scope[iterator + 'ShowStartBtn' + modifier] = true; if (list.fields[scope[iterator + 'SearchField' + modifier]] && list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) { if (scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]) { // if set to a scope variable scope[iterator + 'SearchPlaceholder' + modifier] = scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]; } else { // Set to a string value in the list definition scope[iterator + 'SearchPlaceholder' + modifier] = list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder; } } else { // Default value scope[iterator + 'SearchPlaceholder' + modifier] = 'Search'; } if (list.fields[fld].searchType && list.fields[fld].searchType === 'gtzero') { scope[iterator + "InputDisable" + modifier] = true; scope[iterator + 'ShowStartBtn' + modifier] = false; scope.search(iterator); } else if (list.fields[fld].searchSingleValue) { // Query a specific attribute for one specific value // searchSingleValue: true // searchType: 'boolean|int|etc.' // searchValue: < value to match for boolean use 'true'|'false' > scope[iterator + 'InputDisable' + modifier] = true; scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue; // For boolean type, SearchValue must be an object if (list.fields[fld].searchType === 'boolean' && list.fields[fld].searchValue === 'true') { scope[iterator + "SearchSelectValue" + modifier] = { value: 1 }; } else if (list.fields[fld].searchType === 'boolean' && list.fields[fld].searchValue === 'false') { scope[iterator + "SearchSelectValue" + modifier] = { value: 0 }; } else { scope[iterator + "SearchSelectValue" + modifier] = { value: list.fields[fld].searchValue }; } scope[iterator + 'ShowStartBtn' + modifier] = false; } else if (list.fields[fld].searchType === 'in') { scope[iterator + "SearchType" + modifier] = 'in'; scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue; scope[iterator + "InputDisable" + modifier] = true; scope[iterator + 'ShowStartBtn' + modifier] = false; } else if (list.fields[fld].searchType && (list.fields[fld].searchType === 'boolean' || list.fields[fld].searchType === 'select' || list.fields[fld].searchType === 'select_or')) { scope[iterator + 'SelectShow' + modifier] = true; scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[fld].searchOptions; scope[iterator + 'SearchType' + modifier] = ''; } else if (list.fields[fld].searchType && list.fields[fld].searchType === 'int') { //scope[iterator + 'HideSearchType' + modifier] = true; scope[iterator + 'SearchType' + modifier] = 'int'; } else if (list.fields[fld].searchType && list.fields[fld].searchType === 'isnull') { scope[iterator + 'SearchType' + modifier] = 'isnull'; scope[iterator + 'InputDisable' + modifier] = true; scope[iterator + 'SearchValue' + modifier] = 'true'; scope[iterator + 'ShowStartBtn' + modifier] = false; } setSelectedItem(iterator, label, modifier); scope.search(iterator); }; scope.resetSearch = function (iterator) { // Respdond to click of reset button var i, widgets = (list.searchWidgets) ? list.searchWidgets : 1; for (i = 1; i <= widgets; i++) { // Clear each search widget setDefaults(i); } // Force removal of search keys from the URL window.location = '/#' + $location.path(); scope.search(iterator); }; if (scope.removeDoSearch) { scope.removeDoSearch(); } scope.removeDoSearch = scope.$on('doSearch', function (e, iterator, page, load, calcOnly, deferWaitStop) { // // Execute the search // var url = (calcOnly) ? '' : defaultUrl, connect; if (!calcOnly) { scope[iterator + 'Loading'] = (load === undefined || load === true) ? true : false; scope[iterator + 'Page'] = (page) ? parseInt(page) - 1 : 0; } //finalize and execute the query if (scope[iterator + 'SearchParams']) { if (/\/$/.test(url)) { url += '?' + scope[iterator + 'SearchParams']; } else { url += '&' + scope[iterator + 'SearchParams']; } } connect = (/\/$/.test(url)) ? '?' : '&'; url += (scope[iterator + '_page_size']) ? connect + 'page_size=' + scope[iterator + '_page_size'] : ""; if (page) { connect = (/\/$/.test(url)) ? '?' : '&'; url += connect + 'page=' + page; } if (scope[iterator + 'ExtraParms']) { connect = (/\/$/.test(url)) ? '?' : '&'; url += connect + scope[iterator + 'ExtraParms']; } url = url.replace(/\&\&/g, '&').replace(/\?\&/,'?'); if (calcOnly) { scope.$emit('searchParamsReady', url); } else if (defaultUrl && !/undefined/.test(url)) { Refresh({ scope: scope, set: set, iterator: iterator, url: url, deferWaitStop: deferWaitStop }); } e.stopPropagation(); }); if (scope.removePrepareSearch) { scope.removePrepareSearch(); } scope.removePrepareSearch = scope.$on('prepareSearch', function (e, iterator, page, load, calcOnly, deferWaitStop, spinner) { // // Start building the search key/value pairs. This will process each search widget, if the // selected field is an object type (used on activity stream). // if (spinner) { Wait('start'); } scope[iterator + 'SearchParams'] = ''; var i, modifier, widgets = (list.searchWidgets) ? list.searchWidgets : 1; for (i = 1; i <= widgets; i++) { modifier = (i === 1) ? '' : i; if ($('#search-widget-container' + modifier)) { if (list.fields[scope[iterator + 'SearchField' + modifier]] && list.fields[scope[iterator + 'SearchField' + modifier]].searchObject) { // Search field of object type if (list.fields[scope[iterator + 'SearchField' + modifier]].searchObject !== 'all') { // An object type is selected scope[iterator + 'HideAllStartBtn' + modifier] = false; if (scope[iterator + 'SearchValue' + modifier]) { // A search value was entered scope[iterator + 'ShowStartBtn' + modifier] = false; if (list.fields[scope[iterator + 'SearchField' + modifier]].searchOnID) { scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject + '__id=' + scope[iterator + 'SearchValue' + modifier]; } else { scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject + ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchObject === 'user') ? '__username__icontains=' : '__name__icontains=' ) + scope[iterator + 'SearchValue' + modifier]; } } else { // Search value is empty scope[iterator + 'ShowStartBtn' + modifier] = true; scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].searchField + '=' + list.fields[scope[iterator + 'SearchField' + modifier]].searchObject; } } else { // Object Type set to All scope[iterator + 'HideAllStartBtn' + modifier] = true; } } } } e.stopPropagation(); scope.$emit('prepareSearch2', iterator, page, load, calcOnly, deferWaitStop); }); if (scope.removePrepareSearch2) { scope.removePrepareSearch2(); } scope.removePrepareSearch2 = scope.$on('prepareSearch2', function (e, iterator, page, load, calcOnly, deferWaitStop) { // Continue building the search by examining the remaining search widgets. If we're looking at activity_stream, // there's more than one. var i, modifier, widgets = (list.searchWidgets) ? list.searchWidgets : 1; for (i = 1; i <= widgets; i++) { modifier = (i === 1) ? '' : i; scope[iterator + 'HoldInput' + modifier] = true; if ($('#search-widget-container' + modifier) && list.fields[scope[iterator + 'SearchField' + modifier]] && !list.fields[scope[iterator + 'SearchField' + modifier]].searchObject) { // if the search widget exists and its value is not an object, add its parameters to the query if (scope[iterator + 'SearchValue' + modifier]) { // if user typed a value in the input box, show the reset link scope[iterator + 'ShowStartBtn' + modifier] = false; } else { scope[iterator + 'ShowStartBtn' + modifier] = true; } if ((!scope[iterator + 'SelectShow' + modifier] && !Empty(scope[iterator + 'SearchValue' + modifier])) || (scope[iterator + 'SelectShow' + modifier] && scope[iterator + 'SearchSelectValue' + modifier]) || (list.fields[scope[iterator + 'SearchField' + modifier]] && list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'gtzero')) { if (list.fields[scope[iterator + 'SearchField' + modifier]].searchField) { scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].searchField + '__'; } else if (list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel) { // handle fields whose source is a related model e.g. inventories.organization scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel + '__' + list.fields[scope[iterator + 'SearchField' + modifier]].sourceField + '__'; } else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select' && Empty(scope[iterator + 'SearchSelectValue' + modifier].value) ) { scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier] + '__'; } else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select' && !Empty(scope[iterator + 'SearchSelectValue' + modifier].value) ) { scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier]; } else { scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier] + '__'; } if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'int' || list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'boolean')) { scope[iterator + 'SearchParams'] += 'int='; } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'gtzero') { scope[iterator + 'SearchParams'] += 'gt=0'; } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'isnull') { scope[iterator + 'SearchParams'] += 'isnull='; } else if ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select') && Empty(scope[iterator + 'SearchSelectValue' + modifier].value) && !/\_\_$/.test(scope[iterator + 'SearchParams']) ) { scope[iterator + 'SearchParams'] += '=iexact='; } else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'in') { if (!/\_\_$/.test(scope[iterator + 'SearchParams'])) { scope[iterator + 'SearchParams'] += '__'; } scope[iterator + 'SearchParams'] += 'in='; } else if (/\_\_$/.test(scope[iterator + 'SearchParams'])) { scope[iterator + 'SearchParams'] += 'icontains='; } else { scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType' + modifier] + '='; } if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'boolean' || list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select')) { scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue' + modifier].value; } else { if ((!list.fields[scope[iterator + 'SearchField' + modifier]].searchType) || (list.fields[scope[iterator + 'SearchField' + modifier]].searchType && list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'or' && list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'gtzero')) { scope[iterator + 'SearchParams'] += encodeURI(scope[iterator + 'SearchValue' + modifier]); } } } } } if ((iterator === 'inventory' && scope.inventoryFailureFilter) || (iterator === 'host' && scope.hostFailureFilter)) { //Things that bypass the search widget. Should go back and add a second widget possibly on //inventory pages and eliminate this scope[iterator + 'SearchParams'] += '&has_active_failures=true'; } if (sort_order) { scope[iterator + 'SearchParams'] += (scope[iterator + 'SearchParams']) ? '&' : ''; scope[iterator + 'SearchParams'] += 'order_by=' + encodeURI(sort_order); } e.stopPropagation(); scope.$emit('doSearch', iterator, page, load, calcOnly, deferWaitStop); }); scope.startSearch = function (e, iterator) { // If use clicks enter while on input field, start the search if (e.keyCode === 13) { scope.search(iterator); } }; /** * Initiate a searh. * * @iterator: required, list.iterator value * @Page: optional. Added to accomodate back function on Job Events detail. * @Load: optional, set to false if 'Loading' message not desired * @calcOnly: optional, set to true when you want to calc or figure out search params without executing the search * @deferWaitStop: optional, when true refresh.js will NOT issue Wait('stop'), thus leaving the spinner. Caller is then * responsible for stopping the spinner post refresh. * @spinner: optional, if false, don't show the spinner. */ scope.search = function (iterator, page, load, calcOnly, deferWaitStop, spinner) { page = page || null; load = (load || !scope[set] || scope[set].length === 0) ? true : false; calcOnly = (calcOnly) ? true : false; deferWaitStop = (deferWaitStop) ? true : false; spinner = (spinner === undefined) ? true : spinner; if (load) { scope[set] = []; //clear the list array to make sure 'Loading' is the only thing visible on the list } scope.$emit('prepareSearch', iterator, page, load, calcOnly, deferWaitStop, spinner); }; scope.sort = function (iterator, fld) { // Reset sort icons back to 'icon-sort' on all columns // except the one clicked. $('.list-header').each(function () { if ($(this).attr('id') !== iterator + '-' + fld + '-header') { var icon = $(this).find('i'); icon.attr('class', 'fa fa-sort'); } }); // Toggle the icon for the clicked column // and set the sort direction var icon = $('#' + iterator + '-' + fld + '-header i'), direction = ''; if (icon.hasClass('fa-sort')) { icon.removeClass('fa-sort'); icon.addClass('fa-sort-up'); } else if (icon.hasClass('fa-sort-up')) { icon.removeClass('fa-sort-up'); icon.addClass('fa-sort-down'); direction = '-'; } else if (icon.hasClass('fa-sort-down')) { icon.removeClass('fa-sort-down'); icon.addClass('fa-sort-up'); } // Set the sorder order value and call the API to refresh the list with the new order if (list.fields[fld].searchField) { sort_order = direction + list.fields[fld].searchField; } else if (list.fields[fld].sortField) { sort_order = direction + list.fields[fld].sortField; } else { if (list.fields[fld].sourceModel) { sort_order = direction + list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField; } else { sort_order = direction + fld; } } scope[list.iterator + '_current_search_params'].sort_order = sort_order; Store(iterator + '_current_search_params', scope[iterator + '_current_search_params']); scope.search(list.iterator); }; // Call after modal dialogs to remove any lingering callbacks scope.searchCleanup = function () { scope.removeDoSearch(); scope.removePrepareSearch(); scope.removePrepareSearch2(); }; }; } ]);