0

I have some rather annyoing piece of HTML given and want to iterate over all list elements under the given headline. Unfortunately, there are some comments in between that I'd like to skip.

<header><h2>A</h2></header>
<ul class="list">...</ul>
<header><h2>C</h2></header>
<p>Some comment</p>
<ul class="list">...</ul>
<p>Another comment</p>
<ul class="list">...</ul>
<header><h2>B</h2></header>
<p>Some comment</p>
<ul class="list">...</ul>

I've tried document.querySelectorAll('h2 ~ .list:not(h2)'), but this returns nothing.

So, ideally, I'd go over each header and for each header retrieve the lists, something along the lines of

const headers = document.querySelectorAll('h2');
for(let i = 0; i < headers.length; ++i)
{
  // Somehow get an Array of `list` elements between h2 i and i+1
}
NaCl
  • 2,683
  • 2
  • 23
  • 37
  • _“but this returns nothing”_ - `~` is the general _sibling_ combinator. Your `h2` and `.list` elements _are not_ siblings here. The lists are siblings of the `header` elements, so your combinator would have to start with that. – CBroe Jul 31 '20 at 12:04
  • 1
    And `.list:not(h2)` is kinda pointless to begin with - you do not have a single `h2` element with that class here, so you don’t need to “exclude” them in the first place. – CBroe Jul 31 '20 at 12:06
  • You can't go over each header and get its list because the list isn't a child of any header... if it doesn't then matter that the header isnt the parent of the list and you don't wan't some comment in there can't you just select by the .list class? Honestly with a better html structure you wont have to search all wonky like this and your code will be easier to read better ... – Cengleby Jul 31 '20 at 12:13

2 Answers2

1

If you just need the ul elements you can simplify your selector to 'header ~ .list'. This is because the h2 elements are children of header and have no siblings, therefore the general sibling selector "~" matched nothing.

const lists = document.querySelectorAll('header ~ .list');

console.log(lists.length);
<header><h2>A</h2></header>
<ul class="list">...</ul>
<header><h2>C</h2></header>
<p>Some comment</p>
<ul class="list">...</ul>
<p>Another comment</p>
<ul class="list">...</ul>
<header><h2>B</h2></header>
<p>Some comment</p>
<ul class="list">...</ul>
chazsolo
  • 7,873
  • 1
  • 20
  • 44
0

Assuming you want both the header and the .list it's related to, a single select won't do it for you, you'll have to do some traversal. (If you only want the .lists, see chazsolo's answer.)

  1. Select the header elements.
  2. Loop through them
    1. Loop through the siblings (nextElementSibling) after "this" header handling any uls you find, stopping at the first header

Roughly:

// Ideally, if you can narrow down this initial select, that would be good
const headers = document.querySelectorAll("header");
for (const header of headers) { // *** See note
    let list = header.nextElementSibling;
    while (list && list.tagName !== "HEADER") {
        if (list.tagName === "UL") {
            console.log("Found: " + list.id);
        }
        list = list.nextElementSibling;
    }
}

Live Example:

// Ideally, if you can narrow down this initial select, that would be good
const headers = document.querySelectorAll("header");
for (const header of headers) { // *** See notes
    let list = header.nextElementSibling;
    while (list && list.tagName !== "HEADER") {
        if (list.tagName === "UL") {
            console.log("Found: " + list.id);
        }
        list = list.nextElementSibling;
    }
}
<!-- Added IDs just to make showing them easier -->
<header><h2>A</h2></header>
<ul class="list" id="list1">...</ul>
<header><h2>C</h2></header>
<p>Some comment</p>
<ul class="list" id="list2">...</ul>
<p>Another comment</p>
<ul class="list" id="list3">...</ul>
<header><h2>B</h2></header>
<p>Some comment</p>
<ul class="list" id="list4">...</ul>

Note on the for (const header of headers): That relies on the NodeList from querySelectorAll being iterable. It's specified as iterable and it's iterable on modern browsers, but slightly older ones (and obsolete onse) may have NodeList without it being iterable. In that case, you may be able to use forEach. But obsolete browsers don't even have that. You may be able to polyfill either forEach or iterability or both, see my answer here for details. Otherwise, use an old-fashioned for loop. :-)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Wouldn't just `document.querySelectorAll('header ~ .list')` suffice? – chazsolo Jul 31 '20 at 12:06
  • 1
    @chazsolo - If you don't *also* need the headers, yes, absolutely. I assumed (and I could be wrong here) the OP needed both the `header` and the lists related to it...but I'm not sure why i assumed that. :-) – T.J. Crowder Jul 31 '20 at 12:08