823 lines
30 KiB
JavaScript

/**
* ----------------------------------------------
* Helper functions for BladeWindUI components
* ----------------------------------------------
*/
const currentModal = [];
let elName;
/**
* Shortcut for document.querySelector.
* @param {string} element - The element to find in the DOM.
* @return {(Element|boolean)} The matching DOM element.
* @see {@link https://bladewindui.com/extra/helper-functions#domel}
*/
const domEl = (element) => {
return (document.querySelector(element) !== null) ? document.querySelector(element) : false;
};
/**
* Alias for domEl(element)
*/
const dom_el = (element) => {
return domEl(element);
};
/**
* Shortcut for document.querySelectorAll.
* @param {string} element - The element(s) to find in the DOM.
* @param scope
* @return {NodeListOf<*>|boolean} The collection of DOM elements.
* @see {@link https://bladewindui.com/extra/helper-functions#domels}
*/
const domEls = (element, scope = null) => {
if (scope) {
if (typeof scope === 'string') {
if (scope.indexOf('.') === -1 && scope.indexOf('#') === -1) {
console.log(`${scope} needs to contain . or # to target it in the DOM`);
}
scope = document.querySelector(scope);
}
return scope.querySelectorAll(element);
}
return (document.querySelectorAll(element).length > 0) ? document.querySelectorAll(element) : false;
};
/**
* Alias for domEls(element)
*/
const dom_els = (element) => {
return domEls(element);
};
/**
* Check to see if val is empty
* @param {string} val - The string to test emptiness for
* @return {boolean} True if string is empty
*/
const isEmpty = (val) => {
let regex = /^\s*$/;
return regex.test(val);
};
/**
* Hide an element.
* @param {string} element - The css class (name) of the element to hide.
* @param {boolean} elementIsDomObject - If true, <element> will not be treated as a string but DOM element.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#hide}
*/
const hide = (element, elementIsDomObject = false) => {
if ((!elementIsDomObject && domEl(element) != null) || (elementIsDomObject && element != null)) {
changeCss(element, 'hidden', 'add', elementIsDomObject);
}
};
/**
* Display an element.
* @param {Object|boolean} element - The css class (name) of the element to hide.
* @param {boolean} elementIsDomObject - If true, <element> will not be treated as a string but DOM element.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#unhide}
*/
const unhide = (element, elementIsDomObject = false) => {
if ((!elementIsDomObject && domEl(element) != null) || (elementIsDomObject && element != null)) {
changeCss(element, 'hidden', 'remove', elementIsDomObject);
}
};
/**
* Clear validation errors. Used together with validateForm().
* If the user provides a value for a form field, that was earlier marked as an error, clear it.
* @param {Object} obj - The DOM element to target for clearing.
* @return {void}
*/
const clearErrors = (obj) => {
let el = obj.el;
let elParent = obj.elParent;
let elName = obj.elName;
let showErrorInline = obj.showErrorInline;
if (el.value !== '') {
(elParent !== null) ?
domEl(`.${elParent} .clickable`).classList.remove('!border-red-400') :
el.classList.remove('!border-red-400');
(showErrorInline) ? hide(`.${elName}-inline-error`) : '';
} else {
(elParent !== null) ?
domEl(`.${elParent} .clickable`).classList.add('!border-red-400') :
el.classList.add('!border-red-400');
(showErrorInline) ? unhide(`.${elName}-inline-error`) : '';
}
};
/**
* Modify the css for a DOM element.
* @param {Element|boolean} element - The class name of ID of the DOM element to modify.
* @param {string} css - Comma separated list of css classes to apply to <element>.
* @param {string} mode - Add|Remove. Determines if <css> should be added or removed from <element>.
* @param {boolean} elementIsDomObject - If true, <element> will not be treated as a string but DOM element.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#changecss}
* @example
* changeCss('.email', 'border-2, border-red-500');
* changeCss('.email', 'border-2, border-red-500', 'remove');
* changeCss(domEl('.email'), 'border-2, border-red-500', 'remove', true);
*/
const changeCss = (element, css, mode = 'add', elementIsDomObject = false) => {
// css can be comma separated
// if !elementIsDomObject run it through domEl
if (!elementIsDomObject) element = domEl(element);
if (element) {
if (css.indexOf(',') !== -1 || css.indexOf(' ') !== -1) {
css = css.replace(/\s+/g, '').split(',');
for (let classname of css) {
(mode === 'add') ? element.classList.add(classname.trim()) : element.classList.remove(classname.trim());
}
} else {
if (element.classList !== undefined) {
(mode === 'add') ? element.classList.add(css) : element.classList.remove(css);
}
}
}
};
/**
* Validate a form and highlight each field that fails validation.
* element does not need to be a <form> tag. Can be any element containing form fields.
* @param form
* @return {boolean} True if validation passes and False if validation fails.
* @see {@link https://bladewindui.com/extra/helper-functions#validateform}
*/
const validateForm = (form) => {
let hasError = 0;
let BreakException = {};
let fieldToValidate = [];
try {
fieldToValidate = (typeof (form) === 'string') ? domEls(`${form} .required`) : form.querySelectorAll('.required');
fieldToValidate.forEach((el) => {
changeCss(el, '!border-red-500', 'remove', true);
if (isEmpty(el.value)) {
let elName = el.getAttribute('name');
let elParent = el.getAttribute('data-parent');
let errorMessage = el.getAttribute('data-error-message');
let showErrorInline = el.getAttribute('data-error-inline');
let errorHeading = el.getAttribute('data-error-heading');
(elParent !== null) ?
changeCss(`.${elParent} .clickable`, '!border-red-400') :
changeCss(el, '!border-red-400', 'add', true);
el.focus();
if (errorMessage) {
(showErrorInline) ? unhide(`.${elName}-inline-error`) :
showNotification(errorHeading, errorMessage, 'error');
}
let listenerObj = {
'el': el,
'elParent': elParent,
'elName': elName,
'showErrorInline': showErrorInline
};
el.addEventListener('keyup', clearErrors.bind(null, listenerObj), false);
hasError++;
throw BreakException;
}
});
} catch (e) {
}
return hasError === 0;
};
/**
* Allow only numeric input in a text input field.
* @param {event} event - The event object. Key events.
* @param {boolean} with_dots - Should dots be allowed in the input. Useful when entering decimals.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#isnumberkey}
* @example
* onkeypress="return isNumberKey(event)"
*/
const isNumberKey = (event, with_dots = 1) => {
let acceptedKeys = (with_dots === 1) ? /[\d\b\\.]/ : /\d\b/;
if (!event.key.toString().match(acceptedKeys) && event.keyCode !== 8 && event.keyCode !== 9) {
event.preventDefault();
}
};
/**
* Execute a user-defined function.
* @param {string} func - The function to execute, with or without parameters.
* @return {void}
*/
const callUserFunction = (func) => {
if (func !== '' && func !== undefined) eval(func);
};
/**
* Serialize a form into key/value pairs for ajax submission.
* @param {string} form - The form to serialize.
* @return {object} The serialized object.
* @see {@link https://bladewindui.com/extra/helper-functions#serialize}
*/
const serialize = (form) => {
let data = new FormData(domEl(form));
let obj = {};
for (let [key, value] of data) {
/***
** in some cases the form field name and api parameter differ, and you want to
** display a more meaningful error message from Laravels $errors.. set an attr
** data-serialize-as on the form field. that value will be used instead of [key]
** example: input name="contact_name" data-serialize-as="contact_person"
** Laravel will display contact name field is required but contact_person : value
** will be sent to the API
**/
let thisElement = document.getElementsByName(key);
let serializeAs = thisElement[0].getAttribute('data-serialize-as');
obj[serializeAs ?? key] = value;
}
return obj;
};
/**
* Check if string contains a keyword.
* @param {string} str - The string to check for keyword existence.
* @param {string} keyword - The keyword to check for.
* @return {boolean} True if string contains keyword. False if it does not.
* @see {@link https://bladewindui.com/extra/helper-functions#stringcontains}
*/
const stringContains = (str, keyword) => {
if (typeof str !== 'string') return false;
return (str.indexOf(keyword) !== -1);
};
var doNothing = () => {
}
/**
* Modify the css for DOM elements of the same type.
* @param {string} elements - The class name of ID of the DOM elements to modify.
* @param {string} css - Comma separated list of css classes to apply to <elements>.
* @param {string} mode - Add|Remove. Determines if <css> should be added or removed from <elements>.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#changecssfordomarray}
*/
const changeCssForDomArray = (elements, css, mode = 'add') => {
if (domEls(elements).length > 0) {
domEls(elements).forEach((el) => {
changeCss(el, css, mode, true);
});
}
};
/**
* Animate an element.
* @param {string} element - The css class (name) of the element to animate.
* @param {string} animation - The css animation class to be applied.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#animatecss}
*/
const animateCSS = (element, animation) =>
new Promise((resolve, reject) => {
const animationName = `animate__${animation}`;
const node = domEl(element);
if (node) {
node.classList.remove('hidden');
node.classList.add('animate__animated', animationName);
document.documentElement.style.setProperty('--animate-duration', '.5s');
function handleAnimationEnd(event) {
node.classList.remove('animate__animated', animationName);
event.stopPropagation();
resolve('Animation ended');
}
node.addEventListener('animationend', handleAnimationEnd, {once: true});
}
});
/**
* Display a modal.
* @param {string} element - The css class (name) of the modal.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#showmodal}
*/
const showModal = (element) => {
unhide(`.bw-${element}-modal`);
document.body.classList.add('overflow-hidden');
domEl(`.bw-${element}-modal`).focus();
let index = (currentModal.length === 0) ? 0 : currentModal.length + 1;
animateCSS(`.bw-${element}`, 'zoomIn').then(() => {
currentModal[index] = element;
});
};
/**
* Trap focus within an open modal to prevent scrolling behind the modal.
* @param {Event} event - The event object.
* @return {void}
*/
const trapFocusInModal = (event) => {
let modalName = currentModal[(currentModal.length - 1)];
if (modalName !== undefined) {
const focusableElements = domEls(`.bw-${modalName}-modal input:not([type='hidden']):not([class*='hidden']), .bw-${modalName}-modal button:not([class*="hidden"]), .bw-${modalName}-modal a:not([class*="hidden"])`);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (event.key === 'Tab') {
if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
} else if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
}
};
/**
* Hide a modal.
* @param {string} element - The css class (name) of the modal.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#hidemodal}
*/
const hideModal = (element) => {
animateCSS(`.bw-${element}`, 'zoomOut').then(() => {
hide(`.bw-${element}-modal`);
currentModal.pop();
document.body.classList.remove('overflow-hidden');
domEl(`.bw-${element}-modal`).removeEventListener('keydown', trapFocusInModal);
});
};
/**
* Display the spinning icon on a button.
* @param {string} element - The css class (name) of the button.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#showbuttonspinner}
*/
const showButtonSpinner = (element) => {
unhide(`${element} .bw-spinner`);
};
/**
* Hide the spinning icon on a button.
* @param {string} element - The css class (name) of the button.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#hidebuttonspinner}
*/
const hideButtonSpinner = (element) => {
hide(`${element} .bw-spinner`);
};
/**
* Show the action buttons on a modal.
* @param {string} element - The css class (name) of the modal.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#showmodalactionbuttons}
*/
const showModalActionButtons = (element) => {
unhide(`.bw-${element} .modal-footer`);
};
/**
* Hide the action buttons on a modal.
* @param {string} element - The css class (name) of the modal.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#hidemodalactionbuttons}
*/
const hideModalActionButtons = (element) => {
hide(`.bw-${element} .modal-footer`);
};
/**
* Alias for unhide().
* @see {@link https://bladewindui.com/extra/helper-functions#show}
*/
const show = (element, elementIsDomObject = false) => {
unhide(element, elementIsDomObject);
};
/**
* Add a key/value pair to client's storage.
* @param {string} key - The key.
* @param {string} val - The value corresponding to key.
* @param {string} storageType - The storage key/val should be added to. sessionStorage | localStorage.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#addtostorage}
*/
const addToStorage = (key, val, storageType = 'localStorage') => {
if (window.localStorage || window.sessionStorage) {
(storageType === 'localStorage') ?
localStorage.setItem(key, val) : sessionStorage.setItem(key, val);
}
};
/**
* Retrieve a value from client's storage based on its key.
* @param {string} key - The key.
* @param {string} storageType - The storage to retrieve value from. sessionStorage | localStorage.
* @return {string} The value of <key>
* @see {@link https://bladewindui.com/extra/helper-functions#getfromstorage}
*/
const getFromStorage = (key, storageType = 'localStorage') => {
if (window.localStorage || window.sessionStorage) {
return (storageType === 'localStorage') ?
localStorage.getItem(key) : sessionStorage.getItem(key);
}
};
/**
* Delete a key/value pair from client's storage.
* @param {string} key - The key.
* @param {string} storageType - The storage to remove key/val from. sessionStorage | localStorage.
* @return {void}
* @see {@link https://bladewindui.com/extra/helper-functions#removefromstorage}
*/
const removeFromStorage = (key, storageType = 'localStorage') => {
if (window.localStorage || window.sessionStorage) {
(storageType === 'localStorage') ?
localStorage.removeItem(key) : sessionStorage.removeItem(key);
}
};
/**
* Navigate to a tab.
* @param {string} element - The css class (name) of the tab to navigate to.
* @param {string} colour - The colour of the tab.
* @param {string} scope - The scope within which to find <element>. More like a parent element.
* @return {(void|boolean)}
*/
const goToTab = (element, colour, scope) => {
let scope_ = scope.replace(/-/g, '_');
let tabContent = domEl('.bw-tc-' + element);
if (tabContent === null) return false;
changeCssForDomArray(`.${scope}-headings li.atab span`, `${colour}, is-active`, 'remove');
changeCssForDomArray(`.${scope}-headings li.atab span`, 'is-inactive');
changeCss(`.atab-${element} span`, 'is-inactive', 'remove');
changeCss(`.atab-${element} span`, `is-active, ${colour}`);
domEls(`.${scope_}-tab-contents > div.atab-content`).forEach((element) => {
hide(element, true);
});
unhide(tabContent, true);
};
/**
* Get the offsetWidth of a prefix/suffix label
* @param {string} element - The css class (name) of the prefix/suffix field.
* @return {int}
*/
const getPrefixSuffixOffsetWidth = (element) => {
let ps_element = domEl(element);
const clone = ps_element.cloneNode(true);
clone.style.visibility = 'hidden';
clone.style.position = 'absolute';
clone.style.display = 'block';
document.body.appendChild(clone);
let offsetWidth = clone.offsetWidth;
document.body.removeChild(clone);
return offsetWidth;
};
/**
* Position a prefix in an input field.
* @param {string} element - The css class (name) of the input field.
* @param {string} mode - Event to trigger the positioning.
* @return {void}
*/
const positionPrefix = (element, mode = 'blur') => {
let transparency = domEl(`.dv-${element} .prefix`).getAttribute('data-transparency');
let offset = (transparency === '1') ? -5 : 7;
let prefixWidth = ((getPrefixSuffixOffsetWidth(`.dv-${element} .prefix`)) + offset) * 1;
let defaultLabelLeftPos = '0.875rem';
let inputField = domEl(`input.${element}`);
let labelField = domEl(`.dv-${element} label`);
if (mode === 'blur') {
if (labelField) {
labelField.style.left = (inputField.value === '') ? `${prefixWidth}px` : defaultLabelLeftPos;
}
domEl(`input.${element}`).style.paddingLeft = `${prefixWidth}px`;
inputField.addEventListener('focus', (event) => {
positionPrefix(element, event.type);
// for backward compatibility where {once:true} is not supported
inputField.removeEventListener('focus', positionPrefix);
}, {once: true});
} else if (mode === 'focus') {
if (labelField) labelField.style.left = defaultLabelLeftPos;
inputField.addEventListener('blur', (event) => {
positionPrefix(element, event.type);
// for backward compatibility where {once:true} is not supported
inputField.removeEventListener('blur', positionPrefix);
}, {once: true});
}
};
/**
* Position a suffix in an input field.
* @param {string} element - The css class (name) of the input field.
* @param {string} mode - Event to trigger the positioning.
* @return {void}
*/
const positionSuffix = (element) => {
let transparency = domEl(`.dv-${element} .suffix`).getAttribute('data-transparency');
let offset = (transparency === '1') ? -5 : 7;
let suffixWidth = ((getPrefixSuffixOffsetWidth(`.dv-${element} .suffix`)) + offset) * 1;
domEl(`input.${element}`).style.paddingRight = `${suffixWidth}px`;
};
/**
* Show or hide password in a password input fiield.
* @param {string} element - The css class (name) of the input field.
* @param {string} mode - Show or hide.
* @return {void}
*/
const togglePassword = (element, mode) => {
let inputField = domEl(`input.${element}`);
if (mode === 'show') {
inputField.setAttribute('type', 'text');
unhide(`.dv-${element} .suffix svg.hide-pwd`);
hide(`.dv-${element} .suffix svg.show-pwd`);
} else {
inputField.setAttribute('type', 'password')
unhide(`.dv-${element} .suffix svg.show-pwd`);
hide(`.dv-${element} .suffix svg.hide-pwd`);
}
};
/**
* Partition an array into two separate arrays.
* @param {array} arr - The array to be split.
* @param {function} fn - The evaluation function to run on each element > should return true/false for each element
* @return {[array, array]}
*/
const partition = (arr, fn) => {
return arr.reduce(
(acc, val, i, arr) => {
acc[fn(val, i, arr) ? 0 : 1].push(val);
return acc;
},
[[], []]
);
}
/**
* Filter a table based on keyword.
* @param {string} keyword - The keyword to filter table by.
* @param {string} table - The css class (name) of the table to filter.
* @param {null} field - The field to search.
* @param {array} tableData - The data to filter
* @return {void}
*/
const filterTable = (keyword, table, field, tableData) => {
if (tableData === null) {
// not dynamic table, search row content
domEls(`${table} tbody tr`).forEach((tr) => {
(tr.innerText.toLowerCase().includes(keyword.toLowerCase())) ?
unhide(tr, true) : hide(tr, true);
});
return;
}
let currentPage = domEl(table).getAttribute('data-current-page');
const [showList, hideList] = partition(tableData, (row) => {
if (field) {
return row[field].toLowerCase().match(keyword.toLowerCase());
} else {
return Object.values(row).toString().toLowerCase().match(keyword.toLowerCase());
}
});
hideList.forEach((row) => {
let thisRow = (currentPage !== null) ? `${table} tbody tr[data-id="${row.id}"][data-page="${currentPage}"]` : `${table} tbody tr[data-id="${row.id}"]`;
hide(domEl(thisRow), true);
});
showList.forEach((row) => {
let thisRow = (currentPage !== null) ? `${table} tbody tr[data-id="${row.id}"][data-page="${currentPage}"]` : `${table} tbody tr[data-id="${row.id}"]`;
const elem = domEl(thisRow);
if (elem) {
unhide(elem, true);
}
});
};
/**
* Filter a table based on keyword, .
* @param {string} keyword - The keyword to filter table by.
* @param {string} table - The css class (name) of the table to filter.
* @param {string} field - The field to search.
* @param {int} delay - Number of milliseconds to debouce the search.
* @return {function} - The debounced search function to be run
*/
let debounceTimerId;
const filterTableDebounced = (keyword, table, field = null, delay = 0, minLength = 0, tableData = {}) => {
let currentPage = domEl(table).getAttribute('data-current-page');
let rows = (currentPage !== null) ? `${table} tbody tr.hidden[data-page="${currentPage}"]` : `${table} tbody tr.hidden`;
if (keyword.length >= minLength) {
return (...args) => {
clearTimeout(debounceTimerId);
debounceTimerId = setTimeout(() => filterTable(keyword, table, field, tableData), delay);
};
} else {
return (...args) => {
clearTimeout(debounceTimerId);
debounceTimerId = setTimeout(() => {
domEls(rows).forEach((tr) => {
unhide(tr, true);
});
}, delay);
};
}
};
/**
* Remove trailing comma from string.
* @param {string} element - The input field to remove trailing comma from.
* @return {void}
*/
const stripComma = (element) => {
if (element.value.startsWith(',')) {
element.value = element.value.replace(/^,/, '');
}
const event = new Event('change', {
bubbles: true,
cancelable: true
});
element.dispatchEvent(event);
};
/**
* Select a tag.
* @param {string} value - The value or uuid to pass when tag is selected.
* @param {string} name - The name of the tag.
* @return {void}
*/
const selectTag = (value, name) => {
let input = domEl(`input[name="${name}"]`);
let max_selection = input.getAttribute('data-max-selection');
let tag = domEl(`.bw-${name}-${value}`);
let css = tag.getAttribute('class');
if (input.value.includes(value)) { // remove
let keyword = `(,?)${value}`;
input.value = input.value.replace(input.value.match(keyword)[0], '');
changeCss(tag, css.match(/bg-[\w]+-500/)[0], 'remove', true);
changeCss(tag, (css.match(/bg-[\w]+-500/)[0]).replace('500', '200/80'), 'add', true);
changeCss(tag, css.match(/text-[\w]+-50/)[0], 'remove', true);
changeCss(tag, (css.match(/text-[\w]+-50/)[0]).replace('50', '600'), 'add', true);
} else { // add
let total_selected = (input.value === '') ? 0 : input.value.split(',').length;
if (total_selected < max_selection) {
input.value += `,${value}`;
changeCss(tag, css.match(/bg-[\w]+-200\/80/)[0], 'remove', true);
changeCss(tag, (css.match(/bg-[\w]+-200\/80/)[0]).replace('200/80', '500'), 'add', true);
changeCss(tag, css.match(/text-[\w]+-600/)[0], 'remove', true);
changeCss(tag, (css.match(/text-[\w]+-600/)[0]).replace('600', '50'), 'add', true);
} else {
showNotification(input.getAttribute('data-error-heading'), input.getAttribute('data-error-message'), 'error');
}
}
stripComma(input)
};
/**
* Highlight selected tags.
* @param {string} values - Comma separated list of values corresponding to tags to highlight.
* @param {string} name - The name of the tags.
* @return {void}
*/
const highlightSelectedTags = (values, name) => {
if (values !== '') {
let valuesArray = values.split(',');
for (let x = 0; x < valuesArray.length; x++) {
selectTag(valuesArray[x].trim(), name);
}
}
};
/**
* Compare two dates and display an error if second date is less than first date.
* This is used in the range Datepicker component to ensure dates make sense.
* @param {string} element1 - The first date input field.
* @param {string} element2 - The second date input field.
* @param {string} message - Error message to display if validation fails.
* @param {boolean} inline - Display error inline or in a notification component.
* @return {boolean} True if date 2 is greater than date 1.
* @see {@link https://bladewindui.com/extra/helper-functions#comparedates}
*/
const compareDates = (element1, element2, message, inline) => {
let date1El = domEl(`.${element1}`);
let date2El = domEl(`.${element2}`);
setTimeout(() => {
let startDate = new Date(date1El.value).getTime();
let endDate = new Date(date2El.value).getTime();
if (startDate !== '' && endDate !== '') {
if (startDate > endDate) {
changeCss(date2El, '!border-red-400', 'add', true);
(inline !== 1) ? showNotification('', message, 'error') : domEl(`.error-${element1}${element2}`).innerHTML = message;
return false;
} else {
changeCss(date2El, '!border-red-400', 'remove', true);
return true;
}
}
}, 100);
};
/**
* Validate for minimum and maximum values of an input field
* @param {number} min - The minimum value.
* @param {number} max - The maximum value.
* @param {string} element - The input field to validate.
* @param {boolean} enforce_limits - Ensure input does not exceed maximum or go below minimum
* @return {void}
*/
const checkMinMax = (min, max, element, enforce_limits = false) => {
let field = domEl(`.${element}`);
let minimum = parseInt(min);
let maximum = parseInt(max);
let errorMessage = field.getAttribute('data-error-message');
let showErrorInline = field.getAttribute('data-error-inline');
let errorHeading = field.getAttribute('data-error-heading');
if (field.value !== '' && ((!isNaN(minimum) && field.value < minimum) || (!isNaN(maximum) && field.value > maximum))) {
if (enforce_limits) {
if (field.value < minimum) field.value = minimum;
if (field.value > maximum) field.value = maximum;
} else {
changeCss(field, '!border-red-400', 'add', true);
if (errorMessage) {
(showErrorInline) ? unhide(`.${element}-inline-error`) :
showNotification(errorHeading, errorMessage, 'error');
}
}
} else {
if (errorMessage) hide(`.${element}-inline-error`);
changeCss(field, '!border-red-400', 'remove', true);
}
};
/**
* Display a clear button in an input field that has text.
* @param {string} element - The css class (name) of the input field.
* @return {void}
*/
const makeClearable = (element) => {
let field = domEl(`.${element}`);
let suffixElement = domEl(`.${element}-suffix svg`);
let tableElement = element.replace('bw_search_', 'table.');
let clearingFunction = (domEl(tableElement)) ? field.getAttribute('oninput').replace('this.value', "''") : '';
if (!suffixElement.getAttribute('onclick')) {
suffixElement.setAttribute('onclick', `domEl(\'.${element}\').value=''; hide(this, true); ${clearingFunction}`);
}
(field.value !== '') ? unhide(suffixElement, true) : hide(suffixElement, true);
};
/**
* Convert a selected file to base64.
* @param {string} file - Url of selected file.
* @param {string} element - The input field to write the base64 string to.
* @return {void}
*/
const convertToBase64 = (file, element) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64String = reader.result;//.replace('data:', '').replace(/^.+,/, '');
domEl(element).value = base64String;
};
reader.readAsDataURL(file);
};
/**
* Check if selected file size falls within allowed file size.
* @param {number} fileSize - The selected file size.
* @param {number} maxSize - THe maximum file size.
* @return {boolean} True if <fileSize> if less than <maxSize>
*/
const allowedFileSize = (fileSize, maxSize) => {
return (fileSize <= maxSize * 1000000);
};
/**
* Set the value of a datepicker
* @return {void}
* @param {string} elName - name of the input field to update
* @param {string} date - new value to set
*/
const setDatepickerValue = (elName, date) => {
let input = domEl(`.${elName}`);
if (!input) {
console.error(`No datepicker found with the name ${elName}`);
return;
}
// let alpineComponent = document.querySelector('[x-data]').__x.$data;
if (!input._x_model) {
console.error(`Alpine.js component not found for element ${elName}`);
return;
}
input._x_model.set(date);
};