Skip to content
Snippets Groups Projects
Verified Commit ccb2cba1 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

chore: commit save point

parent a5f8acc0
No related branches found
No related tags found
No related merge requests found
Showing
with 5120 additions and 0 deletions
'use strict';
/**
* Namespace for storages
*
* @namespace Monster.Data.Datasource.Storage
* @memberOf Monster.Data.Datasource
* @author schukai GmbH
*/
/**
* @private
* @type {string}
*/
export const namespace = "Monster.Data.Datasource.Storage";
\ No newline at end of file
'use strict';
/**
* @author schukai GmbH
*/
import {internalSymbol} from "../../../constants.js";
import {assignToNamespace, Monster} from '../../../namespace.js';
import {getGlobalObject} from "../../../types/global.js";
import {Datasource} from "../../datasource.js";
import {Storage, storageObjectSymbol} from "../storage.js";
/**
* You can create an object of this class using the monster namespace `Monster.Data.Datasource.Storage.SessionStorage()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.Data.Datasource.Storage.SessionStorage()
* </script>
* ```
*
* Alternatively you can import the class directly
*
* ```
* <script type="module">
* import {SessionStorage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/datasource/storage/sessionstorage.js';
* new SessionStorage()
* </script>
* ```
*
* @since 1.22.0
* @copyright schukai GmbH
* @memberOf Monster.Data.Datasource.Storage
* @summary The LocalStorage class encapsulates the access to data objects.
*/
class SessionStorage extends Storage {
/**
* @throws {Error} this method must be implemented by derived classes.
* @return {external:sessionStorage}
* @private
*/
[storageObjectSymbol]() {
return getGlobalObject('sessionStorage');
}
/**
* Create Clone
*
* @return {SessionStorage}
*/
getClone() {
const self = this;
return new SessionStorage(self[internalSymbol].getRealSubject()['options'].key);
}
}
assignToNamespace('Monster.Data.Datasource.Storage', SessionStorage);
export {Monster, SessionStorage}
'use strict';
/**
* @author schukai GmbH
*/
import {assignToNamespace, Monster} from '../namespace.js';
import {isArray, isObject} from "../types/is.js";
import {typeOf} from "../types/typeof.js";
/**
* With the diff function you can perform the change of one object to another. The result shows the changes of the second object to the first object.
*
* The operator `add` means that something has been added to the second object. `delete` means that something has been deleted from the second object compared to the first object.
*
* You can call the method via the monster namespace `Monster.Data.Diff()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.Data.Diff(a, b)
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/diff.js';
* Diff(a, b)
* </script>
* ```
*
* @example
*
* import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/diff.js';
*
* // given are two objects x and y.
*
* let x = {
* a: 1,
* b: "Hello!"
* }
*
* let y = {
* a: 2,
* c: true
* }
*
* // These two objects can be compared with each other.
*
* console.log(Diff(x, y));
*
* // the result is then the following
*
* //
* // [
* // {
* // operator: 'update',
* // path: [ 'a' ],
* // first: { value: 1, type: 'number' },
* // second: { value: 2, type: 'number' }
* // },
* // {
* // operator: 'delete',
* // path: [ 'b' ],
* // first: { value: 'Hello!', type: 'string' }
* // },
* // {
* // operator: 'add',
* // path: [ 'c' ],
* // second: { value: true, type: 'boolean' }
* // }
* // ]
*
* @param {*} first
* @param {*} second
* @return {array}
* @since 1.6.0
* @copyright schukai GmbH
* @memberOf Monster.Data
*/
function diff(first, second) {
return doDiff(first, second)
}
/**
* @private
* @param a
* @param b
* @param type
* @return {Set<string>|Set<number>}
*/
function getKeys(a, b, type) {
if (isArray(type)) {
const keys = a.length > b.length ? new Array(a.length) : new Array(b.length);
keys.fill(0);
return new Set(keys.map((_, i) => i));
}
return new Set(Object.keys(a).concat(Object.keys(b)));
}
/**
* @private
* @param a
* @param b
* @param path
* @param diff
* @return {array}
*/
function doDiff(a, b, path, diff) {
let typeA = typeOf(a)
let typeB = typeOf(b)
const currPath = path || [];
const currDiff = diff || [];
if (typeA === typeB && (typeA === 'object' || typeA ==='array')) {
getKeys(a, b, typeA).forEach((v) => {
if (!(Object.prototype.hasOwnProperty.call(a, v))) {
currDiff.push(buildResult(a[v], b[v], 'add', currPath.concat(v)));
} else if (!(Object.prototype.hasOwnProperty.call(b, v))) {
currDiff.push(buildResult(a[v], b[v], 'delete', currPath.concat(v)));
} else {
doDiff(a[v], b[v], currPath.concat(v), currDiff);
}
});
} else {
const o = getOperator(a, b, typeA, typeB);
if (o !== undefined) {
currDiff.push(buildResult(a, b, o, path));
}
}
return currDiff;
}
/**
*
* @param {*} a
* @param {*} b
* @param {string} operator
* @param {array} path
* @return {{path: array, operator: string}}
* @private
*/
function buildResult(a, b, operator, path) {
const result = {
operator,
path,
};
if (operator !== 'add') {
result.first = {
value: a,
type: typeof a
};
if (isObject(a)) {
const name = Object.getPrototypeOf(a)?.constructor?.name;
if (name !== undefined) {
result.first.instance = name;
}
}
}
if (operator === 'add' || operator === 'update') {
result.second = {
value: b,
type: typeof b
};
if (isObject(b)) {
const name = Object.getPrototypeOf(b)?.constructor?.name;
if (name !== undefined) {
result.second.instance = name;
}
}
}
return result;
}
/**
* @private
* @param {*} a
* @param {*} b
* @return {boolean}
*/
function isNotEqual(a, b) {
if (typeof a !== typeof b) {
return true;
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() !== b.getTime();
}
return a !== b;
}
/**
* @private
* @param {*} a
* @param {*} b
* @return {string|undefined}
*/
function getOperator(a, b) {
/**
* @type {string|undefined}
*/
let operator;
/**
* @type {string}
*/
let typeA = typeof a;
/**
* @type {string}
*/
let typeB = typeof b;
if (typeA === 'undefined' && typeB !== 'undefined') {
operator = 'add';
} else if (typeA !== 'undefined' && typeB === 'undefined') {
operator = 'delete';
} else if (isNotEqual(a, b)) {
operator = 'update';
}
return operator;
}
assignToNamespace('Monster.Data', diff);
export {Monster, diff}
'use strict';
/**
* @author schukai GmbH
*/
import {assignToNamespace, Monster} from '../namespace.js';
import {isArray, isObject} from "../types/is.js";
import {typeOf} from "../types/typeof.js";
/**
* Extend copies all enumerable own properties from one or
* more source objects to a target object. It returns the modified target object.
*
* You can call the method via the monster namespace `Monster.Data.extend()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.Data.extend(a, b)
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {extend} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/extend.js';
* extend(a, b)
* </script>
* ```
*
* @param {object} target
* @param {object}
* @return {object}
* @since 1.10.0
* @copyright schukai GmbH
* @memberOf Monster.Data
* @throws {Error} unsupported argument
* @throws {Error} type mismatch
*/
function extend() {
let o, i;
for (i = 0; i < arguments.length; i++) {
let a = arguments[i];
if (!(isObject(a) || isArray(a))) {
throw new Error('unsupported argument ' + JSON.stringify(a));
}
if (o === undefined) {
o = a;
continue;
}
for (let k in a) {
let v = a?.[k];
if (v === o?.[k]) {
continue;
}
if ((isObject(v)&&typeOf(v)==='object') || isArray(v)) {
if (o[k] === undefined) {
if (isArray(v)) {
o[k] = [];
} else {
o[k] = {};
}
} else {
if (typeOf(o[k]) !== typeOf(v)) {
throw new Error("type mismatch: " + JSON.stringify(o[k]) + "(" + typeOf(o[k]) + ") != " + JSON.stringify(v) + "(" + typeOf(v) + ")");
}
}
o[k] = extend(o[k], v);
} else {
o[k] = v;
}
}
}
return o;
}
assignToNamespace('Monster.Data', extend);
export {Monster, extend}
'use strict';
/**
* In this namespace you will find classes and methods for handling data.
*
* @namespace Monster.Data
* @memberOf Monster
* @author schukai GmbH
*/
/**
* @private
* @type {string}
*/
export const namespace = "Monster.Data";
\ No newline at end of file
'use strict';
/**
* @author schukai GmbH
*/
import {assignToNamespace, Monster} from '../namespace.js';
import {Base} from '../types/base.js';
import {isArray, isInteger, isObject, isPrimitive} from '../types/is.js';
import {Stack} from "../types/stack.js";
import {validateInteger, validateString} from '../types/validate.js';
/**
* path separator
*
* @private
* @type {string}
*/
export const DELIMITER = '.';
/**
* @private
* @type {string}
*/
export const WILDCARD = '*';
/**
* You can call the method via the monster namespace `new Monster.Data.Pathfinder()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* console.log(new Monster.Data.Pathfinder())
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/pathfinder.js';
* console.log(new Pathfinder())
* </script>
* ```
*
* With the help of the pathfinder, values can be read and written from an object construct.
*
* ```
* new Pathfinder({
* a: {
* b: {
* f: [
* {
* g: false,
* }
* ],
* }
* }
* }).getVia("a.b.f.0.g"); // ↦ false
* ```
*
* if a value is not present or has the wrong type, a corresponding exception is thrown.
*
* ```
* new Pathfinder({}).getVia("a.b.f.0.g"); // ↦ Error
* ```
*
* The `Pathfinder.exists()` method can be used to check whether access to the path is possible.
*
* ```
* new Pathfinder({}).exists("a.b.f.0.g"); // ↦ false
* ```
*
* pathfinder can also be used to build object structures. to do this, the `Pathfinder.setVia()` method must be used.
*
* ```
* obj = {};
* new Pathfinder(obj).setVia('a.b.0.c', true); // ↦ {a:{b:[{c:true}]}}
* ```
*
* @example
*
* import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/pathfinder.js';
*
* let value = new Pathfinder({
* a: {
* b: {
* f: [
* {
* g: false,
* }
* ],
* }
* }
* }).getVia("a.b.f.0.g");
*
* console.log(value);
* // ↦ false
*
* try {
* new Pathfinder({}).getVia("a.b.f.0.g");
* } catch(e) {
* console.log(e.toString());
* // ↦ Error: the journey is not at its end (b.f.0.g)
* }
*
* @example
*
* import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/pathfinder.js';
*
* let p = new Pathfinder({
* a: {
* x: [
* {c: 1}, {c: 2}
* ],
* y: true
* },
* b: {
* x: [
* {c: 1, d: false}, {c: 2}
* ],
* y: true
* },
* });
*
* let r = p.getVia("*.x.*.c");
* console.log(r);
*
* @since 1.4.0
* @copyright schukai GmbH
* @memberOf Monster.Data
*/
class Pathfinder extends Base {
/**
* @param {array|object|Map|Set} value
* @since 1.4.0
* @throws {Error} the parameter must not be a simple type
**/
constructor(object) {
super();
if (isPrimitive(object)) {
throw new Error('the parameter must not be a simple type');
}
this.object = object;
this.wildCard = WILDCARD;
}
/**
* set wildcard
*
* @param {string} wildcard
* @return {Pathfinder}
* @since 1.7.0
*/
setWildCard(wildcard) {
validateString(wildcard);
this.wildCard = wildcard;
return this;
}
/**
*
* @param {string} path
* @since 1.4.0
* @returns {*}
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @throws {Error} unsupported action for this data type
*/
getVia(path) {
return getValueViaPath.call(this, this.object, validateString(path));
}
/**
*
* @param {string} path
* @param {*} value
* @returns {Pathfinder}
* @since 1.4.0
* @throws {TypeError} unsupported type
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @throws {Error} unsupported action for this data type
*/
setVia(path, value) {
validateString(path);
setValueViaPath.call(this, this.object, path, value);
return this;
}
/**
* Delete Via Path
*
* @param {string} path
* @returns {Pathfinder}
* @since 1.6.0
* @throws {TypeError} unsupported type
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @throws {Error} unsupported action for this data type
*/
deleteVia(path) {
validateString(path);
deleteValueViaPath.call(this, this.object, path);
return this;
}
/**
*
* @param {string} path
* @return {bool}
* @throws {TypeError} unsupported type
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @since 1.4.0
*/
exists(path) {
validateString(path);
try {
getValueViaPath.call(this, this.object, path, true);
return true;
} catch (e) {
}
return false;
}
}
assignToNamespace('Monster.Data', Pathfinder);
export {Monster, Pathfinder}
/**
*
* @param {*} subject
* @param {string} path
* @param {string} check
* @return {Map}
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {Error} unsupported action for this data type
* @private
*/
function iterate(subject, path, check) {
const result = new Map;
if (isObject(subject) || isArray(subject)) {
for (const [key, value] of Object.entries(subject)) {
result.set(key, getValueViaPath.call(this, value, path, check))
}
} else {
let key = path.split(DELIMITER).shift();
result.set(key, getValueViaPath.call(this, subject, path, check));
}
return result;
}
/**
*
* @param {*} subject
* @param [string} path
* @param [boolean} check
* @returns {*}
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {Error} unsupported action for this data type
* @private
*/
function getValueViaPath(subject, path, check) {
if (path === "") {
return subject;
}
let parts = path.split(DELIMITER)
let current = parts.shift();
if (current === this.wildCard) {
return iterate.call(this, subject, parts.join(DELIMITER), check);
}
if (isObject(subject) || isArray(subject)) {
let anchor;
if (subject instanceof Map || subject instanceof WeakMap) {
anchor = subject.get(current);
} else if (subject instanceof Set || subject instanceof WeakSet) {
current = parseInt(current);
validateInteger(current)
anchor = [...subject]?.[current];
} else if (typeof WeakRef === 'function' && subject instanceof WeakRef) {
throw Error('unsupported action for this data type');
} else if (isArray(subject)) {
current = parseInt(current);
validateInteger(current)
anchor = subject?.[current];
} else {
anchor = subject?.[current];
}
if (isObject(anchor) || isArray(anchor)) {
return getValueViaPath.call(this, anchor, parts.join(DELIMITER), check)
}
if (parts.length > 0) {
throw Error("the journey is not at its end (" + parts.join(DELIMITER) + ")");
}
if (check === true) {
const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(subject), current);
if (!subject.hasOwnProperty(current) && descriptor === undefined) {
throw Error('unknown value');
}
}
return anchor;
}
throw TypeError("unsupported type " + typeof subject)
}
/**
*
* @param object
* @param path
* @param value
* @returns {void}
* @throws {TypeError} unsupported type
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {Error} unsupported action for this data type
* @private
*/
function setValueViaPath(object, path, value) {
validateString(path);
let parts = path.split(DELIMITER)
let last = parts.pop();
let subpath = parts.join(DELIMITER);
let stack = new Stack()
let current = subpath;
while (true) {
try {
getValueViaPath.call(this, object, current, true)
break;
} catch (e) {
}
stack.push(current);
parts.pop();
current = parts.join(DELIMITER);
if (current === "") break;
}
while (!stack.isEmpty()) {
current = stack.pop();
let obj = {};
if (!stack.isEmpty()) {
let n = stack.peek().split(DELIMITER).pop();
if (isInteger(parseInt(n))) {
obj = [];
}
}
setValueViaPath.call(this, object, current, obj);
}
let anchor = getValueViaPath.call(this, object, subpath);
if (!isObject(object) && !isArray(object)) {
throw TypeError("unsupported type: " + typeof object);
}
if (anchor instanceof Map || anchor instanceof WeakMap) {
anchor.set(last, value);
} else if (anchor instanceof Set || anchor instanceof WeakSet) {
anchor.append(value)
} else if (typeof WeakRef === 'function' && anchor instanceof WeakRef) {
throw Error('unsupported action for this data type');
} else if (isArray(anchor)) {
last = parseInt(last);
validateInteger(last)
assignProperty(anchor, last, value);
} else {
assignProperty(anchor, last, value);
}
}
/**
* @private
* @param {object} object
* @param {string} key
* @param {*} value
*/
function assignProperty(object, key, value) {
if (!object.hasOwnProperty(key)) {
object[key] = value;
return;
}
if (value === undefined) {
delete object[key];
}
object[key] = value;
}
/**
*
* @param object
* @param path
* @returns {void}
* @throws {TypeError} unsupported type
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {Error} unsupported action for this data type
* @since 1.6.0
* @private
*/
function deleteValueViaPath(object, path) {
const parts = path.split(DELIMITER)
let last = parts.pop();
const subpath = parts.join(DELIMITER);
const anchor = getValueViaPath.call(this, object, subpath);
if (anchor instanceof Map) {
anchor.delete(last);
} else if (anchor instanceof Set || anchor instanceof WeakMap || anchor instanceof WeakSet || (typeof WeakRef === 'function' && anchor instanceof WeakRef)) {
throw Error('unsupported action for this data type');
} else if (isArray(anchor)) {
last = parseInt(last);
validateInteger(last)
delete anchor[last];
} else {
delete anchor[last];
}
}
'use strict';
/**
* @author schukai GmbH
*/
import {assignToNamespace, Monster} from '../namespace.js';
import {Base} from '../types/base.js';
import {validateString} from '../types/validate.js';
import {Transformer} from './transformer.js';
const DELIMITER = '|';
/**
* The pipe class makes it possible to combine several processing steps.
*
* You can call the method via the monster namespace `new Monster.Data.Pipe()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* new Monster.Data.Pipe()
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {Pipe} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/pipe.js';
* new Pipe()
* </script>
* ```
*
* A pipe consists of commands whose input and output are connected with the pipe symbol `|`.
*
* With the Pipe, processing steps can be combined. Here, the value of an object is accessed via the pathfinder (path command).
* the word is then converted to uppercase letters and a prefix Hello is added. the two backslash safe the space char.
*
* @example
* import {Pipe} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/data/pipe.js';
*
* let obj = {
* a: {
* b: {
* c: {
* d: "world"
* }
* }
* }
* }
*
* console.log(new Pipe('path:a.b.c.d | toupper | prefix:Hello\\ ').run(obj));
* // ↦ Hello WORLD
*
* @since 1.5.0
* @copyright schukai GmbH
* @memberOf Monster.Data
*/
class Pipe extends Base {
/**
*
* @param {string} pipe a pipe consists of commands whose input and output are connected with the pipe symbol `|`.
* @throws {TypeError}
*/
constructor(pipe) {
super();
validateString(pipe);
this.pipe = pipe.split(DELIMITER).map((v) => {
return new Transformer(v);
});
}
/**
*
* @param {string} name
* @param {function} callback
* @param {object} context
* @returns {Transformer}
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not a function
*/
setCallback(name, callback, context) {
for (const [, t] of Object.entries(this.pipe)) {
t.setCallback(name, callback, context);
}
return this;
}
/**
* run a pipe
*
* @param {*} value
* @returns {*}
*/
run(value) {
return this.pipe.reduce((accumulator, transformer, currentIndex, array) => {
return transformer.run(accumulator);
}, value);
}
}
assignToNamespace('Monster.Data', Pipe);
export {Monster, Pipe}
This diff is collapsed.
'use strict';
/**
* @author schukai GmbH
*/
import {assignToNamespace, Monster} from '../namespace.js';
import {Base} from '../types/base.js';
import {getGlobalFunction} from "../types/global.js";
import {ProxyObserver} from "../types/proxyobserver.js";
import {validateInstance, validateString} from "../types/validate.js";
/**
* attribute prefix
*
* @type {string}
* @memberOf Monster.DOM
*/
const ATTRIBUTEPREFIX = "data-monster-";
/**
* you can call the method via the monster namespace `new Monster.DOM.Assembler()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* console.log(new Monster.DOM.Assembler())
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {Assembler} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/assembler.js';
* console.log(new Assembler())
* </script>
* ```
*
* @since 1.6.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @summary Allows you to build an html fragment
*/
class Assembler extends Base {
/**
* @param {DocumentFragment} fragment
* @throws {TypeError} value is not an instance of
* @throws {TypeError} value is not a function
* @throws {Error} the function is not defined
*/
constructor(fragment) {
super();
this.attributePrefix = ATTRIBUTEPREFIX;
validateInstance(fragment, getGlobalFunction('DocumentFragment'));
this.fragment = fragment;
}
/**
*
* @param {string} prefix
* @returns {Assembler}
* @throws {TypeError} value is not a string
*/
setAttributePrefix(prefix) {
validateString(prefix);
this.attributePrefix = prefix;
return this;
}
/**
*
* @returns {string}
*/
getAttributePrefix() {
return this.attributePrefix;
}
/**
*
* @param {ProxyObserver|undefined} data
* @return {DocumentFragment}
* @throws {TypeError} value is not an instance of
*/
createDocumentFragment(data) {
if (data === undefined) {
data = new ProxyObserver({});
}
validateInstance(data, ProxyObserver);
let fragment = this.fragment.cloneNode(true);
return fragment;
}
}
assignToNamespace('Monster.DOM', Assembler);
export {Monster, ATTRIBUTEPREFIX, Assembler}
'use strict';
/**
* @author schukai GmbH
*/
import {assignToNamespace, Monster} from '../namespace.js';
import {getGlobalFunction} from "../types/global.js";
import {TokenList} from "../types/tokenlist.js";
import {validateInstance, validateString, validateSymbol} from "../types/validate.js";
import {ATTRIBUTE_OBJECTLINK} from "./constants.js";
/**
* 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.
*
* you can call the method via the monster namespace `Monster.DOM.getUpdaterFromNode()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* console.log(Monster.DOM.findClosestObjectLink())
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {getUpdaterFromNode} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/updater.js';
* console.log(findClosestObjectLink())
* </script>
* ```
*
* @param {HTMLElement} element
* @return {HTMLElement|undefined}
* @since 1.10.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @throws {TypeError} value is not an instance of HTMLElement
*/
function findClosestObjectLink(element) {
return findClosestByAttribute(element, ATTRIBUTE_OBJECTLINK);
}
/**
* You can call the method via the monster namespace `new Monster.DOM.addToObjectLink()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.addToObjectLink();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {addToObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* addToObjectLink();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @param {HTMLElement} element
* @param {Symbol} symbol
* @param {Object} object
* @return {boolean}
*/
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;
}
/**
* You can call the method via the monster namespace `new Monster.DOM.removeObjectLink()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.removeObjectLink();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {removeObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* removeObjectLink();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @param {HTMLElement} element
* @param {Symbol} symbol
* @return {boolean}
*/
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;
}
/**
* You can call the method via the monster namespace `new Monster.DOM.hasObjectLink()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.hasObjectLink();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {hasObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* hasObjectLink();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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.
*
* You can call the method via the monster namespace `new Monster.DOM.getLinkedObjects()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.getLinkedObjects();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {getLinkedObjects} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* getLinkedObjects();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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.
*
* You can call the method via the monster namespace `new Monster.DOM.toggleAttributeToken()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.toggleAttributeToken();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {toggleAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* toggleAttributeToken();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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.
*
* You can call the method via the monster namespace `new Monster.DOM.addAttributeToken()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.addAttributeToken();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {addAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* addAttributeToken();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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.
*
* You can call the method via the monster namespace `new Monster.DOM.removeAttributeToken()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.removeAttributeToken();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {removeAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* removeAttributeToken();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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.
*
* You can call the method via the monster namespace `new Monster.DOM.containsAttributeToken()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.containsAttributeToken();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {containsAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* containsAttributeToken();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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.
*
* You can call the method via the monster namespace `new Monster.DOM.replaceAttributeToken()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.replaceAttributeToken();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {replaceAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* replaceAttributeToken();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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.
*
* You can call the method via the monster namespace `new Monster.DOM.clearAttributeTokens()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.clearAttributeTokens();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {clearAttributeTokens} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* clearAttributeTokens();
* </script>
* ```
*
* @since 1.9.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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
* ```
*
* You can call the method via the monster namespace `new Monster.DOM.findClosestByAttribute()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.findClosestByAttribute();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {findClosestByAttribute} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* findClosestByAttribute();
* </script>
* ```
*
* @since 1.14.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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);
let result = element.closest('[' + selector + ']');
if (result instanceof HTMLElement) {
return result;
}
return undefined;
}
/**
* This function searches, starting from an `HTMLElemement`, 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
* ```
*
* You can call the method via the monster namespace `new Monster.DOM.findClosestByClass()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* Monster.DOM.findClosestByClass();
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {findClosestByClass} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/attributes.js';
* findClosestByClass();
* </script>
* ```
*
* @since 1.27.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @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;
}
let result = element.closest('.' + className);
if (result instanceof HTMLElement) {
return result;
}
return undefined;
}
// exports
assignToNamespace('Monster.DOM', findClosestByClass, getLinkedObjects, addToObjectLink, removeObjectLink, findClosestByAttribute, hasObjectLink, clearAttributeTokens, replaceAttributeToken, containsAttributeToken, removeAttributeToken, addAttributeToken, toggleAttributeToken);
export {
Monster,
addToObjectLink,
removeObjectLink,
hasObjectLink,
findClosestByAttribute,
clearAttributeTokens,
replaceAttributeToken,
containsAttributeToken,
removeAttributeToken,
addAttributeToken,
toggleAttributeToken,
getLinkedObjects,
findClosestObjectLink,
findClosestByClass
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
'use strict';
/**
* @author schukai GmbH
*/
import {assignToNamespace, Monster} from '../namespace.js';
import {isArray,isObject} from "../types/is.js";
import {validateInstance, validateString} from "../types/validate.js";
import {getDocument} from "./util.js";
/**
* You can call the function via the monster namespace `new Monster.DOM.fireEvent()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* new Monster.DOM.fireEvent()
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {fireEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/events.js';
* fireEvent()
* </script>
* ```
*
* @param {HTMLElement|HTMLCollection|NodeList} element
* @param {string} type
* @return {void}
* @since 1.10.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @throws {TypeError} value is not an instance of HTMLElement or HTMLCollection
* @summary Construct and send and event
*/
function fireEvent(element, type) {
const document = getDocument();
if (element instanceof HTMLElement) {
if (type === 'click') {
element.click();
return;
}
let event = new Event(validateString(type), {
bubbles: true,
cancelable: true,
});
element.dispatchEvent(event);
} else if (element instanceof HTMLCollection || element instanceof NodeList) {
for (let e of element) {
fireEvent(e, type);
}
} else {
throw new TypeError('value is not an instance of HTMLElement or HTMLCollection')
}
}
/**
* You can call the function via the monster namespace `new Monster.DOM.fireCustomEvent()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* new Monster.DOM.fireCustomEvent()
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {fireCustomEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/events.js';
* fireCustomEvent()
* </script>
* ```
*
* @param {HTMLElement|HTMLCollection|NodeList} element
* @param {string} type
* @return {void}
* @since 1.29.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
* @throws {TypeError} value is not an instance of HTMLElement or HTMLCollection
* @summary Construct and send and event
*/
function fireCustomEvent(element, type, detail) {
const document = getDocument();
if (element instanceof HTMLElement) {
if (!isObject(detail)) {
detail = {detail};
}
let event = new CustomEvent(validateString(type), {
bubbles: true,
cancelable: true,
detail
});
element.dispatchEvent(event);
} else if (element instanceof HTMLCollection || element instanceof NodeList) {
for (let e of element) {
fireCustomEvent(e, type, detail);
}
} else {
throw new TypeError('value is not an instance of HTMLElement or HTMLCollection')
}
}
/**
* This function gets the path `Event.composedPath()` from an event and tries to find the next element
* up the tree `element.closest()` with the attribute and value. If no value, or a value that is undefined or null,
* is specified, only the attribute is searched.
*
* You can call the function via the monster namespace `new Monster.DOM.findTargetElementFromEvent()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js';
* new Monster.DOM.findTargetElementFromEvent()
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {findTargetElementFromEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/events.js';
* findTargetElementFromEvent()
* </script>
* ```
*
* @since 1.14.0
* @param {Event} event
* @param {string} attributeName
* @param {string|null|undefined} attributeValue
* @throws {Error} unsupported event
* @memberOf Monster.DOM
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an instance of HTMLElement
* @summary Help function to find the appropriate control
*/
function findTargetElementFromEvent(event, attributeName, attributeValue) {
validateInstance(event, Event);
if (typeof event.composedPath !== 'function') {
throw new Error('unsupported event');
}
const path = event.composedPath();
// closest cannot be used here, because closest is not correct for slotted elements
if (isArray(path)) {
for (let i = 0; i < path.length; i++) {
const o = path[i];
if (o instanceof HTMLElement &&
o.hasAttribute(attributeName)
&& (attributeValue === undefined || o.getAttribute(attributeName) === attributeValue)) {
return o;
}
}
}
return undefined;
}
assignToNamespace('Monster.DOM', findTargetElementFromEvent, fireEvent, fireCustomEvent);
export {Monster, findTargetElementFromEvent, fireEvent, fireCustomEvent}
This diff is collapsed.
This diff is collapsed.
'use strict';
/**
* In this namespace you will find classes and methods for handling the DOM.
*
* @namespace Monster.DOM
* @memberOf Monster
* @author schukai GmbH
*/
/**
* @private
* @type {string}
*/
export const namespace = "Monster.DOM";
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment