refactor, lint, separate data transformation logic from display logic

This commit is contained in:
Jake McDermott 2018-03-04 16:25:10 -05:00
parent c12173233b
commit 0adf671de4
No known key found for this signature in database
GPG Key ID: 3B02CAD476EECB35
3 changed files with 424 additions and 402 deletions

View File

@ -44,19 +44,16 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
key = this.replaceDefaultFlags(key);
if (!Array.isArray(values)) {
values = this.replaceEncodedTokens(values);
return `${key}=${values}`;
values = [values];
}
return values
.map(value => {
value = this.replaceDefaultFlags(value);
value = this.replaceEncodedTokens(value);
return `${key}=${value}`;
return [key, value]
})
.join('&');
},
// encodes ui-router params from {operand__key__comparator: value} pairs to API-consumable URL
encodeQueryset(params) {
@ -69,15 +66,33 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
result += '&';
}
return result += this.encodeTerms(value, key);
const encodedTermString = this.encodeTerms(value, key)
.map(([key, value]) => `${key}=${value}`)
.join('&');
return result += encodedTermString;
}, '?');
},
// like encodeQueryset, but return an actual unstringified API-consumable http param object
encodeQuerysetObject(params) {
return _.reduce(params, (obj, value, key) => {
const encodedTerms = this.encodeTerms(value, key);
for (let encodedIndex in encodedTerms) {
const [encodedKey, encodedValue] = encodedTerms[encodedIndex];
obj[encodedKey] = obj[encodedKey] || [];
obj[encodedKey].push(encodedValue)
}
return obj;
}, {});
},
// encodes a ui smart-search param to a django-friendly param
// operand:key:comparator:value => {operand__key__comparator: value}
encodeParam(params){
encodeParam({ term, relatedSearchTerm, searchTerm, singleSearchParam }){
// Assumption here is that we have a key and a value so the length
// of the paramParts array will be 2. [0] is the key and [1] the value
let paramParts = SmartSearchService.splitTermIntoParts(params.term);
let paramParts = SmartSearchService.splitTermIntoParts(term);
let keySplit = paramParts[0].split('.');
let exclude = false;
let lessThanGreaterThan = paramParts[1].match(/^(>|<).*$/) ? true : false;
@ -88,16 +103,16 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
let paramString = exclude ? "not__" : "";
let valueString = paramParts[1];
if(keySplit.length === 1) {
if(params.searchTerm && !lessThanGreaterThan) {
if(params.singleSearchParam) {
if(searchTerm && !lessThanGreaterThan) {
if(singleSearchParam) {
paramString += keySplit[0] + '__icontains';
}
else {
paramString += keySplit[0] + '__icontains_DEFAULT';
}
}
else if(params.relatedSearchTerm) {
if(params.singleSearchParam) {
else if(relatedSearchTerm) {
if(singleSearchParam) {
paramString += keySplit[0];
}
else {
@ -131,8 +146,8 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
}
}
if(params.singleSearchParam) {
return {[params.singleSearchParam]: paramString + "=" + valueString};
if(singleSearchParam) {
return {[singleSearchParam]: paramString + "=" + valueString};
}
else {
return {[paramString] : encodeURIComponent(valueString)};
@ -189,7 +204,14 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
return decodeParamString(value);
}
},
convertToSearchTags(obj) {
const tags = [];
for (let key in obj) {
const value = obj[key];
tags.push(this.decodeParam(value, key));
}
return tags;
},
// encodes a django queryset for ui-router's URLMatcherFactory
// {operand__key__comparator: value, } => 'operand:key:comparator:value;...'
// value.isArray expands to:

View File

@ -1,422 +1,422 @@
export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', 'SmartSearchService', 'i18n', 'ConfigService', '$transitions',
function($stateParams, $scope, $state, GetBasePath, qs, SmartSearchService, i18n, configService, $transitions) {
function SmartSearchController (
$scope,
$state,
$stateParams,
$transitions,
configService,
GetBasePath,
i18n,
qs,
SmartSearchService
) {
const searchKey = `${$scope.iterator}_search`;
const optionsKey = `${$scope.list.iterator}_options`;
let path,
defaults,
queryset,
transitionSuccessListener;
let path;
let defaults;
let queryset;
let transitionSuccessListener;
configService.getConfig()
.then(config => init(config));
configService.getConfig()
.then(config => init(config));
function init(config) {
let version;
function init (config) {
let version;
try {
version = config.version.split('-')[0];
} catch (err) {
version = 'latest';
}
try {
[version] = config.version.split('-');
} catch (err) {
version = 'latest';
}
$scope.documentationLink = `http://docs.ansible.com/ansible-tower/${version}/html/userguide/search_sort.html`;
$scope.documentationLink = `http://docs.ansible.com/ansible-tower/${version}/html/userguide/search_sort.html`;
$scope.searchPlaceholder = i18n._('Search');
if($scope.defaultParams) {
defaults = $scope.defaultParams;
}
else {
// steps through the current tree of $state configurations, grabs default search params
defaults = _.find($state.$current.path, (step) => {
if(step && step.params && step.params.hasOwnProperty(`${$scope.iterator}_search`)){
return step.params.hasOwnProperty(`${$scope.iterator}_search`);
}
}).params[`${$scope.iterator}_search`].config.value;
}
if ($scope.defaultParams) {
defaults = $scope.defaultParams;
} else {
// steps through the current tree of $state configurations, grabs default search params
const stateConfig = _.find($state.$current.path, step => _.has(step, `params.${searchKey}`));
defaults = stateConfig.params[searchKey].config.value;
}
if($scope.querySet) {
queryset = _.cloneDeep($scope.querySet);
}
else {
queryset = $state.params[`${$scope.iterator}_search`];
}
if ($scope.querySet) {
queryset = _.cloneDeep($scope.querySet);
} else {
queryset = $state.params[searchKey];
}
path = GetBasePath($scope.basePath) || $scope.basePath;
generateSearchTags();
qs.initFieldset(path, $scope.djangoModel).then((data) => {
path = GetBasePath($scope.basePath) || $scope.basePath;
generateSearchTags();
qs.initFieldset(path, $scope.djangoModel)
.then((data) => {
$scope.models = data.models;
$scope.options = data.options.data;
if ($scope.list) {
$scope.$emit(`${$scope.list.iterator}_options`, data.options);
}
});
$scope.searchPlaceholder = $scope.disableSearch ? i18n._('Cannot search running job') : i18n._('Search');
function compareParams(a, b) {
for (let key in a) {
if (!(key in b) || a[key].toString() !== b[key].toString()) {
return false;
}
}
for (let key in b) {
if (!(key in a)) {
return false;
}
}
return true;
}
if(transitionSuccessListener) {
transitionSuccessListener();
}
transitionSuccessListener = $transitions.onSuccess({}, function(trans) {
// State has changed - check to see if this is a param change
if(trans.from().name === trans.to().name) {
if(!compareParams(trans.params('from')[`${$scope.iterator}_search`], trans.params('to')[`${$scope.iterator}_search`])) {
// Params are not the same - we need to update the search. This should only happen when the user
// hits the forward/back navigation buttons in their browser.
queryset = trans.params('to')[`${$scope.iterator}_search`];
qs.search(path, queryset).then((res) => {
$scope.dataset = res.data;
$scope.collection = res.data.results;
$scope.$emit('updateDataset', res.data);
});
$scope.searchTerm = null;
generateSearchTags();
}
$scope.$emit(optionsKey, data.options);
}
});
$scope.$on('$destroy', transitionSuccessListener);
function compareParams(a, b) {
for (let key in a) {
if (!(key in b) || a[key].toString() !== b[key].toString()) {
return false;
}
}
for (let key in b) {
if (!(key in a)) {
return false;
}
}
return true;
}
$scope.$watch('disableSearch', function(disableSearch){
if(disableSearch) {
$scope.searchPlaceholder = i18n._('Cannot search running job');
}
else {
$scope.searchPlaceholder = i18n._('Search');
if (transitionSuccessListener) {
transitionSuccessListener();
}
transitionSuccessListener = $transitions.onSuccess({}, trans => {
// State has changed - check to see if this is a param change
if (trans.from().name === trans.to().name) {
if (!compareParams(trans.params('from')[searchKey], trans.params('to')[searchKey])) {
// Params are not the same - we need to update the search. This should only
// happen when the user hits the forward/back browser navigation buttons.
queryset = trans.params('to')[searchKey];
qs.search(path, queryset).then((res) => {
$scope.dataset = res.data;
$scope.collection = res.data.results;
$scope.$emit('updateDataset', res.data);
});
$scope.searchTerm = null;
generateSearchTags();
}
}
});
$scope.$on('$destroy', transitionSuccessListener);
$scope.$watch('disableSearch', disableSearch => {
if (disableSearch) {
$scope.searchPlaceholder = i18n._('Cannot search running job');
} else {
$scope.searchPlaceholder = i18n._('Search');
}
});
}
function generateSearchTags () {
$scope.searchTags = [];
const querysetCopy = angular.copy(queryset);
if ($scope.singleSearchParam && querysetCopy[$scope.singleSearchParam]) {
const searchParam = querysetCopy[$scope.singleSearchParam].split('%20and%20');
delete querysetCopy[$scope.singleSearchParam];
$.each(searchParam, (index, param) => {
const paramParts = decodeURIComponent(param).split(/=(.+)/);
const reconstructedSearchString = qs.decodeParam(paramParts[1], paramParts[0]);
$scope.searchTags.push(reconstructedSearchString);
});
}
function generateSearchTags() {
$scope.searchTags = [];
$scope.searchTags = $scope.searchTags.concat(qs.stripDefaultParams(querysetCopy, defaults));
}
let querysetCopy = angular.copy(queryset);
if($scope.singleSearchParam && querysetCopy[$scope.singleSearchParam]) {
let searchParam = querysetCopy[$scope.singleSearchParam].split('%20and%20');
delete querysetCopy[$scope.singleSearchParam];
$.each(searchParam, function(index, param) {
let paramParts = decodeURIComponent(param).split(/=(.+)/);
let reconstructedSearchString = qs.decodeParam(paramParts[1], paramParts[0]);
$scope.searchTags.push(reconstructedSearchString);
});
function revertSearch (queryToBeRestored) {
queryset = queryToBeRestored;
// https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic
// This transition will not reload controllers/resolves/views
// but will register new $stateParams[$scope.iterator + '_search'] terms
if (!$scope.querySet) {
$state.go('.', { [searchKey]: queryset });
}
qs.search(path, queryset).then((res) => {
if ($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = res.data;
$scope.collection = res.data.results;
});
$scope.searchTags = $scope.searchTags.concat(qs.stripDefaultParams(querysetCopy, defaults));
$scope.searchTerm = null;
generateSearchTags();
}
$scope.toggleKeyPane = () => {
$scope.showKeyPane = !$scope.showKeyPane;
};
function searchWithoutKey (term, singleSearchParam = null) {
if (singleSearchParam) {
return { [singleSearchParam]: `search=${encodeURIComponent(term)}` };
}
return { search: encodeURIComponent(term) };
}
function isAnsibleFactSearchTerm (termParts) {
const rootField = termParts[0].split('.')[0].replace(/^-/, '');
return rootField === 'ansible_facts';
}
function isRelatedField (termParts) {
const rootField = termParts[0].split('.')[0].replace(/^-/, '');
const listName = $scope.list.name;
const baseRelatedTypePath = `models.${listName}.base.${rootField}.type`;
const isRelatedSearchTermField = (_.contains($scope.models[listName].related, rootField));
const isBaseModelRelatedSearchTermField = (_.get($scope, baseRelatedTypePath) === 'field');
return (isRelatedSearchTermField || isBaseModelRelatedSearchTermField);
}
function getSearchInputQueryset ({ terms, singleSearchParam }) {
let params = {};
// remove leading/trailing whitespace
terms = (terms) ? terms.trim() : '';
let splitTerms;
if (singleSearchParam === 'host_filter') {
splitTerms = SmartSearchService.splitFilterIntoTerms(terms);
} else {
splitTerms = SmartSearchService.splitSearchIntoTerms(terms);
}
function revertSearch(queryToBeRestored) {
queryset = queryToBeRestored;
// https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic
// This transition will not reload controllers/resolves/views
// but will register new $stateParams[$scope.iterator + '_search'] terms
if(!$scope.querySet) {
$state.go('.', {
[$scope.iterator + '_search']: queryset });
const combineSameSearches = (a, b) => {
if (!a) {
return undefined;
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = res.data;
$scope.collection = res.data.results;
});
$scope.searchTerm = null;
generateSearchTags();
}
function searchWithoutKey(term) {
if($scope.singleSearchParam) {
return {
[$scope.singleSearchParam]: "search=" + encodeURIComponent(term)
};
if (_.isArray(a)) {
return a.concat(b);
}
return {
search: encodeURIComponent(term)
};
}
$scope.toggleKeyPane = function() {
$scope.showKeyPane = !$scope.showKeyPane;
if (singleSearchParam) {
return `${a}%20and%20${b}`;
}
return [a, b];
};
// add a search tag, merge new queryset, $state.go()
$scope.addTerm = function(terms) {
let params = {},
origQueryset = _.clone(queryset);
// Remove leading/trailing whitespace if there is any
terms = (terms) ? terms.trim() : "";
if(terms && terms !== '') {
let splitTerms;
if ($scope.singleSearchParam === 'host_filter') {
splitTerms = SmartSearchService.splitFilterIntoTerms(terms);
} else {
splitTerms = SmartSearchService.splitSearchIntoTerms(terms);
}
_.forEach(splitTerms, (term) => {
let termParts = SmartSearchService.splitTermIntoParts(term);
function combineSameSearches(a,b){
if (_.isArray(a)) {
return a.concat(b);
}
else {
if(a) {
if($scope.singleSearchParam) {
return a + "%20and%20" + b;
}
else {
return [a,b];
}
}
}
}
if($scope.singleSearchParam) {
if (termParts.length === 1) {
params = _.merge(params, searchWithoutKey(term), combineSameSearches);
}
else {
let root = termParts[0].split(".")[0].replace(/^-/, '');
if(_.has($scope.models[$scope.list.name].base, root) || root === "ansible_facts") {
if(_.has($scope.models[$scope.list.name].base[root], "type") && $scope.models[$scope.list.name].base[root].type === 'field'){
// Intent is to land here for searching on the base model.
params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true, singleSearchParam: $scope.singleSearchParam ? $scope.singleSearchParam : false}), combineSameSearches);
}
else {
// Intent is to land here when performing ansible_facts searches
params = _.merge(params, qs.encodeParam({term: term, searchTerm: true, singleSearchParam: $scope.singleSearchParam ? $scope.singleSearchParam : false}), combineSameSearches);
}
}
else if(_.contains($scope.models[$scope.list.name].related, root)) {
// Intent is to land here for related searches
params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true, singleSearchParam: $scope.singleSearchParam ? $scope.singleSearchParam : false}), combineSameSearches);
}
// Its not a search term or a related search term - treat it as a string
else {
params = _.merge(params, searchWithoutKey(term), combineSameSearches);
}
}
}
else {
// if only a value is provided, search using default keys
if (termParts.length === 1) {
params = _.merge(params, searchWithoutKey(term), combineSameSearches);
} else {
// Figure out if this is a search term
let root = termParts[0].split(".")[0].replace(/^-/, '');
if(_.has($scope.models[$scope.list.name].base, root)) {
if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') {
params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches);
}
else {
params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches);
}
}
// The related fields need to also be checked for related searches.
// The related fields for the search are retrieved from the API
// options endpoint, and are stored in the $scope.model. FYI, the
// Django search model is what sets the related fields on the model.
else if(_.contains($scope.models[$scope.list.name].related, root)) {
params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches);
}
// Its not a search term or a related search term - treat it as a string
else {
params = _.merge(params, searchWithoutKey(term), combineSameSearches);
}
}
}
});
queryset = _.merge({}, queryset, params, (objectValue, sourceValue, key, object) => {
if (object[key] && object[key] !== sourceValue){
if(_.isArray(object[key])) {
// Add the new value to the array and return
object[key].push(sourceValue);
return object[key];
}
else {
if($scope.singleSearchParam) {
if(!object[key]) {
return sourceValue;
}
else {
let singleSearchParamKeys = object[key].split("%20and%20");
if(_.includes(singleSearchParamKeys, sourceValue)) {
return object[key];
}
else {
return object[key] + "%20and%20" + sourceValue;
}
}
}
// Start the array of keys
return [object[key], sourceValue];
}
}
else {
// // https://lodash.com/docs/3.10.1#merge
// If customizer fn returns undefined merging is handled by default _.merge algorithm
return undefined;
}
});
// Go back to the first page after a new search
delete queryset.page;
// https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic
// This transition will not reload controllers/resolves/views
// but will register new $stateParams[$scope.iterator + '_search'] terms
if(!$scope.querySet) {
$state.go('.', {[$scope.iterator + '_search']:queryset }).then(function(){
// ISSUE: same as above in $scope.remove. For some reason deleting the page
// from the queryset works for all lists except lists in modals.
delete $stateParams[$scope.iterator + '_search'].page;
});
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = res.data;
$scope.collection = res.data.results;
})
.catch(function() {
revertSearch(origQueryset);
});
$scope.searchTerm = null;
generateSearchTags();
}
};
// remove tag, merge new queryset, $state.go
$scope.removeTerm = function(index) {
let tagToRemove = $scope.searchTags.splice(index, 1)[0],
termParts = SmartSearchService.splitTermIntoParts(tagToRemove),
removed;
let removeFromQuerySet = function(set) {
_.each(removed, (value, key) => {
if (Array.isArray(set[key])){
_.remove(set[key], (item) => item === value);
// If the array is now empty, remove that key
if (set[key].length === 0) {
delete set[key];
}
} else {
if ($scope.singleSearchParam && set[$scope.singleSearchParam] && set[$scope.singleSearchParam].includes("%20and%20")) {
let searchParamParts = set[$scope.singleSearchParam].split("%20and%20");
// The value side of each paramPart might have been encoded in SmartSearch.splitFilterIntoTerms
_.each(searchParamParts, (paramPart, paramPartIndex) => {
searchParamParts[paramPartIndex] = decodeURIComponent(paramPart);
});
var index = searchParamParts.indexOf(value);
if (index !== -1) {
searchParamParts.splice(index, 1);
}
set[$scope.singleSearchParam] = searchParamParts.join("%20and%20");
} else {
delete set[key];
}
}
});
};
_.each(splitTerms, term => {
const termParts = SmartSearchService.splitTermIntoParts(term);
let termParams;
if (termParts.length === 1) {
removed = searchWithoutKey(tagToRemove);
termParams = searchWithoutKey(term, singleSearchParam);
} else if (isAnsibleFactSearchTerm(termParts)) {
termParams = qs.encodeParam({ term, singleSearchParam });
} else if (isRelatedField(termParts)) {
termParams = qs.encodeParam({ term, singleSearchParam, related: true });
} else {
let root = termParts[0].split(".")[0].replace(/^-/, '');
let encodeParams = {
term: tagToRemove,
singleSearchParam: $scope.singleSearchParam ? $scope.singleSearchParam : false
};
if($scope.models[$scope.list.name]) {
if($scope.singleSearchParam) {
removed = qs.encodeParam(encodeParams);
}
else if(_.has($scope.models[$scope.list.name].base, root)) {
if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') {
encodeParams.relatedSearchTerm = true;
}
else {
encodeParams.searchTerm = true;
}
removed = qs.encodeParam(encodeParams);
}
else if(_.contains($scope.models[$scope.list.name].related, root)) {
encodeParams.relatedSearchTerm = true;
removed = qs.encodeParam(encodeParams);
}
else {
removed = searchWithoutKey(termParts[termParts.length-1]);
}
}
else {
removed = searchWithoutKey(termParts[termParts.length-1]);
}
termParams = qs.encodeParam({ term, singleSearchParam });
}
removeFromQuerySet(queryset);
if (!$scope.querySet) {
$state.go('.', {
[$scope.iterator + '_search']: queryset }).then(function(){
// ISSUE: for some reason deleting a tag from a list in a modal does not
// remove the param from $stateParams. Here we'll manually check to make sure
// that that happened and remove it if it didn't.
removeFromQuerySet($stateParams[`${$scope.iterator}_search`]);
});
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
$scope.querySet = queryset;
}
params = _.merge(params, termParams, combineSameSearches);
});
$scope.dataset = res.data;
$scope.collection = res.data.results;
generateSearchTags();
});
};
$scope.clearAllTerms = function(){
let cleared = _.cloneDeep(defaults);
delete cleared.page;
queryset = cleared;
if(!$scope.querySet) {
$state.go('.', {[$scope.iterator + '_search']: queryset});
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = res.data;
$scope.collection = res.data.results;
});
$scope.searchTags = qs.stripDefaultParams(queryset, defaults);
};
return params;
}
function mergeQueryset (qset, additional, singleSearchParam) {
const merged = _.merge({}, qset, additional, (objectValue, sourceValue, key, object) => {
if (!(object[key] && object[key] !== sourceValue)) {
// // https://lodash.com/docs/3.10.1#each
// If this returns undefined merging is handled by default _.merge algorithm
return undefined;
}
if (_.isArray(object[key])) {
object[key].push(sourceValue);
return object[key];
}
if (singleSearchParam) {
if (!object[key]) {
return sourceValue;
}
const singleSearchParamKeys = object[key].split('%20and%20');
if (_.includes(singleSearchParamKeys, sourceValue)) {
return object[key];
}
return `${object[key]}%20and%20${sourceValue}`;
}
// Start the array of keys
return [object[key], sourceValue];
});
return merged;
}
$scope.addTerms = terms => {
const { singleSearchParam } = $scope;
const origQueryset = _.clone(queryset);
// Remove leading/trailing whitespace if there is any
terms = (terms) ? terms.trim() : '';
if (!(terms && terms !== '')) {
return;
}
const searchInputQueryset = getSearchInputQueryset({ terms, singleSearchParam });
queryset = mergeQueryset(queryset, searchInputQueryset, singleSearchParam);
// Go back to the first page after a new search
delete queryset.page;
// https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic
// This transition will not reload controllers/resolves/views but will register new
// $stateParams[searchKey] terms.
if (!$scope.querySet) {
$state.go('.', { [searchKey]: queryset })
.then(() => {
// same as above in $scope.remove. For some reason deleting the page
// from the queryset works for all lists except lists in modals.
delete $stateParams[searchKey].page;
});
}
qs.search(path, queryset)
.then(({ data }) => {
if ($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = data;
$scope.collection = data.results;
})
.catch(() => revertSearch(origQueryset));
$scope.searchTerm = null;
generateSearchTags();
};
function removeTermsFromQueryset(qset, term, singleSearchParam = null) {
const returnedQueryset = _.cloneDeep(qset);
const removeSingleTermFromQueryset = (value, key) => {
const space = '%20and%20';
if (Array.isArray(returnedQueryset[key])) {
returnedQueryset[key] = returnedQueryset[key].filter(item => item !== value);
if (returnedQueryset[key].length < 1) {
delete returnedQueryset[key];
}
} else if (singleSearchParam && _.get(returnedQueryset, singleSearchParam, []).includes(space)) {
const searchParamParts = returnedQueryset[singleSearchParam].split(space);
// The value side of each paramPart might have been encoded in
// SmartSearch.splitFilterIntoTerms
_.each(searchParamParts, (paramPart, paramPartIndex) => {
searchParamParts[paramPartIndex] = decodeURIComponent(paramPart);
});
const paramPartIndex = searchParamParts.indexOf(value);
if (paramPartIndex !== -1) {
searchParamParts.splice(paramPartIndex, 1);
}
returnedQueryset[singleSearchParam] = searchParamParts.join(space);
} else {
delete returnedQueryset[key];
}
};
const termParts = SmartSearchService.splitTermIntoParts(term);
let removed;
if (termParts.length === 1) {
removed = searchWithoutKey(term, singleSearchParam);
} else if (isRelatedField(termParts)) {
removed = qs.encodeParam({ term, singleSearchParam, related: true });
} else {
removed = qs.encodeParam({ term, singleSearchParam });
}
if (!removed) {
removed = searchWithoutKey(termParts[termParts.length - 1], singleSearchParam);
}
_.each(removed, removeSingleTermFromQueryset);
return returnedQueryset;
}
// remove tag, merge new queryset, $state.go
$scope.removeTerm = index => {
const { singleSearchParam } = $scope;
const [term] = $scope.searchTags.splice(index, 1);
const modifiedQueryset = removeTermsFromQueryset(queryset, term, singleSearchParam);
if (!$scope.querySet) {
$state.go('.', { [searchKey]: modifiedQueryset })
.then(() => {
// for some reason deleting a tag from a list in a modal does not
// remove the param from $stateParams. Here we'll manually check to make sure
// that that happened and remove it if it didn't.
const clearedParams = removeTermsFromQueryset(
$stateParams[searchKey], term, singleSearchParam);
$stateParams[searchKey] = clearedParams;
});
}
qs.search(path, queryset)
.then(({ data }) => {
if ($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = data;
$scope.collection = data.results;
});
generateSearchTags();
};
$scope.clearAllTerms = () => {
const cleared = _.cloneDeep(defaults);
delete cleared.page;
queryset = cleared;
if (!$scope.querySet) {
$state.go('.', { [searchKey]: queryset });
}
qs.search(path, queryset)
.then(({ data }) => {
if ($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = data;
$scope.collection = data.results;
});
$scope.searchTags = qs.stripDefaultParams(queryset, defaults);
};
}
SmartSearchController.$inject = [
'$scope',
'$state',
'$stateParams',
'$transitions',
'ConfigService',
'GetBasePath',
'i18n',
'QuerySet',
'SmartSearchService',
];
export default SmartSearchController;

View File

@ -3,11 +3,11 @@
<div class="SmartSearch-bar" ng-class="{'SmartSearch-bar--fullWidth': searchBarFullWidth}">
<div class="SmartSearch-searchTermContainer">
<!-- string search input -->
<form name="smartSearch" class="SmartSearch-form" aw-enter-key="addTerm(searchTerm)" novalidate>
<form name="smartSearch" class="SmartSearch-form" aw-enter-key="addTerms(searchTerm)" novalidate>
<input class="SmartSearch-input" ng-model="searchTerm" placeholder="{{searchPlaceholder}}"
ng-disabled="disableSearch">
</form>
<div type="submit" class="SmartSearch-searchButton" ng-disabled="!searchTerm" ng-click="addTerm(searchTerm)">
<div type="submit" class="SmartSearch-searchButton" ng-disabled="!searchTerm" ng-click="addTerms(searchTerm)">
<i class="fa fa-search"></i>
</div>
</div>