NOTE: You are viewing documentation for the MoovJS/Adapt version of the Moovweb SDK
View documentation for next-gen Moovweb XDN & PWA framework
Moovweb | moov_cheerio.js
Menu Developer Moovweb University

moov_cheerio.js

/* globals tag, txt */
'use strict';

const cheerio = require('cheerio');

cheerio.prototype.options.withDomLvl1 = false;

const slice = Array.prototype.slice;

function copy(node, children, next, prev, parent) {
    return {
        // __proto__: node.__proto__, // uncomment if withDomLvl1 is true
        type: node.type,
        name: node.name,
        attribs: node.attribs,
        children: children,
        next: next,
        prev: prev,
        parent: parent
    };
}

/**
 * @function tag
 * @summary moov_cheerio
 * @description Creates a new element. Note that this is <i>not</i> a Cheerio
 * object: you should wrap it in `$()` if Cheerio methods need to be invoked on
 * it.
 * @param {String} name A string for the name of the element.
 * @param {Object} [attribs] An object containing the attribute-value pairs of
 * the new element.
 * @param {String} [content] A string for the text node for the new element.
 * @return {Object} An object associated with the newly-created DOM element.
 * defined by the arguments passed in (denoted by a `type` value of "tag").
 * @example
 * let grape = tag("li", {class: "grape"}, "Grape");
 * grape.insertAfter(".apple");
 * // => Throws: TypeError (grape is not a Cheerio object, and the object
 * //    associated with the grape DOM element does not have an insertAfter
 * //    method defined)
 * $body.find("ul").append(grape);
 * // => Note: This is OK - you can append a non-Cheerio object here
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="grape">Grape</li>
 * //      <li class="pear">Pear</li>
 * //      <li class="orange">Orange</li>
 * //    </ul>
 * @example
 * let $grape = $(tag("div", {class: "grape"}, "Grape"));
 * $grape.insertAfter(".apple");
 * // => Returns: original Cheerio object associated with the newly-created
 * //    `.grape` element
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="grape">Grape</li>
 * //      <li class="pear">Pear</li>
 * //      <li class="orange">Orange</li>
 * //    </ul>
 */
global.tag = function(name, attribs, content) {
    let type = name === 'script' || name === 'style' ? name : 'tag';
    return {
        // for some reason <script> and <style> aren't exactly elements in htmlparser2
        // __proto__: type == 'tag' ? ElementPrototype : NodePrototype, // uncomment if withDomLvl1 is true
        type: type,
        name: name,
        attribs: attribs || {},
        children: content ? [txt(content)] : [],
        next: null,
        prev: null,
        parent: null
    };
};

/**
 * @function txt
 * @summary moov_cheerio
 * @description Creates a new text node.
 * @param {string} content A string for the content that the text node will
 * contain.
 * @return {Object} An object associated with the newly-created text node
 * defined by the `content` argument passed in (denoted by a `type` value of
 * "text").
 * @example
 * $body.find(".apple").append(txt(", more text"));
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple, more text</li>
 * //      <li class="pear">Pear</li>
 * //      <li class="orange">Orange</li>
 * //    </ul>
 *
 */
global.txt = function(content) {
    return {
        // __proto__: NodePrototype, // uncomment if withDomLvl1 is true
        data: content,
        type: 'text',
        next: null,
        prev: null,
        parent: null
    };
};

/**
 * @method replace
 * @summary moov_cheerio
 * @memberof $
 * @description Replaces all or a portion of an old value of an attribute with a
 * new value.
 * @param {string} attr A string for the attribute whose value is to be
 * replaced.
 * @param {string} oldval A string for the old value to be replaced.
 * @param {string} newval A string for a new value to replace the old value.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find(".apple").replace("data-which", "uji", "lorina");
 * // => Returns: original Cheerio object associated with $body.find(".apple")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="florina">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.replace = function(attr, oldVal, newVal) {
    for (let i = 0, len = this.length; i < len; i++) {
        if (this[i].type === 'tag') {
            if (this[i].attribs[attr] !== undefined) {
                this[i].attribs[attr] = this[i].attribs[attr].replace(oldVal, newVal);
            }
        }
    }
    return this;
};

/**
 * @method name
 * @summary moov_cheerio
 * @memberof $
 * @description Changes the node names of each element in the matched set.
 * @param {string} tagName A string for the new tag name.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("ul").name("ol");
 * // => Returns: original Cheerio object originally associated with
 * //    $body.find("ul") (which is now the ol)
 * // => HTML output:
 * //    <ol id="fruits">
 * //      <li class="apple" data-which="florina">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ol>
 */
cheerio.prototype.name = function(tagName) {
    for (let i = 0, len = this.length; i < len; i++) {
        this[i].name = tagName;
    }
    return this;
};

/**
 * @method removeAttrs
 * @summary moov_cheerio
 * @memberof $
 * @description Removes all attributes for each element in the matched set.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find(".apple").removeAttrs();
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li>Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.removeAttrs = function() {
    for (let i = 0, len = this.length; i < len; i++) {
        this[i].attribs = {};
    }
    return this;
};

// [Note: all .wrap()-related methods are all written with a specific signature,
// since the originals naturally accept "tag()" arguments. However, they are
// documented to coerce arguments into that form, so that they can take
// arguments in the way that tag() does.]

/**
 * @method wrapTextChildren
 * @summary moov_cheerio
 * @memberof $
 * @description Wraps any text nodes for each element in the matched set. This
 * includes text nodes that are comprised entirely of whitespace.
 * @param {String} name A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("li").wrapTextChildren("span", {class: "text"});
 * // => Returns: original Cheerio object originally with $body.find("li")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji"><span class="text">Apple</span></li>
 * //      <li class="orange"><span class="text">Orange</span></li>
 * //      <li class="pear"><span class="text">Pear</span></li>
 * //    </ul>
 * @example
 * $body.find("ul").wrapTextChildren("li", {class: "text"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits"><li class="text">
 * //    </li><li class="apple" data-which="fuji">Apple</li><li class="text">
 * //    </li><li class="orange">Orange</li><li class="text">
 * //    </li><li class="pear">Pear</li><li class="text">
 * //    </li></ul>
 */
