querySelector() - first inner element - javascript

I'm using SVG.js select() function which uses querySelector() function.
Currently, the command I use is: select("[id='1']") (1 could be replaced by some other number)
What I'd like to do is to select the first inner element inside this element. Alternatively, I could select it by tag name.
How to do it?
I tried select("[id='1']:first") but received an error.
By the way, the reason I select it like that is that apparently querySelector has a problem with id's which are numbers.

:first is a jQuery thing. For what you're doing, you can use :first-child, which is a CSS thing:
select("[id='1'] > :first-child");
That selector matches all elements that are the first child of elements with id="1", but if select is using querySelector under the covers, you'll get the first such element.
Note that the > in that is the child combinator: It means we're looking for :first-child within [id='1']. (An earlier version of this answer used [id='1'] :first-child, which uses a descendant combinator [just whitespace]. It would matter for selecting a list of elements, but not if selecting only on the first match.) (You need one or the other, since without any combinator ([id='1']:first-child) it would b elooking for the first [id='1'] that was also a :first-child.)

"I'm using SVG.js select() function which uses querySelector() function."
But your comment under TJ's answer suggests it uses querySelectorAll(). There's a difference.
"What I'd like to do is to select the first inner element inside this element."
If it does use querySelector, then use this selector:
"[id='1'] > *"
That'll give you the first child element inside the [id='1'] element.
But if it actually uses querySelectorAll, then using TJ's :first-child selector will work, but as he noted, you need to be aware that it will return all elements that are the first child of their parent.
You can use the > child selector to ensure just one.
"[id='1'] > :first-child"
"Alternatively, I could select it by tag name. How to do it?"
I don't know which element you're referring to, but in general, include the tag name if the selector is selecting on attribute or position. That'll greatly help the engine to narrow down the set of elements.
// querySelector // querySelectorAll
"div[id='1'] > p" ... "div[id='1'] > :first-child"
"I tried select("[id='1']:first") but received an error."
As TJ noted, that's an invalid selector. jQuery's selector engine is non-conforming to the standards in several different ways. Keep your selectors pure as much as possible so that you don't get hooked on needless dependencies.
"By the way, the reason I select it like that is that apparently querySelector has a problem with id's which are numbers."
You can select by numbers if you escape the leading number.
"#\\1 > *"

Related

Why does the jquery selector ("id,id") selects all elements with duplicate IDs

I came across some year old code written by a good developer (yes, I knew him personally) to access all elements having the same id.
$("#choice,#choice")
It returns all elements having the id. But if we use the below
$("#choice")
It returns only the first match, as expected.
After searching for some time, I'm unable to figure out any official links pointing to his technique, as to how it selected all elements with duplicate id.
Can anyone please explain how is this working ?
UPDATE
Please see the question is not about what alternative to use. I'm aware of classSelectors and attributeSelectors and know having duplicate IDs is not recommended, but sometimes you just have to live with years old code the way it is (if you know what I mean).
http://jsbin.com/zodeyexigo/1/edit?html,js,output
If you look at the code of sizzle.js that jQuery uses for selecting elements based on selector you will understand why this happens. It uses following regex to match simple ID, TAG or class selector:
// Easily-parseable/retrievable ID or TAG or CLASS selectors
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
but as the selector in question is $("#ID,#ID") it does not match with the selector and uses querySelectorAll (line no 270 in ref link), which replaces selector to "[id='" + nid + "'] " (line no 297 in ref link) which selects all the elements with matching ID.
However, I agree with the other people in this thread, that it is not good idea to have same ID for multiple elements.
Having 2 elements with the same ID is not valid html according to the W3C specification.
When your CSS selector only has an ID selector (and is not used on a specific context), jQuery uses the native document.getElementById method, which returns only the first element with that ID.
However, in the other two instances, jQuery relies on the Sizzle selector engine (or querySelectorAll, if available), which apparently selects both elements. Results may vary on a per browser basis.
However, you should never have two elements on the same page with the same ID. If you need it for your CSS, use a class instead.
If you absolutely must select by duplicate ID, use an attribute selector:
$('[id="a"]');
Take a look at the fiddle: http://jsfiddle.net/P2j3f/2/
Note: if possible, you should qualify that selector with a tag selector, like this:
$('span[id="a"]');
Having duplicated id on the page making your html not valid . ID is unique identifier for one element on the page (spec). Using classes, that are classify similar elements that's your case and $('.choice') will return set of elements
So in JS Fiddle i have shown an example of what jQuery is doing.
https://jsfiddle.net/akp3a7La/
When you have a
$('#choice,#choice');
It is actually getting all the instances of the objects #choice twice, and then filtering out any duplicates.
in my example i show you how it does that also when you have something like this
$("#choice,li");
Where items are actually your #choice items.
In the Jquery Documentation
https://api.jquery.com/multiple-selector/
it talks about multiple Selectors, which is what i think is happening here, your developer friend is selecting the same ID twice, and it would be returning it twice. as you can only have one input with the same ID once on a page (good html syntax)

