Choose a Range of DOM Elements

My challenge is to select a range of DOM elements, starting from element until element. This can be done in jQuery like this: (Source)

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

I want to achieve the same using JavaScript only.

Here's my attempt so far, but it seems to result in an 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>

Answer №1

When looking for the previous element sibling, you can utilize Element.previousElementSibling:

var container = document.getElementById('container'),
  containerChildren = container.children;

console.log(findPreviousElements(containerChildren[2], containerChildren[0]));

function findPreviousElements(element, stopAt) {
  var result = [element];

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

  return result;
}
<ul id="container">
  <li class="item">Item #01</li>
  <li class="item">Item #02</li>
  <li class="item">Item #03</li>
  <li class="item">Item #04</li>
</ul>

Answer №2

The variables element and prevTill remain constant within the while loop, causing it to potentially never execute if they are not initially equal. Additionally, the array of elements to iterate over is missing from the function.

To address this, consider updating the function to include the array of elements to iterate over, along with the start and end indices for the desired subset. This will allow for proper iteration over the array within the 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>

Answer №3

The key issue is the lack of iteration over the elements. By not changing the 'element' variable, it will never be equal to 'prevTill'. One way to solve this is by passing an array of element's children and iterating through it with a 'for' loop to define the desired range. This approach involves passing the array of elements along with the minimum and maximum indices for the child elements you wish to target.

Answer №4

Were you interested in implementing the following logic?

while (target !== null && target !== prevTarget)
{
    output.push(target);
    target = target.previousElementSibling;
}

Check out the latest demo.

Answer №5

There have been multiple responses provided, but I have come up with a comprehensive solution that caters to your specific needs.

function extractItemsInRange(startIndex, endIndex) {
   var index;
   var output = [];
   if (startIndex > endIndex) {
    // iterating backwards
    index = startIndex;

    while (index >= endIndex) { 
        output.push(dataArray[index]);
        index--;
    }
   }
   else if (startIndex < endIndex) {
    // iterating forwards
    index = startIndex;

    while (index <= endIndex) {
        output.push(dataArray[index]);
        index++;
    }
   }
   else {
    // return just one item
    output.push(dataArray[startIndex]);
   }

   return output;
}

Answer №6

To ensure you don't end up in an endless loop, it's important to iterate through elements. A functional example of the code could be structured like this:

function previousRange(element, previousElement) {
    var result = [];
    if (element === previousElement) {
        result.push(element);
        return result;
    }
    var siblings = element.parentNode.children; // The previous element is expected among the siblings
    var startSelection = false;
    for (var i = 0, child; child = siblings[i]; ++i) { // Iterating through siblings
        if (child === element || child === previousElement) { // Doesn't matter which comes first
            result.push(child);
            startSelection = !startSelection; // Start or stop selection
        } else if (startSelection) {
            result.push(child);
        }
    }

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

Answer №7

There are multiple techniques to achieve this task. Personally, I find Nikhil's approach for moving backward quite effective, and a similar method can be implemented for moving forward using the nextSibling property.

In my experience, there have been challenges with utilizing next and previous methods, especially on browsers like IE and Edge - Thanks Microsoft!

Here is another straightforward alternative that does not depend on 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;
}

Check out the modified version here: fiddle

Answer №8

Utilizing the Array prototype methods slice and indexOf.

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

Since ChildNodes do not naturally have array methods, we are using the call method to apply that functionality. By allowing childNodes to be treated like array elements, we can then determine the indexOf the child elements and slice the desired range.

Example HTML:

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

Resulting in:

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

JSFiddle example

Answer №9

If you want to automate the process, consider using a TreeWalker:

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