cheerio.prototype.wrapTextChildren = function(node, ignore_empty) {
    if (typeof node !== 'object') {
        node = tag.apply(null, arguments);
    }

    for (let i = 0, len = this.length; i < len; i++) {
        let elem = this[i], children = elem.children;
        for (let i = 0, len = children.length; i < len; i++) {
            let child = children[i];
            if (child.type === 'text') {
                if ( (child.data === null || child.data.trim() === '') && ignore_empty === true) {
                    // if empty and we are ignoring empty, do nothing
                } else {
                    let clone = copy(node, [child], child.next, child.prev, child.parent);
                    let next = child.next, prev = child.prev, root = child.root;
                    if (next) {
                        next.prev = clone;
                    }
                    if (prev) {
                        prev.next = clone;
                    }
                    if (root) {
                        clone.root = root;
                        child.root = null;
                    }
                    child.next = child.prev = null;
                    child.parent = clone;
                    children[i] = clone;
                }
            }
        }
    }

    return this;
};

/**
 * @method wrap
 * @summary moov_cheerio
 * @memberof $
 * @description Wraps each element in the matched set with a newly-defined input
 * element. Note that this is different from the original Cheerio's .wrap()
 * implementation.
 * @param {String} name A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("#fruits").wrap("div", {class: "wrapper"});
 * // => Returns: original Cheerio object originally with $body.find("#fruits")
 * // => HTML output:
 * //    <div class="wrapper">
 * //      <ul id="fruits">
 * //        <li class="apple" data-which="fuji">Apple</li>
 * //        <li class="orange">Orange</li>
 * //        <li class="pear">Pear</li>
 * //      </ul>
 * //    </div>
 */
cheerio.prototype.wrap = function(node) {
    if (typeof node !== 'object') {
        node = tag.apply(null, arguments);
    }
    for (let i = 0, len = this.length; i < len; i++) {
        let elem = this[i], parent = elem.parent;
        let siblings = parent ? parent.children : elem.root.children;
        let clone = copy(node, [elem], elem.next, elem.prev, elem.parent);
        let next = elem.next, prev = elem.prev, root = elem.root;
        if (next) {
            next.prev = clone;
        }
        if (prev) {
            prev.next = clone;
        }
        if (root) {
            clone.root = root;
            elem.root = null;
        }
        elem.next = elem.prev = null;
        elem.parent = clone;
        siblings[siblings.indexOf(elem)] = clone;
    }
    return this;
};

/**
 * @method wrapInner
 * @summary moov_cheerio
 * @memberof $
 * @description Wraps inner contents of each element in the matched set.
 * @param {String} name A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("#fruits").name("div");
 * $body.find("#fruits").wrapInner("ol", {class: "fruits-inner"});
 * // => Returns: original Cheerio object originally with $body.find("#fruits")
 * // => HTML output:
 * //    <div id="fruits">
 * //      <ol class="fruits-inner">
 * //        <li class="apple" data-which="fuji">Apple</li>
 * //        <li class="orange">Orange</li>
 * //        <li class="pear">Pear</li>
 * //      </ol>
 * //    </div>
 */
cheerio.prototype.wrapInner = function(node) {
    if (typeof node !== 'object') {
        node = tag.apply(null, arguments);
    }
    for (let i = 0, len = this.length; i < len; i++) {
        let elem = this[i], children = elem.children;
        let clone = copy(node, children, null, null, elem);
        for (let i = 0, len = children.length; i < len; i++) {
            children[i].parent = clone;
        }
        elem.children = [clone];
    }
    return this;
};

/**
 * @method wrapTogether
 * @summary moov_cheerio
 * @memberof $
 * @description Wraps all elements in a matched set under a single newly-defined
 * element, and insert this new element where the first element in the matched
 * set once was.
 * @param {String} name A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("#fruits").name("div");
 * $body.find("li").wrapTogether("ol", {class: "fruits-inner"});
 * // => Returns: original Cheerio object originally with $body.find("li")
 * // => HTML output:
 * //    <div id="fruits">
 * //      <ol class="fruits-inner">
 * //        <li class="apple" data-which="fuji">Apple</li>
 * //        <li class="orange">Orange</li>
 * //        <li class="pear">Pear</li>
 * //      </ol>
 * //    </div>
 */
cheerio.prototype.wrapTogether = function(node) {
    if (typeof node !== 'object') {
        node = tag.apply(null, arguments);
    }
    if (this.length === 0) {
        return this;
    }
    let first = this[0],
        parent = first.parent,
        siblings = parent.children,
        root = first.root;
    first.parent = {};
    let clone = copy(node, slice.call(this), first.next, first.prev, parent);
    if (root) {
        clone.root = root;
    }

    for (let i = 0, len = this.length; i < len; i++) {
        let elem = this[i],
            next = elem.next,
            prev = elem.prev,
            parent = elem.parent,
            siblings = parent ? parent.children : elem.root.children,
            root = elem.root;
        if (next) {
            next.prev = prev;
        }
        if (prev) {
            prev.next = next;
        }
        elem.next = this[i+1] || null;
        elem.prev = this[i-1] || null;
        elem.parent = clone;
        if (root) {
            elem.root = null;
        }
        if (siblings) {
            siblings.splice(siblings.indexOf(elem), 1);
        }
    }
    siblings[siblings.indexOf(first)] = clone;
    return this;
};

/**
 * @method unwrap
 * @summary moov_cheerio
 * @memberof $
 * @description Removes the wrapping element while retaining selected element
 * itself, for each element in the matched set.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("ul").unwrap();
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <li class="apple" data-which="fuji">Apple</li>
 * //    <li class="orange">Orange</li>
 * //    <li class="pear">Pear</li>
 */
cheerio.prototype.unwrap = function() {
    var copies = this.children();
    this.replaceWith(copies);
};

