getElementsBySelector()
Inspired by Andy, I decided to have a crack at something I’ve been thinking about trying for a long time. document.getElementsBySelector is a javascript function which takes a standard CSS style selector and returns an array of elements objects from the document that match that selector. For example:
document.getElementsBySelector('div#main p a.external')
This will return an array containing all of the links that have ’external’ in their class attribute and are contained inside a paragraph which is itself contained inside a div with its id attribute set to ’main’.
So far I’ve only tested it on Phoenix but it seems to work as intended for the small number of test cases I’ve tried. If you spot any bugs please let me know. I’m about to fire up a Windows PC and see how much it breaks in IE...
Update: I’ve put together a demo page showing the function in action. It works fine in IE 6.
gilmae - 25th March 2003 22:10 - #
Andrew Hayward - 26th March 2003 01:19 - #
document.getElementsByTagNamedoesn't exist.Simon Willison - 26th March 2003 01:21 - #
gilmae - 26th March 2003 06:57 - #
Simon Willison - 26th March 2003 08:56 - #
Daniel Glazman - 26th March 2003 09:53 - #
Ian Hickson - 26th March 2003 19:35 - #
Jeremy Dunck - 26th March 2003 21:23 - #
Eliminating whitespace between selectors results in failure. The following examples use your demo.
This doesn't work:
javascript:alert(document.getElementsBySelector("b ody>#foo"));But this does:
javascript:alert(document.getElementsBySelector("b ody > #foo"));And this selector text works (moz only)
javascript:void(document.styleSheets[0].insertRule ("body>#foo {background-color: oldlace;}", 3));adding multiple class selectors (e.g.
".foo.bar"-- match elements with classfoowith classbar) would be yet another challenge.I'd rather use getElementsBySelector instead of document.getElementsBySelector (no expando).
You'd have to enhance this:
// Split selector in to tokensvar tokens = selector.split(' ');Removing expando would be the first thing I would do. There's lots of other improvements that could be made, but perhaps those improvements would affect the "geek factor" more than the usefulness.
For instance, you could use
returnedCollection = [];and thenreturn returnedCollectionat the end. This would give you the ability to eliminate all of thereturn new Array()statements in the middle of the function.It all depends how much you need to use it, I guess. I've written a number of much simpler functions, though I've never needed anything this comprehensive. In case I do, it's good to know it's already done.
Garrett Smith - 5th August 2003 22:29 - #
Eliminating whitespace between selectors results in failure. The following examples use your demo.
This doesn't work:
javascript:alert(document.getElementsBySelector("b ody>#foo"));But this does:
javascript:alert(document.getElementsBySelector("b ody > #foo"));And this selector text works (moz only)
javascript:void(document.styleSheets[0].insertRule ("body>#foo {background-color: oldlace;}", 3));adding multiple class selectors (e.g.
".foo.bar"-- match elements with classfoowith classbar) would be yet another challenge.I'd rather use getElementsBySelector instead of document.getElementsBySelector (no expando).
You'd have to enhance this:
// Split selector in to tokensvar tokens = selector.split(' ');Removing expando would be the first thing I would do. There's lots of other improvements that could be made, but perhaps those improvements would affect the "geek factor" more than the usefulness.
For instance, you could use
returnedCollection = [];and thenreturn returnedCollectionat the end. This would give you the ability to eliminate all of thereturn new Array()statements in the middle of the function.It all depends how much you need to use it, I guess. I've written a number of much simpler functions, though I've never needed anything this comprehensive. In case I do, it's good to know it's already done.
Garrett Smith - 5th August 2003 22:29 - #
Már Örlygsson - 3rd December 2003 13:25 - #
Már Örlygsson - 5th December 2003 10:21 - #
Graste - 27th May 2004 15:25 - #
Sébastien Cramatte - 17th October 2004 21:51 - #
Kingsley Joseph - 11th July 2005 22:55 - #
matt - 12th August 2005 12:17 - #
Kramer - 7th December 2005 23:49 - #
Great work on the script, extremely handy!
I've come across one small bug while using it though. If you use a selector that references an invalid ID paired with a tag name (e.g.
body#profile, when#profiledoesn't exist), a script error occurs; yet using#profilealone works fine.The source of the error is line 89:
if (tagName && element.nodeName.toLowerCase() != tagName) {It's finding the tagName (
body), butelementis undefined due to the ID being invalid. A slight change to the above if statement fixes the problem.if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) {Maybe somebody will find that useful. Thanks again for the great code!
James Gregory - 24th March 2006 11:26 - #
I've been toying with this hot little number at work the past few days, and I've found a little bug.
When you use hyphens in a class or ID, getElementsBySelector() ignores the hypen and everything after it.
HTML:<div id="container"> <ul class="nav"> <li class="nav-home">Home</li> </ul> </div>Javascript:var found = document.getElementsBySelector('#container .nav');What I expect is thatfoundwill contain the UL element, which it does. But it also countains the LI element, even though it has a different (and valid per W3C spec) class that doesn't match the selector I asked for.Brett - 5th April 2006 19:33 - #
Frankie - 18th May 2006 14:55 - #
sdfsdfsdfsdf - 20th May 2006 06:14 - #
Nagu - 26th May 2006 17:42 - #
VIMAL - 17th June 2006 08:40 - #
sagel - 30th June 2006 18:50 - #
I came across the same problem mentioned above (.nav-home matching .nav). The problem is that the regular expression uses word boundries to test the class name (and '-' is a word boundary). The fix is very simple, just replace
new RegExp('\\b'+className+'\\b')with
new RegExp('(\\s|^)'+className+'(\\s|$)')and
new RegExp('\\b'+attrValue+'\\b')with
new RegExp('(\\s|^)'+attrValue+'(\\s|$)')I have also changed the class match loop so that the regular expression is created before the loop and reused for each iteration. I haven't compared performance differences, but it should atleast create less garbage that needs to be collected. Also, it seems that the tokens should be split by a whitespace regex (/\s+/) not a single space as in the code (' '). I may be wrong, but I think the spec allows for any amount of whitespace between 'tokens'.
Jeremy - 14th July 2006 14:35 - #