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);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc