• How can I group a collection of elements by their adjacency?

    1.1k Views Asked by At

    Let's say I have a list that looks like this:

    <ul>
        <li id="q"></li>
        <li id="w"></li>
        <li id="e"></li>
        <li id="r"></li>
        <li id="t"></li>
        <li id="y"></li>
        <li id="u"></li>
        <li id="i"></li>
        <li id="o"></li>
    </ul>
    

    I need to do something like this:

    function get_important_elements() {
        // completely contrived;
        // elements are guaranteed to be contained within same ul
        // but nothing else unique in common (class, attrs, etc)
        return $('#q, #w, #r, #u, #i, #o');
    }
    
    function group_adjacent($elems) {
        return $elems; //:(    
    }
    
    $(function () {
        var $filtered_list = get_important_elements();
    
        var groups = group_adjacent($filtered_list);
    
        // groups should be 
        // (shown by ID here, should contained actual elements contained
        // within jQuery objects): 
        // [$([q, w]), $([r]), $([u, i, o])]
    });
    

    How could I go about this?

    Note that the IDs and classes used in the list are 100% contrived. In the real code upon which I'm basing this, I have a collection of li elements that are a subset of the lis contained in a single ul. This subset was determined by their contents, which are not important to the question at hand, not by class. They only share a class in the example for ease of getting my point across.

    3

    There are 3 best solutions below

    7
    Kevin B On BEST ANSWER
    function group_adjacent($elems) {
        var rArr = [],
            currArr = $([]);
        $elems.each(function() {
            var $this = $(this);
            currArr = currArr.add($this);
            if (!$elems.filter($this.next()).length) {
                rArr.push(currArr);
                currArr = $([]);
            }
        });
        return rArr;
    }
    

    http://jsfiddle.net/adamjford/5q8fZ/3/

    4
    thagorn On
    function group_adjacent($elems) {
        var temp = new Array();
        var i = 0;
        var j = 0;
        var last = null;
        for(i = 0; i < $elems.length; i++) {
             if(last == $elems[i].previousSibling) {
                 temp[i][j++] = $elems[i];
             }
             else {
                 j = 0;
                 temp[i] = new Array();
                 temp[i][j++] = $elems[i];
             }
             last = $elems[i];
        }
        return temp;
    }  
    
    3
    RobG On

    Using plain script, you can get a collection of all the LIs and loop over it. If an element has the true class put it in a group array. If the next element has the class, put it in the same array. If it doesn't, start a new array. e.g.

    function groupLis(){
        var el, els = document.getElementsByTagName('li');
        var group = [], groups = [group];
    
        for (var i=0, iLen=els.length; i<iLen; i++) {
          el = els[i];
    
          if (hasClass(el, 'true')) {
    
            if (!group) {
              group = [];
              groups.push(group);
            }
            group.push(el);
    
          } else if (group && group.length != 0) {
            group = null;
          }
        }
        return groups;
    }
    
    // Helper function
    function hasClass(el, cName) {
        var re = new RegExp('(^|\\s+)' + cName + '(\\s+|$)');
        return el && re.test(el.className);
    }
    

    Edit

    Ok, here is an answer for the revised question: given an array of elements, group them into arrays of adjacent siblings.

    Note that it doesn't care if the elements are the same type, only that they are adjacent siblings (or not).

    // el is a DOM node
    // Returns the next element sibling, or undefined if here isn't one
    function getNextSiblingElement(el) {
      while ((el = el.nextSibling)) {
        if (el.nodeType == 1) {
          return el;
        }
      }
    }
    
    // els is an array of DOM elements
    // Returns an array of sibling element arrays
    function groupEls2(els) {
      var el = els[0],
          group = [el],
          groups = [group];
    
      for (var i=1, iLen=els.length; i<iLen; i++) {
        el = els[i];
        el == getNextSiblingElement(els[i-1])? group.push(el) : groups.push((group = [el]));
      }
      return groups;
    }