UNPKG

@knighttower/utility

Version:

UtilityJs is a utility library that provides a collection of utility functions for various tasks. The library is designed to be easy to use and covers the most common use cases.

257 lines (235 loc) 7.42 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); // Author Knighttower // MIT License // [2022] [Knighttower] https://github.com/knighttower /** * @module DomObserver * Detect DOM changes * @name DomObserver * @param {window} selector * @param {Function} * @return DomObserver * @example DomObserver.addOnNodeChange('elementIdentifier', () => { console.log('Node changed') }) * @example DomObserver.removeOnNodeChange('elementIdentifier') */ /** * Holds memory of registered functions * @private */ const executeOnNodeChanged = {}; let observer; /** * When node change * @param {String} id * @param {Function} callback Callback when any node changes/ add/deleted/modified * @memberof DomObserver * @usage domObserver.addOnNodeChange('elementIdentifier', () => { console.log('Node changed') }) * @return {Void} */ const addOnNodeChange = (id, callback) => { if (callback) { executeOnNodeChanged[id] = callback; } }; /** * Remove from node change * @param {String} id * @memberof DomObserver * @usage domObserver.removeOnNodeChange('elementIdentifier') * @return {Void} */ const removeOnNodeChange = (id) => { if (id) { delete executeOnNodeChanged[id]; } }; /** * Deep cleanup * @return {Void} */ const cleanup = () => { Object.keys(executeOnNodeChanged).forEach((key) => delete executeOnNodeChanged[key]); }; /** * Observer * @private * @return {MutationObserver} */ const start = () => { if (typeof window !== 'undefined' && !observer) { const callback = (mutationList) => { for (const mutation of mutationList) { if (mutation.type === 'childList') { for (const id in executeOnNodeChanged) { executeOnNodeChanged[id](); } } } }; const config = { childList: true, subtree: true, }; observer = new MutationObserver(callback); if (document.body) { return observer.observe(document.body, config); } document.addEventListener('DOMContentLoaded', (event) => { return observer.observe(document.body, config); }); } }; /** * @exports domObserver * @type {Object} * @usage domObserver.addOnNodeChange('elementIdentifier', () => { console.log('Node changed') }) * @usage domObserver.removeOnNodeChange('elementIdentifier') * @usage domObserver.cleanup() */ const domObserver = { start, addOnNodeChange, removeOnNodeChange, cleanup, }; // Author Knighttower // MIT License // Copyright (c) [2022] [Knighttower] https://github.com/knighttower /** * @class Adds some extra functionality to interact with a DOM element * @param {String|Object} selector Class or ID or DOM element * @param {String} scope The scope to search in, window, document, dom element. Defaults to document * @return {Object} * @example new ElementHelper('elementSelector') * @example new ElementHelper('elementSelector', domElement|window|document) * */ class ElementHelper { /** * Constructor * @param {String|Object} selector * @return {Object} */ constructor(selector, scope = document) { domObserver.start(); this.selector = selector; if (typeof selector === 'object') { this.domElement = selector; } else if (String(selector).includes('//')) { this.domElement = this.getElementByXpath(selector); } else { this.domElement = scope.querySelector(selector); } } // ========================================= // --> Public // -------------------------- /** * Check if the element exists or is visible. It will keep querying * @return {Boolean} */ isInDom() { return Boolean(this.domElement?.outerHTML); } /** * Wait for element exists or is visible. It will keep querying * @function whenInDom * @return {Promise} */ whenInDom() { let $this = this; let callbackId = Date.now() + Math.floor(Math.random() * 1000); return new Promise(function (resolveThis) { if (!$this.isInDom()) { domObserver.addOnNodeChange(callbackId, () => { let element = new ElementHelper($this.selector); if (element.isInDom()) { $this = element; resolveThis($this); domObserver.removeOnNodeChange(callbackId); } }); } else { resolveThis($this); } }); } /** * Find element by Xpath string * @param {String} xpath * @example getElementByXpath("//html[1]/body[1]/div[1]") * @return {Object} DOM element */ getElementByXpath(xpath) { return document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null) .singleNodeValue; } /** * Get the element xpath string * @author Based on https://stackoverflow.com/questions/2631820/how-do-i-ensure-saved-click-coordinates-can-be-reload-to-the-same-place-even-if/2631931#2631931 * @return {String} */ getXpathTo() { let element = this.domElement; if (element.id) { return `//*[@id='${element.id}']`; } if (element === document.body) { return '//' + element.tagName; } let ix = 0; let siblings = element.parentNode.childNodes; for (let i = 0; i < siblings.length; i++) { let sibling = siblings[i]; if (sibling === element) { return ( new ElementHelper(element.parentNode).getXpathTo() + '/' + element.tagName + '[' + (ix + 1) + ']' ); } if (sibling.nodeType === 1 && sibling.tagName === element.tagName) { ix++; } } } /** * Get the element attribute, but parse it if it is an object or array * @param {String} attr Atrribute name * @return {String|Array|Object|Null} */ getAttribute(attr) { return this.domElement.getAttribute(attr) || null; } /** * Create a unique has for the element derived from its xpath * @author Based on https://www.geeksforgeeks.org/how-to-create-hash-from-string-in-javascript/ * @return {String} */ getHash() { let string = String(this.getXpathTo()); let hash = 0; if (string.length === 0) { return hash; } for (let i = 0; i < string.length; i++) { let char = string.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return hash; } } // ========================================= // --> Utilities // -------------------------- const selectElement = (selector, scope = document) => new ElementHelper(selector, scope); const elementHelper = (selector, scope = document) => new ElementHelper(selector, scope); exports.ElementHelper = ElementHelper; exports.default = ElementHelper; exports.elementHelper = elementHelper; exports.selectElement = selectElement;