function findElementRange(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(findElementRange(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>

For more information on how TreeWalkers work, check out this link to MDN.

It's also beneficial to be familiar with the TreeWalker interface object.

I hope this explanation was useful.

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

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

function findElementRange(elementStart, elementEnd) {
  var result = [];
  var rootNode;
  var indexStart;
  var indexEnd;
  
  rootNode = elementStart.parentNode;
  if(rootNode !== elementEnd.parentNode){
      return console.log("Cannot find Element Range, elements are not siblings");
  }
  
  //Determine traversal direction
  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(findElementRange(wrapperChildren[1], wrapperChildren[3]));
console.log(findElementRange(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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting Safari compatibility with CSS transitions: a first attempt

Greetings everyone, I am a complete beginner here so please bear with me. After looking at some examples, I have managed to create a start/stop slowdown feature. Here is the JSFiddle link for reference .small-wheel, .big-wheel{ transition: all 2s ease ...

Having trouble importing my function component into a router in reactjs. Need some guidance on how to properly set it up

I am working on a Slider component function called export default Slider. In my App.js file, I have the following code: function App() { return ( <Router> <Routes> <Route exact path='/' element={<Home />} /> ...

Ways to troubleshoot the "TypeError: Cannot read property 'value' of null" issue in a ReactJS function

I keep encountering a TypeError: Cannot read property 'value' of null for this function and I'm struggling to pinpoint the source of the issue. Can someone help me figure out how to resolve this problem? By the way, this code is written in R ...

Activate Click, or Pop-up Box

I've been attempting to log in to the Udemy site using JavaScript, but I'm having trouble triggering the click action on the "log in" link. Unfortunately, the .click() method doesn't seem to be working when I try to select the element. The l ...

No reversematch error occurs despite the successful import of the primary key

Even though the pk has been imported, I am still getting a NoReverseMatch Error. Error Traceback: Reverse for 'profile_page' with no arguments not found. 1 pattern(s) tried: ['profile_page/(?P<pro>[^/]+)$'] My urls.py configurat ...

What is the reason behind utilizing arrow functions in React event handlers?

function ButtonIncrement(){ const [count,setCount] = useState(0); render(){ <div> <h3> <button onClick={() => setCount(count+1)}>Increase count for amusement</button> <p>Total C ...

Pass an array containing an HTML string to the $.post method in jQuery

I have a problem with displaying HTML content fetched from a PHP script using the jQuery $.post method: $.post('{$site_url}tests/tests/content', params, function (data) { $('#some-div1').html(data[1]); $('#some-div2').htm ...

The computed value failing to refresh

I'm facing an interesting issue. I am currently developing a simple time tracking application. Here is the form I have created: <form class="form" @submit.prevent="saveHours"> <div class="status"> <div class="selector" v-f ...

"Troubleshooting the issue with the @click event failing to update the data

Working in Vue.js, my v-for loop is set up to go through a list and generate buttons. Each button has a click event that should change the value of upUrl to its index: <div class="mt-3" v-for="(buttonPic, index) in buttonPics" ...

Utilizing Parent Scope Variables in AngularJS Directives with Isolated Scopes

I'm feeling a bit lost here (edit: after clarification, my question is "Why isn't the directive recognizing the attributes passed to it?"), even though I thought I had a good grasp on it. I'm having trouble accessing the properties of the pa ...

Leveraging the yeoman-generator variable within the template that is being generated

When creating an angularJS project with yeoman, I pass an argument to the generator which I retrieve from the main generator's script (index.js) using: this.argument('name', { type: String }); I can access this parameter within index.js as ...

Progress Circles on Canvas in FireFox

Currently, I am experimenting with this codepen demo that utilizes canvas to generate progress circles: The functionality functions perfectly in Chrome and Safari; however, it only displays black circles in FireFox. My knowledge of canvas is limited, so I ...

Labels are overlapping each other and being aligned to the right side

Working with the most up-to-date version of Bootstrap, I am currently designing a search bar featuring a dropdown menu preceding the search button. Upon clicking the dropdown, various controls are revealed. Below is the HTML code for this configuration: ...

What could be causing my Divs to not adjust their location automatically?

When using JQuery, I have a group of four tabs that initially appear as shown below: <ul class="tabs"> <li> <input type="radio" name="tabs" id="tab1" checked /> <label for="tab1">Item One</label> & ...

The mysterious case of the vanishing jQuery traversal box

I'm currently navigating using previous and next buttons, but it seems to be malfunctioning if I click too quickly. My goal is for the navigation to remain smooth without breaking. var $currDiv = $("#start"); $("div").hide(); $currDiv.c ...

Ways to interact with a button that toggles between states

I am working with a button that has both an enabled and disabled state. Enabled State: <button type="button" class="btt-download-csv site-toolbar-menu-button icon-download no-icon-margins ng-isolate-scope" title="Download CSV" rc-download-csv="" curren ...

By utilizing the power of JSONP, data transfer limitations can be eliminated

Is it possible to remove the limitation on data sent using JSONP? Here's my code. I'm attempting to send 3000 characters (an image converted to base64 data) at a time to a service (serviceCall.ashx). Since my data is quite large, up to 30,000-40, ...

Refresh the texture mapping within ThreeJS

Hey there! Just wanted to share that I was able to find a solution by using mesh.material.map.image.src = texture; Here was the original question: I'm working on a ThreeJS project and I'm trying to change the texture on a mesh. Unfortunately, I ...

In the realm of web development, the Phoenix library introduces the concept

Is there a way to combine a dynamic classname and a static one effectively? <div class=<%="#{getClass(app.status)} two" %> What happens is <div class="one" two> Instead of the desired output of <div class="one t ...

"Manipulating a drop-down menu in HTML with data from a MySQL database using PHP

Hello there, I could use some assistance. I am working on a straightforward web application and require help with a specific task. Here's the scenario: I have an HTML form with a single combo box. I would like to populate this combo box with data fro ...