W3C home > Mailing lists > Public > public-webapps@w3.org > July to September 2009

Re: [selectors-api] Scoped Selectors

From: Lachlan Hunt <lachlan.hunt@lachy.id.au>
Date: Sat, 26 Sep 2009 22:36:23 +0200
Message-ID: <4ABE7B47.5030807@lachy.id.au>
To: John Resig <jeresig@gmail.com>
Cc: public-webapps <public-webapps@w3.org>
John Resig wrote:
>> 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.
> I don't see the purpose of making a distinction between the root node used
> for the query and the node being used for the scope - they should be one and
> the same.
> // Doesn't make sense:
> document.querySelectorAll("div div", document.body)
> // Does make sense
> document.body.querySelectorAll("div div")

It does make sense.  It just means something slightly different from 
what you appear to be thinking.  In the above, the document.body element 
wouldn't have any effect because it's not a scoped selector and there is 
no :scope pseudo-class used.

> Also, I don't think it's been made clear in the discussion thus far but
> while cases like handling ">  div" are nice - we're mostly concerned about
> cases like "div div" (that escape outside the original root of the query).

The problems with handling ">div", "+div" and "div div" are exactly the 
same issue.  The only difference is that the first 2 begin with a child 
combinator and sibling combinator, respectively, and the latter has an 
implied descendant combinator.  They can all be addressed with the same 

> Given this DOM:
> <body>
>    <div id="one">
>      <div id="two"></div>
>    </div>
> </body>
> // All of these should return nothing
> document.getElementById("one").querySelelctor("div div")
> document.getElementById("one").querySelelctor("body div")
> document.getElementById("one").querySelelctor("div #two")

The real benefit of the API as I first designed it, is that it elegantly 
provides ways to address nearly every use case raised and more, using 
the simple, yet extremely powerful concept of contextual reference 
elements, which is in fact not so different from the pattern used in 
JQuery: $("selector", context);.

> There doesn't need to be scoping for matchesSelector. matchesSelector
> implies that it it's starting from the specified node and doing a match.
> Additionally the use case of .matchesSelector(">  div") doesn't really exist.

The use case doesn't really exist when the context node is considered to 
be the contextual reference element.  But allowing reference nodes to be 
specified, it allows the query to check where it is in relation to 
another element.  So, for example you could more easily check if the 
parent of element elm is one of the nodes in the collection elms.

var elms = [div1, div2, div3];
e.g. elm.matchesSelector(":scope>*", elms);

Returns true if the elm.parent == div1, elm.parent == div2 or elm.parent 
== div3.

> With that in mind, option #3 looks the best to me. It's lame that the API
> will be longer but we'll be able to use basic object detection to see if it
> exists. Unfortunately the proper scoping wasn't done the first time the
> Selectors API was implemented so we kind of have to play the hand we've been
> dealt.
> Thus there would be two new methods:
> queryScopedSelectorAll
> queryScopedSelector

I really didn't want to introduce new methods for this if it could be 
avoided.  I realise one problem with the first draft of the API I posted 
yesterday was that is was too cumbersome for scripts to create and use 
scoped selectors, rather than normal selectors.  That draft required 
scripts to do the following:

var selector = document.createSelector("+p", true);
document.querySelector(selector, elm);

I have come up with a significantly simpler, alternative solution that 
not only abolishes the createSelector() method and the 
SelectorExpression interfaces, but also avoids introducing additional 
methods like queryScopedSelector(), or extra parameters.

The draft now defines the concept of a *selector string* and a *scoped 
selector string*.  The selector string is just an ordinary selector, as 
supported by current implementations.

A scoped selector string is a string that begins with an exclamation 
point followed by a the remainder of the selector.  The purpose of the 
exclamation point is to clearly identify the string as a scoped selector 
that requries an extra pre-processing step to turn it into a valid group 
of selectors.

There are also slightly different requirements for the processing 
Element.querySelectorAll() when the selector argument is a scoped 
selector string.  This allows for the sibling combinator cases to work.

e.g. The selector ">em, >strong" supported by JS libraries can simply be 
prefixed with a "!", like "!>em, >strong" and the implementation will be 
able to process it to become ":scope>em, :scope>strong".  Of course, it 
will also work with the other combinators.

This allows JS libraries to trivially prepend "!" to the selector before 
passing it to the API, rather than requiring any complicated 
pre-processing.  In current browser implementations, the "!" will 
trigger a syntax error and allow JS libraries to fallback to their 
custom processing.

The following examples illustrate how this API can be used to address 
the use cases.  Assume the variable elm refers to a single element and 
and elms refers to  a collection of elements, such as an array or nodelist.

Use Case: Select elements based on their relationship to one specific 
element.  i.e. Given an Element elm, be able to select child, grandchild 
or sibling elements.

In JQuery:

   $(">em, >strong", elm);
   $("div div", elm);
   $("+p", elm)

or the equivalents using $(elm).find("...");

To do either of these in Selectors API, use:

   // Equivalent to ":scope>em, :scope>strong". Use either:
   document.querySelectorAll("!>em, >strong", elm);
   elm.querySelectorAll("!>em, >strong");

   // Equivalent to ":scope div div". Use either:
   document.querySelectorAll("!div div", elm);
   elm.querySelectorAll("!div div");

   // Equivalent to ":scope+p". Use either:
   document.querySelectorAll("!+p", elm);

Use Case: Select elements based on their relationship to elements in a 
collection.  i.e. Given a collection of elements elms, be able to select 
child or grandchild elements, or sibling elements, of each element in 
that collection.

In JQuery:
   $(">em, >strong", elms);
   $("div div", elms);
   $("+p", elms)

This effectively works the same as the previous use case.  Just create 
the selectors in the same way, and pass the elms collection to 
querySelectorAll(), as in:

   document.querySelectorAll("!+p", elms);

Use Case: Given a collection of elements, find a subset that meets 
specific conditions.  e.g. Given an array of elements elms, select only 
those that have a specific class name.

For instance, a document contains several input elements, like:

<input type="text" name="addr-line1" class="required">
<input type="text" name="addr-line2">
<input type="text" name="addr-suburb" class="required">
<input type="text" name="addr-country">

In JQuery:
var elms = $("input");
// do something with elms.
// Now select only the required controls for additional processing:
var filterdList = elms.filter(".required");

document.querySelectorAll(".required:scope", elms)

(There's currently no shorthand syntax that can be used by JS libraries 
to have this generated automatically.  It might be worth defining one 
analogous to scoped selector strings.)

The one limitation of the above is that if the elms collection contained 
nodes from both within the document and disconnected elements, only 
matching elements within the document would be selected.  (The results 
would be similar, if this was done on a DocumentFragment or disconnected 
Element node).  So it's not quite identical to running the query on each 
individual element and merging the results.  But it's not clear whether 
or not such a use case is significant.  It seems more natural to want to 
restrict the results to those within the same tree.

See the current editors draft for more details.

Lachlan Hunt - Opera Software
Received on Saturday, 26 September 2009 20:37:08 GMT

This archive was generated by hypermail 2.3.1 : Tuesday, 26 March 2013 18:49:33 GMT