@devstore/html-js
Version:
DOM manipulation utils
294 lines (232 loc) • 8.68 kB
JavaScript
/*
* Copyright (C) 2023 svinokot.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
export const createCustomElement = (name) => {
const native = document.createElement('div');
native.innerHTML = `<${name}></${name}>`;
return native.firstElementChild;
};
export const getEventPath = event => event.path || (event.composedPath && event.composedPath());
export const create = (name, attributes, ...classes) => {
const element = addAttributes(document.createElement(name), attributes);
return classes.length ? addClass(element, ...classes) : element;
};
export const removeChildren = (container) => {
while (container.lastChild) {
container.lastChild.remove();
}
return container;
};
export const addClass = (element, ...classes) => {
element.classList?.add(...classes.filter(c => c));
return element;
};
export const removeClass = (element, ...classes) => {
element.classList?.remove(...classes);
return element;
};
export const toggleClass = (element, ...classes) => {
classes && Object.keys(classes).forEach(key => classes[key] ? addClass(element, classes[key]) : removeClass(element, classes[key]));
return element;
};
const _flat = (acc, item) => item[Symbol.iterator] ? acc.concat(Array.from(item)) : acc.concat(item);
//const _flat = (acc, item) => acc.concat(item);
export const append = (container, ...childs) => {
const elements = childs
.filter(e => e)
.reduce(_flat, [])
.map(c => (typeof c === 'object') ? c : document.createTextNode(c.toString()));
container.append
? container.append(...elements)
: elements.forEach(c => c && container.appendChild(c));
return container;
};
export const getAttribute = (element, attribute) => {
const value = element.getAttribute && element.getAttribute(attribute);
if (value === '') {
return true;
} else if (value) {
const number = +value;
return isNaN(number) ? value : number;
} else {
return value;
}
};
export const setAttribute = (element, attribute, value = true) => {
if (value === null || value === undefined || value === false) {
element.removeAttribute(attribute);
} else {
element.setAttribute(attribute, typeof value === 'boolean' ? '' : value.toString());
}
return element;
};
export const addAttributes = (element, attributes) => {
attributes && Object.keys(attributes).forEach(key => setAttribute(element, key, attributes[key]));
return element;
};
export const removeAttributes = (element, ...attributes) => {
attributes.forEach(key => element.removeAttribute(element, key));
return element;
};
export const hasAttribute = (element, attribute, value = '', ...values) => {
return [value, ...values].some(v => getAttribute(element, attribute) === v);
};
export const addListeners = (element, events, eventOptions) => {
Object.keys(events).forEach(key => events[key] && element.addEventListener(key, e => events[key](e, e.detail || {}), eventOptions));
return element;
};
export const subscribe = (element, events, eventOptions) => {
const unsubsribers = Object
.keys(events)
.map(key => {
if (!events[key])
return;
const callback = events[key];
const wrapper = e => callback(e, element);
element.addEventListener(key, wrapper, eventOptions);
return () => element.removeEventListener(key, wrapper, eventOptions);
});
return () => unsubsribers.forEach(u => u && u());
};
const _defaultComparator = (a, b) => a === b;
const _getCallbackProps = (callbackOrProps) =>
typeof callbackOrProps === 'function' ? { callback: callbackOrProps } : callbackOrProps;
export const addProperty = (element, name, callbackOrProps) => {
const { callback, comparator = _defaultComparator, reflect, value } = _getCallbackProps(callbackOrProps);
const store = `_${name}`;
const reflectName = (reflect === true) ? name : reflect;
Object.defineProperty(element, name, {
get() { return element[store]; },
set(value) {
const prevValue = element[store];
if (!comparator(value, prevValue)) {
element[store] = value;
reflectName && setAttribute(element, reflectName, value);
callback && callback(element, { value, prevValue });
}
}
});
element[store] = value;
reflectName && value && setAttribute(element, reflectName, value);
return element;
};
export const addProperties = (element, properties) => {
Object.keys(properties).forEach(key => addProperty(element, key, properties[key]));
return element;
};
export const addEventProperty = (element, name, props = {}) => {
const handlerName = `${name}Changed`;
const {camparator, callback, reflect, ...eventOptions} = props;
return addProperty(
element,
name,
{
callback: (element, detail) => {
element.dispatchEvent(new CustomEvent(handlerName, { ...eventOptions, detail }));
callback && callback(element, detail);
},
camparator,
reflect
}
);
};
export const addEventReflectAttributes = (element, ...attributes) => {
attributes.forEach(a => addListeners(element, {[`${a}Changed`]: (e, {value}) => setAttribute(element, a, value)}));
return element;
};
const _defaultBehaviorOptions = {
filter: element => !hasAttribute(element, 'active', 'false', null, undefined),
eventName: 'click'
};
export const addReaction = (element, callback, options = _defaultBehaviorOptions) => {
const { filter, eventName } = options;
return addListeners(element, {
[eventName]: event => {
const sender = filter
? getEventPath(event).find(filter)
: element.target;
if (sender) {
callback(event, { sender });
event.stopPropagation();
}
}
});
};
const _defaultStyleCompound = (name, value) => `${name}: ${value}`;
export const getStyleString = (values, compound = _defaultStyleCompound) => {
return Object.keys(values).map(k => compound(k, values[k])).join('; ');
};
export const setStyle = (element, style) => {
(style && typeof style === 'object')
? Object.keys(style).forEach(key => element.style[key] = style[key])
: setAttribute(element, 'style', style.toString());
return element;
};
const _unique = array => [ ...new Set(array) ];
export const get = (elementOrSelector, idOrSelector) => {
if (typeof elementOrSelector === 'string') {
return elementOrSelector.startsWith('#')
? document.getElementById(elementOrSelector.substring(1))
: document.querySelector(elementOrSelector);
} else {
return elementOrSelector.querySelector(idOrSelector);
}
};
const _querySelector = (all, root, selector) => {
if (all) {
return Array.from(root.querySelectorAll(selector));
} else {
const element = root.querySelector(selector);
return element ? [element] : [];
}
};
const _select = (all, root, ...selectors) => {
const elements = selectors.reduce((acc, selector) => {
const elements = (typeof selector === 'string')
? _querySelector(all, root, selector)
: selector;
return acc.concat(elements);
}, []);
return _unique(elements);
};
const _selectResolve = (all, elementOrSelector, ...selectors) => {
return typeof elementOrSelector === 'string'
? _select(all, document, elementOrSelector, ...selectors)
: (selectors.length) ? _select(all, elementOrSelector, ...selectors) : elementOrSelector;
};
const batch = (elements, assembler, ...args) => {
const result = [];
for (const key in elements) {
result.push(assembler(elements[key], ...args));
}
return result;
};
export const select = (elementOrSelector, ...selectors) => _createBatch(_selectResolve(false, elementOrSelector, ...selectors));
export const selectAll = (elementOrSelector, ...selectors) => _createBatch(_selectResolve(true, elementOrSelector, ...selectors));
function _createBatch(elements) {
const _elements = [...elements];
const append = elementOrList => _createBatch(_unique(_elements.concat(elementOrList)));
return {
batch: (assembler, ...args) => batch(_elements, assembler, ...args),
select: (elementOrSelector, ...selectors) => {
const current = _selectResolve(false, elementOrSelector, ...selectors);
return append(current);
},
selectAll: (elementOrSelector, ...selectors) => {
const current = _selectResolve(true, elementOrSelector, ...selectors);
return append(current);
},
append,
elements: _elements
};
}