Manual Reference Source Test

src/collection.js

/**
*  @file Collection definition class
*  @author  Liqueur de Toile <contact@liqueurdetoile.com>
*  @license Apache-2.0 {@link https://www.apache.org/licenses/LICENSE-2.0}
*/

import EventifiedElement from 'elements/eventifiedelement';
import HtmlElement from 'elements/htmlelement';
import Q from 'query';

/**
*  Class for handling a collection of any HtmlElement (or extended) objects.
*
*  A collection is pretty straight forwarding. All methods from HtmlElement class
*  are mapped to a collection to let iterate a call on items from collection itself.
*
*  @since 1.0.0
*  @version 1.0.0
*  @author Liqueur de Toile <contact@liqueurdetoile.com>
*/

class Collection {
  /**
  *  A new Collection can be instantiated though it's more likely the result of a query in the DOM.
  *
  *  @returns {Collection} A collection of [HtmlElement]{@link HtmlElement} objects
  */
  constructor() {
    /**
    *  Array of elements contained in the collection (could be empty)
    *  @type {Array}
    *  @since 1.0.0
    */
    this.elements = [];
  }

  /**
  *  Run a callback on each element in the collection.
  *  This method is an alias to this.elements.forEach.
  *
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *
  *  @param {ForEachCallback} callback Callback to run on each element
  *  @returns {void}
  */
  forEach(callback) {
    this.elements.forEach((element, index) => callback.call(element, element, index));
  }

  /**
  *  Number of elements in the collection
  *  @type {Number}
  *  @since 1.0.0
  */
  get length() {
    return this.elements.length;
  }

  /**
  *  Push an element into the collection
  *
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *
  *  @param   {Element|HtmlElement} element Element (enhanced ot not) to insert into the collection
  *  @throws  {TypeError}  Throw an error if the argument is not an Element or an HtmlElement
  *  @returns {this} Chainable
  */
  push(element) {
    // Convert Element Node to HtmlElement
    if (element.nodeType === 1) element = Q(element);
    if (element.enhanced) this.elements.push(element);
    else throw new TypeError('Only Element Object can be added to a collection of Elements');
    return this;
  }

  /**
  *  Merge all elements in collection under a DocumentFragment Element
  *
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *
  *  @type {HtmlElement}
  */
  get element() {
    const container = Q();

    this.elements.forEach(function (element) {
      container.append(element);
    });

    return container.node;
  }

  /**
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *
  *  @type {HtmlElement}
  *  @see {@link Collection#element}
  */
  get node() {
    return this.element;
  }
}

// Map EventifiedElement and HTMLElement methods to Collection prototype
function map(c) {
  Object.getOwnPropertyNames(c.prototype).forEach(method => {
    const prop = Object.getOwnPropertyDescriptor(c.prototype, method);

    if (
      typeof prop.value === 'function' &&
      method !== 'constructor' &&
      method !== 'forEach'
    ) {
      Collection.prototype[method] = function () {
        let ret = [];

        this.elements.forEach((el) => {
          ret.push(el[method].apply(el, Array.prototype.slice.call(arguments)));
        });
        return ret;
      };
    }
  });
}

map(EventifiedElement);
map(HtmlElement);

export default Collection;