From 080c87ffeabe2b328d760b751d67b5fb0ec3eb3a Mon Sep 17 00:00:00 2001 From: Ere Maijala Date: Wed, 18 Dec 2024 15:15:57 +0200 Subject: [PATCH] Fix multi-facet selection on older browsers. This includes all browsers pre-2023 (e.g. Safari on iSO 16.x). --- themes/bootstrap3/js/common.js | 69 +++++++++++++++++++++++++++++++++- themes/bootstrap3/js/facets.js | 38 +++++++++++-------- themes/bootstrap5/js/common.js | 69 +++++++++++++++++++++++++++++++++- themes/bootstrap5/js/facets.js | 38 +++++++++++-------- 4 files changed, 180 insertions(+), 34 deletions(-) diff --git a/themes/bootstrap3/js/common.js b/themes/bootstrap3/js/common.js index b939b531b839..ad50c88b0871 100644 --- a/themes/bootstrap3/js/common.js +++ b/themes/bootstrap3/js/common.js @@ -487,6 +487,70 @@ var VuFind = (function VuFind() { elem.style.transitionDuration = state; } + /** + * Check if URLSearchParams contains the given key+value + * + * URLSearchParams.has(key, value) support is not yet widespread enough to be used + * (see https://caniuse.com/mdn-api_urlsearchparams_has_value_parameter) + * + * @param {URLSearchParams} params URLSearchParams to check + * @param {string} key Key + * @param {string} value Value + * + * @returns boolean + */ + function inURLSearchParams(params, key, value) { + for (const [paramsKey, paramsValue] of params) { + if (paramsKey === key && paramsValue === value) { + return true; + } + } + return false; + } + + /** + * Delete a key+value from URLSearchParams + * + * URLSearchParams.delete(key, value) support is not yet widespread enough to be used + * (see https://caniuse.com/mdn-api_urlsearchparams_delete_value_parameter) + * + * @param {URLSearchParams} params URLSearchParams to delete from + * @param {string} deleteKey Key to delete + * @param {string} deleteValue Value to delete + * + * @returns URLSearchParams + */ + function deletePairFromURLSearchParams(params, deleteKey, deleteValue) { + const newParams = new URLSearchParams(); + for (const [key, value] of params) { + if (key !== deleteKey || value !== deleteValue) { + newParams.append(key, value); + } + } + return newParams; + } + + /** + * Delete a set of parameters from URLSearchParams + * + * URLSearchParams.delete(key, value) support is not yet widespread enough to be used + * (see https://caniuse.com/mdn-api_urlsearchparams_delete_value_parameter) + * + * @param {URLSearchParams} params URLSearchParams to delete from + * @param {URLSearchParams} deleteParams URLSearchParams containing all params to delete + * + * @returns URLSearchParams + */ + function deleteParamsFromURLSearchParams(params, deleteParams) { + const newParams = new URLSearchParams(); + for (const [key, value] of params) { + if (!inURLSearchParams(deleteParams, key, value)) { + newParams.append(key, value); + } + } + return newParams; + } + //Reveal return { defaultSearchBackend: defaultSearchBackend, @@ -519,7 +583,10 @@ var VuFind = (function VuFind() { setElementContents: setElementContents, getBootstrapMajorVersion: getBootstrapMajorVersion, disableTransitions: disableTransitions, - restoreTransitions: restoreTransitions + restoreTransitions: restoreTransitions, + inURLSearchParams: inURLSearchParams, + deletePairFromURLSearchParams: deletePairFromURLSearchParams, + deleteParamsFromURLSearchParams: deleteParamsFromURLSearchParams }; })(); diff --git a/themes/bootstrap3/js/facets.js b/themes/bootstrap3/js/facets.js index 27a526c65872..e8fbcca5fcd7 100644 --- a/themes/bootstrap3/js/facets.js +++ b/themes/bootstrap3/js/facets.js @@ -167,8 +167,15 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() { initialParams.append(key, normalizeValue(key, value)); } - // Update query params for every date range selector + /** + * Update query params for every date range selector + * + * @param {URLSearchParams} queryParams + * + * @returns {URLSearchParams} + */ function processRangeSelector(queryParams) { + let newParams = new URLSearchParams(queryParams.toString()); for (const form of rangeSelectorForms) { const rangeName = form.dataset.name; const rangeFilterField = form.dataset.filterField; @@ -184,24 +191,25 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() { if (valuesExist) { // Update query params: for (const input of dateInputs) { - queryParams.set(input.name, input.value); + newParams.set(input.name, input.value); } - queryParams.set(rangeFilterField, rangeName); + newParams.set(rangeFilterField, rangeName); } else { // Delete from query params: for (const input of dateInputs) { - queryParams.delete(input.name); + newParams.delete(input.name); } - queryParams.delete(rangeFilterField, rangeName); + newParams = VuFind.deletePairFromURLSearchParams(newParams, rangeFilterField, rangeName); } // Remove any filter[]=rangeName:... from query params: const paramStart = rangeName + ':'; - for (const value of queryParams.getAll('filter[]')) { + for (const value of newParams.getAll('filter[]')) { if (value.startsWith(paramStart)) { - queryParams.delete('filter[]', value); + newParams = VuFind.deletePairFromURLSearchParams(newParams, 'filter[]', value); } } } + return newParams; } // Goes through all modified facets to compile into 2 arrays of added and removed URL parameters @@ -215,13 +223,14 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() { // Add parameters that did not initially exist: for (const [key, value] of elemParams) { - if (!initialParams.has(key, value)) { + // URLSearchParams.has(key, value) seems to be broken on iOS 16, so check with our own method: + if (!VuFind.inURLSearchParams(initialParams, key, value)) { globalAddedParams.append(key, value); } } // Remove parameters that this URL no longer has: for (const [key, value] of initialParams) { - if (!elemParams.has(key, value)) { + if (!VuFind.inURLSearchParams(elemParams, key, value)) { globalRemovedParams.append(key, value); } } @@ -232,19 +241,16 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() { function getHrefWithNewParams() { processModifiedFacets(); - const newParams = new URLSearchParams(initialParams); - // Remove parameters: - for (const [key, value] of globalRemovedParams) { - newParams.delete(key, value); - } + // Create params without the removed parameters: + const newParams = VuFind.deleteParamsFromURLSearchParams(initialParams, globalRemovedParams); + // Add newly added parameters: for (const [key, value] of globalAddedParams) { newParams.append(key, value); } - processRangeSelector(newParams); // Take base url from data attribute if present (standalone full facet list): const baseUrl = defaultContext.dataset.searchUrl || window.location.pathname; - return baseUrl + '?' + newParams.toString(); + return baseUrl + '?' + processRangeSelector(newParams).toString(); } function applyMultiFacetsSelection() { diff --git a/themes/bootstrap5/js/common.js b/themes/bootstrap5/js/common.js index b939b531b839..ad50c88b0871 100644 --- a/themes/bootstrap5/js/common.js +++ b/themes/bootstrap5/js/common.js @@ -487,6 +487,70 @@ var VuFind = (function VuFind() { elem.style.transitionDuration = state; } + /** + * Check if URLSearchParams contains the given key+value + * + * URLSearchParams.has(key, value) support is not yet widespread enough to be used + * (see https://caniuse.com/mdn-api_urlsearchparams_has_value_parameter) + * + * @param {URLSearchParams} params URLSearchParams to check + * @param {string} key Key + * @param {string} value Value + * + * @returns boolean + */ + function inURLSearchParams(params, key, value) { + for (const [paramsKey, paramsValue] of params) { + if (paramsKey === key && paramsValue === value) { + return true; + } + } + return false; + } + + /** + * Delete a key+value from URLSearchParams + * + * URLSearchParams.delete(key, value) support is not yet widespread enough to be used + * (see https://caniuse.com/mdn-api_urlsearchparams_delete_value_parameter) + * + * @param {URLSearchParams} params URLSearchParams to delete from + * @param {string} deleteKey Key to delete + * @param {string} deleteValue Value to delete + * + * @returns URLSearchParams + */ + function deletePairFromURLSearchParams(params, deleteKey, deleteValue) { + const newParams = new URLSearchParams(); + for (const [key, value] of params) { + if (key !== deleteKey || value !== deleteValue) { + newParams.append(key, value); + } + } + return newParams; + } + + /** + * Delete a set of parameters from URLSearchParams + * + * URLSearchParams.delete(key, value) support is not yet widespread enough to be used + * (see https://caniuse.com/mdn-api_urlsearchparams_delete_value_parameter) + * + * @param {URLSearchParams} params URLSearchParams to delete from + * @param {URLSearchParams} deleteParams URLSearchParams containing all params to delete + * + * @returns URLSearchParams + */ + function deleteParamsFromURLSearchParams(params, deleteParams) { + const newParams = new URLSearchParams(); + for (const [key, value] of params) { + if (!inURLSearchParams(deleteParams, key, value)) { + newParams.append(key, value); + } + } + return newParams; + } + //Reveal return { defaultSearchBackend: defaultSearchBackend, @@ -519,7 +583,10 @@ var VuFind = (function VuFind() { setElementContents: setElementContents, getBootstrapMajorVersion: getBootstrapMajorVersion, disableTransitions: disableTransitions, - restoreTransitions: restoreTransitions + restoreTransitions: restoreTransitions, + inURLSearchParams: inURLSearchParams, + deletePairFromURLSearchParams: deletePairFromURLSearchParams, + deleteParamsFromURLSearchParams: deleteParamsFromURLSearchParams }; })(); diff --git a/themes/bootstrap5/js/facets.js b/themes/bootstrap5/js/facets.js index 27a526c65872..e8fbcca5fcd7 100644 --- a/themes/bootstrap5/js/facets.js +++ b/themes/bootstrap5/js/facets.js @@ -167,8 +167,15 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() { initialParams.append(key, normalizeValue(key, value)); } - // Update query params for every date range selector + /** + * Update query params for every date range selector + * + * @param {URLSearchParams} queryParams + * + * @returns {URLSearchParams} + */ function processRangeSelector(queryParams) { + let newParams = new URLSearchParams(queryParams.toString()); for (const form of rangeSelectorForms) { const rangeName = form.dataset.name; const rangeFilterField = form.dataset.filterField; @@ -184,24 +191,25 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() { if (valuesExist) { // Update query params: for (const input of dateInputs) { - queryParams.set(input.name, input.value); + newParams.set(input.name, input.value); } - queryParams.set(rangeFilterField, rangeName); + newParams.set(rangeFilterField, rangeName); } else { // Delete from query params: for (const input of dateInputs) { - queryParams.delete(input.name); + newParams.delete(input.name); } - queryParams.delete(rangeFilterField, rangeName); + newParams = VuFind.deletePairFromURLSearchParams(newParams, rangeFilterField, rangeName); } // Remove any filter[]=rangeName:... from query params: const paramStart = rangeName + ':'; - for (const value of queryParams.getAll('filter[]')) { + for (const value of newParams.getAll('filter[]')) { if (value.startsWith(paramStart)) { - queryParams.delete('filter[]', value); + newParams = VuFind.deletePairFromURLSearchParams(newParams, 'filter[]', value); } } } + return newParams; } // Goes through all modified facets to compile into 2 arrays of added and removed URL parameters @@ -215,13 +223,14 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() { // Add parameters that did not initially exist: for (const [key, value] of elemParams) { - if (!initialParams.has(key, value)) { + // URLSearchParams.has(key, value) seems to be broken on iOS 16, so check with our own method: + if (!VuFind.inURLSearchParams(initialParams, key, value)) { globalAddedParams.append(key, value); } } // Remove parameters that this URL no longer has: for (const [key, value] of initialParams) { - if (!elemParams.has(key, value)) { + if (!VuFind.inURLSearchParams(elemParams, key, value)) { globalRemovedParams.append(key, value); } } @@ -232,19 +241,16 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() { function getHrefWithNewParams() { processModifiedFacets(); - const newParams = new URLSearchParams(initialParams); - // Remove parameters: - for (const [key, value] of globalRemovedParams) { - newParams.delete(key, value); - } + // Create params without the removed parameters: + const newParams = VuFind.deleteParamsFromURLSearchParams(initialParams, globalRemovedParams); + // Add newly added parameters: for (const [key, value] of globalAddedParams) { newParams.append(key, value); } - processRangeSelector(newParams); // Take base url from data attribute if present (standalone full facet list): const baseUrl = defaultContext.dataset.searchUrl || window.location.pathname; - return baseUrl + '?' + newParams.toString(); + return baseUrl + '?' + processRangeSelector(newParams).toString(); } function applyMultiFacetsSelection() {