Jessica
Jessica

Reputation: 9830

Select Range of Dom Elements

I'm trying to select a range of dom elements, from element until element. That's achievable in JQuery like this: (Source)

$('#id').nextUntil('#id2').andSelf().add('#id2')

I'm trying to achieve that using JavaScript ONLY.

Here's what I tried, but I get a infinite loop:

function prevRange(element, prevTill) {
    var result = [];

    while (element !== prevTill)
        result.push(element);
    return result;
}

JSFiddle

var wrapper = document.getElementById('wrapper'),
  wrapperChildren = wrapper.children;

console.log(prevRange(wrapperChildren[2], wrapperChildren[0]));

function prevRange(element, prevTill) {
  var result = [];

  /*while (element !== prevTill)
      result.push(element);*/
  return result;
}
<ul id="wrapper">
  <li class="inner">I'm #01</li>
  <li class="inner">I'm #02</li>
  <li class="inner">I'm #03</li>
  <li class="inner">I'm #04</li>
</ul>

Upvotes: 0

Views: 775

Answers (9)

Kenneth Moore
Kenneth Moore

Reputation: 1237

You could create a TreeWalker that does this for you:

var wrapper = document.getElementById('wrapper');
var wrapperChildren = wrapper.children;

function getElementRange(rangeRoot, elementStart, elementEnd) {
  var result = [];
  var itr = document.createTreeWalker(
    rangeRoot,
    NodeFilter.SHOW_ELEMENT,
    null, // no filter
    false);
  itr.currentNode = elementStart;

  do {
    result.push(itr.currentNode);
  } while(itr.currentNode !== elementEnd && itr.nextSibling());

  return result;
}

console.log(getElementRange(wrapper, wrapperChildren[0], wrapperChildren[2]));
<ul id="wrapper">
  <li class="inner">I'm #01</li>
  <li class="inner">I'm #02</li>
  <li class="inner">I'm #03</li>
  <li class="inner">I'm #04</li>
</ul>

Check out This link to MDN For more information on creating TreeWalkers

I also recommend having an understanding of the TreeWalker interface object

Hope this helps.

*Edit - Here is an example that supports bidirectional ranges:

var wrapper = document.getElementById('wrapper');
var wrapperChildren = wrapper.children;

function getElementRange(elementStart, elementEnd) {
  var result = [];
  var rootNode;
  var indexStart;
  var indexEnd;
  
  rootNode = elementStart.parentNode;
  if(rootNode !== elementEnd.parentNode){
      return console.log("Cannot getElementRange, elements are not siblings");
  }
  
  //Get direction to traverse nodes
  indexStart = Array.prototype.indexOf.call(rootNode.childNodes, elementStart);
  indexEnd = Array.prototype.indexOf.call(rootNode.childNodes, elementEnd);

  var itr = document.createTreeWalker(
    rootNode,
    NodeFilter.SHOW_ELEMENT,
    null, // no filter
    false);
  itr.currentNode = elementStart;
  var iterateMethod = indexStart < indexEnd ? 'nextSibling' : 'previousSibling';
  do {
    result.push(itr.currentNode);
  } while(itr.currentNode !== elementEnd && itr[iterateMethod]());
  
  return result;
}

console.log(getElementRange(wrapperChildren[1], wrapperChildren[3]));
console.log(getElementRange(wrapperChildren[3], wrapperChildren[1]));
<ul id="wrapper">
  <li class="inner">I'm #01</li>
  <li class="inner">I'm #02</li>
  <li class="inner">I'm #03</li>
  <li class="inner">I'm #04</li>
</ul>

Upvotes: 0

bloodyKnuckles
bloodyKnuckles

Reputation: 12079

Incorporating Array prototype slice and indexOf methods.

getRangeElements(
  document.getElementById('wrapper'), // parent
  document.getElementById('li1'),     // start
  document.getElementById('li3')      // end
)

function getRangeElements (parent, start, end) {
  var children = parent.children
  return [].slice.call(
    children, 
    [].indexOf.call(children, start),
    [].indexOf.call(children, end) + 1
  )
}

ChildNodes don't inherently have array methods, so using the call method to apply that functionality. Once the childNodes can be treated like array elements, then it's a matter of first getting the indexOf the child elements, then sliceing the range you're looking for.

Given:

<ul id="wrapper">
  <li class="inner" id="li1">I'm #01</li>
  <li class="inner" id="li2">I'm #02</li>
  <li class="inner" id="li3">I'm #03</li>
  <li class="inner" id="li4">I'm #04</li>
</ul>

...returns:

