UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

418 lines (371 loc) 10.3 kB
/** * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact Volker Schukai. * * SPDX-License-Identifier: AGPL-3.0 */ import { getGlobalFunction } from "../types/global.mjs"; import { TokenList } from "../types/tokenlist.mjs"; import { validateInstance, validateString, validateSymbol, } from "../types/validate.mjs"; import { ATTRIBUTE_OBJECTLINK } from "./constants.mjs"; export { findClosestObjectLink, addToObjectLink, removeObjectLink, hasObjectLink, getLinkedObjects, toggleAttributeToken, addAttributeToken, removeAttributeToken, containsAttributeToken, replaceAttributeToken, clearAttributeTokens, findClosestByAttribute, findClosestByClass, }; /** * Get the closest object link of a node * * if a node is specified without a object link, a recursive search upwards is performed until the corresponding * object link is found, or undefined is returned. * * @param {HTMLElement} element * @return {HTMLElement|undefined} * @license AGPLv3 * @since 1.10.0 * @copyright Volker Schukai * @throws {TypeError} value is not an instance of HTMLElement */ function findClosestObjectLink(element) { return findClosestByAttribute(element, ATTRIBUTE_OBJECTLINK); } /** * Adds a class attribute to an element. * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param element * @param symbol * @param object * @return {*} */ function addToObjectLink(element, symbol, object) { validateInstance(element, HTMLElement); validateSymbol(symbol); if (element?.[symbol] === undefined) { element[symbol] = new Set(); } addAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); element[symbol].add(object); return element; } /** * Removes an object from an element * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {Symbol} symbol * @return {*} */ function removeObjectLink(element, symbol) { validateInstance(element, HTMLElement); validateSymbol(symbol); if (element?.[symbol] === undefined) { return element; } removeAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); delete element[symbol]; return element; } /** * Checks if an element has an object link * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {Symbol} symbol * @return {boolean} */ function hasObjectLink(element, symbol) { validateInstance(element, HTMLElement); validateSymbol(symbol); if (element?.[symbol] === undefined) { return false; } return containsAttributeToken( element, ATTRIBUTE_OBJECTLINK, symbol.toString(), ); } /** * The ObjectLink can be used to attach objects to HTMLElements. The elements are kept in a set under a unique * symbol and can be read via an iterator {@see {@link getLinkedObjects}}. * * In addition, elements with an objectLink receive the attribute `data-monster-objectlink`. * * With the method {@see {@link addToObjectLink}} the objects can be added. * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {Symbol} symbol * @return {Iterator} * @throws {Error} there is no object link for symbol */ function getLinkedObjects(element, symbol) { validateInstance(element, HTMLElement); validateSymbol(symbol); if (element?.[symbol] === undefined) { throw new Error(`there is no object link for ${symbol.toString()}`); } return element?.[symbol][Symbol.iterator](); } /** * With this method tokens in an attribute can be switched on or off. For example, classes can be switched on and off in the elements class attribute. * * Tokens are always separated by a space. * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {string} key * @param {string} token * @return {HTMLElement} */ function toggleAttributeToken(element, key, token) { validateInstance(element, HTMLElement); validateString(token); validateString(key); if (!element.hasAttribute(key)) { element.setAttribute(key, token); return element; } element.setAttribute( key, new TokenList(element.getAttribute(key)).toggle(token).toString(), ); return element; } /** * This method can be used to add a token to an attribute. Tokens are always separated by a space. * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {string} key * @param {string} token * @return {HTMLElement} */ function addAttributeToken(element, key, token) { validateInstance(element, HTMLElement); validateString(token); validateString(key); if (!element.hasAttribute(key)) { element.setAttribute(key, token); return element; } element.setAttribute( key, new TokenList(element.getAttribute(key)).add(token).toString(), ); return element; } /** * This function can be used to remove tokens from an attribute. * * Tokens are always separated by a space. * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {string} key * @param {string} token * @return {HTMLElement} */ function removeAttributeToken(element, key, token) { validateInstance(element, HTMLElement); validateString(token); validateString(key); if (!element.hasAttribute(key)) { return element; } element.setAttribute( key, new TokenList(element.getAttribute(key)).remove(token).toString(), ); return element; } /** * This method can be used to determine whether an attribute has a token. * * Tokens are always separated by a space. * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {string} key * @param {string} token * @return {boolean} */ function containsAttributeToken(element, key, token) { validateInstance(element, HTMLElement); validateString(token); validateString(key); if (!element.hasAttribute(key)) { return false; } return new TokenList(element.getAttribute(key)).contains(token); } /** * Tokens are always separated by a space. * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {string} key * @param {string} from * @param {string} to * @return {HTMLElement} */ function replaceAttributeToken(element, key, from, to) { validateInstance(element, HTMLElement); validateString(from); validateString(to); validateString(key); if (!element.hasAttribute(key)) { return element; } element.setAttribute( key, new TokenList(element.getAttribute(key)).replace(from, to).toString(), ); return element; } /** * Tokens are always separated by a space. * * @license AGPLv3 * @since 1.9.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {string} key * @return {HTMLElement} */ function clearAttributeTokens(element, key) { validateInstance(element, HTMLElement); validateString(key); if (!element.hasAttribute(key)) { return element; } element.setAttribute(key, ""); return element; } /** * This function searches, starting from an `HTMLElemement`, for the next element that has a certain attribute. * * ```html * <div data-my-attribute="2" id="2"> * <div id="1"></div> * </div> * ``` * * ```javascript * // if no value is specified (undefined), then only the attribute is checked. * findClosestByAttribute(document.getElementById('1'),'data-my-attribute'); // ↦ node with id 2 * findClosestByAttribute(document.getElementById('2'),'data-my-attribute'); // ↦ node with id 2 * * // if a value is specified, for example an empty string, then the name and the value are checked. * findClosestByAttribute(document.getElementById('1'),'data-my-attribute', ''); // ↦ undefined * findClosestByAttribute(document.getElementById('1'),'data-my-attribute', '2'); // ↦ node with id 2 * ``` * * @license AGPLv3 * @since 1.14.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {string} key * @param {string|undefined} value * @return {HTMLElement|undefined} * @summary find closest node */ function findClosestByAttribute(element, key, value) { validateInstance(element, getGlobalFunction("HTMLElement")); if (element.hasAttribute(key)) { if (value === undefined) { return element; } if (element.getAttribute(key) === value) { return element; } } let selector = validateString(key); if (value !== undefined) selector += `=${validateString(value)}`; const result = element.closest(`[${selector}]`); if (result instanceof HTMLElement) { return result; } return undefined; } /** * This function searches, starting from an `HTMLElement`, for the next element that has a certain attribute. * * ```html * <div class="myclass" id="2"> * <div id="1"></div> * </div> * ``` * * ```javascript * // if no value is specified (undefined), then only the attribute is checked. * findClosestByClass(document.getElementById('1'),'myclass'); // ↦ node with id 2 * findClosestByClass(document.getElementById('2'),'myclass'); // ↦ node with id 2 * ``` * * ``` * <script type="module"> * import {findClosestByClass} from '@schukai/monster/source/dom/attributes.mjs'; * findClosestByClass(); * </script> * ``` * * @license AGPLv3 * @since 1.27.0 * @copyright Volker Schukai * @param {HTMLElement} element * @param {string} className * @return {HTMLElement|undefined} * @summary find closest node */ function findClosestByClass(element, className) { validateInstance(element, getGlobalFunction("HTMLElement")); if (element?.classList?.contains(validateString(className))) { return element; } const result = element.closest(`.${className}`); if (result instanceof HTMLElement) { return result; } return undefined; }