source Node-Traversal.js

    import Sizzle from 'sizzle';

/**
 * Extends {@link smx.Node Node} with tree traversal utility methods.
 * @mixin Node-Traversal
 * @see smx.Node
 */

let Traversal = {

  /**
   * Gets a list of all ancestor nodes matching the given selector, ordered from outer to inner.
   * @param {String} selector
   * @return {smx.Node[]}
   * @memberof Node-Traversal
   */
  getAncestors: function(selector){
    
    if(!selector) return this.ancestors;
    return this.ancestors.filter((n)=>n.isMatch(selector));
    
  },
  
  // EXTRA - PARENT RELATED OPERATIONS

  /**
   * Checks if node is an ancestor of another.
   * @param {smx.Node} node - reference node
   * @return {Boolean}
   * @memberof Node-Traversal
   */
  isAncestorOf: function(node){
    
    if (!node.parent) return false;
    var ancestorsId = node.ancestors.map((n)=>{ return n.id });
    if (ancestorsId.indexOf(this.id)>-1) return true;
    else return false;
    
  },


  /**
   * Checks if node matches the given selector.
   * @param {String} selector - css selector to match
   * @return {Boolean}
   * @memberof Node-Traversal
   */
  isMatch: function(selector) {
    
    return Sizzle.matchesSelector(this[0],selector);
    
  },
  
  // CHILD RELATED OPERATIONS

  /**
   * Finds all descendant nodes matching the given selector.
   * @param {String} selector - search selector
   * @return {Array.<Node>}
   * @memberof Node-Traversal
   */
  find: function(selector) {
    
    if (!selector) return [];
    if (!this.children.length) return [];
    
    return this.document.find(selector, this);
    
  },


  /**
   * This method is like `find` but returns only the first result.
   * @param {String} selector - search selector
   * @return {smx.Node}
   * @memberof Node-Traversal
   */
  findOne: function(selector) {
    
    return this.find(selector)[0];
    
  },



  /**
   * Gets the children nodes matching the given selector.
   * @param {String=} selector
   * @return {Array.<Node>}
   * @memberof Node-Traversal
   */
  getChildren: function(selector) {
    
    if(!selector) return this.children;
    
    return this.children.filter((n) => n.isMatch(selector));
    
  },
  

  /**
   * Gets the first child node matching the given selector.
   * @param {String=} selector
   * @return {smx.Node}
   * @memberof Node-Traversal
   */
  getFirst: function(selector) {
    
    if(!selector) return this.first;
    
    var children = this.children;
    var i=0, len=children.length, result;
    while(i<len && !result){
      if (children[i].isMatch(selector))
        result = children[i];
      i++;
    }
    
    return result;
    
  },


  /**
   * Gets the last child node matching the given selector.
   * @param {String=} selector
   * @return {smx.Node}
   * @memberof Node-Traversal
   */
  getLast: function(selector) {
    
    if(!selector) return this.last;
    
    var children = this.children.reverse();
    var i=0, len=children.length, result;
    while(i<len && !result){
      if (children[i].isMatch(selector))
        result = children[i];
      i++;
    }
    
    return result;
    
  },



  // EXTRA - CHILD RELATED OPERATIONS

  /**
   * Gets child node at given index
   * @param {Integer} index - index position
   * @return {smx.Node}
   * @memberof Node-Traversal
   */
  getChildAt: function(index) {
    
    return this.children[index];
    
  },


  /**
   * Checks if a node is child of another
   * @param {smx.Node} node - reference node
   * @return {Boolean}
   * @memberof Node-Traversal
   */
  isDescendantOf: function(node) {
    
    if (!node.parent) return false;
    var ancestorsId = this.ancestors.map((n) => { return n.id });
    if (ancestorsId.indexOf(node.id)>-1) return true;
    else return false;
    
  },


  // SIBLING RELATED OPERATIONS


  /**
   * Gets the next sibling node matching the given selector.
   * @param {String=} selector - filter selector
   * @return {smx.Node}
   * @memberof Node-Traversal
   */
  getNext: function(selector) {
    
    if(!selector)
      return this.next;
    else {
      var n = this.next;
      var isMatch = false;
      while (!isMatch && n) {
        if(n.isMatch(selector))
          isMatch = true;
        else
          n = n.next;
      }
      return (isMatch)? n : undefined;
    }
    
  },

  /**
   * Gets all next sibling nodes matching the given selector.
   * @param {String=} selector - filter selector
   * @return {smx.Node[]}
   * @memberof Node-Traversal
   */
  getAllNext: function (selector) {

    if (!this.next) return [];
    else {
      //fill up nodes array walking all next nodes
      var n = this.next;
      var nodes = [n];
      while (n && n.next) {
        n = n.next;
        nodes.push(n);
      }
      if(!selector)
        return nodes;
      else //return filtered by selector
        return nodes.filter((n) => { return n.isMatch(selector) });
    }

  },

  /**
   * Gets the previous sibling node matching the given selector.
   * @param {String=} selector - filter selector
   * @return {smx.Node}
   * @memberof Node-Traversal
   */
  getPrevious: function(selector) {
    
    if (!selector)
      return this.previous;
    else {
      var n = this.previous;
      var isMatch = false;
      while (!isMatch && n) {
        if (n.isMatch(selector))
          isMatch = true;
        else
          n = n.previous;
      }
      return (isMatch) ? n : undefined;
    }
    
  },

  /**
   * Gets all previous sibling nodes matching the given selector.
   * @param {String=} selector - filter selector
   * @return {smx.Node[]}
   * @memberof Node-Traversal
   */
  getAllPrevious: function (selector) {
    
    if (!this.previous) return [];
    
    else {
      //fill up nodes array walking all previous nodes
      var n = this.previous;
      var nodes = [n];
      while (n && n.previous) {
        n = n.previous;
        nodes.unshift(n);
      }
      if (!selector)
        return nodes;
      else //return filtered by selector
        return nodes.filter((n) => { return n.isMatch(selector) });
    }
    
  }
  
};

export default Traversal;