/**
 * @method attributes
 * @summary moov_cheerio
 * @memberof $
 * @description Updates the set of attributes for each element in the matched
 * set. If an attribute in the input is undefined, it will remove that attribute
 * from the selected nodes.
 * @param {Object} attributes Attribute-value pairs in key-value object
 * notation.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("ul").attributes({id: "food", "data-type": "fruits"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="food" data-type="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * $body.find("ul").attributes({id: undefined});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.attributes = function(attributes) {
    for (let i = 0, len = this.length; i < len; i++) {
        for (let attr in attributes) {
            if (attributes[attr] !== undefined) {
                this[i].attribs[attr] = attributes[attr];
            }
            else {
                delete this[i].attribs[attr];
            }
        }
    }
    return this;
};

/**
 * @method removeTextNodes
 * @summary moov_cheerio
 * @memberof $
 * @description Removes text nodes for each element in the matched set.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find(".apple").removeTextNodes();
 * // => Returns: original Cheerio object originally with $body.find(".apple")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji"></li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * $body.find("ul").removeTextNodes();
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits"><li class="apple" data-which="fuji">Apple</li><li class="orange">Orange</li> <li class="pear">Pear</li></ul>
 */
cheerio.prototype.removeTextNodes = function() {
    for (let i = 0, len = this.length; i < len; i++) {
        let elem = this[i], children = elem.children;
        if (elem.type === 'text') {
            elem.remove();
        }
        for (let i = 0, len = children.length; i < len; i++) {
            let child = children[i];
            if (child !== undefined && child.type === 'text') {
                if (child.next !== null) {
                    let next_child = child.next;
                    next_child.prev = child.prev;
                }
                elem.children.splice(i,1);
            }
        }
    }
    return this;
};

/**
 * @method trim
 * @summary moov_cheerio
 * @memberof $
 * @description Trims whitespace for each element in the matched set. Only looks
 * one level deep for whitespace; nested text nodes remain untouched.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * let $grape = $(tag("li", {class: "grape"}, "     Grape     "));
 * $body.find("ul").append($grape);
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //      <li class="grape">     Grape     </li>
 * //    </ul>
 * $grape.trim();
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //      <li class="grape">Grape</li>
 * //    </ul>
 */
cheerio.prototype.trim = function() {
    for (let i = 0, len = this.length; i < len; i++) {
        let elem = this[i], children = elem.children;
        if (elem.type === 'text') {
            elem.data.trim();
        }
        for (let i = 0, len = children.length; i < len; i++) {
            let child = children[i];
            if (child.type === 'text') {
                child.data = child.data.trim();
            }
        }
    }
    return this;
};

/**
 * @method removeIfEmpty
 * @summary moov_cheerio
 * @memberof $
 * @description Removes each element in a matched set, if it has no content
 * inside. Trims the selection, then removes any nodes that have no hide param
 * is for when we can't remove the element(s).
 * @param {Object}  [options] An object of options.
 * @param {Boolean} [options.hide] A boolean stating whether the elements should
 * instead have "mw-hide" and "mw-hidden-empty-element" classes added, instead
 * of being removed completely.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * let $apple = $body.find(".apple");
 * $apple.removeIfEmpty();
 * // => Returns: original Cheerio object originally with $body.find(".apple")
 * // => HTML output (stays the same):
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * $apple.empty();
 * $apple.removeIfEmpty();
 * // => Returns: original Cheerio object originally with $body.find(".apple")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.removeIfEmpty = function (options) {
    options = options || {};

    let elements = this.filter(':not(.mw-not-remove)');
    let elementsKeys = Object.keys(this).reverse().filter(function (value) {
        return !isNaN(+value);
    });

    elementsKeys.forEach(function (key) {
        let element = cheerio(elements[key]);

        element.contents().each(function () {
          let content = cheerio(this);
          if( content[0].type === "text" && content.text().trim() === "") {
            content.remove();
          }
        });

        if(element.contents().length === 0) {
          if(options.hide) {
            element.addClass('mw-hide mw-hidden-empty-element');
          } else {
            element.remove();
          }
        }
    });

    return this;
};

/**
 * @method wrapNonemptyTextChildren
 * @summary moov_cheerio
 * @memberof $
 * @description Wraps any text nodes for each element in the matched set. This
 * differs from wrapTextChildren(), since this ignores text nodes that are
 * comprised entirely of whitespace.
 * @param {String} name A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("li").wrapNonemptyTextChildren("span", {class: "text"});
 * // => Returns: original Cheerio object originally with $body.find("li")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji"><span class="text">Apple</span></li>
 * //      <li class="orange"><span class="text">Orange</span></li>
 * //      <li class="pear"><span class="text">Pear</span></li>
 * //    </ul>
 * @example
 * $body.find("ul").wrapNonemptyTextChildren("li", {class: "text"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output (stays the same):
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.wrapNonemptyTextChildren = function(node) {
    if (typeof node !== 'object') {
        node = tag.apply(null, arguments);
    }
    return this.wrapTextChildren(node, true);
};

/**
 * @method setText
 * @summary moov_cheerio
 * @memberof $
 * @description Performs a regular expression replacement for each element in
 * the matched set.
 * @param {string} reg1 Original text.
 * @param {string} reg2 New text.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find("li").setText("e", "x");
 * // => Returns: original Cheerio object originally with $body.find("li")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Applx</li>
 * //      <li class="orange">Orangx</li>
 * //      <li class="pear">Pxar</li>
 * //    </ul>
 */
cheerio.prototype.setText = function(reg1, reg2) {
  this.each(function(i, elem) {
    var new_text = cheerio(elem).text().replace(reg1, reg2);
    cheerio(elem).text(new_text);
  });
  return this;
};

/**
 * @method setUpper
 * @summary moov_cheerio
 * @memberof $
 * @description Sets the text of each element in the matched set to uppercase.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find(".apple").setUpper();
 * // => Returns: original Cheerio object originally with $body.find(".apple")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">APPLE</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.setUpper = function() {
  this.each(function(i, elem) {
    var new_text = cheerio(elem).text().toUpperCase();
    cheerio(elem).text(new_text);
  });
  return this;
};

/**
 * @method setLower
 * @summary moov_cheerio
 * @memberof $
 * @description Sets the text of each element in the matched set to lowercase.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find(".apple").setLower();
 * // => Returns: original Cheerio object originally with $body.find(".apple")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.setLower = function() {
  this.each(function(i, elem) {
    var new_text = cheerio(elem).text().toLowerCase();
    cheerio(elem).text(new_text);
  });
  return this;
};

/**
 * @method dumpTables
 * @summary moov_cheerio
 * @memberof $
 * @description Dumps all tables under each element of the matched set.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * let $ul = $body.find("ul");
 * let $table = $(tag("table"));
 * let $thead = $(tag("thead"));
 * let $tr1 = $(tag("tr"));
 * let $th = $(tag("th"));
 * let $tbody = $(tag("tbody"));
 * let $tr2 = $(tag("tr"));
 * let $td= $(tag("td"));
 * let $tfoot = $(tag("tfoot"));
 * let $tableClone;
 *
 * $table.append($thead.append($tr1.append($th)));
 * $table.append($tbody.append($tr2.append($td)));
 * $table.append($tfoot);
 *
 * // create a clone to see that all given tables get dumped
 * $tableClone = $table.clone();
 * $tableClone.addClass("table-clone");
 * $ul.after($table);
 * $ul.after($tableClone);
 *
 * $body.dumpTables();
 * // => Returns: original Cheerio object associated with $body
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * //    <div class="table-clone mw-was-table">
 * //      <div class="mw-was-thead">
 * //        <div class="mw-was-th"></div>
 * //      </div>
 * //      <div class="mw-was-tbody">
 * //        <div class="mw-was-td></div>
 * //      </div>
 * //      <div class="mw-was-tfoot"></div>
 * //    </div>
 * //    <div class="mw-was-table">
 * //      <div class="mw-was-thead">
 * //        <div class="mw-was-th"></div>
 * //      </div>
 * //      <div class="mw-was-tbody">
 * //        <div class="mw-was-td></div>
 * //      </div>
 * //      <div class="mw-was-tfoot"></div>
 * //    </div>
 */
cheerio.prototype.dumpTables = function() {
  this.find("table, thead, tbody, tfoot, tr, td, th").each(function(){
    let nodeName = this.name;
    cheerio(this).name("div")
      .removeAttr("width")
      .removeAttr("height")
      .removeAttr("style")
      .removeAttr("cellpadding")
      .removeAttr("cellspacing")
      .removeAttr("colspan")
      .removeAttr("rowspan")
      .addClass("mw-was-" + nodeName);
  });
  return this;
}

/**
 * @method dumpTable
 * @summary moov_cheerio
 * @memberof $
 * @description Converts selected tables into a div structure. It removes the
 * original table attributes and adds classnames corresponding to its former
 * node name.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * let $ul = $body.find("ul");
 * let $table = $(tag("table"));
 * let $thead = $(tag("thead"));
 * let $tr1 = $(tag("tr"));
 * let $th = $(tag("th"));
 * let $tbody = $(tag("tbody"));
 * let $tr2 = $(tag("tr"));
 * let $td= $(tag("td"));
 * let $tfoot = $(tag("tfoot"));
 * let $tableClone;
 *
 * $table.append($thead.append($tr1.append($th)));
 * $table.append($tbody.append($tr2.append($td)));
 * $table.append($tfoot);
 *
 * // create a clone to compare dumped and non-dumped tables side-by-side
 * $tableClone = $table.clone();
 * $tableClone.addClass("table-clone");
 * $ul.after($table);
 * $ul.after($tableClone);
 *
 * $tableClone.dumpTable();
 * // => Returns: original Cheerio object associated with $tableClone
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * //    <div class="table-clone mw-was-table">
 * //      <div class="mw-was-thead">
 * //        <div class="mw-was-th"></div>
 * //      </div>
 * //      <div class="mw-was-tbody">
 * //        <div class="mw-was-td></div>
 * //      </div>
 * //      <div class="mw-was-tfoot"></div>
 * //    </div>
 * //    <table>
 * //      <thead>
 * //        <th></th>
 * //      </thead>
 * //      <tbody>
 * //        <td></td>
 * //      </tbody>
 * //      <tfoot></tfoot>
 * //    </table>
 */
cheerio.prototype.dumpTable = function() {
  this.each(function(){
    let nodeName = "";
    if( this.name === "table" ) {
      cheerio(this).name("div")
        .removeAttr("width")
        .removeAttr("height")
        .removeAttr("style")
        .removeAttr("cellpadding")
        .removeAttr("cellspacing")
        .removeAttr("colspan")
        .removeAttr("rowspan")
        .addClass("mw-was-table")
        .find("thead, tbody, tfoot, tr, td, th").each(function(){
          nodeName = this.name;
          cheerio(this).name("div")
            .removeAttr("width")
            .removeAttr("height")
            .removeAttr("style")
            .removeAttr("cellpadding")
            .removeAttr("cellspacing")
            .removeAttr("colspan")
            .removeAttr("rowspan")
            .addClass("mw-was-" + nodeName);
        });
    }
  });
  return this;
}

/**
 * @method revertDumpTable
 * @summary moov_cheerio
 * @memberof $
 * @description Reverts the cheerio.dumpTable back to a table, using the
 * ".mw-was-" classes ascribed by calls to the original .dumpTable() or
 * .dumpTables() methods.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * let $ul = $body.find("ul");
 * let $table = $(tag("table"));
 * let $thead = $(tag("thead"));
 * let $tr1 = $(tag("tr"));
 * let $th = $(tag("th"));
 * let $tbody = $(tag("tbody"));
 * let $tr2 = $(tag("tr"));
 * let $td= $(tag("td"));
 * let $tfoot = $(tag("tfoot"));
 *
 * $table.append($thead.append($tr1.append($th)));
 * $table.append($tbody.append($tr2.append($td)));
 * $table.append($tfoot);
 *
 * $ul.after($table);
 * $body.dumpTables();
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * //    <div class="mw-was-table">
 * //      <div class="mw-was-thead">
 * //        <div class="mw-was-th"></div>
 * //      </div>
 * //      <div class="mw-was-tbody">
 * //        <div class="mw-was-td></div>
 * //      </div>
 * //      <div class="mw-was-tfoot"></div>
 * //    </div>
 *
 * $table.revertDumpTable();
 * // => Returns: original Cheerio object associated with $table
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * //    <table class="mw-no-changes mw-table-dump-reverted">
 * //      <thead class="mw-table-dump-reverted-thead">
 * //        <th class="mw-table-dump-reverted-th"></th>
 * //      </thead>
 * //      <tbody class="mw-table-dump-reverted-tbody">
 * //        <td class="mw-table-dump-reverted-td"></td>
 * //      </tbody>
 * //      <tfoot class="mw-table-dump-reverted-tfoot"></tfoot>
 * //    </table>
 */
