Hi Lachy,
Here's a proposal.
querySelector*(selector, context) // allows selectors with :scope
pseudo-class
queryScopedSelector*(selector, context) // allows selectors with implied
:scope
matchesSelector(selector, context) // allows selectors with :scope
pseudo-class
To check if the :scope pseudo-class is available, use:
try { document.body.matchesSelector(":scope", document.body); }
catch (error) { /* not supported */ }
OR
try { document.querySelector(":scope", document.body); }
catch (error) { /* not supported */ }
Now, querySelector*() can't accept selectors with implied :scope because
while "> em" is unambiguously ":scope > em", "p em" would become
ambiguous. (is it "p em" or ":scope p em")
So we need a new method, such as queryScopedSelector*().
element.querySelector*() limits selection to descendants of elements,
and element.queryScopedSelector*() should be consistent.
If element is the scope then element.queryScopedSelector*("~p") will
return no elements.
If we want to support sibling queries then we need to provide a scope
explicitly, so:
element.parentNode.queryScopedSelector*("~p", element);
Notes:
1. I don't think browsers should provide queryScopedSelector*()
2. I think :context is a better name than :scope
3. If the context argument of these methods could be an element or a
NodeList it might satisfy some of the other feature requests.
Lachlan Hunt wrote:
Hi,
I'm trying to find a suitable solution for the scoped selector
issues, but figuring out what the most suitable API is proving
challenging.
*Use Cases*
1. JS libraries like JQuery and others, accept special selector
strings beginning with combinators. e.g. ">em,+strong". These
libraries behave as if there was a selector that matched the context
node.
e.g. In JQuery:
$("+p", elm);
This would select the p element that is a sibling of elm.
2. It would be useful to be able to check if an a given element
matches a selector in relation to a specified reference element
(:scope). For example, check if an event target is a sibling of a
specific element, and if the parent element has a specifc class name set.
e.g. Matches the selector: ".foo>:scope~input[type=text]"
This may be particularly useful for event delgation.
3. Obtain a collection of elements based on their relation to more
than one specified reference elements.
e.g.
Query to the document to obtain elements matching ":scope+span", where
:scope is intended to match any of the elements in a specific
collection. This would be simpler than iterating all of the nodes in
the collection, running the query on each of them and then merging the
results.
*Problems*
1. Need a way to allow the browser to parse implicitly scoped
selectors beginning with combinators and imply the presence of :scope
before each in the group.
2. Need to allow :scope to be used within the selector strings, and
specify one or more scope elements that will be matched by :scope.
This needs to be useable with all of the querySelector(),
querySelectorAll() and matchesSelector() methods, or others with
equivalent functionality.
3. Ideally, there would be an easy, reliable way for scripts to test
if the implementation supports scoped selectors (at least, implicitly
scoped selectors. Those using :scope could only be discovered by
capturing the SYNTAX_ERR exception) For legacy browsers that don't,
they can fall back to their own selector engines.
*Possible Solutions*
1. Define a Selector object that can be used to parse and store a
selector, and which can handle pre-parsing the selector and
specifying the scope elements upon creation. This selector object
can then be passed anywhere that accepts a selector string. (This is
basically part of the createSelector() and Selector interface
proposal from earlier).
2. Add parameters to the querySelector(), querySelectorAll() and
matchesSelector() methods for:
a. Indicating whether the selectors parameter should be processed
with an implied scope.
b. Specifying one or more reference elements that would match :scope.
3. Create new scoped versions of the existing methods that accept one
or more reference elements that would match the implied scope.
Add an optional parameter to the existing querySelector*() methods
that would Allow one or more reference elements to be specified to
match the explicit use of :scope in the selector.
Option 2 doesn't provide an easy way to detect browser support.
Option 3 creates an additional queryScopedSelector*() and
matchesScopedSelector() methods, but this could get quite verbose if
we also add equivalent NS methods to handle the namespace issue, to
both the scoped and non-scoped versions. This would create an
unreasonable number of different methods that would make understanding
the API quite complex. Option 1 is syntactically messy, and requires
the creation of a new object just to handle a scoped selector, even if
that selector is only used once.
I'm not sure which alternative would be best, and I'm kind of hoping
there's a 4th alternative I haven't thought of yet that can address
the use cases easliy enough.