/** * Add some controls to debounce function. * * @interface DebounceFunction * @extends {Function} */ interface DebounceFunction extends Function { (): void; __debounce: number; __lastRun: number; } /** * DOM Utils methods. * * @export * @abstract * @class DOMUtils */ export abstract class DOMUtils { /** * Returns the value of a cookie. * * @static * @param {(string | null)} name Cookie's name. * @return {String} Cookie's value. * @memberof DOMUtils */ public static getCookie(name: string): string | null { const regex = new RegExp(`(^|;)[ ]*${name}=([^;]*)`); const match = regex.exec(document.cookie); return match ? decodeURIComponent(match[2]) : null; } /** * Returns the value of a Local Storage key. * * @static * @template T * @param {string} key Local Storage key name. * @return {T} Local Storage value. * @memberof DOMUtils */ public static getStorage(key: string): T { try { const value = localStorage.getItem(key); if (value != null) { return JSON.parse(value); } } catch (e) { return undefined; } return undefined; } /** * Changes the value of a key in LocalStorage. * * @static * @param {string} key Key's name. * @param {object} data Key's value. * @memberof DOMUtils */ public static setStorage(key: string, data: object) { try { const value = JSON.stringify(data); localStorage.setItem(key, value); } catch (e) { return; } } /** * Find elements matched by a selector. * * @static * @param {string} selector Search selector. * @param {NodeSelector} [element=document] Relative element. * @return {(HTMLElement[] | null)} Found elements. * @memberof DOMUtils */ public static find(selector: string, element: ParentNode = document): HTMLElement[] | null { const elements = Array.from(element.querySelectorAll(selector)); return elements.length > 0 ? (elements as HTMLElement[]) : null; } /** * Find the first element matched by a selector. * * @static * @param {string} selector Search selector. * @param {NodeSelector} [element=document] Relative element. * @return {(HTMLElement[] | null)} Found element. * @memberof DOMUtils */ public static findOne(selector: string, element: ParentNode = document): HTMLElement { return element.querySelector(selector); } /** * Get element by ID. * * @static * @param {string} id ID to search for. * @return {(HTMLElement | null)} Found element. * @memberof DOMUtils */ public static findById(id: string): HTMLElement | null { return document.getElementById(id); } /** * Find elements by tag. * * @static * @param {string} tag Tag to search for. * @return {(HTMLElement[] | null)} Found elements. * @memberof DOMUtils */ public static findByTag(tag: string): HTMLElement[] | null { const elements = Array.from(document.getElementsByTagName(tag)) as HTMLElement[]; return elements.length > 0 ? elements : null; } /** * Find first element by tag. * * @static * @param {string} tag Tag to search for. * @return {(HTMLElement | null)} Found element. * @memberof DOMUtils */ public static findOneByTag(tag: string): HTMLElement | null { const elements = DOMUtils.findByTag(tag); return elements != null ? elements[0] : null; } /** * Find the previous element of a selector. * * @static * @param {HTMLElement} element Root search element * @param {string} selector Search selector * @return {(HTMLElement | null)} Found element * @memberof DOMUtils */ public static previous(element: HTMLElement, selector?: string): HTMLElement | null { if (!selector) { return element.previousElementSibling as HTMLElement; } // eslint-disable-next-line Element.prototype.matches = Element.prototype.matches || (Element.prototype as any).msMatchesSelector; // eslint-disable-next-line if (!Element.prototype.matches) { return null; } let currElement = element.previousElementSibling as HTMLElement; while (currElement && !currElement.matches(selector)) { currElement = currElement.previousElementSibling as HTMLElement; } return currElement; } /** * Find the next element of a selector. * * @static * @param {HTMLElement} element Root search element * @param {string} selector Search selector * @return {(HTMLElement | null)} Found element * @memberof DOMUtils */ public static next(element: HTMLElement, selector?: string): HTMLElement | null { if (!selector) { return element.nextElementSibling as HTMLElement; } // eslint-disable-next-line Element.prototype.matches = Element.prototype.matches || (Element.prototype as any).msMatchesSelector; // eslint-disable-next-line if (!Element.prototype.matches) { return null; } let currElement = element.nextElementSibling as HTMLElement; while (currElement && !currElement.matches(selector)) { currElement = currElement.nextElementSibling as HTMLElement; } return currElement; } /** * Find the parent element of a selector. * * @static * @param {HTMLElement} element Root search element * @param {string} selector Search selector * @return {(HTMLElement | null)} Found element * @memberof DOMUtils */ public static parent(element: HTMLElement, selector?: string): HTMLElement | null { if (!selector) { return element.parentElement; } // eslint-disable-next-line Element.prototype.matches = Element.prototype.matches || (Element.prototype as any).msMatchesSelector; // eslint-disable-next-line if (!Element.prototype.matches) { return null; } let currElement = element.parentElement; while (currElement && !currElement.matches(selector)) { currElement = currElement.parentElement; } return currElement; } /** * Remove an element from the DOM. * * @static * @param {(HTMLElement | null)} element Element to be removed. * @memberof DOMUtils */ public static remove(element: HTMLElement | null) { if (element != null && element.parentNode != null) { element.parentNode.removeChild(element); } } /** * Create a new Element. * * @static * @param {string} tag Element's tag. * @return {HTMLElement} Created element. * @memberof DOMUtils */ public static create(tag: string): HTMLElement { return document.createElement(tag); } /** * Add a new event to an element. * * @static * @param {EventTarget} element Element to add the event. * @param {string} type Event type. * @param {(e: Event) => void} callback Event callback. * @memberof DOMUtils */ /** * Add a new event to an element. * * @static * @param {EventTarget} element Element to add the event. * @param {string} type Event type. * @param {Function} callback Event callback. * @memberof DOMUtils */ public static addEvent(element: EventTarget, type: string, callback: (e: Event) => void) { if (element != null) { element.addEventListener(type, callback); } } /** * Remove an event from an element. * * @static * @param {EventTarget} element Element to remove the event from. * @param {string} type Event type. * @param {Function} callback Event callback. * @memberof DOMUtils */ public static removeEvent(element: EventTarget, type: string, callback: (e: Event) => void) { if (element != null) { element.removeEventListener(type, callback); } } /** * Trigger a customized event. * * @static * @param {string} type Event type. * @param {EventTarget} [target=window] Event target. Global if not present. * @memberof DOMUtils */ public static triggerEvent(type: string, target: EventTarget = window) { const event = document.createEvent("HTMLEvents"); event.initEvent(type, true, true); target.dispatchEvent(event); } /** * Wrap HTML in an element. * * @static * @param {string} tag Wrapper tag. * @param {string} html Content. * @return {HTMLElement} Created element. * @memberof DOMUtils */ public static wrapCreate(tag: string, html: string): HTMLElement { const element = DOMUtils.create(tag); element.innerHTML = html; return element; } /** * Validate if an element has a class. * * @static * @param {HTMLElement} element Element to validate. * @param {string} className Class to search for. * @return {boolean} If it has. * @memberof DOMUtils */ public static hasClass(element: HTMLElement, className: string): boolean { const regex = new RegExp("(\\s|^)" + className + "(\\s|$)"); // eslint-disable-next-line return !!(typeof element.className === "string" && element.className.match(regex)); } /** * Add a class to an element. * * @static * @param {HTMLElement} element Element to add class to. * @param {string} className Class to add. * @memberof DOMUtils */ public static addClass(element: HTMLElement, className: string) { if (!DOMUtils.hasClass(element, className)) { element.className += " " + className; element.className = element.className.trim(); } } /** * Remove class from element. * * @static * @param {HTMLElement | HTMLElement[]} elements Element to remove class from. * @param {...string[]} classList Class to remove. * @memberof DOMUtils */ public static removeClass(elements: HTMLElement | HTMLElement[], ...classList: string[]) { if (!(elements instanceof Array)) { elements = [elements]; } for (const element of elements) { for (const className of classList) { const regex = new RegExp("(\\s|^)" + className + "(\\s|$)"); if (DOMUtils.hasClass(element, className)) { element.className = element.className.replace(regex, " ").trim(); if (element.className.length === 0) { element.removeAttribute("class"); } } } } } /** * Toggle class in an element. * * @static * @param {HTMLElement} element Element to toggle. * @param {string} className Class to toggle. * @memberof DOMUtils */ public static toggleClass(element: HTMLElement, className: string) { if (this.hasClass(element, className)) { this.removeClass(element, className); } else { this.addClass(element, className); } } /** * Check if is a child element. * * @static * @param {HTMLElement} parent Parent element. * @param {HTMLElement} child Possible child element. * @return {boolean} If parent contains child. * @memberof DOMUtils */ public static isDescendant(parent: HTMLElement, child: HTMLElement): boolean { let node = child.parentNode; while (node != null) { if (node === parent) { return true; } node = node.parentNode; } return false; } /** * Events debounce. * * @static * @param {Function} method Debounce method. * @param {number} delay Delay (in ms). * @memberof DOMUtils */ public static debounce(method: () => void, delay: number) { const debounceMethod = method as DebounceFunction; window.clearTimeout(debounceMethod.__debounce); debounceMethod.__debounce = window.setTimeout(() => method(), delay); } /** * Events first debounce. * * @static * @static * @param {Function} method Debounce method. * @param {number} delay Delay (in ms). * @memberof DOMUtils */ public static firstDebounce(method: (...args: T[]) => void, delay: number, ...args: T[]) { const debounceMethod = method as DebounceFunction; const now = new Date().getTime(); const lastRun = debounceMethod.__lastRun || 0; const interval = now - lastRun; if (interval > delay) { debounceMethod.__lastRun = now; method(...args); } } /** * Check if text matches, ignoring diacritics. * * @static * @param {string} text Original text. * @param {string} match Text to match. * @return {boolean} If matches. * @memberof DOMUtils */ public static textMatch(text: string, match: string): boolean { text = this.removeDiacritics(text).toLowerCase(); match = this.removeDiacritics(match).toLowerCase(); return text.includes(match); } /** * Compare two objects. * * @static * @param {*} leftObject Object A. * @param {*} rightObject Object B. * @return {boolean} If equals * @memberof DOMUtils */ public static deepEquals(leftObject: T, rightObject: U): boolean { return JSON.stringify(leftObject) === JSON.stringify(rightObject); } /** * Remove diacritics from text. * * @static * @param {string} text Text to change. * @return {string} Text without diacritics. * @memberof DOMUtils */ public static removeDiacritics(text: string): string { return text .replace(/ç/gi, "c") .replace(/à/gi, "a") .replace(/á/gi, "a") .replace(/ã/gi, "a") .replace(/â/gi, "a") .replace(/é/gi, "e") .replace(/è/gi, "e") .replace(/ê/gi, "e") .replace(/í/gi, "i") .replace(/ì/gi, "i") .replace(/ò/gi, "o") .replace(/ó/gi, "o") .replace(/õ/gi, "o") .replace(/ô/gi, "o") .replace(/ú/gi, "u") .replace(/ù/gi, "u"); } }