/*global bootstrap, grecaptcha, isPhoneNumberValid, loadCovers */
/*exported VuFind, bulkFormHandler, deparam, escapeHtmlAttr, extractClassParams, getFocusableNodes, getUrlRoot, htmlEncode, phoneNumberFormHandler, recaptchaOnLoad, resetCaptcha, setupMultiILSLoginFields, unwrapJQuery */
var VuFind = (function VuFind() {
var defaultSearchBackend = null;
var path = null;
var _initialized = false;
var _submodules = [];
var _cspNonce = '';
var _searchId = null;
var _icons = {};
var _translations = {};
var _elementBase;
var _iconsCache = {};
// Event controls
let listeners = {};
function unlisten(event, fn) {
if (typeof listeners[event] === "undefined") {
return;
}
const index = listeners[event].indexOf(fn);
if (index > -1) {
listeners[event].splice(index, 1);
}
}
// Add a function to call when an event is emitted
//
// Options:
// - once: remove this listener after it's been called
function listen(event, fn, { once = false } = {}) {
if (typeof listeners[event] === "undefined") {
listeners[event] = [];
}
listeners[event].push(fn);
const removeListener = () => unlisten(event, fn);
if (once) {
// Remove a "once" listener after calling
// Add the function to remove the listener
// to the array, listeners are called in order
listeners[event].push(removeListener);
}
// Return a function to disable the listener
// Makes it easier to control activating and deactivating listeners
// This is common for similar libraries
return removeListener;
}
// Broadcast an event, passing arguments to all listeners
function emit(event, ...args) {
// No listeners for this event
if (typeof listeners[event] === "undefined") {
return;
}
// iterate over a copy of the listeners array
// this prevents listeners from being skipped
// if the listener before it is removed during execution
for (const fn of Array.from(listeners[event])) {
fn(...args);
}
}
// Module control
var register = function register(name, module) {
if (_submodules.indexOf(name) === -1) {
_submodules.push(name);
this[name] = typeof module == 'function' ? module() : module;
// If the object has already initialized, we should auto-init on register:
if (_initialized && this[name].init) {
this[name].init();
}
}
};
/**
* Evaluate a callback
*/
var evalCallback = function evalCallback(callback, event, data) {
if ('function' === typeof window[callback]) {
return window[callback](event, data);
}
var parts = callback.split('.');
if (typeof window[parts[0]] === 'object') {
var obj = window[parts[0]];
for (var i = 1; i < parts.length; i++) {
if (typeof obj[parts[i]] === 'undefined') {
obj = false;
break;
}
obj = obj[parts[i]];
}
if ('function' === typeof obj) {
return obj(event, data);
}
}
console.error('Callback function ' + callback + ' not found.');
return null;
};
var initDisableSubmitOnClick = () => {
var forms = document.querySelectorAll("[data-disable-on-submit]");
forms.forEach(form =>
form.addEventListener("submit", () => {
var submitButtons = form.querySelectorAll('[type="submit"]');
// Disable submit elements via setTimeout so that the submit button value gets
// included in the submitted data before being disabled:
setTimeout(() => {
submitButtons.forEach(button => button.disabled = true);
}, 0);
}));
};
var initClickHandlers = function initClickHandlers() {
let checkClickHandlers = function (event, elem) {
if (elem.hasAttribute('data-click-callback')) {
return evalCallback(elem.dataset.clickCallback, event, {});
}
if (elem.hasAttribute('data-click-set-checked')) {
document.getElementById(elem.dataset.clickSetChecked).checked = true;
event.preventDefault();
}
if (elem.hasAttribute('data-toggle-aria-expanded')) {
elem.setAttribute('aria-expanded', elem.getAttribute('aria-expanded') === 'true' ? 'false' : 'true');
event.preventDefault();
}
// Check also parent node for spans (e.g. a button with icon)
if (!event.defaultPrevented && elem.localName === 'span' && elem.parentNode) {
checkClickHandlers(event, elem.parentNode);
}
};
window.addEventListener(
'click',
function handleClick(event) {
checkClickHandlers(event, event.target);
}
);
window.addEventListener(
'change',
function handleChange(event) {
let elem = event.target;
if (elem.hasAttribute('data-submit-on-change')) {
elem.form.requestSubmit();
}
}
);
};
var addTranslations = function addTranslations(s) {
for (var i in s) {
if (Object.prototype.hasOwnProperty.call(s, i)) {
_translations[i] = s[i];
}
}
};
var translate = function translate(op, _replacements) {
var replacements = _replacements || {};
var translation = _translations[op] || op;
if (replacements) {
for (var key in replacements) {
if (Object.prototype.hasOwnProperty.call(replacements, key)) {
translation = translation.replace(key, replacements[key]);
}
}
}
return translation;
};
var addIcons = function addIcons(s) {
for (var i in s) {
if (Object.prototype.hasOwnProperty.call(s, i)) {
_icons[i] = s[i];
}
}
};
/**
* Get an icon identified by a name.
*
* @param {String} name Name of the icon to create
* @param {Object} attrs Object containing attributes,
* key is the attribute of an HTMLElement,
* value is the values to add for the attribute.
* @param {Boolean} returnElement [Optional] Should the function return an HTMLElement.
* Default is false.
*
* @returns {String|HTMLElement}
*/
var icon = function icon(name, attrs = {}, returnElement = false) {
if (typeof _icons[name] == "undefined") {
console.error("JS icon missing: " + name);
return name;
}
// Create a template element for icon function
if (!_elementBase) {
_elementBase = document.createElement('div');
}
const cacheKey = `${name}||${JSON.stringify(attrs)}`;
if (_iconsCache[cacheKey]) {
return returnElement
? _iconsCache[cacheKey].cloneNode(true)
: _iconsCache[cacheKey].outerHTML;
}
const clone = _elementBase.cloneNode();
clone.insertAdjacentHTML('afterbegin', _icons[name]);
let element = clone.firstChild;
// Add additional attributes
function addAttrs(_element, _attrs = {}) {
Object.keys(_attrs).forEach(key => {
if (key !== 'class') {
_element.setAttribute(key, _attrs[key]);
return;
}
let newAttrs = _attrs[key].split(" ");
const oldAttrs = _element.getAttribute(key) || [];
const newAttrsSet = new Set([...newAttrs, ...oldAttrs.split(" ")]);
_element.className = Array.from(newAttrsSet).join(" ");
});
}
if (typeof attrs == "string") {
addAttrs(element, { class: attrs });
} else if (Object.keys(attrs).length > 0) {
addAttrs(element, attrs);
}
_iconsCache[cacheKey] = element;
return returnElement ? element.cloneNode(true) : element.outerHTML;
};
// Icon shortcut methods
var spinner = function spinner(extraClass = "") {
let className = ("loading-spinner " + extraClass).trim();
return '' + icon('spinner') + '';
};
var loading = function loading(text = null, extraClass = "") {
let className = ("loading-spinner " + extraClass).trim();
let string = translate(text === null ? 'loading_ellipsis' : text);
return '' + icon('spinner') + ' ' + string + '';
};
/**
* Reload the page without causing trouble with POST parameters while keeping hash
*/
var refreshPage = function refreshPage() {
var parts = window.location.href.split('#');
if (typeof parts[1] === 'undefined') {
window.location.reload();
} else {
var href = parts[0];
// Force reload with a timestamp
href += href.indexOf('?') === -1 ? '?_=' : '&_=';
href += new Date().getTime() + '#' + parts[1];
window.location.href = href;
}
};
var getCspNonce = function getCspNonce() {
return _cspNonce;
};
var setCspNonce = function setCspNonce(nonce) {
_cspNonce = nonce;
};
var updateCspNonce = function updateCspNonce(html) {
// Fix any inline script nonces
return html.replace(/(