the ultimate getElementsBy*: Object.getElementsWhere()
Update: for compatibility, Object.getElementsWhere(”condition”) is being changed to getElementsWhere(node, condition) where node is the node to search in.
I’ve looked a lot lately at the getElementsByClass() function and other getElementsBy functions, but they all seemed rather inadequate to me.
I rewrote the function about three times before I figured out what to do. What I really wanted was a function that could perform a custom comparison on a property of an html element. What I finally came up with was a function that could take a comparison as a string and execute it on each element within the object you run it for. For example:
The following code will get all elements inside of the mydiv element that have a width less than 100:
getElementsWhere(mydiv "width < 100" );
The combinations you can choose are endless. Use any comparison operator you want! You can also run functions that belong to the object on the value before it is compared. For example, if you need to compare the css width of a node, do this:
For numeric style attributes, do something like this:
getElementsWhere( document, "style.width.replace('px','') > 100" );
The idea that sparked all this was my desire for a getElementsById(regex) function. Hence, you can also perform regular expressions on the value of the objects, like this:
getElementsWhere(document "id.match( /^image_/ )");
In fact, you can perform any of the functions that apply to the variable type of the element’s attribute you specify.
Here’s the code:
function getElementsWhere( /*HTMLElement:*/node, /*String:*/comparison) {
var found = [];
var all = node.getElementsByTagName(”*”);
for( var i = 0; i < all.length; i++ ) {
if( eval( “all[i].” + comparison ) ) {
found.push( all[i] );
}
}
return found;
};
And here’s how it works…
First, create two arrays. The found array starts out empty and will contain each element who’s attribute matches the comparison. The all array contains every html element on the page.
var found = []; var all = node.getElementsByTagName(”*”);
Then we loop through each array element in the ‘all’ array:
for( var i = 0; i < all.length; i++ ) {
Below, we perform the comparison. It’s really quite simple. Since the comparison was passed in as a string, we simply use eval to run it as part of the code. So if comparison is “width == 100″, then the code below really is equivalent to: if( all[i].width == 100 )
if( eval( "all[i].” + comparison ) ) {
If the comparison evaluated to true, push the element onto the found array:
found.push( all[i] );
Then return the found array to the caller:
return found;
Well, I hope you like it!
Comments
Comment from Peter Goodman
Time: January 7, 2008, 10:14 pm
Although a nifty function, it would be incredibly slow. First, it iterates over every DOM node. That can be problematic if in some browsers the DOM is not properly updated when it is manipulated by third-party javascript. Second, for each node it performs an eval–another very slow process. I suggest sticking to the fast implementations of xpath/cssquery as much of the ‘niftiness’ of this function is already captured by those methods.
Comment from ColdShine
Time: January 29, 2008, 7:32 am
I assume you didn’t try this on 1000+ tags web pages, did you? This is going to be insanely slow. You really should learn XPath.
Some hints to improve this, anyway: you could allow to specify the desired attribute and the match RegExp, and please, please!, stop using eval(), it’s extremely slow.
Comment from Gabe
Time: May 28, 2008, 3:04 am
Why not pass a function as the comparison instead of a string… that removes the eval.
eg: getElementsWhere(div, function(el) { return el.style.width.replace(’px’,”) > 100; });
Comment from maxgandalf
Time: July 7, 2008, 3:49 am
http://pitfalls.wordpress.com/2008/07/07/querying-it-jquery-way-getelementsbyclass/
Comment from Ezrad Lionel
Time: January 3, 2008, 2:17 pm
very impressive man, I’m working on a templating system and I’m always looking for useful functions like this! I ahvent had use for it yet, but now you’ve got me thinking!