vjsrouter
Version:
A modern, file-system based router for vanilla JavaScript with SSR support.
101 lines (87 loc) • 4.08 kB
JavaScript
// File: src/utils/DOMUtils.js
import { logger, LogLevel } from './Logger.js';
/**
* @description A collection of utility functions for safe and efficient DOM manipulation.
* These functions are designed to be robust, handling potential null elements gracefully
* and providing clear logging for debugging purposes.
*/
export const DOMUtils = {
/**
* @description Safely clears all child nodes from a given parent element.
* This is more performant than setting `innerHTML = ''` for complex DOM trees
* as it avoids reparsing the HTML and properly removes event listeners on child nodes.
*
* @param {HTMLElement|null} parentElement - The DOM element to clear.
* @returns {boolean} - True if the element was cleared successfully, false otherwise.
*/
clearElement(parentElement) {
const source = 'DOMUtils.clearElement';
if (!(parentElement instanceof HTMLElement)) {
logger.warn(source, 'Provided argument is not a valid HTMLElement. Cannot clear.', { parentElement });
return false;
}
logger.debug(source, 'Clearing element...', { element: parentElement });
while (parentElement.firstChild) {
parentElement.removeChild(parentElement.firstChild);
}
logger.debug(source, 'Element cleared successfully.');
return true;
},
/**
* @description Appends a child element to a parent element with safety checks.
*
* @param {HTMLElement|null} parentElement - The element to append the child to.
* @param {Node|null} childElement - The node (e.g., an HTMLElement, TextNode) to append.
* @returns {boolean} - True if the child was appended successfully, false otherwise.
*/
appendChild(parentElement, childElement) {
const source = 'DOMUtils.appendChild';
if (!(parentElement instanceof HTMLElement)) {
logger.error(source, 'Parent element is not a valid HTMLElement. Cannot append child.', { parentElement });
return false;
}
if (!(childElement instanceof Node)) {
logger.error(source, 'Child element is not a valid Node. Cannot append.', { childElement });
return false;
}
logger.debug(source, 'Appending child to parent.', { parent: parentElement, child: childElement });
parentElement.appendChild(childElement);
return true;
},
/**
* @description Sets the document's title, including a fallback for safety.
*
* @param {string} title - The title to set. If empty or not a string, a default title will be used.
*/
setDocumentTitle(title) {
const source = 'DOMUtils.setDocumentTitle';
if (typeof title === 'string' && title.trim() !== '') {
document.title = title;
logger.debug(source, `Document title set to "${title}".`);
} else {
document.title = 'vjsrouter App';
logger.warn(source, `Provided title was invalid. Using default title.`, { providedTitle: title });
}
},
/**
* @description Finds the closest ancestor of an element that matches a given selector.
* This is particularly useful for event delegation, for example, finding if a click
* happened inside a link, even if the target was a nested element like a <span> or <img>.
*
* @param {EventTarget|null} startElement - The element to start the search from (e.g., event.target).
* @param {string} selector - The CSS selector to find (e.g., 'a[href]').
* @returns {HTMLElement|null} - The matching ancestor element, or null if no match is found.
*/
findClosestAncestor(startElement, selector) {
const source = 'DOMUtils.findClosestAncestor';
if (!(startElement instanceof Element)) {
logger.debug(source, 'Provided startElement is not a valid Element. Cannot find ancestor.', { startElement });
return null;
}
// The `closest()` method is a modern, efficient, and built-in way to do this.
// It starts with the element itself and travels up the DOM tree.
const matchingElement = startElement.closest(selector);
logger.debug(source, `Search for selector "${selector}" found:`, { matchingElement });
return matchingElement;
}
};