cheerio.prototype.revertDumpTable = function() {
  this.each(function(){
    let node = cheerio(this);
    let nodeName = "";

    if(node.hasClass('mw-was-table')) {
      node.name("table")
        .removeClass('mw-was-table')
        .addClass('mw-no-changes mw-table-dump-reverted')
        .find(".mw-was-thead, .mw-was-tbody, .mw-was-tfoot, .mw-was-tr, .mw-was-td, .mw-was-th").each(function() {
            nodeName = cheerio(this).attr('class').match(/mw-was-(.+?)(\s|$)/)[1];
            cheerio(this)
              .removeClass('mw-was-thead mw-was-tbody mw-was-tfoot mw-was-tr mw-was-td mw-was-th')
              .addClass('mw-table-dump-reverted-' + nodeName)
              .name(nodeName);
        });
    }
  });
  return this;
}

/**
 * @method domManipulator
 * @summary moov_cheerio
 * @memberof $
 * @description Manipulates the DOM elements around each of the elements in the
 * matched set, by creating new or moving existing elements around them. Generic
 * wrapper of the .create() and .move() methods (see for more details on
 * positioning specifics).
 * @param {String|Object} element A string for an element name, or a Cheerio
 * object containing a matched set
 * @param {String} position A string for the position where the element should
 * be created ("top", "bottom", "before", or "after").
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Create and place an element and move it to a position
 * $body.find("ul").domManipulator("li", "top");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li></li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // Create and place an element with attributes passed in
 * $body.find("ul").domManipulator("li", "top", {content: "Content of the li", id: "test"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li id="test">Content of the li</li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // Move an existing element to a position
 * $body.find(".pear").domManipulator($body.find(".orange"), "after");
 * // => Returns: original Cheerio object originally with $body.find(".pear")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="pear">Pear</li>
 * //      <li class="orange">Orange</li>
 * //    </ul>
 * @example
 * // "Create" a new element using tag() (i.e. move it from memory to a position)
 * $body.find(".pear").domManipulator($(tag("li", {id: "test"})), "after");
 * // => Returns: original Cheerio object originally with $body.find(".pear")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //      <li id="test"></li>
 * //    </ul>
 */
cheerio.prototype.domManipulator = function(element, position, options) {
    let currentElement = this;

    options = options || {};

    if(typeof element === 'undefined') {
        throw new TypeError("Cheerio domManipulator - the element param must be an object or a string");
    }

    if(typeof position === 'undefined') {
        throw new TypeError("Cheerio domManipulator - the position param must be a string");
    }

    element = typeof element === 'string'
                    ?
                        cheerio(!/<.*?>/.test(element)
                            ? tag(element)
                            : element)
                    : element;

    if(!element.length) {
        return this;
    }

    if(options.content) {
        element.text(options.content);
        delete options.content;
    }

    if(Object.keys(options).length) {
        element.attr(options);
    }

    //Data dictionary of cheerio positions
    let positions = {
        top     : 'prepend',
        bottom  : 'append',
        after   : 'after',
        before  : 'before'
    };

    //Get cheerio position(it will be used as cheerio function, like $('.element').append..)
    let cheerioPosition = positions[position];

    if(typeof cheerioPosition === 'undefined') {
        throw new TypeError("Cheerio Create - The position " + position + ' doesn\'t exist.');
    }

    //Execute the cheerio function
    currentElement[cheerioPosition](element);

    return currentElement;
};

/**
 * @method create
 * @summary moov_cheerio
 * @memberof $
 * @description Creates a new element for each of the elements in the matched
 * set, at the specified position.
 * @param {String} element A string for an element name.
 * @param {String} position A string for the position where the element should
 * be created ("top", "bottom", "before", or "after").
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Create and place an element and move it to a position
 * $body.find("ul").create("li", "top");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li></li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // Create and place an element with attributes passed in
 * $body.find("ul").create("li", "top", {content: "Content of the li", id: "test"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li id="test">Content of the li</li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // "Create" a new element using tag() (i.e. move it from memory to a position)
 * $body.find("ul").create($(tag("li", {id: "test"})), "top");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li id="test"></li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.create = function(element, position, options) {
    if(typeof element === 'undefined') {
        throw new TypeError("Cheerio create - the element param must be an object or a string");
    }

    if(typeof position === 'undefined') {
        throw new TypeError("Cheerio create - the position param must be a string");
    }

    this.domManipulator.apply(this, arguments);
    return this;
};

/**
 * @method createTop
 * @summary moov_cheerio
 * @memberof $
 * @description Creates a new element at the top of each element in the matched
 * set. Alias for the legacy .create_top() method.
 * @param {String} element A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Create and place an element and move it to a position
 * $body.find("ul").createTop("li");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li></li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // Create and place an element with attributes passed in
 * $body.find("ul").createTop("li", {content: "Content of the li", id: "test"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li id="test">Content of the li</li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // "Create" a new element using tag() (i.e. move it from memory to a position)
 * $body.find("ul").createTop($(tag("li", {id: "test"})));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li id="test"></li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.createTop = cheerio.prototype.create_top = function(element, options) {
  this.create.call(this, element, 'top', options);
  return this;
};

/**
 * @method createBottom
 * @summary moov_cheerio
 * @memberof $
 * @description Creates a new element at the bottom of each element in the
 * matched set. Alias for the legacy .create_bottom() method.
 * @param {String} element A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Create and place an element and move it to a position
 * $body.find("ul").createBottom("li");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //      <li></li>
 * //    </ul>
 * @example
 * // Create and place an element with attributes passed in
 * $body.find("ul").createBottom("li", {content: "Content of the li", id: "test"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //      <li id="test">Content of the li</li>
 * //    </ul>
 * @example
 * // "Create" a new element using tag() (i.e. move it from memory to a position)
 * $body.find("ul").createBottom($(tag("li", {id: "test"})));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //      <li id="test"></li>
 * //    </ul>
 */
cheerio.prototype.createBottom = cheerio.prototype.create_bottom = function(element, options) {
  this.create.call(this, element, 'bottom', options);
  return this;
};

/**
 * @method createAfter
 * @summary moov_cheerio
 * @memberof $
 * @description Creates a new element after each element in the matched set.
 * Alias for the legacy .create_after() method.
 * @param {String} element A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Create and place an element and move it to a position
 * $body.find("ul").createAfter("div");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * //    <div></div>
 * @example
 * // Create and place an element with attributes passed in
 * $body.find("ul").createAfter("div", {content: "Content of the div", id: "test"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * //    <div id="test">Content of the div</div>
 * @example
 * // "Create" a new element using tag() (i.e. move it from memory to a position)
 * $body.find("ul").createAfter($(tag("div", {id: "test"})));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * //    <div id="test"></div>
 */
cheerio.prototype.createAfter = cheerio.prototype.create_after = function(element, options) {
  this.create.call(this, element, 'after', options);
  return this;
};

/**
 * @method createBefore
 * @summary moov_cheerio
 * @memberof $
 * @description Creates a new element before each element in the matched set.
 * Alias for the legacy .create_before() method.
 * @param {String} element A string for an element name.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Create and place an element and move it to a position
 * $body.find("ul").createBefore("div");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <div></div>
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // Create and place an element with attributes passed in
 * $body.find("ul").createBefore("div", {content: "Content of the div", id: "test"});
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <div id="test">Content of the div</div>
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // "Create" a new element using tag() (i.e. move it from memory to a position)
 * $body.find("ul").createBefore($(tag("div", {id: "test"})));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <div id="test"></div>
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.createBefore = cheerio.prototype.create_before = function(element, options) {
  this.create.call(this, element, 'before', options);
  return this;
};

/**
 * @method move
 * @summary moov_cheerio
 * @memberof $
 * @description Moves all elements in an input matched set to a specified
 * position of each element in the selected matched set. If there's more than
 * one element in the matched set for the target, each "moved" element is
 * duplicated.
 * @param {Object} element A Cheerio object containing a matched set.
 * @param {String} position A string for a position where the element should be
 * moved ("top", "bottom", "before", or "after").
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs to add to or replace any existing set of attributes.
 * @param {Object} [options.content] A reserved property name within the options
 * object allowing for the text node content of the element to be changed.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Move an existing element to a position
 * $body.find(".pear").move($body.find(".orange"), "after");
 * // => Returns: original Cheerio object originally with $body.find(".pear")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // Move a new element from memory to a position
 * $body.find("ul").move($(tag("li", {id: "test"})), "bottom");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //      <li id="test"></li>
 * //    </ul>
 */
cheerio.prototype.move = function(element, position, options) {
    if(typeof element === 'undefined' || Object.prototype.toString.call(element) !== '[object Object]') {
        throw new TypeError("Cheerio create - the element param must be an object");
    }

    if(typeof position === 'undefined') {
        throw new TypeError("Cheerio create - the position param must be a string");
    }

    this.domManipulator.apply(this, arguments);
    return this;
};

/**
 * @method moveTop
 * @summary moov_cheerio
 * @memberof $
 * @description Moves all elements in an input matched set to the top of each
 * element in the selected matched set. If there's more than one element in the
 * selected matched set, each "moved" element is duplicated. Alias for the
 * legacy .move_top() method.
 * @param {Object} element A Cheerio object containing a matched set.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs to add to or replace any existing set of attributes.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Move an existing element to a position
 * $body.find("ul").moveTop($body.find(".orange"));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="orange">Orange</li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // Move a new element from memory to a position
 * $body.find("ul").moveTop($(tag("li", {id: "test"})));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li id="test"></li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.moveTop = cheerio.prototype.move_top = function(element, options) {
  this.move.call(this, element, 'top', options);
  return this;
};

/**
 * @method moveBottom
 * @summary moov_cheerio
 * @memberof $
 * @description Moves all elements in an input matched set to the bottom of each
 * element in the selected matched set. If there's more than one element in the
 * selected matched set, each "moved" element is duplicated. Alias for the
 * legacy .move_bottom() method.
 * @param {Object} element A Cheerio object containing a matched set.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs to add to or replace any existing set of attributes.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Move an existing element to a position
 * $body.find("ul").moveBottom($body.find(".orange"));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="pear">Pear</li>
 * //      <li class="orange">Orange</li>
 * //    </ul>
 * @example
 * // Move a new element from memory to a position
 * $body.find("ul").moveBottom($(tag("li", {id: "test"})));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //      <li id="test"></li>
 * //    </ul>
 */
cheerio.prototype.moveBottom = cheerio.prototype.move_bottom = function(element, options) {
  this.move.call(this, element, 'bottom', options);
  return this;
};

/**
 * @method moveAfter
 * @summary moov_cheerio
 * @memberof $
 * @description Moves all elements in an input matched set after each element in
 * the selected matched set. If there's more than one element in the selected
 * matched set, each "moved" element is duplicated. Alias for the legacy
 * .move_after() method.
 * @param {Object} element A Cheerio object containing a matched set.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs to add to or replace any existing set of attributes.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Move an existing element to a position
 * $body.find(".pear").moveAfter($body.find(".orange"));
 * // => Returns: original Cheerio object originally with $body.find(".pear")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="pear">Pear</li>
 * //      <li class="orange">Orange</li>
 * //    </ul>
 * @example
 * // Move a new element from memory to a position
 * $body.find("ul").moveAfter($(tag("div", {id: "test"})));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * //    <div id="test"></div>
 */
cheerio.prototype.moveAfter = cheerio.prototype.move_after = function(element, options) {
  this.move.call(this, element, 'after', options);
  return this;
};

/**
 * @method moveBefore
 * @summary moov_cheerio
 * @memberof $
 * @description Moves all elements in an input matched set before each element
 * in the selected matched set. If there's more than one element in the selected
 * matched set, each "moved" element is duplicated. Alias for the legacy
 * .move_before() method.
 * @param {Object} element A Cheerio object containing a matched set.
 * @param {Object} [options] An object containing a list of HTML attribute-value
 * pairs to add to or replace any existing set of attributes.
 * @param {Object} [options.content] A reserved property name within the options
 * object containing the content of the element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Move an existing element to a position
 * $body.find(".pear").moveBefore($body.find(".apple"));
 * // => Returns: original Cheerio object originally with $body.find(".pear")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="orange">Orange</li>
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 * @example
 * // Move a new element from memory to a position
 * $body.find("ul").moveBefore($(tag("div", {id: "test"})));
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <div id="test"></div>
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear</li>
 * //    </ul>
 */