Multiple not() DOM Selectors

I want to select a particular node with two not clauses, but I had no success so far. What I need to do is, select an element whose div contains the string 0008, but it's not 10008 and also it does not contain the tag "style", so, in theory it should work like that:
document.querySelectorAll(" div[id*='0008']:not([id='10008'][style])")
However, as you might suspect, it doesn't work that way.
document.querySelectorAll(" div[id*='0008']:not([id='10008'])")
document.querySelectorAll(" div[id*='0008']:not([style])")
Both of them work perfectly individually, of course.
not 10008 and also it does not …
That's not what your current selector checks, it test whether it has not ( the id and a style attribute ) . Use this instead:
div[id*='0008']:not([id='10008']):not([style])
Your original solution also was not a valid selector, since :not() may only contain one simple selector, while you had two of them. Yet, selector libraries like jQuery's sizzle engine might support them. So with jQuery, the following would work as well:
div[id*='0008']:not([id='10008'],[style])
jsFiddle Demo
Logically, you are trying to exclude elements that match either of the two undesired selectors, not elements that match them both. In jQuery, the multiple selector (which will then match all of the undesired elements, then be negated) is simply a comma-separated listing. Therefore you simply do this:
$("div[id*='0008']:not([id='10008'],[style])")
From the jQuery docs (since this question is tagged jQuery):
All selectors are accepted inside :not(), for example: :not(div a) and :not(div,a).
I'd just do:
var elems = $('div[id*="0008"]').filter(function() {
return !this.hasAttribute("style") && this.id == '10008';
});
I don't think I really get this, but this would filter out:
<div id="10008" style="color: black">10008</div>
but not:
<div id="10008">10008</div>
ID's are of course unique, and there could be a real world use case for this, but it still seems like an edge case that you'd normally handle some other way, as once you'd filtered out the ID, why exactly do you need to match a style tag as well ?

How is the jQuery selector $('#foo a') evaluated?

