Skip to content
Snippets Groups Projects
Select Git revision
  • 7ea69a9a198e6209486bdd1a624f755e8424097c
  • master default protected
  • 1.31
  • 4.38.2
  • 4.38.1
  • 4.38.0
  • 4.37.2
  • 4.37.1
  • 4.37.0
  • 4.36.0
  • 4.35.0
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
  • 4.32.0
  • 4.31.0
  • 4.30.1
  • 4.30.0
  • 4.29.1
  • 4.29.0
23 results

dom-based-templating-implementation.md

Blame
  • attributes.mjs 10.04 KiB
    /**
     * Copyright © schukai GmbH 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 schukai GmbH.
     *
     * 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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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 schukai GmbH
     * @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;
    }