cheerio.prototype.moveBefore = cheerio.prototype.move_before = function(element, options) {
  this.move.call(this, element, 'before', options);
  return this;
};

/**
 * @method prependText
 * @summary moov_cheerio
 * @memberof $
 * @description Prepends an input string to each element in the matched set.
 * This coerces any text or any children into text, before prepending the input
 * string.
 * @param {string} str Text to be prepended for each matched element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find(".pear").prependText("extra text, ");
 * // => Returns: original Cheerio object originally with $body.find(".pear")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">extra text, Pear</li>
 * //    </ul>
 * @example
 * $body.find("ul").prependText("extra text, ");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      extra text,
 * //      Apple
 * //      Orange
 * //      Pear
 * //    </ul>
 */
cheerio.prototype.prependText = function(str) {
  this.each(function(i, elem) {
    var currentString = cheerio(elem).text();
    cheerio(elem).text(str + currentString);
  });
  return this;
};

/**
 * @method appendText
 * @summary moov_cheerio
 * @memberof $
 * @description Appends an input string to each element in the matched set. This
 * coerces any text or any children into text, before appending the input
 * string.
 * @param {string} str Text to be appended for each matched element.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * $body.find(".pear").appendText(", extra text");
 * // => Returns: original Cheerio object originally with $body.find(".pear")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      <li class="apple" data-which="fuji">Apple</li>
 * //      <li class="orange">Orange</li>
 * //      <li class="pear">Pear, extra text</li>
 * //    </ul>
 * @example
 * $body.find("ul").appendText(", extra text");
 * // => Returns: original Cheerio object originally with $body.find("ul")
 * // => HTML output:
 * //    <ul id="fruits">
 * //      Apple
 * //      Orange
 * //      Pear
 * //    , extra text
 * //    </ul>
 */
cheerio.prototype.appendText = function(str) {
  this.each(function(i, elem) {
    var currentString = cheerio(elem).text();
    cheerio(elem).text(currentString + str);
  });
  return this;
};

/**
 * @method makeToggler
 * @summary moov_cheerio
 * @memberof $
 * @description Creates a Uranium toggler using toggler-specific data-attributes
 * and new class names.
 * @param {Object} options An object containing specified toggler options.
 * @param {String} options.container A string for the selector for the toggler
 * container (data-ur-set="toggler").
 * @param {String} options.button A string for the selector for the toggler
 * button (data-ur-toggler-component="button").
 * @param {String} options.content A string for the selector for the toggler
 * content (data-ur-toggler-component="content").
 * @param {String} [options.skin] A string for a class that "skins" the toggler
 * container.
 * @param {String} [options.viewMoreText] A string for the text of the main
 * category. To be used in conjunction with the `wrapLink` setting.
 * @param {String} [options.wrapLink] A string for an element name to wrap
 * around the "View More" button (given by the `viewMoreText` setting above),
 * which will have the class "mw-wrap-link-toggler". The class and the Uranium
 * data-attribute for the button element must be set manually if this setting is
 * given.
 * @param {Boolean} [options.keepOpened] A boolean to prevent any Uranium
 * attributes from being added, keeping all toggler content elements opened.
 * @return {Object} The updated form of the original Cheerio matched-set object.
 * @example
 * // Simple Toggler
 * let $button = $(tag("a", {href: "#"}, "Fruits"));
 * let $wrapper;
 * let $pageContent;
 *
 * $body.find("ul").wrap("div", {class: "wrapper"});
 * $wrapper = $body.find(".wrapper");
 * $wrapper.prepend($button);
 * $wrapper.wrap("div", {id: "pageContent"});
 * $pageContent = $body.find("#pageContent");
 *
 * $pageContent.makeToggler({
 *   container: ".wrapper",
 *   button: "a",
 *   content: "ul"
 * });
 * // => Returns: original Cheerio object containing the `#pageContent` element
 * // => HTML output:
 * //    <div id="pageContent" class="mw-toggler-main-wrap">
 * //      <div class="wrapper mw-toggle-depth-1 mw-toggler-container mw-default-toggler" data-ur-set="toggler">
 * //        <a href="#" class="mw-toggler-button" data-ur-toggler-component="button" data-ur-state="disabled">Fruits</a>
 * //        <ul id="fruits" class="mw-toggler-content" data-ur-toggler-component="content" data-ur-state="disabled">
 * //          <li class="apple" data-which="fuji">Apple</li>
 * //          <li class="orange">Orange</li>
 * //          <li class="pear">Pear</li>
 * //        </ul>
 * //      </div>
 * //    </div>
 * @example
 * // Toggler with skin
 * let $button = $(tag("a", {href: "#"}, "Fruits"));
 * let $wrapper;
 * let $pageContent;
 *
 * $body.find("ul").wrap("div", {class: "wrapper"});
 * $wrapper = $body.find(".wrapper");
 * $wrapper.prepend($button);
 * $wrapper.wrap("div", {id: "pageContent"});
 * $pageContent = $body.find("#pageContent");
 *
 * $pageContent.makeToggler({
 *   container: ".wrapper",
 *   button: "a",
 *   content: "ul",
 *   skin: "mw-blue-toggler"
 * });
 * // => Returns: original Cheerio object containing the `#pageContent` element
 * // => HTML output:
 * //    <div id="pageContent" class="mw-toggler-main-wrap mw-blue-toggler-wrap">
 * //      <div class="wrapper mw-toggle-depth-1 mw-toggler-container mw-default-toggler mw-blue-toggler" data-ur-set="toggler">
 * //        <a href="#" class="mw-toggler-button" data-ur-toggler-component="button" data-ur-state="disabled">Fruits</a>
 * //        <ul id="fruits" class="mw-toggler-content" data-ur-toggler-component="content" data-ur-state="disabled">
 * //          <li class="apple" data-which="fuji">Apple</li>
 * //          <li class="orange">Orange</li>
 * //          <li class="pear">Pear</li>
 * //        </ul>
 * //      </div>
 * //    </div>
 * @example
 * // Toggler with viewMoreText and wrapLink
 * let $button = $(tag("a", {href: "/fruits"}, "Fruits"));
 * let $wrapper;
 * let $pageContent;
 *
 * $body.find("ul").wrap("div", {class: "wrapper"});
 * $wrapper = $body.find(".wrapper");
 * $wrapper.prepend($button);
 * $wrapper.wrap("div", {id: "pageContent"});
 * $pageContent = $body.find("#pageContent");
 *
 * $pageContent.makeToggler({
 *   container: ".wrapper",
 *   button: "a",
 *   content: "ul",
 *   wrapLink: "li",
 *   viewMoreText: "View all"
 * });
 * $pageContent.find(".mw-toggler-container").children("div").attributes({
 *   class: "mw-toggler-button",
 *   "data-ur-toggler-component": "button"
 * });
 * // => Returns: original Cheerio object containing the `#pageContent` element
 * // => HTML output:
 * //    <div id="pageContent" class="mw-toggler-main-wrap">
 * //      <div class="wrapper mw-toggle-depth-1 mw-toggler-container mw-default-toggler" data-ur-set="toggler">
 * //        <div data-ur-toggler-component="button">Fruits</div>
 * //        <ul id="fruits" class="mw-toggler-content" data-ur-toggler-component="content" data-ur-state="disabled">
 * //          <li class="mw-wrap-link-toggler">
 * //            <a href="/fruits">View all</a>
 * //          </li>
 * //          <li class="apple" data-which="fuji">Apple</li>
 * //          <li class="orange">Orange</li>
 * //          <li class="pear">Pear</li>
 * //        </ul>
 * //      </div>
 * //    </div>
 * @example
 * // Toggler with keepOpened 
 * let $button = $(tag("a", {href: "#"}, "Fruits"));
 * let $wrapper;
 * let $pageContent;
 *
 * $body.find("ul").wrap("div", {class: "wrapper"});
 * $wrapper = $body.find(".wrapper");
 * $wrapper.prepend($button);
 * $wrapper.wrap("div", {id: "pageContent"});
 * $pageContent = $body.find("#pageContent");
 *
 * $pageContent.makeToggler({
 *   container: ".wrapper",
 *   button: "a",
 *   content: "ul",
 *   keepOpened: true
 * });
 * // => Returns: original Cheerio object containing the `#pageContent` element
 * // => HTML output:
 * //    <div id="pageContent" class="mw-toggler-main-wrap mw-blue-toggler-wrap">
 * //      <div class="wrapper mw-toggle-depth-1 mw-toggler-container mw-default-toggler">
 * //        <a href="#" class="mw-toggler-button">Fruits</a>
 * //        <ul id="fruits" class="mw-toggler-content">
 * //          <li class="apple" data-which="fuji">Apple</li>
 * //          <li class="orange">Orange</li>
 * //          <li class="pear">Pear</li>
 * //        </ul>
 * //      </div>
 * //    </div>
 */
