Reputation: 237
I have the following DOM structure:
var parent = document.querySelector(".test");
var navChild = parent.querySelectorAll("ul > li > a");
for (i = 0; i < navChild.length; i++) {
navChild[i].style.backgroundColor = "red";
}
console.log(navChild.lenght);
console.log(navChild);
<!-- begin snippet: js hide: false console: true babel: false -->
<ul>
<li class="test">
<a>ssss</a> <!--this will also be affected -->
<ul>
<li><a>fsfsf</a></li>
<li><a>sff</a></li>
<li><a>ggg</a></li>
</ul>
</li>
</ul>
I want to select the 3 a
tags using vanilla javascript and starting from the li
with the class test. I put this in a parent
variable and then use querySelectorAll to try to get only the a
tags that are inside the next list. However, the nodelist that is returned also includes the very first a
tag even though it's not a descendant. This is my code:
var navChild = parent.querySelectorAll("ul > li > a");
What's really weird is that if I query for just the li
descendants I get only the 3. So I don't understand why when I query for the child descendant of that li
I also get that first a
tag that is two levels up on the DOM.
I know I could do this using jQuery but I don't want to use it.
EDITED to show how I'm setting the parent variable
I'm trying to add those a
tags to the childs
variable after I've hit the enter keyboard button while on the first a
tag that I don't want to include in the childs node list.
var alinks = document.querySelectorAll('.test > a');
[].forEach.call(alinks, function(link){
link.addEventListener('keyup', function(e){
e.preventDefault();
if(e.keyCode === 13) {
var linkParent = e.target.parentElement;
var childs = menuParent.querySelectorAll('ul > li > a');
}
})
});
When I log linkParent
it is correctly being set as the li
with class test
. I don't understand why then if I run the query from there it still includes the very first a
tag. As I previously stated, if I query just ul > li
it gives me the correct li
tags.
Upvotes: 3
Views: 2798
Reputation: 7913
This is because the first <a>
tag you're trying to ignore still falls into the selector rule ul > li > a
. So, even though you start the query with the <li class="test">
as the root (which does work by the way, I don't know why the other answers say that the document is still the root), the first child element it finds is the <a>
tag and, indeed, it is the child of an <li>
which is the child of a <ul>
. The fact that this winds up going "above" your specified root is ignored.
Edit: If you want the selector rule to be scoped to the root as well, you can use this instead:
parent.querySelectorAll(":scope ul > li > a");
And to further clarify, browser CSS engines evaluate CSS rules right-to-left. In your mind, you want the browser to start at the root (the parent <li class="test">
) and then find a <ul>
tag and then find a <li>
tag and then find an <a>
tag.
However, the browser starts with the root, but then looks for all of the <a>
tags below the root. Then it checks if the <a>
tag is within an <li>
and then if the <li>
is within a <ul>
. So the <li class="parent">
tag really is the root, but it does not follow the left-to-right hierarchy of your selector rule after that, it goes right-to-left.
Upvotes: 3
Reputation: 370729
It's because your first <a>
also matches ul > li > a
, and since said a
is still a descendant of the element qSA was called upon, it gets included in the NodeList.
https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll#Javascript
Element.querySelector and Element.querySelectorAll are somewhat quirky in that respect: although the only matching elements must be children of the Element you called it on, the selector string still starts at the document level - it doesn't verify the selector by isolating the element and its descendants first.
const child = document.querySelector('#child');
const span = child.querySelector('#parent span');
console.log(span.textContent);
<div id="parent">
<div id="child">
<span>text</span>
</div>
</div>
Upvotes: 1
Reputation: 1460
Use this to define your var navChild = document.querySelectorAll("test > ul > li > a");
. I had to use a running example to solve the issue. This will get the 3 vanilla <a>
you want
The querySelectorAll() method returns all elements in the document that matches a specified CSS selector(s), as a static NodeList object.
I presume this is true even when you set a parent within the document.
var navChild = document.querySelectorAll(".test > ul > li > a");
for (i = 0; i < navChild.length; i++) {
navChild[i].style.backgroundColor = "red";
}
//console.log(navChild.lenght);
//console.log(navChild);
<ul>
<li class="test">
<a>qqqq</a>
<ul>
<li><a>aaa</a></li>
<li><a>bbb</a></li>
<li><a>ccc</a></li>
</ul>
</li>
</ul>
Upvotes: 0