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 solution.

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);
  elm.querySelectorAll("!+p");


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.
http://dev.w3.org/2006/webapi/selectors-api2/

--
Lachlan Hunt - Opera Software
http://lachy.id.au/
http://www.opera.com/

Reply via email to