I need help. I have weird problem with jQuery's nextUntil()
method. It is not working as I am expecting...
I have two comment tags inside my HTML which delimit the elements I am interested in and which I want to get into a jQuery collection. For that I first find both comment nodes, and apply nextUntil
on the first, providing as argument the second. But nextUntil
returns all of the children that follow the first comment, seemingly ignoring the argument I have passed to it.
How can I prevent this from happening, and get only the children between the two comments?
Here is my code:
HTML:
<ul>
<li>1</li>
<li>2</li>
<!-- from -->
<li>3</li>
<li>4</li>
<!-- to -->
<li>5</li>
<li>6</li>
</ul>
JavaScript:
const fromSign = 'from'
const toSign = 'to'
// filter callback - return only comment
// element with specific text
function commentWith (elm, txt) {
return elm.nodeType === 8 &&
elm.nodeValue.trim() === txt
}
// Step - 1 find comment with "from"
// text and save returned element
const $start = $('ul').contents().filter(
(idx, elm) => commentWith(elm, fromSign)
)
// Find comment with "to"
// text and save returned element
const $stop = $('ul').contents().filter(
(idx, elm) => commentWith(elm, toSign)
)
console.info($start, $stop) // So far, so good
// Step 2 - find all elements
// between "from" and "to" comments
let $inner = $start.nextUntil($stop)
// Not works - inner contains elements
// beyond "to" comment
console.log($inner)
// Step 2 again - find all elements
// between "from" and "to" comments
// with "redundant" prev/next
$inner = $start.nextUntil($stop.prev().next())
// OK, just li 3 and 4,
// but why? What am I missing?
console.log($inner)
PS: If you use <div class="from">
and <div class="end">
or other elements to select start and end, nextAll()
works as expected. But, I want use comments for marking the selection....
I tried with prev
/next
"hacks" as alternative (see also code above), but they don't behave well when there are no elements preceding and/or following the comment markers.
The reason the first variant of your code fails is that most jQuery methods only look at elements, not nodes of other node types. As stated in the jQuery documentation on contents
:
Please note that most jQuery operations don't support text nodes and comment nodes. The few that do will have an explicit note on their API documentation page.
In your case the call to nextUntil
will step through elements only, not even looking at other nodes, and so it will "miss" the $stop node.
A workaround is to use index()
. This will also work well when there are no elements before the start mark, and/or no elements after the stop mark. The index returned for a non-element node will be the index it would have if it were an element node.
const fromSign = 'from'
const toSign = 'to'
// filter callback - return only comment
// element with specific text
function commentWith (elm, txt) {
return elm.nodeType === 8 &&
elm.nodeValue.trim() === txt
}
// Step - 1 find comment with "from"
// text and save returned index
const startIndex = $('ul').contents().filter(
(idx, elm) => commentWith(elm, fromSign)
).index();
// Find comment with "to"
// text and save returned index
const stopIndex = $('ul').contents().filter(
(idx, elm) => commentWith(elm, toSign)
).index();
console.log('indexes:', startIndex, stopIndex);
// Step 2 - get all child elements
// between "from" and "to" comments
let $inner = $('ul').children().slice(startIndex, stopIndex);
// Now it works - inner contains the elements between the markings
console.log('selected first and last text:', $inner.first().text(), $inner.last().text());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>1</li>
<li>2</li>
<!-- from -->
<li>3</li>
<li>4</li>
<!-- to -->
<li>5</li>
<li>6</li>
</ul>