DateTime = luxon.DateTime class Commons { datatables = []; pageTranslations = []; locale = 'en-US'; dt = null; constructor() { this.locale = navigator.language; } /** * @return {DataTable[]} */ getDatatables() { return this.datatables; } /** * @param {string} key * @param {object} mappings * @returns {string} */ translate(key, mappings = {}) { if (this.pageTranslations.hasOwnProperty(key)) { let translation = this.pageTranslations[key]; for (const mapKey in mappings) { let mapValue = mappings[mapKey]; translation = translation.replace(`:${mapKey}`, mapValue); } return translation; } return key; } static UID() { const tod = new Date().getTime(); return `${Math.floor(tod / 1000)}.${tod % 1000}`; } autoCorrectInput() { document.addEventListener("DOMContentLoaded", function () { document.querySelectorAll('input.auto-correct') .forEach(el => el.addEventListener('input', function (e) { if (this.checkValidity()) { this.previousValue = this.value; } else { this.value = this.previousValue ?? ''; } })) }); } serverSideFilters() { return document.querySelectorAll('[data-filter-datatable]'); } serverSideProcessChanges() { document.addEventListener("DOMContentLoaded", () => { commons.serverSideFilters().forEach( el => el.addEventListener('change', e => { commons.datatables.forEach(el => { el.ajax.reload() }); } )) }); } serverSideDataProcessing(data) { this.serverSideFilters().forEach( el => { if (el.type === 'checkbox' || el.type === 'radio') { data[el.name] = el.checked ? 1 : 0; } else { data[el.name] = el.value; } } ) data.processed = 1; } static debounce(callback, timeout = 100) { let timer; return function (event) { if (timer) { clearTimeout(timer); } timer = setTimeout(callback, timeout, event); }; } /** * @param {Date|String} date * @param {String} format * @return {String} */ static toStandardDate(date, format = "yyyy-MM-dd'T'HH:mm:ssZZ") { let dt; if (date instanceof Date) { dt = DateTime.fromJSDate(date) } else if (typeof date === 'string' || date instanceof String) { dt = DateTime.fromISO(date) } else { dt = DateTime.fromObject(date) } return dt.toFormat(format) } /** * @param {object} obj */ static removeUndefinedValueFromObject(obj) { return Object.fromEntries(Object.entries(obj).filter(([key, value]) => value !== undefined)); } static validateEmail(email) { const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(String(email).trim().toLowerCase()); } static showModal(modalId, options = {}) { let modal = new bootstrap.Modal( document.getElementById(modalId), options ) modal.show(); return modal; } /** * @param {HTMLElement} el * @param {string} className * @param {boolean} status */ static setClass(el, className, status) { if (status) { el.classList?.add(className); } else { el.classList?.remove(className); } } /** * @param {HTMLElement} el */ static setSpinner(el) { el.querySelector('i')?.classList?.remove('d-none'); el.querySelector('svg')?.classList?.remove('d-none'); } /** * @param {HTMLElement} el */ static unsetSpinner(el) { el.querySelector('i')?.classList?.add('d-none'); el.querySelector('svg')?.classList?.add('d-none'); } /** * @param {string} modalId * @return {*} */ static hideModal(modalId) { let modal = bootstrap.Modal.getOrCreateInstance( document.getElementById(modalId) ) modal.hide(); return modal; } /** * @return {boolean} */ static isMobile() { const toMatch = [ /Android/i, /webOS/i, /iPhone/i, /iPad/i, /iPod/i, /BlackBerry/i, /Windows Phone/i ]; return toMatch.some((toMatchItem) => { return navigator.userAgent.match(toMatchItem); }); } /** * @param {number} ms * @return {Promise} */ static sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } static warnChangedOnQuit() { Commons.preventQuit('Changes you made may not be saved'); } static preventQuit(msg) { window.onbeforeunload = function () { if (!app.formSubmitted) { return msg; } }; } static nl2br(str, is_xhtml) { if (typeof str === 'undefined' || str === null) { return ''; } let breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '
' : '
'; return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2'); } static createHiddenInput(name, value = null) { return Commons.createInput('hidden', name, value) } static createInput(type, name, value = null) { let input = document.createElement('input') input.type = type; input.name = name; input.value = value return input; } static inputGetOrCreateInstance(container, type, name, value = null) { let elements = document.getElementsByName(name); if (elements.length) { elements.forEach(el => el.value = value); } else { container.append(Commons.createInput(type, name, value)) } } static copyToClipboard(id) { navigator.permissions.query( { name: "clipboard-write" }).then((result) => { if (result.state === "granted" || result.state === "prompt") { return navigator.clipboard.writeText( document.getElementById(id).innerHTML ); } }); } static async isBase64UrlImage(base64String) { let image = new Image() image.src = base64String return await (new Promise((resolve) => { image.onload = function () { if (image.height === 0 || image.width === 0) { resolve(false); return; } resolve(true) } image.onerror = () => { resolve(false) } })) } static getRawPostOptions(form) { let data = new FormData(form) let csrf = document.querySelector('meta[name="csrf-token"]').content; return { method: "POST", headers: { 'X-CSRF-TOKEN': csrf, }, body: data, }; } /** * @param {string} json */ static getPostOptions(json) { let csrf = document.querySelector('meta[name="csrf-token"]').content; return { method: "POST", headers: { 'Accept': 'application/json', "Content-Type": "application/json", 'X-CSRF-TOKEN': csrf, }, body: json ?? '', }; } /** * @param {string} url * @param {string} image * @param {function(number)} callback * @return {Promise} */ static async postImage(url, image, callback) { const config = { headers: { 'Content-Type': 'text/plain', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, }, onUploadProgress: function (progressEvent) { if (progressEvent.bytes) { callback(progressEvent.loaded * 100 / progressEvent.total); } } }; return await axios.post(url, image, config); } /** * @param {string} url * @param {HTMLInputElement} fileInput * @param {function(number)} callback * @return {Promise} */ static async postFile(url, fileInput, callback) { let formData = new FormData(); formData.append('file', fileInput.files[0]); const config = { headers: { "Content-Type": "multipart/form-data", 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, }, onUploadProgress: (progressEvent) => { if (progressEvent.bytes) { callback(progressEvent.loaded * 100 / progressEvent.total); } } }; return await axios.post(url, formData, config); } static postBodyDataObject(url, data = {}, callback = null) { return this.postBody(url, JSON.stringify(data), callback); } /** * @param {string} url * @param {string|FormData} json * @param {function|null} callback * @return {Promise|boolean} */ static postBody(url, json = '', callback = null) { let fetchOptions = Commons.getPostOptions(json); if (app.getOperationInProgress()) { return false; } app.formSubmitSpinner(true); app.setRefreshingState(true); return fetch(url, fetchOptions) .then((response) => { response.json().then((json) => { if (callback) { PageNotifier.notify(json); callback(json) } else { Commons.handleAjaxResponse(json) } }) }).finally(() => { app.formSubmitSpinner(false); app.setRefreshingState(false); }) } /** @param {object} data @param {HTMLElement[]|null} enabled */ static handleAjaxResponse(data, enabled = null) { if ((data.status ?? false) || (data.errors ?? false)) { PageNotifier.notify(data); if (data.reload) { document.location.reload(); return; } if (data.redirectHref) { window.location.href = data.redirectHref; } if (!data.reloadAfter && enabled) { enabled.forEach(el => el.disabled = false) } } } /** * @param {HTMLFormElement} form * @return {string} */ static serializeForm(form) { const formData = new FormData(form); const params = new URLSearchParams(); for (const [name, value] of formData) { params.append(name, value); } return params.toString(); } /** * @param {string} html * @return {ChildNode|HTMLElement|null} */ static htmlToDOMElement(html) { if (!html) { return null; } let template = document.createElement('template'); template.innerHTML = html.trim(); return template.content.firstChild; } /** * @param {string} s * @returns {string} */ static capitalize(s) { return s && s[0].toUpperCase() + s.slice(1) } /** * @param {string} selector * @return {number} */ static getComputedHeight(selector) { let cssObj = window.getComputedStyle(document.querySelector(selector)); return parseInt(cssObj.height); } /** * @param {string} selector * @return {number} */ static getComputedWidth(selector) { let cssObj = window.getComputedStyle(document.querySelector(selector)); return parseInt(cssObj.width); } /** * @param {string} str * @return {string} */ static slugify(str) { return String(str) .normalize('NFKD') // split accented characters into their base characters and diacritical marks .replace(/[\u0300-\u036f]/g, '') // remove all the accents, which happen to be all in the \u03xx UNICODE block. .trim() // trim leading or trailing whitespace .toLowerCase() // convert to lowercase .replace(/[^a-z0-9 -]/g, '') // remove non-alphanumeric characters .replace(/\s+/g, '-') // replace spaces with hyphens .replace(/-+/g, '-'); // remove consecutive hyphens } } let commons = new Commons(); commons.autoCorrectInput(); commons.serverSideProcessChanges(); /** * @param {string} key * @param {object} mappings * @return {string} */ function __(key, mappings = {}) { return commons.translate(key, mappings); } /** * @param {string} dataSelector * @return {string} * @private */ function __message(dataSelector) { return document.querySelector(`[${dataSelector}]`).innerHTML } /** * @param {object} translations */ function __translations(translations) { Object.assign(commons.pageTranslations, translations); }