[li#li1.inner, li#li2.inner, li#li3.inner]

JSFiddle example

Upvotes: 0

Ethan
Ethan

Reputation: 817

Well there are a few ways to do it... I like Nikhil's answer for going backwards, and you could do the same thing going forward with the nextSibling property.

I've personally had issues with next and previous (particularly IE and edge, YAY MS!!)

This is another alternative that's pretty simple but doesn't rely on the DOM next/prev.

var wrapper = document.getElementById('wrapper'),
    wrapperChildren = wrapper.children;

console.log(stopWhen(wrapperChildren[0],wrapperChildren[2]));

function stopWhen(start,end){
    var results = new Array()
        ,parent = start.parentElement
      ,startPos = Array.prototype.indexOf.call(parent.childNodes, start)-2
      ,endPos = Array.prototype.indexOf.call(parent.childNodes, end)-2;
      for(var i=startPos; i < endPos; i++){    
        if(parent.children[i] != null){
            results.push(parent.children[i]);
        }
      }
      return results;
}

Forked fiddle

Upvotes: 0

Alex Kudryashev
Alex Kudryashev

Reputation: 9460

You have to iterate elements to avoid infinite loop. Working code can look like this:

function prevRange(element, prevTill) {
    var result = [];
    if(element === prevTill){
       result.push(element);
       return result;
    }
    var siblings = element.parentNode.children;//prevTill is expected among siblings
    var startSelection = false;
    for(var i=0,ch;ch=siblings[i];++i){//iterate siblings
    if(ch === element || ch === prevTill){//no matter which go first
       result.push(ch);
       startSelection = !startSelection;//start/stop
    }
    else if(startSelection)
       result.push(ch);
    }

    /*while (element !== prevTill) this is your code
        result.push(element);*/
    return result;
}

Upvotes: 0

KRONWALLED
KRONWALLED

Reputation: 1442

There are a lot of answers already but I've implemented a all around solution for what I think you are trying to achieve.

function selectRange(from, to) {
   var i;
   var result = [];
   if (from > to) {
    // backwards
    i = from;

    while (i >= to) { 
        result.push(wrapperChildren[i]);
        i--;
    }
   }
   else if (from < to) {
    // forward
    i = from;

    while (i <= to) {
        result.push(wrapperChildren[i]);
        i++;
    }
   }
   else {
    // return just one element
    result.push(wrapperChildren[from]);
   }

   return result;
}

Upvotes: 0

Andrew Mairose
Andrew Mairose

Reputation: 10985

Your variables element and prevTill are never being changed inside your while loop. So if element and prevTill are not equal when you first enter your while loop, they never will be, and your while loop will never execute.

Plus you are not passing in the array of elements that you are trying to iterate over and get a subset of.

I would modify your function to take in the array of elements you are trying to get a subset of, and the starting and ending index for the subset that you want, then you can iterate over the array in your while loop.

var wrapper = document.getElementById('wrapper'),
  wrapperChildren = wrapper.children;

console.log(prevRange(wrapperChildren, 0, 2));

function prevRange(array, start, end) {
  var result = [];

  var curr = start;

  while (curr <= end) {
    result.push(array[curr]);
    curr++;
  }

  return result;
}
<ul id="wrapper">
  <li class="inner">I'm #01</li>
  <li class="inner">I'm #02</li>
  <li class="inner">I'm #03</li>
  <li class="inner">I'm #04</li>
</ul>

Upvotes: 1

Patrick Roberts
Patrick Roberts

Reputation: 51756

Use Element.previousElementSibling:

var wrapper = document.getElementById('wrapper'),
  wrapperChildren = wrapper.children;

console.log(prevRange(wrapperChildren[2], wrapperChildren[0]));

function prevRange(element, prevTill) {
  var result = [element];

  while (element && element !== prevTill) {
    element = element.previousElementSibling;
    result.push(element);
  }

  return result;
}
<ul id="wrapper">
  <li class="inner">I'm #01</li>
  <li class="inner">I'm #02</li>
  <li class="inner">I'm #03</li>
  <li class="inner">I'm #04</li>
</ul>

Upvotes: 1

Nikhil Girraj
Nikhil Girraj

Reputation: 1143

Didn't you want to do this:

while (element !== null && element !== prevTill)
{
    result.push(element);
    element = element.previousElementSibling;
}

Updated fiddle.

Upvotes: 0

Ethan Leyden
Ethan Leyden

Reputation: 298

You aren't iterating over the elements. element will never equal prevTill because you are never changing element. What if you passed the array of the element's children, and iterated through it for the range that you wanted using a for loop? So you would pass the array of elements, the minimum index for the first child that you want, and the maximum index for the last child that you want.

Upvotes: 0

Related Questions