Select Git revision
attributes.mjs

Volker Schukai authored
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;
}