Home Reference Source Repository

src/Element.js

import Node from './Node';
import ParentNode from './ParentNode';
import { querySelector as _querySelector, querySelectorAll as _querySelectorAll } from './utils/querySelectorHelper';

/**
 * The Element interface represents an object within a DOM document.
 * This interface describes methods and properties common to all kinds of elements.
 * Specific behaviors are described in interfaces which inherit from Element but add additional functionality.
 *
 * @see https://developer.mozilla.org/en/docs/Web/API/Element
 */
/**
 * The Element.innerHTML property sets or gets the HTML syntax describing the element's descendants.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
 * @member {string} Element#innerHTML
 */
/**
 * The outerHTML attribute of the element DOM interface gets the serialized HTML fragment
 * describing the element including its descendants.
 * It can be set to replace the element with nodes parsed from the given string.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML
 * @member {string} Element#outerHTML
 */

export default class Element extends ParentNode {
    /**
     * The id of the element.
     *
     * @type {string}
     */
    get id() {
        return this.getAttribute('id');
    }

    /**
     * @param {string} id
     */
    set id(id) {
        this.setAttribute('id', id);
    }

    /**
     * The tag name of the element.
     *
     * @type {string}
     * @readonly
     */
    get tagName() {
        return this.nodeName;
    }

    /**
     * Returns a live {@link HTMLCollection} containing all objects of type {@link Element}
     * that are children of this ParentNode.
     *
     * Note: this currently returns a non-live array.
     *
     * later type {HTMLCollection}
     *
     * @type {Element[]}
     * @readonly
     */
    get children() {
        return this._childNodes.filter(node => node instanceof Element);
    }

    /**
     * Returns the {@link Element} that is the first child of this ParentNode, or null if there is none.
     *
     * @type {Element}
     * @readonly
     */
    get firstElementChild() {
        return this._childNodes[0] || null;
    }

    /**
     * Returns the {@link Element} that is the first child of this ParentNode, or null if there is none.
     *
     * @type {Element}
     * @readonly
     */
    get lastElementChild() {
        return this._childNodes.length === 0 ? null : this._childNodes[this._childNodes.length - 1];
    }

    /**
     * Returns the {@link Element} immediately following the specified one in its parent's children list,
     * or null if the specified element is the last one in the list.
     *
     * @type {Element}
     * @readonly
     */
    get nextElementSibling() {
        const siblings = this.parentNode.children;
        if (siblings && siblings.length > 1) {
            let index = siblings.indexOf(this);
            if (index + 1 < siblings.length) {
                return siblings[index + 1];
            }
        }

        return null;
    }

    /**
     * Returns the Element immediately prior to the specified one in its parent's children list, or null
     * if the specified element is the first one in the list.
     *
     * @type {Element}
     * @readonly
     */
    get previousElementSibling() {
        const siblings = this.parentNode.children;
        if (siblings && siblings.length > 1) {
            let index = siblings.indexOf(this);
            if (index !== 0) {
                return siblings[index - 1];
            }
        }

        return null;
    }

    /**
     * Returns an unsigned long giving the amount of children that the object has.
     *
     * @type {number}
     * @readonly
     */
    get childElementCount() {
        return this._childNodes.length;
    }

    /**
     * Returns a reference to the element by its ID.
     *
     * @param {string} id case-sensitive string representing the unique ID of the element being sought
     * @return {Element} reference to an Element, or null if an element with the specified ID is not in the document.
     */
    getElementById(id) {
        return this._childNodesRecursiveFind(node => node instanceof Element && node.getAttribute('id') === id) || null;
    }

    /**
     * Returns an HTMLCollection of elements with the given tag name.
     * The complete document is searched, including the root node.
     * The returned HTMLCollection is live, meaning that it updates itself automatically to stay in sync
     * with the DOM treewithout having to call document.getElementsByTagName() again.
     *
     * @param {string} tagName
     * @return {HTMLCollection}
     */
    getElementsByTagName(tagName) {
        if (!tagName) {
            return this._createCollection(child => true);
        }

        tagName = tagName.toLowerCase();
        return this._createCollection(child => child.nodeName.toLowerCase() === tagName);
    }

    /**
     * The Element.getElementsByClassName() method returns a live HTMLCollection containing all child
     * elements which have all of the given class names. When called on the document object,
     * the complete document is searched, including the root node.
     *
     * @param {string} names is a string representing the list of class names to match;
     *                class names are separated by whitespace
     * @return {HTMLCollection}
     */
    getElementsByClassName(names) {
        const classes = names.split(' ');
        return this._createCollection(child => classes.every(token => child.classList.contains(token)));
    }

    /**
     * Returns the first element that is a descendant of the element on which it is invoked that matches the
     * specified group of selectors.
     *
     * @param {string} query CSS query for selecting element
     * @return {Element|null}
     */
    querySelector(query) {
        return _querySelector(this, query);
    }

    /**
     * Returns a non-live NodeList of all elements descended from the element on which it is invoked that match the
     * specified group of CSS selectors.
     *
     * @param {string} query
     * @return {Element[]}
     */
    querySelectorAll(query) {
        return _querySelectorAll(this, query);
    }

    get attributes() {
        return this._attributes;
    }
}

/**
 * @constant {string} Comment#nodeType
 */
Object.defineProperty(Element.prototype, 'nodeType', { value: Node.ELEMENT_NODE });