X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/ddb7f33868ea499ab8f48a7062f145e8c0fbe02f..refs/pull/4317/head:/resources/js/services/dom.js diff --git a/resources/js/services/dom.js b/resources/js/services/dom.js index 966a4540e..bcfd0b565 100644 --- a/resources/js/services/dom.js +++ b/resources/js/services/dom.js @@ -1,3 +1,33 @@ +/** + * Create a new element with the given attrs and children. + * Children can be a string for text nodes or other elements. + * @param {String} tagName + * @param {Object} attrs + * @param {Element[]|String[]}children + * @return {*} + */ +export function elem(tagName, attrs = {}, children = []) { + const el = document.createElement(tagName); + + for (const [key, val] of Object.entries(attrs)) { + if (val === null) { + el.removeAttribute(key); + } else { + el.setAttribute(key, val); + } + } + + for (const child of children) { + if (typeof child === 'string') { + el.append(document.createTextNode(child)); + } else { + el.append(child); + } + } + + return el; +} + /** * Run the given callback against each element that matches the given selector. * @param {String} selector @@ -5,7 +35,7 @@ */ export function forEach(selector, callback) { const elements = document.querySelectorAll(selector); - for (let element of elements) { + for (const element of elements) { callback(element); } } @@ -17,7 +47,7 @@ export function forEach(selector, callback) { * @param {Function} callback */ export function onEvents(listenerElement, events, callback) { - for (let eventName of events) { + for (const eventName of events) { listenerElement.addEventListener(eventName, callback); } } @@ -25,17 +55,61 @@ export function onEvents(listenerElement, events, callback) { /** * Helper to run an action when an element is selected. * A "select" is made to be accessible, So can be a click, space-press or enter-press. - * @param listenerElement - * @param callback + * @param {HTMLElement|Array} elements + * @param {function} callback */ -export function onSelect(listenerElement, callback) { - listenerElement.addEventListener('click', callback); - listenerElement.addEventListener('keydown', (event) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); +export function onSelect(elements, callback) { + if (!Array.isArray(elements)) { + elements = [elements]; + } + + for (const listenerElement of elements) { + listenerElement.addEventListener('click', callback); + listenerElement.addEventListener('keydown', event => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + callback(event); + } + }); + } +} + +/** + * Listen to key press on the given element(s). + * @param {String} key + * @param {HTMLElement|Array} elements + * @param {function} callback + */ +function onKeyPress(key, elements, callback) { + if (!Array.isArray(elements)) { + elements = [elements]; + } + + const listener = event => { + if (event.key === key) { callback(event); } - }); + }; + + elements.forEach(e => e.addEventListener('keydown', listener)); +} + +/** + * Listen to enter press on the given element(s). + * @param {HTMLElement|Array} elements + * @param {function} callback + */ +export function onEnterPress(elements, callback) { + onKeyPress('Enter', elements, callback); +} + +/** + * Listen to escape press on the given element(s). + * @param {HTMLElement|Array} elements + * @param {function} callback + */ +export function onEscapePress(elements, callback) { + onKeyPress('Escape', elements, callback); } /** @@ -48,7 +122,7 @@ export function onSelect(listenerElement, callback) { * @param {Function} callback */ export function onChildEvent(listenerElement, childSelector, eventName, callback) { - listenerElement.addEventListener(eventName, function(event) { + listenerElement.addEventListener(eventName, event => { const matchingChild = event.target.closest(childSelector); if (matchingChild) { callback.call(matchingChild, event, matchingChild); @@ -66,10 +140,54 @@ export function onChildEvent(listenerElement, childSelector, eventName, callback export function findText(selector, text) { const elements = document.querySelectorAll(selector); text = text.toLowerCase(); - for (let element of elements) { + for (const element of elements) { if (element.textContent.toLowerCase().includes(text)) { return element; } } return null; -} \ No newline at end of file +} + +/** + * Show a loading indicator in the given element. + * This will effectively clear the element. + * @param {Element} element + */ +export function showLoading(element) { + element.innerHTML = '
'; +} + +/** + * Get a loading element indicator element. + * @returns {Element} + */ +export function getLoading() { + const wrap = document.createElement('div'); + wrap.classList.add('loading-container'); + wrap.innerHTML = '
'; + return wrap; +} + +/** + * Remove any loading indicators within the given element. + * @param {Element} element + */ +export function removeLoading(element) { + const loadingEls = element.querySelectorAll('.loading-container'); + for (const el of loadingEls) { + el.remove(); + } +} + +/** + * Convert the given html data into a live DOM element. + * Initiates any components defined in the data. + * @param {String} html + * @returns {Element} + */ +export function htmlToDom(html) { + const wrap = document.createElement('div'); + wrap.innerHTML = html; + window.$components.init(wrap); + return wrap.children[0]; +}