Hello Everyone -

I just wanted to quickly pull together some of my thoughts concerning 
querySelectorAll. I've been asked by a number of people to provide my feedback 
here. Please forgive me if I've missed some previous discussions on the subject 
matter.

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

This is due to the fact that element-rooted queries are handled by "finding all 
the elements that match the given selector -- rooted in the document -- then 
filtering by the ones that have the specified element as an ancestor." This is 
completely unacceptable. Not only is it not intuitive (finding elements that 
don't match the correct expression) but it goes against what every single 
JavaScript library provides. If there behavior were persisted then there would 
be serious ramifications for the usefulness of this function in the wild.

I asked some of the other library developers what their thoughts were and they 
agreed with my conclusion.

  Andrew Dupont (creator of Prototype's selector engine): "My issue with this 
is that it violates principle of least surprise and bears no resemblance to the 
APIs in the wild."

  Alex Russell (creator of Dojo's selector engine): "This is a spec bug."

* 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");
  
  // DOM
  document.getElementById("foo").querySelectorAll(":root > span")

This is something that a library can easily detect and inject.

* Error-handling

I'm perfectly fine with the proposed try/catch solution however there must be a 
way of easily determining what the invalid portion of the selector was. 
Currently the following occurs in Safari:

  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"
  }

The resulting solution in most libraries would then look something like (of 
course different caching could take place, as well):

  try {
    results = document.querySelectorAll(selector);
  } catch(e) {
    results = filterQuery(
      document.querySelectorAll( selector.slice(0, e.position) ),
      selector.slice(e.position)
    );
  }

There will be some form of a performance hit here but I think, if done 
correctly, it'll be negligible (especially in comparison to the benefits that 
are being received).

I hope these proposed changes work well for the members of this group as they 
will greatly benefit general web developers - and especially library developers.

John Resig
jQuery JavaScript Library, Mozilla Corporation


Reply via email to