As a example of jQuery code (https://coderwall.com/p/7uchvg), I read that the expression $('#foo a'); behaves like this:
Find every a in the page and then filter a inside #foo.
And it does not look efficient.
Is that correct? And if yes, how should we do that in a better way?
That is correct - Sizzle (jQuery's selector engine) behaves the same way as CSS selectors. CSS and Sizzle selectors are evaluated right-to-left, and so #foo a will find all a nodes, then filter those by nodes that descend from #foo.
You improve this by ensuring that your leaf selectors have a high specificity, usually by giving them a class or ID.
how should we do that in a better way?
Use the context parameter from jQuery.
$('a', '#foo');
Now jQuery will search all anchors within the context of the element with id: foo.
In your query the context is defaulted to document when omitted:
$('#foo a'); == $('#foo a', document);
In this case, your query is indeed not efficient.
You might take a look at this article.
While it is true that Sizzle is a right-to-left engine (which is the same way css is interpreted), it is not true that the specific selector in your example would select all anchor elements on the page and then filter their parents to match the id of "foo". Sizzle actually optimizes any selector that starts with an ID and uses that as the context for the entire selection, rather than using the document. In other words, the selector you've chosen basically translates to:
document.getElementById("foo").getElementsByTagName("a")
Really, that's not a bad selector at all.
However, given the other things jQuery needs to do (which includes looping over the elements to merge them onto the jQuery instance), jQuery("#foo").find("a") will always be the fastest because jQuery implements a jQuery object creation shortcut for id-only selectors, and then it does the find rooted from #foo.
In other words, Sizzle itself is not much different when doing Sizzle("#foo a") and Sizzle("a", document.getElementById("foo")), but jQuery("#foo").find... will be faster because of jQuery's own ID shortcut.
By the way, my remarks on Sizzle is assuming querySelectorAll is not being used. If it is, Sizzle just passes it on to qsa, which still isn't as fast as using jQuery's ID shortcut.
You can use find() for more granular control on your selector order:
$('#foo').find('a');
This will of course be more impressive with more complex selectors, where you can chain find() and filter().
For the record $('#foo').find('a') === $('a','#foo')
[Update] ok, I realized later that it's exactly what your link says...
The jQuery selector engine (Sizzle) has been refactored last year, you'll find detailed explanations here:
http://www.wordsbyf.at/2011/11/23/selectors-selectoring/
Instead of filtering with a inside #foo elements, simply attach a class to a elements and get a elements with class like $("a.class");. This would be more efficient.
Yet another "try it for yourself":
jsperf for various selectors on 10000 elements
jsperf for various selectors on 300 elements
jsperf for various selectors on a "more representative DOM"
Doesn't seem to be much difference with a "flat" DOM (1 & 2), but the performance varies much more with a nested DOM.
Also note that some of the test cases aren't selecting the correct elements (i.e. $('.a') vs $('.a', context)), but I left them from the original tests just for comparison.
This example will retrieve the all anchors elements a in an element called foo, to Find every a in the page and then filter a inside #foo as you want u should select a #foo
$("a #foo");
this will retrieve all the foo elements inside a elements.

Selecting previous anchor tag and adding a class to it with jQuery

Basically I've got two anchor tags separated by a div.
<a class="foo"></a>
<div class="count">0</a>
<a class="bar"></a>
and I'm doing something like this:
var addClass = $(this).parent().children('a:first-child').addClass('new');
It will then add it to the first anchor tag like so:
<a class="foo new"></a>
I know using the nth:child selector is pretty expensive, so I was wondering if there was an easier way to select that previous anchor node, (while ignoring the second one).
You could do this
$(this).parent().children('a:eq(0)').addClass('new');
Learn more about :eq()
Alternatively, if there are no elements between the <a> and the <div>, you could do
$(this).prev('a');
Learn more about .prev()
I'd probably combine prevAll with first (or eq(0), since first just calls eq(0) — but I find first more readable):
$(this).prevAll("a").first().addClass("new");
// or
$(this).prevAll("a").eq(0).addClass("new");
prevAll returns all of the element's preceding siblings, in order starting with the closest sibling, and of course first / eq(0) reduces the set to the first element.
You might be tempted to use the :first selector with prevAll to avoid building up an unnecessary list of siblings, but to my surprise it works better to use first after-the-fact.
How about:
$(this).siblings('a').eq(0).addClass(0);
Actually, if your structure is as simple as in your example, you can just do a simple:
$(this).prev().addClass(0);
jQuery's traversal methods give you lots of ways to get to any given destination.

Will jQuery search for the ID before filtering other parameters in the selector?

This question is related to performance.
If I use a selector like the following
$('#myID a') // Does this find #myID and filter by a?
Or should I write the statement like this?
$('#myID').find('a')
I'm not sure if jQuery is smart enough to execute this statement using the ID first or if it operates exactly like CSS and reads right to left. It's not such a big deal using tags but when you run something like
$('#myID .myClass')
It makes a HUGE difference in performance.
From a NetTuts article: http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-think-right-to-left-with-jquery/
As an example, if Sizzle comes across a selector like $('#box p'),
it’s true that it works right-to-left, but there’s also a quick regex
optimization that will first determine whether the first section of
the selector is an id. If so, it’ll use that as the context, when
searching for the paragraph tags.
Relevant comment from SizzleJS:
// Take a shortcut and set the context if the root selector is an ID
// (but not if it'll be faster if the inner selector is an ID)
When an Id is in the selector. jQuery will first execute document.getElementById then begin filtering for child elements.
basically this is why it is never a great idea to use just attribute or class selectors $('.someclass') or $('[name=myname]') without being more specific. Because it causes the code to traverse the DOM and look at every element to find that class.
By just adding a tagname to the same selector $('div.someclass') or $('div.[name=myname]') you improve efficiency becuase it will first run. document.getElementsByTagName narrowing the number of elements to search.

Categories