cheerio.prototype.makeToggler = function(options) {
    if(Object.prototype.toString.call(options) !== '[object Object]') {
        throw new TypeError("Cheerio makeToggler - the options should an object");
    }

    let wrap = this;
    let container = wrap.find(options.container);
    let wrapLink = options.wrapLink;
    let skin = options.skin;

    wrap.addClass('mw-toggler-main-wrap');
    container.removeIfEmpty();

    if(!container.length) {
        return this;
    }

    options.content = options.content || '';
    wrap.children(container).addClass('mw-toggle-depth-1');

    // Wrap link
    if(wrapLink) {

        let firstDepthButtons = wrap.find('.mw-toggle-depth-1').children(options.button);

        if(options.content === '') {
            throw new TypeError("Cheerio makeToggler - the option.content must be passed if wrapLink is required");
        }

        firstDepthButtons.each(function () {
            let firstDepthButton = cheerio(this);
            let firstDepthContainer = firstDepthButton.parent();
            let firstDepthContent = firstDepthContainer.children(options.content);

            if(wrapLink && firstDepthButton.is('a')) {
                let buttonCopy = firstDepthButton.clone();
                buttonCopy.text(options.viewMoreText || 'See all');

                buttonCopy = cheerio(tag(wrapLink, { class: 'mw-wrap-link-toggler' })).append(buttonCopy);

                firstDepthContent.prepend(buttonCopy);
                firstDepthButton.name('div').removeAttr('href');
            }
        });

        container = wrap.find(options.container);
    }

    container.each(function () {
        container = cheerio(this);

        let button = container.children(options.button, '.mw-toggler-was-link-button');
        let content = container.children(options.content);

        container.addClass('mw-toggler-container mw-default-toggler');

        if(skin) {
          container.addClass(skin);
          container.closest('.mw-toggler-main-wrap').addClass(skin + '-wrap');
        }

        button.addClass('mw-toggler-button');

        if(button.find('img').length || button.find('.mw-icon').length) {
          let togglerIcon = button.children('img');

          if(!togglerIcon.length) {
            togglerIcon = button.children('.mw-icon');
          }

          togglerIcon.addClass('mw-toggler-button-icon');
          button.wrapTextChildren('span', { class: 'mw-toggler-button-text' });
          button.filter(':not(.mw-toggler-button-icon)').wrapTogether('span', { class: 'mw-toggler-button-content' });
          button.closest('.mw-toggler-container').addClass('mw-toggler-with-icon');
        }

        if(content.children().length) {
          let urState = typeof options.keepOpened !== 'undefined' ? 'enabled' : 'disabled';
          content.addClass('mw-toggler-content');

          if(!options.keepOpened) {
            container.attr('data-ur-set', 'toggler');
            content.attr({
                      'data-ur-toggler-component': 'content',
                      'data-ur-state': urState
                    });

            button.attr({
                    'data-ur-toggler-component': 'button',
                    'data-ur-state': urState
                   });
          }

        } else {
            container.addClass('mw-toggler-no-content');
        }
    });

    return this;
};

module.exports = {
    cheerio: cheerio,
    tag: tag,
    txt: txt
};
Last updated Mon Feb 26 2018 22:51:23 GMT+0000 (UTC)