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

Error: The specified object does not contain the 'tableRow' method

I am currently working on a contacts book project and I need a table to update as the user inputs data. However, I keep encountering an error related to 'tableRow'. I have tried changing function names and other solutions but haven't been ab ...

Using indented, multi-line logging in a NodeJS environment can help to

I'm looking for a way to display objects that have been printed with JSON.stringify() in the console, specifically within the context of a Mocha test suite output. While my tests are running, I want the object log lines to be indented further to the ...

What is the best way to create a button that will trigger a modal window to display a message?

I am looking to create a button that will open a modal window displaying a message. However, when I tried to add a label and viewed the page, the desired window appeared on top of the rest of the content. But unfortunately, clicking the button did not prod ...

Specifying file types in an HTML upload form

Is there a way to restrict my form so that it only allows jpeg files? Currently, it is displaying all types of files. <input name="image" type="file" /> Also, are there any JavaScript functions available for showing progress? ...

Executing a query with a `has many` relationship in MongoDB: Step-by-step guide

In my setup with Node, Express, and backbone, I am successfully retrieving records from MongoDB collections using simple queries. However, I am struggling to understand how to query more complex data, such as the one below: db.employees.aggregate( [ ...

How can a JQuery slideshow be programmed to only iterate once?

Looking to create a slideshow that transitions between images every two seconds? Check out the code snippet below: HTML: <div class="fadeIn"> <img src="img/city.png" class="remimg" id="city"> <img src="img/shop.png" class="remimg" ...

hiding elements yet passing down

Despite disabling the style sheet, a dynamically created form is displaying strangely. This form will show or hide various details based on selected options. For instance, many of the elements are generated with C#.net... formOutput += "<div class=&bs ...

Troubleshooting the error "The 'listener' argument must be a function" in Node.js HTTP applications

I'm facing an issue resolving this error in my code. It works perfectly fine on my local environment, but once it reaches the 'http.get' call, it keeps throwing the error: "listener argument must be a function." Both Nodejs versions are iden ...

Extract a value from a json document

Hey there! I'm looking to create an unwhitelist command. When a user is mentioned or their ID is provided, I want it to be removed from the JSON file without checking if the ID exists. Do you have any suggestions on how to accomplish this? Here is my ...

Hide modal once form has been successfully submitted

Is it best practice to pass handleClose into ForgotPasswordFormComponent in order to close the modal after form submission, or is there a better way to achieve this? <StyledModal open={openModal} onClose={handleClose} closeAfterTransition slots={{ bac ...

Looking for a solution to align the header properly in your React app?

Struggling with aligning a header? The CSS property of the header is inherited from the app's CSS file. For example, in the image below, "Our Mission" is the header that needs to be left-aligned. Looking for some assistance here. You can find the app. ...

The Shopify Pixel Extension has encountered an issue - error code 1

Looking to develop a web pixel extension for my Shopify app, I followed the official guide: While building the app, encountered this error: extensions | my-app-pixel (C:\projects\shopify\my-app-pixel\node_modules\.bin\shopify ...

What is the best approach to concurrently update a single array from multiple functions?

In my React app, I have a form with various input fields and checkboxes. Before making an API call to submit the data, I have functions set up to check if any fields are left blank or unchecked. These check functions are triggered when the form button is ...

The issue of an undefined Node.js variable post "await"

While I know similar questions have been asked before, I assure you that I've gone through them; however, I'm still facing a challenge. I have a simple code snippet to retrieve a token for a 3rd-party API service: let tok = ''; const g ...

Shut down the active tab

For some reason, using window.close(); in my JavaScript script is not closing the currently opened tab as expected. I'm looking for a way to automatically close a manually opened tab using a JavaScript function. Any ideas on what might be going wrong? ...

Tips on customizing the color of checkboxes in a ReactJS material table

I'm working on a project that involves using the Material table, and I need to change the color of the checkbox when it's selected. Can anyone help me with this? https://i.stack.imgur.com/JqVOU.png function BasicSelection() { return ( <M ...

Express.js - display the complete information

Trying to display an array of objects (highcharts points) is giving me some trouble. Instead of the actual data, I'm seeing [object Object]. It seems that JSON.stringify() doesn't play well with HTML. util.inspect also doesn't work as expe ...

"Positioned at the top of the page is the alert box, with the

I'm looking to add an alert at the top of my webpage containing a form for visitors to enter their phone number. Currently, I have a "alert alert-info" div with the form inside it placed at the top of my body tag, which works perfectly. However, when ...

Creating a recursive function using NodeJS

This particular challenge I am tackling is quite intricate. My objective is to develop a recursive function in NodeJS that can interact with the database to retrieve results. Based on the retrieved data, the function should then recursively call itself. F ...

Navigating through the content of slots within recurring slots in a subcomponent in Vue.js

I am encountering an issue with a child component, where each row in an object is rendered inside a div with a specific slot. I need to pass data from the parent for each of these elements. I've been attempting to iterate through every element of the ...