简体   繁体   中英

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> 

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> 

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> 

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.

Didn't you want to do this:

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

Updated fiddle .

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;
}

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;
}

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

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 slice ing 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

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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM