UNPKG

@aegis-framework/artemis

Version:

Aegis Framework Javascript Library

993 lines (875 loc) 24.1 kB
/** * ============================== * DOM * ============================== */ /** * Simple DOM manipulation functions * * @class */ export class DOM { /** * Create a new DOM object * * @constructor * @param {string|Object|array} selector - Selector or DOM element to use * @return {DOM} - New instance of DOM */ constructor (selector) { if (selector === null) { this.collection = []; this.length = 0; return; } if (typeof selector == 'string') { this.collection = document.querySelectorAll (selector); this.length = this.collection.length; this._selector = selector; } else if (selector instanceof NodeList) { this.collection = selector; this.length = selector.length; this._selector = selector; } else if (selector instanceof DOM) { this.collection = selector.collection; this.length = this.collection.length; this._selector = selector._selector; } else if (selector instanceof HTMLElement) { this.collection = [selector]; this.length = this.collection.length; this._selector = selector; } else if (typeof selector == 'object') { if (selector.length >= 1) { this.collection = selector; } else { this.collection = [selector]; } this.length = this.collection.length; this._selector = selector; } else { return undefined; } } /** * Hide elements by setting their `display` property to 'none'. * * @return {DOM} - Current instance */ hide () { for (const element of this.collection) { element.style.display = 'none'; } return this; } /** * Show elements by setting their `display` property to the given value. * * @param {string} [display='block'] - Display property to set * * @return {DOM} - Current instance */ show (display = 'block') { for (const element of this.collection) { element.style.display = display; } return this; } /** * Add a class to the classList object * * @param {string} newClass - Class name to add * * @return {DOM} - Current instance */ addClass (newClass) { for (const element of this.collection) { element.classList.add (newClass); } return this; } /** * Remove a given class from the classList object * * @param {string} [oldClass=null] - Class to remove. If it's empty or null, * all classes will be removed * * @return {DOM} - Current instance */ removeClass (oldClass = null) { if (oldClass !== null) { for (const element of this.collection) { element.classList.remove (oldClass); } } else { for (const element of this.collection) { while (element.classList.length > 0) { element.classList.remove (element.classList.item (0)); } } } return this; } /** * Toggle between two classes * * @param {string} classes - Space separated class names * * @return {DOM} - Current instance */ toggleClass (classes) { classes = classes.split (' '); for (const element of this.collection) { for (let j = 0; j < classes.length; j++) { element.classList.toggle (classes[j]); } } return this; } /** * Check if the first element matching the selector has the given class * * @param {string} classToCheck - Class name to check for * * @return {boolean} - Whether the class is present or not */ hasClass (classToCheck) { for (const element of this.collection) { if (!element.classList.contains (classToCheck)) { return false; } } return true; } /** * Get or set the value from the first element matching the selector * * @param {string} value - Value to set to the element. * * @return {string|DOM} - If a value is provided, this returns the current * instance, otherwise it returns the value of the element instead of * setting it */ value (value) { if (typeof value !== 'undefined') { for (const element of this.collection) { element.value = value; } return this; } else { if (this.length > 0) { return this.collection[0].value; } } } /** * Focus on the first element matching the selector * * @return {DOM} - Current instance */ focus () { if (this.length > 0) { this.collection[0].focus (); } return this; } /** * Add a callback for the 'click' event on every element matching the selector * * @param {function} callback - Callback function to run when the event is triggered * * @return {DOM} - Current instance */ click (callback) { for (const element of this.collection) { element.addEventListener ('click', callback, false); } return this; } /** * Add a callback for the 'keyup' event on every element matching the selector * * @param {function} callback - Callback function to run when the event is triggered * * @return {DOM} - Current instance */ keyup (callback) { for (const element of this.collection) { element.addEventListener ('keyup', callback, false); } return this; } /** * Add a callback for the 'keydown' event on every element matching the selector * * @param {function} callback - Callback function to run when the event is triggered * * @return {DOM} - Current instance */ keydown (callback) { for (const element of this.collection) { element.addEventListener ('keydown', callback, false); } return this; } /** * Add a callback for the 'submit' event on every element matching the selector * * @param {function} callback - Callback function to run when the event is triggered * * @return {DOM} - Current instance */ submit (callback) { for (const element of this.collection) { element.addEventListener ('submit', callback, false); } return this; } /** * Add a callback for the 'change' event on every element matching the selector * * @param {function} callback - Callback function to run when the event is triggered * * @return {DOM} - Current instance */ change (callback) { for (const element of this.collection) { element.addEventListener ('change', callback, false); } return this; } /** * Add a callback for the 'scroll' event on every element matching the selector * * @param {function} callback - Callback function to run when the event is triggered * * @return {DOM} - Current instance */ scroll (callback) { for (const element of this.collection) { element.addEventListener ('scroll', callback, false); } return this; } /** * Add a callback function to a given event * * @param {string} event - Event to add the listener to * @param {string} target - Target element on which to detect the event * @param {function} callback - Callback function to run when the event is triggered * * @return {DOM} - Current instance */ on (event, target, callback) { event = event.split(' '); for (const element of this.collection) { for (let j = 0; j < event.length; j++) { // Check if no target was defined and just a function was provided if (typeof target === 'function') { element.addEventListener(event[j], target, false); } else if (typeof target === 'string' && typeof callback === 'function') { element.addEventListener(event[j], (e) => { if (!e.target) { return; } const targetElement = $_(e.target).closestParent (target, this._selector); if (targetElement.exists ()) { callback.call (targetElement.get (0), e); } else { return; } }, false); } } } return this; } /** * Filter from the current collection to only those matching the new selector * * @param {string} element - Selector to filter the collection with * * @return {DOM} - New DOM instance with the filtered collection */ filter (selector) { if (this.length > 0) { return new DOM (this.collection[0].querySelector (selector)); } return new DOM (null); } /** * Check if there are any elements that match the selector. * * @return {boolean} - Whether elements matching the selector existed or not */ exists () { return this.length > 0; } /** * Get or set a `data` property * * @param {string} name - Name of the data property * @param {string} [value] - Value of the property * * @return {string|DOM} - If no value is provided, this function returns * the first matching element value, otherwise it returns the current instance */ data (name, value) { if (typeof value !== 'undefined') { for (const element of this.collection) { element.dataset[name] = value; } return this; } else { if (this.length > 0) { return this.collection[0].dataset[name]; } } } /** * Remove a data property from all the elements on the collection given its * name. * * @param {string} name - Name of the data property to remove * * @return {DOM} - Current instance */ removeData (name) { for (const element of this.collection) { delete element.dataset[name]; } return this; } /** * Get or set the text of the first element matching the selector * * @param {string} [value] - Value to set the text to * * @return {string|DOM} - If no value is provided, this function returns the * inner text of the first matching element. Otherwise it returns the current * instance. */ text (value) { if (typeof value !== 'undefined') { for (const element of this.collection) { element.textContent = value; } return this; } else { if (this.length > 0) { return this.collection[0].textContent; } } } /** * Get or set the inner HTML of the first element matching the selector * * @param {string} [value] - Value to set the HTML to * * @return {string|DOM} - If no value is provided, this function returns the * first matching element HTML. Otherwise it returns the current instance */ html (value) { if (typeof value !== 'undefined') { for (const element of this.collection) { element.innerHTML = value; } return this; } else { if (this.length > 0) { return this.collection[0].innerHTML; } } } /** * Append an element to the first element matching the selector * * @param {string} element - String representation of the element to add * * @return {DOM} - Current instance */ append (element) { if (this.length > 0) { if (typeof element === 'string') { const div = document.createElement ('div'); if (typeof element === 'string') { div.innerHTML = element.trim (); } else { div.innerHTML = element; } this.collection[0].appendChild (div.firstChild); } else { this.collection[0].appendChild (element); } } return this; } /** * Prepend an element to the first element matching the selector * * @param {string} element - String representation of the element to add * * @return {DOM} - Current instance */ prepend (element) { if (this.length > 0) { if (typeof element === 'string') { const div = document.createElement ('div'); if (typeof element === 'string') { div.innerHTML = element.trim (); } else { div.innerHTML = element; } if (this.collection[0].childNodes.length > 0) { this.collection[0].insertBefore (div.firstChild, this.collection[0].childNodes[0]); } else { this.collection[0].appendChild (div.firstChild); } } else { if (this.collection[0].childNodes.length > 0) { this.collection[0].insertBefore (element, this.collection[0].childNodes[0]); } else { this.collection[0].appendChild (element); } } } return this; } /** * Iterate over the collection of elements matching the selector * * @param {function} callback - Callback to run for every element * * @return {DOM} - Current instance */ each (callback) { for (const element of this.collection) { callback (element); } return this; } /** * Get an element from the collection given it's index * * @param {int} index - Index of the element to retrieve * * @return {HTMLElement} - HTML Element in the position indicated by the index */ get (index) { return this.collection[index]; } /** * Get the first element in the collection * * @return {DOM} - DOM instance with the first element */ first () { if (this.length > 0) { return new DOM (this.collection[0]); } return new DOM (null); } /** * Get the last element in the collection * * @return {DOM} - DOM instance with the last element */ last () { if (this.length > 0) { return new DOM (this.collection[this.collection.length - 1]); } return new DOM (null); } /** * Check if the elements in the collection are visible by checking their * display, offsetWidth and offsetHeight properties * * @return {boolean} - Whether the elements are visible or not */ isVisible () { for (const element of this.collection) { if (element.display != 'none' && element.offsetWidth > 0 && element.offsetHeight > 0) { return true; } } return false; } /** * Get the parent of the first element matching the selector * * @return {DOM} - DOM instance of the parent element */ parent () { if (this.length > 0) { return new DOM (this.collection[0].parentElement); } return new DOM (null); } /** * Find an element that matches the given selector in the first element of the collection * * @param {string} selector - Selector to find the element with * * @return {DOM} - DOM instance with the element if found */ find (selector) { if (this.length > 0) { return new DOM (this.collection[0].querySelectorAll (selector)); } return new DOM (null); } /** * Get the top and left offsets of the first element matching the selector * * @return {object|void} - Object with `top` and `left` offsets */ offset () { if (this.length > 0) { const rect = this.collection[0].getBoundingClientRect (); return { top: rect.top + document.body.scrollTop, left: rect.left + document.body.scrollLeft }; } } /** * Find the closest element matching the given selector. This bubbles up * from the initial object and then follows to its parents. * * @param {string} selector - Selector to match the closest element with * * @return {DOM} - DOM instance with the closest HTML element matching the selector */ closest (selector) { let found = null; let element = this; while (element.exists () && found === null) { // Check if the current element matches the selector const matches = element.matches (selector); if (matches === true) { return element; } const search = element.find (selector); if (search) { if (search.length > 0) { found = search; } } element = element.parent (); } if (found !== null) { return found; } return element; } /** * Find the closest parent element matching the given selector. This bubbles up * from the initial object and then follows to its parents. * * @param {string} selector - Selector to match the closest element with * @param {string} limit - Selector for limit element. If the limit is reached * and no element matching the provided selector was found, it will cause an * early return. * * @return {DOM} - DOM instance with the closest HTML element matching the selector */ closestParent (selector, limit) { let element = this; while (element.exists ()) { // Check if the current element matches the selector const matches = element.matches (selector); if (matches === true) { return element; } if (typeof limit === 'string') { if (element.matches (limit)) { break; } } element = element.parent (); } return new DOM (null); } /** * Get or set the value of a given attribute * * @param {string} attribute - Attribute's name * @param {string|Number} [value] - Value to set the attribute to * * @return {string|number|DOM} - If no value is provided, this function returns the current * value of the provided attribute */ attribute (attribute, value) { if (typeof value !== 'undefined') { for (const element of this.collection) { element.setAttribute (attribute, value); } return this; } else { if (this.length > 0) { return this.collection[0].getAttribute (attribute); } } } /** * Check whether an element has an attribute or not * * @param {string} attribute - The name of the attribute to check existance for * * @returns {boolean} - Whether or not the attribute is present */ hasAttribute (attribute) { for (const element of this.collection) { if (!element.hasAttribute (attribute)) { return false; } } return true; } /** * Insert content to the `after` property of an element * * @param {string} content - String representation of the content to add * * @return {DOM} - Current instance */ after (content) { for (const element of this.collection) { element.insertAdjacentHTML ('afterend', content); } return this; } /** * Insert content to the `before` property of an element * * @param {string} content - String representation of the content to add * * @return {DOM} - Current instance */ before (content) { for (const element of this.collection) { element.insertAdjacentHTML ('beforebegin', content); } return this; } /** * Get or modify the `style` properties of the elements matching the selector * * @param {string|Object} properties - Properties to change or get. Can be * either an individual property or a JSON object with key-value pairs * @param {string} [value] - Value to set the property to when only changing * one property * * @return {string|DOM} - If a property is given but not a value for it, this * function will return its current value */ style (properties, value) { for (let i = 0; i < this.collection.length; i++) { if (typeof properties === 'string' && typeof value !== 'undefined') { this.collection[i].style[properties] = value; } else if (typeof properties === 'string' && typeof value === 'undefined') { return this.collection[i].style[properties]; } else if (typeof properties === 'object') { for (const property in properties) { this.collection[i].style[property] = properties[property]; } } } return this; } /** * Animate the given `style` properties on all elements in the collection in * with a given time duration * * @param {Object} style - JSON object with the key-value pairs of properties * to animate * @param {int} time - Time in milliseconds during which the properties will * be animated * * @return {DOM} - Current instance */ animate (style, time) { for (let i = 0; i < this.collection.length; i++) { for (const property in style) { const start = new Date().getTime(); const collection = this.collection; let timer; let initialValue; if (typeof this.collection[i].style[property] !== 'undefined') { initialValue = this.collection[i].style[property]; timer = setInterval (() => { const step = Math.min (1, (new Date ().getTime () - start) / time); collection[i].style[property] = (initialValue + step * (style[property] - initialValue)); if (step == 1) { clearInterval (timer); } }, 25); this.collection[i].style[property] = initialValue; } else if (typeof (this.collection[i])[property] !== 'undefined') { initialValue = (this.collection[i])[property]; timer = setInterval(() => { const step = Math.min (1, (new Date ().getTime () - start) / time); (collection[i])[property] = (initialValue + step * (style[property] - initialValue)); if (step == 1) { clearInterval (timer); } }, 25); (this.collection[i])[property] = initialValue; } } } return this; } /** * Use a fade in animation i the first element matching the selector * * @param {type} [time=400] - Time duration for the animation * @param {type} callback - Callback function to run once the animation is over * * @return {DOM} - Current instance */ fadeIn (time = 400, callback) { if (this.length > 0) { const element = this.collection[0]; element.style.opacity = 0; let last = +new Date(); const tick = () => { element.style.opacity = +element.style.opacity + (new Date() - last) / time; last = +new Date(); if (+element.style.opacity < 1) { (window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16); } else { if (typeof callback === 'function') { callback(); } } }; tick(); } return this; } /** * Use a fade out animation i the first element matching the selector * * @param {type} [time=400] - Time duration for the animation * @param {type} callback - Callback function to run once the animation is over * * @return {DOM} - Current instance */ fadeOut (time = 400, callback) { if (this.length > 0) { let last = +new Date (); const element = this.collection[0]; const tick = () => { element.style.opacity = +element.style.opacity - (new Date() - last) / time; last = +new Date (); if (+element.style.opacity > 0) { (window.requestAnimationFrame && requestAnimationFrame (tick)) || setTimeout(tick, 16); } else { if (typeof callback === 'function') { callback (); } } }; tick (); } return this; } /** Check if the first element in the collection matches a given selector * * @param {string} selector - Selector to match * * @return {boolean} - Whether the element matches the selector or not */ matches (selector) { const check = Element.prototype; const polyfill = check.matches || check.webkitMatchesSelector || check.mozMatchesSelector || check.msMatchesSelector || function () { return [].indexOf.call (document.querySelectorAll (selector), this) !== -1; }; if (this.length > 0) { return polyfill.call (this.collection[0], selector); } return false; } /** * Remove all elements in the collection * * @return {DOM} - Current instance */ remove () { for (const element of this.collection) { element.parentNode.removeChild (element); } return this; } /** * Replace the first element in the collection with a new one * * @return {DOM} - Current instance */ replaceWith (newElement) { let replaceElement = newElement; if (typeof newElement === 'string') { const div = document.createElement ('div'); div.innerHTML = newElement; replaceElement = div.firstChild; } for (const element of this.collection) { element.parentElement.replaceChild (replaceElement, element); } return this; } /** * Reset every element in the collection * * @return {DOM} - Current instance */ reset () { for (const element of this.collection) { element.reset (); } return this; } /** * Get or set a property for the first element in the collection * * @param {string} property - Property name to set or get * @param {string|Number} [value] - Value to set the property to * * @return {string|Number} - If no value is provided, this function will return the * current value of the indicated property */ property (property, value) { if (typeof value !== 'undefined') { for (const element of this.collection) { element[property] = value; } return this; } else { if (this.length > 0) { return this.collection[0][property]; } } } } /** * Simple wrapper function to use the DOM library * * @param {string|Object|array} selector - Selector or DOM element to use * * @return {DOM} - DOM instance or class if no selector is used */ export function $_ (selector) { if (typeof selector !== 'undefined') { return new DOM (selector); } else { return DOM; } } /** * Utility function to attach the 'load' listener to the window * * @param {function} callback - Callback function to run when the window is ready */ export function $_ready (callback) { window.addEventListener ('load', callback); }