toolshed-8.x-1.x-dev/assets/Element.es6.js
assets/Element.es6.js
(({ Toolshed: ts }) => {
/**
* Functionality wrapper for HTMLElements. Allows for more convienent creation
* of HTMLElements and altering them.
*/
ts.Element = class ToolshedElement {
/**
* Create a new instance of the ToolshedElement wrapper.
*
* @param {string|HTMLElement} element
* Either a HTML tag string or an HTML element to wrap. If value is a
* string then create an HTMLElement of that tag.
* @param {Object=} attrs
* Attributes to apply to the HTMLElement wrapped by this object.
* @param {ToolshedElement|HTMLElement=} appendTo
* A parent element to attach this new element to.
*/
constructor(element, attrs, appendTo) {
if (ts.isString(element)) {
element = document.createElement(element.toUpperCase());
}
this.el = element;
this.eventListeners = new Map();
if (attrs) this.setAttrs(attrs);
if (appendTo) this.attachTo(appendTo);
}
/* ------- getters / setters -------- */
get id() {
return this.el.id;
}
set id(value) {
this.el.id = value;
}
get tagName() {
return this.el.tagName;
}
get className() {
return this.el.className;
}
set className(names) {
this.el.className = names;
}
get classList() {
return this.el.classList;
}
get style() {
return this.el.style;
}
get dataset() {
return this.el.dataset;
}
get parentNode() {
return this.el.parentNode;
}
get parentElement() {
return this.el.parentElement;
}
get innerHTML() {
return this.el.innerHTML;
}
set innerHTML(html) {
this.el.innerHTML = html;
}
get textContent() {
return this.el.textContent;
}
set textContent(text) {
this.el.textContent = text;
}
/* ------- element modifiers ------- */
/**
* Add CSS classes to the wrapped HTMLElement.
*
* @param {string|string[]} classes
* Either a single string to add, or an array of class names to add.
*/
addClass(classes) {
// Add array of classes one at a time for old IE compatibility.
// Should this be removed now that IE is not supported anymore?
Array.isArray(classes)
? classes.forEach((i) => this.classList.add(i))
: this.classList.add(classes);
}
/**
* Remove CSS classes from the wrapped HTMLElement.
*
* @param {string|string[]} classes
* Either a single class name or an array of class names to remove.
*/
removeClass(classes) {
Array.isArray(classes)
? classes.forEach((i) => this.classList.remove(i))
: this.classList.remove(classes);
}
/**
* Apply a keyed value list of style values to the wrapped HTMLElement.
*
* @param {Object} styles
* Keyed style values to apply to the element's style property.
*/
setStyles(styles) {
Object.assign(this.style, styles);
}
/**
* Get the value of an attribute.
*
* @param {string} name
* Name of the attribute to fetch.
*
* @return {string|null}
* The value of the attribute if it exists. If there is no attribute with
* the requested name NULL is returned.
*/
getAttr(name) {
return this.el.hasAttribute(name) ? this.el.getAttribute(name) : null;
}
/**
* Set value for a single HTML attribute.
*
* @param {string} name
* The name of the attribute to set.
* @param {sting|array|object} value
* The value to set for the attribute. Style can be an object, class can
* be a array.
*/
setAttr(name, value) {
switch (name) {
case 'class':
this.addClass(value);
break;
case 'style':
if (!ts.isString(value)) {
this.setStyles(value);
break;
}
case 'html':
this.innerHTML = value;
break;
case 'text':
this.textContent = value;
// eslint-ignore-next-line no-fallthrough
default:
this.el.setAttribute(name, value);
}
}
/**
* Apply keyed attribute values to the wrapped HTMLElement.
*
* Most attributes should just be string values, but exceptions are:
* - class: can be a string or an array of class names
* - style: Can be a string or an Object of keyed style values.
*
* @param {Object} attrs
* Keyed values to apply as attributes to the wrapped HTMLElement.
*/
setAttrs(attrs) {
Object.entries(attrs).forEach(([k, v]) => this.setAttr(k, v));
}
/**
* Remove specified attributes from the element.
*
* @param {string|string[]} attrs
* The names of the attributes to remove from the element.
*/
removeAttrs(attrs) {
if (ts.isString(attrs)) {
attrs = [attrs];
}
attrs.forEach((i) => this.el.removeAttribute(i));
}
/* --------- DOM Modifiers --------- */
/**
* Add an element to the start the wrapped HTMLElement's children nodes.
*
* @param {ToolshedElement|HTMLElement} item
* The child to prepend to the element.
*/
prependChild(item) {
this.insertBefore(item, this.el.firstElementChild);
}
/**
* Append an element to this wrapped HTMLElement.
*
* @param {ToolshedElement|HTMLElement} item
* Element to append.
*/
appendChild(item) {
this.insertBefore(item);
}
/**
* Insert an element as a child of the wrapped HTMLELement.
*
* @param {ToolshedElement|HTMLElement} item
* The element to insert as a child of the element.
* @param {ToolshedElement|HTMLElement=} refNode
* Element to use as a reference point for insertion. If reference node
* is NULL then add the element after the last child element.
*/
insertBefore(item, refNode) {
item = item instanceof ToolshedElement ? item.el : item;
refNode = refNode instanceof ToolshedElement ? refNode.el : refNode;
this.el.insertBefore(item, refNode);
}
/**
* Remove an element from this wrapped HTMLElement.
*
* @param {ToolshedElement|HTMLElement} item
* Element to remove.
*/
removeChild(item) {
this.el.removeChild(item instanceof ToolshedElement ? item.el : item);
}
/**
* Remove all nodes and elements from this element.
*/
empty() {
while (this.el.firstChild) {
this.el.removeChild(this.el.lastChild);
}
}
/**
* Insert this element into the DOM based on the reference node provided.
* The type parameter is used to determine if the reference node is the
* parent or sibling.
*
* @param {ToolshedElement|HTMLElement} refNode
* The element to use as a reference point for insertion. Could be the
* parent or the sibling depending on the value of "type".
* @param {string=} type
* If type = "after" then element is inserted after the reference node,
* if type = "before" then element is inserted before. Otherwise, the
* element is appended to the reference node.
*/
attachTo(refNode, type = 'parent') {
if ('after' === type || 'before' === type) {
(refNode.parentNode || document.body).insertBefore(this.el, ('before' === type) ? refNode : refNode.nextSibling);
} else {
refNode.appendChild(this.el);
}
}
/**
* Detach this element from the DOM.
*/
detach() {
if (this.parentNode) this.parentNode.removeChild(this.el);
}
/**
* Finds all descendent element matching a selector query.
*
* @param {string} query
* The selector query to use for matching descendent elements with.
* @param {bool} multiple
* Return all matching elements? If true find all matching elements,
* otherwise only return the first matched element.
*
* @return {NodeList|Node}
* List of nodes matching the queried criteria when multipled are
* requested or just a single node if the multiple parameter is false.
*/
find(query, multiple = true) {
return multiple
? this.el.querySelectorAll(query)
: this.el.querySelector(query);
}
/**
* Find all child DOM elements with a class name.
*
* @param {string} className
* Class name to search for descendent elements for.
*
* @return {HTMLCollection}
* A collection of HTML element which are descendents of the element with
* the class name searched for.
*/
findByClass(className) {
return this.el.getElementsByClassName(className);
}
// -------- event listeners -------- //
/**
* Add an event listener to the element.
*
* @param {string} event
* Event to attach the event for.
* @param {function} handler
* The callback event handler.
* @param {AddEventListenerOptions=} options
* Event listener options to apply to the event listener.
*/
on(event, handler, options = {}) {
const map = this.eventListeners;
if (map.has(event)) {
map.get(event).push(handler);
}
else {
map.set(event, [handler]);
}
this.el.addEventListener(event, handler, options);
}
/**
* Removes event listeners from the element. If only event is provided then
* all tracked event listeners are removed (listeners added with "on()").
*
* @param {string} event
* Name of the event to remove listeners from.
* @param {function=} handler
* The listener to remove. If only the event name is provided, then
* all listeners for the event are removed.
*/
off(event, handler) {
const handlers = this.eventListeners.get(event);
// If a handler was specified, only remove that specific handler.
// otherwise remove all handlers registered for the event.
if (handler) {
if (handlers) {
const i = handlers.indexOf(handler);
if (i > -1) handlers.splice(i, 1);
}
this.el.removeEventListener(event, handler);
}
else if (handlers) {
handlers.forEach((h) => this.el.removeEventListener(h));
}
}
// --------- house keeping --------- //
/**
* Clean-up element resources and event listeners.
*
* @param {bool} detach
* Should the element also be detached from the DOM parent.
*/
destroy(detach) {
this.eventListeners.forEach((v, k) => {
v.forEach((f) => this.el.removeEventListener(k, f));
});
if (detach) this.detach();
}
};
/**
* Wrapper for form input elements.
*/
ts.FormElement = class FormElement extends ts.Element {
/**
* Get the current value for the form input element.
*
* @return {*}
* The current form input value.
*/
get value() {
return this.el.value;
}
/**
* Set the value of this form element.
*
* @param {*} val
* The value to set for this form element.
*/
set value(val) {
this.el.value = val;
}
/**
* Retrieve the form which this form element belongs to.
*
* @return {FormElement|null}
* The form element which owns this form element.
*/
get form() {
return this.el.form || this.el.closest('form');
}
};
})(Drupal);
