Reputation: 9830
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;
}
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
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
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 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]
Upvotes: 0
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
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
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
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
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
Reputation: 1143
Didn't you want to do this:
while (element !== null && element !== prevTill)
{
result.push(element);
element = element.previousElementSibling;
}
Updated fiddle.
Upvotes: 0
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