querySelector :scope is null - javascript

I am trying to select a sibling of the current element, but when I use a.querySelector(':scope') on the element it is null. When I try to add a sibling selector to the :scope it is still null, but if I use it within .matches(':scope') it returns true. How can I use the current element within the selector?
let a = document.querySelector('div > a')
console.log(a.querySelector(':scope'))
console.log(a.querySelector(':scope + span'))
console.log(a.matches(':scope'))
<div>
<span></span>
</div>

querySelector selects the descendants of the context element.
The element itself won't be a descendant of itself, and nor will any of its siblings.
You can use :scope if you are targetting a descendant.
For example, to search only the children and not deeper descendants you can use :scope with the child combinator.
let a = document.querySelector('#foo')
console.log(a.querySelector(':scope > span'))
<div id="foo">
<div><span>2</span></div>
<span>1</span>
</div>

Related

How to make a selector for direct child of the root node?

Consider that you are given a node node and you must provide all direct children given by selector Direct. Selector for direct child is:
childParent > directChild
However, the following fails with an error in console:
document.body.querySelectorAll(">div")
SyntaxError: '>div' is not a valid selector
I have a function that needs to do something on select direct child nodes, but I'm not sure how to handle this. Except of course using the for loop and analyse the children with my own code, abandoning selectors completely.
The following code does not work. Can it be changed so that it does what is intended?
function doWithDirectChildren(parentNode) {
// does not work, the selector is invalid
const children = parentNode.querySelector(">.shouldBeAffected");
for(const direct of children) {
// do something with the direct child
}
}
I'm asking for a solution, not a workaround.
The proper way to do this is with the :scope psuedo class.
According to the documentation at MDN:
When used from a DOM API such as querySelector(), querySelectorAll(), matches(), or Element.closest(), :scope matches the element on which the method was called.
For example:
let parent = document.querySelector('#parent');
let scoped = parent.querySelectorAll(':scope > span');
Array.from(scoped).forEach(s => {
s.classList.add('selected');
});
.selected {
background: yellow;
}
<div id="parent">
<span> Select Me </span> <br>
<span> Me Too </span>
</div>
<span> Not Selected </span>
The child combinator operator > is a binary operator so using it with nothing on the left side is invalid.
The child combinator (>) is placed between two CSS selectors. It
matches only those elements matched by the second selector that are
the direct children of elements matched by the first.
If you can provide individual parent and child selectors you can do something simple like this
let directChildren = (parent, child) => document.querySelectorAll(`${parent} > ${child}`);
directChildren('body','div');//...
If your parent argument is a node or collection you would have to use a method of converting it back to a selector, like this one
jQuery solves this problem in 2 ways. Consider this code:
$('div.root').find('> .p2').addClass('highlighted');
$('div.root').children('.p2').addClass('red');
.highlighted {
background: yellow
}
.red {
color: red
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="root">
<p>div 1</p>
<p class="p2">paragraph 2</p>
<p>paragraph 3</p>
<div>
<p class="p2">paragraph 2 2</p>
</div>
</div>
using .find('> selector) finds only direct children matching the selector, and using .children('selector') also does the same.

Weird behaviour when using querySelector

As my understanding, when using element.querySelector(), the query should be start on particular element.
However, when I run using code below, it keep selected the first DIV tag in particular element.
const rootDiv = document.getElementById('test');
console.log(rootDiv.querySelector('div').innerHTML);
console.log(rootDiv.querySelector('div > div').innerHTML);
console.log(rootDiv.querySelector('div > div > div').innerHTML);
console.log(rootDiv.querySelector('div > div > div > div').innerHTML);
console.log(rootDiv.querySelector('div > div > div > div > div').innerHTML);
<div>
<div>
<div id="test">
<div>
<div>
This is content
</div>
</div>
</div>
</div>
</div>
As you can see, the first few results is the same.
This this a bug? Or it will query from start of the document?
What querySelector does is it finds an element somewhere in the document that matches the CSS selector passed, and then checks that the found element is a descendant of the element you called querySelector on. It doesn't start at the element it was called on and search downwards - rather, it always starts at the document level, looks for elements that match the selector, and checks that the element is also a descendant of the calling context element. It's a bit unintuitive.
So:
someElement.querySelector(selectorStr)
is like
[...document.querySelectorAll(selectorStr)]
.find(elm => someElement.contains(elm));
A possible solution is to use :scope to indicate that you want the selection to start at the rootDiv, rather than at document:
const rootDiv = document.getElementById('test');
console.log(rootDiv.querySelector(':scope > div').innerHTML);
console.log(rootDiv.querySelector(':scope > div > div').innerHTML);
console.log(rootDiv.querySelector(':scope > div > div > div').innerHTML);
<div>
<div>
<div id="test">
<div>
<div>
This is content
</div>
</div>
</div>
</div>
</div>
:scope is supported in all modern browsers but Edge.
The currently accepted answer somehow provides a valid logical explanation as to what happens, but they are factually wrong.
Element.querySelector triggers the match a selector against tree algorithm, which goes from the root element and checks if its descendants do match the selector.
The selector itself is absolute, it doesn't have any knowledge of a Document and doesn't even require that your Element be appended to any. And apart from the :scope attribute, it doesn't either care with which root you called the querySelector method.
If we wanted to rewrite it ourselves, it would be more like
const walker = document.createTreeWalker(element, {
NodeFilter.SHOW_ELEMENT,
{ acceptNode: (node) => return node.matches(selector) && NodeFilter.FILTER_ACCEPT }
});
return walker.nextNode();
const rootDiv = document.getElementById('test');
console.log(querySelector(rootDiv, 'div>div').innerHTML);
function querySelector(element, selector) {
const walker = document.createTreeWalker(element,
NodeFilter.SHOW_ELEMENT,
{
acceptNode: (node) => node.matches(selector) && NodeFilter.FILTER_ACCEPT
});
return walker.nextNode();
};
<div>
<div>
<div id="test">
<div>
<div>
This is content
</div>
</div>
</div>
</div>
</div>
With the big difference that this implementation doesn't support the special :scope selector.
You may think it's the same going from the document or going from the root element, but not only will it make a difference in terms of performances, it will also allow for using this method while the element is not appended to any document.
const div = document.createElement('div');
div.insertAdjacentHTML('beforeend', '<div id="test"><div class="bar"></div></div>')
console.log(div.querySelector('div>.bar')); // found
console.log(document.querySelector('div>.bar')); // null
In the same way, matching elements in the Shadow-DOM would not be possible if we only had Document.querySelector.
The query selector div > div > div only means:
Find a div which has a parent and a granparent which are both also a div.
And if you start with the first child of test and check the selector, it is true. And this is the reason why only your last query selects the innermost div, since it has the first predicate (find a div with a great-great-grandparent-div) which is not fulfilled by the first child of test.
The query-selector will only test descendants, but it will evaluate the expression in scope of the whole document. Just imagine a selector like checking properties of an element - even if you only view the child element, it is still the child of its parent.

How to apply CSS on parent using its child

Hello I want to apply CSS on parent using its child
<li>
<div>
<label>
<b style="display: none;">Name and Date of Event*: </b>
</label>
</div>
</li>
I want to hidden to list. but I don'n know how to Apply CSS.
I used just
jQuery("b:contains('Name and Date of Event*:')").css('display','none');
But it hidden only <b> Tag I want to hide li.
How can I hide parent li using child.Is it Possible.
Use .closest() function. It accepts a selector and finds the the appropriate ancestor.
$('#btn').on('click', function(){
$("b:contains('Name and Date of Event*:')").parents('li').css('display','none');
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li>
Li Content
<div>
<label>
<b style="display: none;">Name and Date of Event*: </b>
</label>
</div>
</li>
<button id="btn">Hide li</button>
Why use .closest and not .parents ?
.closest()
Begins with the current element
Travels up the DOM tree until it finds a match for the supplied selector
The returned jQuery object contains zero or one element for each element in the original set
.parents()
Begins with the parent element
Travels up the DOM tree to the document’s root element, adding each ancestor element to a temporary collection; it then filters that collection based on a selector if one is supplied
The returned jQuery object contains zero or more elements for each element in the original set
You can apply .parents() OR .closest()
Try this
jQuery("b:contains('Name and Date of Event*:')").parents('li').css('display','none');
OR
jQuery("b:contains('Name and Date of Event*:')").closest('li').css({'display':'none'});

Get child-element from nested div

I have a div-structure like this:
<div id="company1">
<div class="space m-d p-r s-t">
<div class="zzr">
<div class="myTemplate">abc123</div>
</div>
</div>
</div>
I want to get the content form the class "myTemplate" over my "id"-div "company1"
Is it necessary to call all classes in my selector? Would be not good becaus of responsive design, the classes will change. So I woul prefer to call the "#company1" and then directly the "myTemplate". Tried this, but the content is empty and also the selector.
$('#company-'+currentTabIndex).children('.myTemplate').html()
//currentTabIndex has the current Tab-Index, in this case: 1
Firstly, the id property in your HTML has no - in it. Secondly, children looks at direct descendants, whereas you need to use find():
$('#company' + currentTabIndex).find('.myTemplate').html()
That said, you can use a single selector and remove the find() completely:
$('#company' + currentTabIndex + ' .myTemplate').html()
You want .find, not .children:
$('#company-'+currentTabIndex).find('.myTemplate').html()
.find looks for descendant elements. .children just looks for immediate children.
Or a single selector using the descendant combinator (the space before .myTemplate below — gotta love that name):
$('#company-' + currentTabIndex + ' .myTemplate').html()
See also Rory's note about the - in your selector, which isn't in your id. Either remove it from the selector, or add it to the id.
Children searches only for single level child elements, you have to use find().
$('#company-'+currentTabIndex).find('.myTemplate').html()
Given a jQuery object that represents a set of DOM elements, the
.children() method allows us to search through the children of these
elements in the DOM tree and construct a new jQuery object from the
matching elements. The .children() method differs from .find() in that
.children() only travels a single level down the DOM tree while
.find() can traverse down multiple levels to select descendant
elements (grandchildren, etc.) as well.
Reference: .find() - .children().
Instead of
$('#company-'+currentTabIndex).children('.myTemplate').html();
Try
$('#company'+currentTabIndex).find('.myTemplate').html(); //remove '-' from the selector
Use .find() instead of .children() as shown above.

jQuery "find" method alternative

$('.wrapper a').find('a'); //return empty object
But i am looking for a way get all anchors by selector. Problem is find method look at only descendants so what is alternative of it ?
Please test it on jsfiddle.net
jQuery find gets the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.
children gets the children of each element in the set of matched elements, optionally filtered by a selector.
I think you are trying to find the elements at the same level then you should use children. Alternatively you can also use filter to filter the matched results based on selector.
filter reduces the set of matched elements to those that match the selector or pass the function's test.
Try this
var div = $('.wrapper div').filter('.parent');
Looking for this?
var div = $('.wrapper div').filter('.parent');
A forked demo of yours
the alternatives of .find() function which are as below:
Child Selector (“parent > child”) it selects only first-level descendants or direct child elements. for example $('#parent_id > #child_id') or $(".parent > .first-level-child")
Descendant Selector (“ancestor descendant”) it selects a child, grandchild, great-grandchild, and so on, of that element. in it you can use $('#parent_id #child_id') or $('#parent_id #grandchild_id') or $(".parent .great-grand-child") or $( "form input" )
.filter() only search in those elements that match the precondition.
.parent() get the parent of each element in the current set of matched elements, optionally filtered by a selector.
.children() it works exactly the same way as find, but it will only find first-level-children, not more distant descendants.
.closest() get the closest (first) element that matches the selector, starting at the current element.
for detailed info about jquery selectors check JQuery Selectors
$('.wrapper a').find('a'); find links inside links that are descendants of .wapprer.
I think you might have meant $('.wrapper').find('a');. In your fiddle that would be
$('.wrapper').find('.parent');`
insetead of:
$('.wrapper div').find('.parent');

Categories