views:

82

answers:

5

Hi guys. Is there any way to test if a selector would match a given DOM Element? Preferably, without the use of an external library like Sizzle. This is for a library and I would like to minimize the amount of third party plugins required for the "core" library. If it ends up requiring Sizzle I'll just add that as a plugin to the library for those who want the feature it would enable.

For example, I would be able to do something like:

var element = <input name="el" />

matches("input[name=el]", element) == true

EDIT: After thinking about it more, I came up with a solution, this technically works, but it doesn't seem optimal in terms of efficiency:

function matchesSelector(selector, element) { 
    var nodeList = document.querySelectorAll(selector); 
    for ( var e in nodeList ) {
        return nodeList[e] === element; 
    }
    return false; 
}

Basically the function queries the entire document with the given selector, and then it iterates over the nodeList. If the given element is in the nodeList, then it returns true, and if it isn't it will return false.

If anyone can come up with a more efficient answer I would gladly mark their response as the answer.

EDIT: Flavius Stef pointed me towards a browser specific solution for Firefox 3.6+, mozMatchesSelector. I also found the equivalent for Chrome (version compatibility unknown, and it may or may not work on Safari or other webkit browsers): webkitMatchesSelector, which is basically the same as the Firefox implementation. I have not found any native implementation for the IE browsers yet.

For the above example, the usage would be:

element.(moz|webkit)MatchesSelector("input[name=el]")

It seems the W3C has also addressed this in the Selectors API Level 2 (still a draft at this moment) specification. matchesSelector will be a method on DOM Elements once approved.

W3C Usage: element.matchesSelector(selector)

Since that specification is still a draft and there is a lag time before popular browsers implement the methods once it becomes the standard, it may be a while until this actually usable. Good news is, if you use any of the popular frameworks, chances are they probably implement this functionality for you without having to worry about cross browser compatibility. Although that doesn't help those of us who can't include third party libraries.

Frameworks or libraries that implement this functionality:

http://www.prototypejs.org/api/element/match

http://developer.yahoo.com/yui/docs/YAHOO.util.Selector.html

http://docs.jquery.com/Traversing/is

http://extjs.com/deploy/dev/docs/output/Ext.DomQuery.html#Ext.DomQuery-methods

http://base2.googlecode.com/svn/doc/base2.html#/doc/!base2.DOM.Element.matchesSelector

http://wiki.github.com/jeresig/sizzle/

A: 

Just use an id for your element? HTML-IDs have to be unique…

sebastian
It's for a library, so I don't know what specifically they will be using it for. Matching only by ID would be virtually useless in the context of the library (a form validation library).
CD Sanchez
+1  A: 

Modern browsers can do it with the document.querySelectorAll function.

http://www.w3.org/TR/selectors-api/

no
+3  A: 

The W3C selectors API (http://www.w3.org/TR/selectors-api/) specifies document.querySelectorAll(). This is not supported on all browsers, so you'd have to google the ones that do support it: http://www.google.com/search?q=browsers+implementing+selector+api

Flavius Stef
Thanks for the reply. I'm already using querySelectorAll, but it doesn't let you choose what elements it will test on. It selects from the whole document.
CD Sanchez
The spec contains this example, so you probably could use it like element.querySelectorAll(): var div = document.getElementById("bar"); var p = div.querySelector("body p");
Flavius Stef
@Flavius Stef: Unfortunately that only selects from the element's descendants/children, not the element itself. So far the only thing I can come up with is run the selector on the whole document, and iterate through the results to see if my element is in the NodeList. Sounds awfully inefficient though.
CD Sanchez
A: 

In the absence of xMatchesSelector, I'm thinking to try adding a style with the requested selector to a styleSheet object, along with some arbitrary rule and value that is not likely to be already in use. Then check the computed/currentStyle of the element to see if it has inherited the added CSS rule. Something like this for IE:

function ieMatchesSelector(selector, element) {
  var styleSheet = document.styleSheets[document.styleSheets.length-1];

  //arbitrary value, probably should first check 
  //on the off chance that it is already in use
  var expected = 91929;

  styleSheet.addRule(selector, 'z-index: '+expected+' !important;', -1);

  var result = element.currentStyle.zIndex == expected;

  styleSheet.removeRule(styleSheet.rules.length-1);

  return result;
}

There's probably a handbag full of gotcha's with this method. Probably best to find some obscure proprietary CSS rule that is less likely to have a visual effect than z-index, but since it is removed almost immediately after it is set, a brief flicker should be the only side effect if that. Also a more obscure rule will be less likely to be overridden by a more specific selector, style attribute rules, or other !important rules (if IE even supports that). Anyway, worth a try at least.

MooGoo
A: 

A cleaner and more performant solution I can think of is to search only the element's parentNode, instead of the entire document.

It will work as long as your element isn't the document itself, as its parentNode will return null, but that's only a matter of adding an extra if.

var nodeList = element.parentNode.querySelectorAll(selector);
Anurag