John Resig wrote:
There's three major points that I wanted to discuss:
* DOMElement.querySelectorAll returning incorrect elements
This is the most critical issue. As it stands DOM Element-rooted
queries are borderline useless to libraries - and users. Their
default behavior is unexpected and confusing. Demonstrated with an
example, using Dojo:
<div><p id="foo"><span></span></p></div>
<script src="http://o.aolcdn.com/dojo/1.1.0/dojo/dojo.xd.js"></script>
<script>
var foo = document.getElementById("foo");
alert( dojo.query('div span', foo).length ); // should return nothing
alert( foo.querySelectorAll('div span').length ); // will return the SPAN
</script>
The demo can be run online here:
http://ejohn.org/files/bugs/qsa-root/dojo.html
If we were to do the implementation similarly to Dojo, then it would be
impossible to select only child elements on the context node. Consider
this:
<div id=foo>
<div> <!-- A -->
<div> <!-- B -->
</div>
</div>
</div>
foo.querySelectorAll("div");
Assuming the selector won't match the context node (foo) itself, that
would contain both descendant div elements (A and B). But in the case
where only child nodes of foo are wanted (A), this is impossible.
This following would not work because the previous assumption was that
selectors would not match the element itself.
foo.querySelectorAll("#foo>div");
Dojo gives the same result for that scenario.
dojo.query('#foo>div', foo);
* Combinator-rooted Queries
I read about some prior discussion concerning this (especially in
relation to DOMElement.querySelectorAll-style queries). This is an
important part of most libraries, as it stands. Maciej's proposed
solution of using :root to allow for front-leading combinators is
perfectly acceptable to me (where :root is made equivalent to the
element, not the document element).
// jQuery $("#foo").find("> span");
This can't work because that is an invalid selector. It would require
the use of an implied simple selector which matches only the context
node, which gets prefixed to each sequence of simple selectors within
the selector.
i.e.
foo.querySelectorAll(">strong, em");
would have to be equivalement to:
foo.querySelectorAll("$self>strong,$self em");
where $self is some simple selector (or a sequence of simple selectors)
that only matches the node foo.
// DOM document.getElementById("foo").querySelectorAll(":root >
span")
This is something that a library can easily detect and inject.
There has been much discussion of this solution in the past, using
various pseudo-classes such as ":this", ":root" and ":scope". AFAICT,
this was first suggested in June 2007 on member-webapi:
| There is, however, one possible use case that isn't covered easily.
| When you want to do element.get*() and only match child nodes of that
| element, rather than all descendants.
|
| e.g. foo.getAll("?>div"); where the "?" is some simple selector that
| only matches the foo element (the root of the subtree). It could be
| handled using an ID selector, but that doesn't work when there is no
| ID on the element. That's one possible use case for making :root
| match the root of the subtree, but I can't justify that given its
| definition in selectors. It would probably need a new special
| pseudo-class, but that's up to the CSSWG.
http://lists.w3.org/Archives/Member/member-webapi/2007Jun/0019.html
| Oh, that's like the issue I pointed out at the end of a previous post
| [1]. If there were significant use cases for it, I think we would
| need a special self referencing selector of some kind. e.g.
|
| myElem.get(':this foo > bar');
|
| or maybe call it :scope or something.
http://lists.w3.org/Archives/Member/member-webapi/2007Jun/0024.html
There are several reasons the spec is defined the way it is on this issue.
1. It's how selectors work in existing Selector implementations. i.e.
Given any element, the selector is evaluated against it in the context
of the entire document tree. That makes it easier for implementations to
implement this API without requiring them to significantly alter their
selector engines.
2. This makes it more flexible for authors, so that they can select
based upon ancestor elements if needed.
3. Introducing a :scope selector (or equivalent) solves all the issues
you have raised, without sacrificing potentially useful functionality.
:scope would also be useful in contexts outside of Selectors API, such
as within HTML5's scoped stylesheets and possibly XBL.
So while you haven't convinced me that we need to significantly alter
the spec to address this issue, you have convinced me that we should try
to expedite the specification and implementation of a :scope selector
(or equivalent).
* Error-handling
try { document.querySelectorAll("div:foo"); } catch(e) { alert(e); //
"Error: SYNTAX_ERR: DOM Exception 12" }
If there was an extra property to point to what the inappropriate
selector was, that'd be fundamentally important. Probably the best
solution (for both implementers and JavaScript library authors) would
be to simply provide a character index, working something like the
following:
var selector = "div:foo"; try { document.querySelectorAll(selector);
} catch(e) { alert(selector.slice(e.position)); // ":foo" }
I think Boris has raised some fairly significant issues with this
proposed solution. While I'm not opposed to the concept of providing
authors with more diagnostic information about errors, the proposed
solution seems difficult to implement and inadequate, and I'm not sure
how to improve it.
--
Lachlan Hunt - Opera Software
http://lachy.id.au/
http://www.opera.com/