The Ultimate getElementsByClassName

New version released, with major updates Tuesday, May 27th, 2008.

NOTE: The code below is outdated.

A completely rewritten version of getElementsByClassName has been released, taking into account all new available web browser features and possibilities, to offer you the best performing and most flexible implementation. Read about the new version or go and download it straight away.

 

Updated on Tuesday, November 8th, 2005.

Updated again, Tuesday, November 8th, 2005
Switched from using word boundaries to checking for spaces and/or start/end of string. Also, see my comment about this.

Once again updated Tuesday, November 8th, 2005
I forgot a break; statement in the advanced version. Nothing that would break it, but bad performance-wise.

Updated Tuesday, November 8th, 2005
This is getting ridicilous now. Changed name of the linked JavaScript file to getElementsByClassName.js.

Updated July 14th 2006

As Wilfred and Sander correctly pointed out, there is a way to make the script more efficient in IE 5.x when using the wildcard character to call it. The code in this post and the downloadable file have been updated accordingly.

Updated May 11th 2007

It’s been a while since I took a look at this, and with speed comparisons and all, I have revised so it should be just a tad faster. The new script is just below and added to the downloadable JavaScript file as well.

One of the major differences is that tag name and containing element are optional, and if not supplied, will default to * respectively document. This means that the order of the parameters are also changed, so className to look for is the first one, followed by tag and then elm. However, for best performance, I recommend sending in all three parameters as closely specified as possible.

Updated May 30th 2007

The revised version in the file was ok, but the published version in the post just below missed a couple of \. This has now been adressed.

Revised version May 11th 2007


function getElementsByClassName(className, tag, elm){
	var testClass = new RegExp("(^|\\s)" + className + "(\\s|$)");
	var tag = tag || "*";
	var elm = elm || document;
	var elements = (tag == "*" && elm.all)? elm.all : elm.getElementsByTagName(tag);
	var returnElements = [];
	var current;
	var length = elements.length;
	for(var i=0; i<length; i++){
		current = elements[i];
		if(testClass.test(current.className)){
			returnElements.push(current);
		}	
	}
	return returnElements;
}

 

Good JavaScript usage on the Internet is based on making it unobtrusive, meaning that web pages aren’t dependant on it to work and that that the HTML code shouldn’t be riddled with inline event handlers and JavaScripts. Using javascript: is forbidden, stop that!

What you do is to apply the events to desired elements from an external JavaScript file, normally performed when the page has loaded. For instance, if you want to apply a certain event to some a elements, you loop through the a elements in the page and then apply the events accordingly, e.g. if the element has a certain class name.

Last week, I felt the need to have a script that accessed all elements in a web page with a certain class name and returned them as an array to work with. I wrote my function, but ran into problems when it came to distinguishing class names that contained a hyphen (-). Then I remembered that Jonathan Snook wrote a function a while ago, so I went to his web page to see if I’d missed something. Interestingly enough, it was very similar to mine, and when I tested his it didn’t work either.

So, since Jonathan and I talk on and off, I contacted him about this over MSN. Jonathan, being the cool and helpful guy that he is, immediately took the time to discuss this with me. I coded away, told him what happened as I went along, and we brainstormed about how we could solve it. After some work, we came up with something that seems to work really fine, supporting class names with hyphens and multiple class names on the same element. It is actually very similar to Jonathan’s original function but with an escape fix and some performance add-ons.

Let me present The Ultimate getElementsByClassName:


/*
	Written by Jonathan Snook, http://www.snook.ca/jonathan
	Add-ons by Robert Nyman, http://www.robertnyman.com
*/

function getElementsByClassName(oElm, strTagName, strClassName){
	var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
	var arrReturnElements = new Array();
	strClassName = strClassName.replace(/\-/g, "\\-");
	var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
	var oElement;
	for(var i=0; i<arrElements.length; i++){
		oElement = arrElements[i];		
		if(oRegExp.test(oElement.className)){
			arrReturnElements.push(oElement);
		}	
	}
	return (arrReturnElements)
}

Some ways to call it

To get all a elements in the document with a “info-links” class.
getElementsByClassName(document, "a", "info-links");
To get all div elements within the element named “container”, with a “col” class.
getElementsByClassName(document.getElementById("container"), "div", "col");
To get all elements within in the document with a “click-me” class.
getElementsByClassName(document, "*", "click-me");

The first line in the function is to cover-up for a flaw in IE 5 where one can’t use the wildcard selector * to get all elements. The rest is basically about setting up a regular expression with the class name we’re looking for, where we escape hyphens and then match that to the class names of the elements where we’re looking for it.

Please try it out. Our hope is that it will help you develop unobtrusive JavaScripts and that it will make it easier for you to maintain your web sites. Any problems with the function, please let us know.

Go crazy now! :-)

 

Updateded! Since Anne asked for support to look for multiple class names in the same call, I’ve revised the function. The above function is intact and supports multiple class names if they’re entered in that order on the element. If they’re not, you should use the below function. Kudos to Curtis for inspiration.


function getElementsByClassName(oElm, strTagName, oClassNames){
	var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
	var arrReturnElements = new Array();
	var arrRegExpClassNames = new Array();
	if(typeof oClassNames == "object"){
		for(var i=0; i<oClassNames.length; i++){
			arrRegExpClassNames.push(new RegExp("(^|\\s)" + oClassNames[i].replace(/\-/g, "\\-") + "(\\s|$)"));
		}
	}
	else{
		arrRegExpClassNames.push(new RegExp("(^|\\s)" + oClassNames.replace(/\-/g, "\\-") + "(\\s|$)"));
	}
	var oElement;
	var bMatchesAll;
	for(var j=0; j<arrElements.length; j++){
		oElement = arrElements[j];
		bMatchesAll = true;
		for(var k=0; k<arrRegExpClassNames.length; k++){
			if(!arrRegExpClassNames[k].test(oElement.className)){
				bMatchesAll = false;
				break;						
			}
		}
		if(bMatchesAll){
			arrReturnElements.push(oElement);
		}
	}
	return (arrReturnElements)
}

Ways of calling the function now are:

To get all a elements in the document with a “info-links” class.
getElementsByClassName(document, "a", "info-links");
To get all div elements within the element named “container”, with a “col” and a “left” class.
getElementsByClassName(document.getElementById("container"), "div", ["col", "left"]);

Note that you can still use a string when only looking for a single class name, but an array when looking for multiple class names.

Also, if this is to work in IE 5.0, you need to include this add-on to get support for the push method on the Array object:


if(typeof Array.prototype.push != "function"){
	Array.prototype.push = ArrayPush;
	function ArrayPush(value){
		this[this.length] = value;
	}
}

 

If you don’t like to read the code here, you can download the JavaScript file.

Posted in JavaScript |

Leave a Reply

Your email address will not be published. Required fields are marked *