@caspingus/lt
Version:
A utility library of helpers and tools for working with Learnosity APIs.
107 lines (93 loc) • 3.5 kB
JavaScript
import logger from './logger';
/**
* A utility DOM object.
* @module Utils/Dom
*/
/**
* Checks DOM element for existence. If not found we retry
* n number of times with a delay.
* @since 2.22.2
* @param {string} id
* @param {*} callback
* @param {number} retries
*/
export function waitForElement(id, callback, retries = 5) {
const element = document.getElementById(id);
if (element) {
callback(element);
} else if (retries > 0) {
setTimeout(() => waitForElement(id, callback, retries - 1), 10);
} else {
console.warn(`Element with ID "${id}" not found after ${retries} attempts.`);
}
}
/**
* Observe the DOM for the first appearance of an element matching the selector.
* Prevents multiple observers from being created for the same selector.
*
* @param {string} selector - CSS selector to watch for.
* @param {function} callback - Function to call when element is found.
* @param {object} [options] - The root element to observe (options.root).
* @param {object} [state] - Cache of any current active observers (state.activeObservers).
* @returns {function} disconnect - Call to stop observing manually.
* @since 2.24.0
* @ignore
*/
export function setObserver(selector, callback, options, state) {
const root = options.root || document.body;
const observeConfig = {
childList: true,
subtree: true,
...options.observeConfig,
};
// Prevent duplicate observers for the same selector
if (state.activeObservers.has(selector)) {
logger.debug(`${state.logPrefix}Already observing`, selector);
return () => {};
}
// Check immediately for element already in the DOM
const existing = root.querySelector(selector);
if (existing) {
logger.debug(`${state.logPrefix}Element already in DOM`, selector);
callback(existing);
return () => {};
}
// Register selector to prevent duplicate observers
state.activeObservers.add(selector);
const observer = new MutationObserver((mutations, observerInstance) => {
for (const mutation of mutations) {
if (mutation.type !== 'childList') {
continue;
}
for (const node of mutation.addedNodes) {
if (node.nodeType !== 1) {
continue;
}
logger.debug(`${state.logPrefix}Observing changes: `, root, node);
if (node.matches && node.matches(selector)) {
observerInstance.disconnect();
logger.debug(`${state.logPrefix}Disconnecting ${selector}`);
state.activeObservers.delete(selector);
callback(node);
return;
}
if (node.querySelector) {
const match = node.querySelector(selector);
if (match) {
observerInstance.disconnect();
logger.debug(`${state.logPrefix}Disconnecting ${selector}`);
state.activeObservers.delete(selector);
callback(match);
return;
}
}
}
}
});
logger.debug(`${state.logPrefix}Observing for ${selector}`);
observer.observe(root, observeConfig);
return function disconnect() {
observer.disconnect();
state.activeObservers.delete(selector);
};
}