getElementsMatching()
In response to a previous post, I was challenged to come up with a way to determine that elements had certain similarities without using CSS queries. Since this may benefit others, I decided to spin off the long comment thread into another post. I’ve called my solution getElementsMatching():
function getElementsMatching(tagName, matcher) {
var elements = document.getElementsByTagName(tagName);
var result = [];
for (var i=0, len=elements.length; i < len; i++){
if (matcher(elements[i])){
result.push(elements[i]);
}
}
return result;
}
This function works be accepting a tag name to look for as well as a function that determines if an element passes the test to be included in the resultset. For instance, to return all of the div elements with a class of “special”, you could do the following:
var results = getElementsMatching("div", function (element){
return (element.className == "special");
});
The second argument should be a function that accepts an element as an argument and returns true if the element should be part of the resultset or false if not.
Of course, this function could be augmented to accept a reference node from which the initial search is conducted (instead of using document as the base), but I think you get the point. Let the flaming begin!
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of Yahoo!, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




9 Comments
This reminds me of YUI’s "getElementsBy" - which is just a boolean method where you pass in the condition to match, and it returns the elements that passed your test.
http://developer.yahoo.com/yui/docs/YAHOO.util.Dom.html#getElementsBy
Dustin Diaz on February 22nd, 2007 at 5:24 pm
Hey Dustin, good to hear from you. You’re right, again. Every time I turn around, I find out something new in the YUI library. This is where my ignorance to all of the YUI features really comes in. I have to admit I’ve not gone through the API documentation and tend to reuse the same methods over and over again. Maybe I should sign up for a class or something.
Nicholas C. Zakas on February 22nd, 2007 at 6:08 pm
I understand what you are saying and this is by no means flaming, however this is a very simplistic example that does not extend itself well at all. Using this method, you would have to rebuild entire functions rather than reusing a single one. In the end, with multiple calls and any complexity, this method does not extend itself well.
As stated above, for simple classname only validation, there is no difference between the proposed method and those included in something like Prototype (except for the lack of usability and scaling). Note the Prototype getElementsByClassName
[Edited by admin]
Again, as I said, if you are just looking for classname based querying it would make no sense to carry a large library, but if you are using a library such as Prototype, YUI, Mootools, jQuery etc. OR you have the need for cross-browser, complex CSS (and even xpath in some cases) querying then it makes perfect sense. The getElementsByClassName code obviously has capabilities to look for cases of multiple classes, parent etc. that could be added to your method. However, how wasteful would it be to continually call your method from a bits standpoint? For simple class querying this would need to be rewritten for that purpose alone… When your needs are more complex then it is completely unrealistic to use this method (which is what I had asked for).
Continued..
benb on February 23rd, 2007 at 12:03 pm
Given the proposed method, I would have to craft a potentially complex function for every call that is made, rather than simply passing in values. To stay with Prototype as an example, consider the following taken from examples:
$$(’#contents a[rel]‘)
// -> all links inside the element of ID "contents" with a rel attrib
Or with DomQuery doing something like this:
getEls("tr:nth-child(odd)").addClass(’odd’);
getEls("#test-data span:last-child");
getEls("#test-data span.hl-default:not(:first-child)");
Or consider the following using Dean Edwards cssQuery:
cssQuery("p>a:first-child+input[type=text]~span");
In the proposed method, rather than simply calling with what amounts to a query, you would have to repeatedly blow out long functions to include in the call and do so every time OR you would have to start building callable, reusable methods like those included in these libraries.
Depending on what you are looking for functionality wise, some of these solutions are not even that large - especially when packed. cssQuery can be anywhere from 1.5k up to 6k for the packed version.
continued..
benb on February 23rd, 2007 at 12:05 pm
So I do not see that this solution is viable at all as a replacement for these, and is not particularly crafted in a way that would make it a great solution for simple use due to reuse issues. I had asked for a solution that is in line with the examples above:
"a desire to find element x under container v whose attributes are currently w,y,z,"
Finally, as an owner of your books i.e. respect for your view, I hope this is being taken as it is intended, and that is of discussion rather than a flame war.
Some References:
http://www.jackslocum.com/blog/2007/01/11/domquery-css-selector-basic-xpath-implementation-with-benchmarks/
docs.jquery.com/DOM/Traversing/Selectors#CSS_Selectors
dean.edwards.name/my/cssQuery/
http://www.prototypejs.org/api/utility/dollar-dollar
benb on February 23rd, 2007 at 12:10 pm
i get you. been using cssquery, jquery, etc. find your solution to be much more to the point. if you want whole lib-thing, cool. but, hey why such a big axe to kill a small chicken?
anyway, biggest diff i nmy experience is that className usuall holds more than one item. so my getElementsByClassName(classString, starting Element[optional]) makes sure not to do a direct compare, but a ‘contains’ check.
finally, i also use a getElmentsByAttribute(attributeName, attributeValue [optional], startingElement[optional]) that really solves most all my problems.
mamund on February 23rd, 2007 at 4:25 pm
@Ben - First, I had to edit your first comment to remove the Prototype code. Please be careful not to post copyrighted material (which the Prototype code is).
My solution is very extendable. You could create a getElementsByClassName() that wraps getElementsMatching() and provides the appopriate function, allowing for easy use and reuse.
And I don’t doubt that there are cases where I can’t easily replicate the things that a CSS query does…but again, my point was that I’ve never really needed something that did all of what a CSS query engine could do. getElementsMatching() was just a response to your assertion that there wasn’t any easy way to get a bunch of elements by their class name.
Nicholas C. Zakas on February 25th, 2007 at 2:55 pm
@Nicholas
Actually I said that it was not easy to replicate the get x where y,z are true which was precisely what the CSS query engines are for.
I do not argue your example is not extensible, it truly is, however as I said it is not feasible to extend it out without ending up with something that is equal to any number of the libraries.
At the end of the day, what I am asserting is that counter to your first statement, there are times when a) the power of CSS querying may be needed even if you have not needed that power and b) in those cases any number of the preexisting libraries or code bases are effective ways to go.
Re: the copyright issue - sorry about that, I had meant to add the actual copyright line on there with the attribution where it came from.
Ben
benb on February 25th, 2007 at 8:18 pm
So I’m not the sharpest tool in the shed.
What is a common usage for needing to use a CSS Query?
Why would I need to, say, find all elements who’s style has them having a 1px solid black border? (or some other real world example?)
Sean on March 1st, 2007 at 3:37 pm