Your Favourite getElementsByClassName

There's a variety of scripts out there for getElementsByClassName. Oddly though, the first 20 or so results from Google are confusing or completely inaccurate. So, what's your favourite getElementsByClassName script?

Here's something I threw together based on an unusable script I just saw on Digital Media Minute.

function getElementsByClassName(node, classname)
{
    var a = [];
    var re = new RegExp('\\b' + classname + '\\b');
    var els = node.getElementsByTagName("*");
    for(var i=0,j=els.length; i<j; i++)
        if(re.test(els[i].className))a.push(els[i]);
    return a;
}

Note: Be sure to wrap your code examples in like so: <pre><code> </code></pre>. It'll format better that way.

UPDATE:As mentioned by Cameron, the regular expression I use above may not catch every valid class name. More specifically, class names with hyphens in them causing false positives. Using Cameron's technique of '(^| )'+classname+'( |$)' should prove more reliable.

For those unsure of what it all means, in regular expression speak, \b is used to indicate word boundaries. The problem being that a search for \bhelp\b would also find help-me which isn't what we want. The ^ indicates beginning of the string, | is an OR statement and the space is, well, a space. Similar thing on the other end except the $ means match at the end of a string. Therefore, in English, find the class name if it's the first or last part of the string or if there is a space on either side of the class name (for those of you who actually use more than one class on an element).

Published May 27, 2005 · Updated September 17, 2005
Categorized as JavaScript
Short URL: http://snook.ca/s/370

Conversation

24 Comments · RSS feed
Randy Peterman said on May 28, 2005

You should check out Simon Willison's slick JavaScript function that grabs any CSS selector, including, I believe, class names:
getElementsBySelector

Matthew Pennell said on May 28, 2005

I used the one from here for CSS Reboot:

function getElementsByClassName(needle) {
	var my_array = document.getElementsByTagName("*");
	var retvalue = new Array();
	var i;
	var j;
	for (i=0,j=0;i<my_array.length;i++) {
		var c = " " + my_array[i].className + " ";
		if (c.indexOf(" " + needle + " ") != -1) retvalue[j++] = my_array[i];
	}
	return retvalue;
}
Fred said on May 29, 2005

Both seems great, but both with the same change, because of IE5
first one

function getElementsByClassName(classname)
{
    var a = [];
    var re = new RegExp('\b' + classname + '\b');
    var els = document.all?document.all:document.getElementsByTagName("*");
    for(var i=0,j=els.length; i<j; i++)
        if(re.test(els[i].className))a.push(els[i]);
    return a;
}

second one

function getClass(xx){
var rl=new Array();
var ael=document.all?document.all:document.getElementsByTagName('*')
for(i=0,j=0;i<ael.length;i++){
if((ael[i].className==xx)){
rl[j]=ael[i];
j++;}}
return rl;}

The first one seems also great, I am using already the second with together this function

function classRepl(whatReplace,withWhatReplace){
for(var i=0;i<getClass(whatReplace).length;i++){
getClass(whatReplace)[i].onclick=function(){
this.className=this.className==withWhatReplace?whatReplace:withWhatReplace;
}}}

It realy very universal a usefull function

Michel Bertier said on May 30, 2005

I'm happily using
http://neo.dzygn.com/archive/2003/10/attributecrawler

flevour said on May 30, 2005

Are all these methods certified working in all the recent browsers?

Mats said on May 30, 2005

Why use regexp?


function getElementsByClassName(className) {
  var a = [];
  var els = document.getElementsByTagName("*");
  for ( var i = 0, j = els.length ; i < j ; i++ ) {
    if (els.item(i).className.indexOf(className) != -1)
      a.push(els.item(i));
  }
  return a;
}
Jonathan Snook said on May 30, 2005

Mats: the regex helps match against word boundaries. The problem with your function is that a search for "mydiv" would return elements "mydiv" AND "notmydiv".

flevour: mine hasn't been specifically tested in all recent browsers. Some research seemed to indicate that older versions of Safari (iirc) don't support using * with getElementsByTagName and would therefore return an empty array.

Mark Wubben said on May 30, 2005

Hmm, somebody is using my Attribute Crawler script? Honestly I had forgotten about it.... but looking at the code now, the implementation looks quite nice. Perhaps I should revisit it :)

Check the `parseSelector` method I wrote for sIFR, that one is pretty nice too.

BTW, in IE 5.01 you can't select all tags using `document.getElementsByTagName("*")`. You'll have to use `document.all`. Not sure if it applies to IE 5.5 as well....

Mats said on May 31, 2005

That's a big 10-4! :-D

mat said on May 31, 2005

Any version that uses getElementsByTagName will be pretty expensive to use.

See what daniel glazman said on the subject a while ago:
http://daniel.glazman.free.fr/weblog/newarchive/2003_06_01_glazblogarc.html#s95287073

agoni said on June 01, 2005

dead in the wake of it all

Cameron Adams said on June 04, 2005

Hey Jonathan,

That regex for finding a classname is actually unreliable. JavaScript word boundaries match on some things you don't want.

It's safer to parse it yourself:

/(^| )classname( |$)/

victor said on June 07, 2005

I like the site! nice and clean.

Todd said on June 23, 2005

It seems to me this would be all i'd want:

function getElementsByClassName( className ){

  var ret = new Array();
  var nodes = document.getElementsByTagName("*");

  for( var i = 0; i < nodes.length; ++i ){
    var node = nodes[i];
    if( node.hasAttribute("class") &&
        node.getAttribute("class") == className ){
      ret.push( node );
    }
  }
  return ret;
}
Dave Strus said on June 28, 2005

Todd, that would miss any elements that contain the class you're searching for if it's accompanied by any additional classes.

For example, if you're looking for elements with the class "foo", your code wouldn't return any elements with, say class="bar foo".

Max Starkenburg said on July 28, 2005

Since I'm working on a page that both a) needs to support MacIE and b) is trying to getElementsByClassName of elements that sometimes have multiple classes (e.g. sometimes just class="item" and other times class="item last"), I have been having a little trouble with the getElementsByClassNames functions I have found up till now (MacIE doesn't support the push() method).

I think a hybrid of the functions I've found here on this page (and another page) seem to have worked, though:

function getElementsByClassName(classname){
        var rl = new Array();
        var re = new RegExp('(^| )'+classname+'( |$)');
        var ael = document.getElementsByTagName('*');
        var op = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;
        if (document.all && !op) ael = document.all;
        for(i=0, j=0 ; i<ael.length ; i++) {
                if(re.test(ael[i].className)) {
                        rl[j]=ael[i];
                        j++;
                }
        }
        return rl;
}

I haven't tested the Opera part, but I can't imagine it doing much damage.

BTW, the positioning and previewing of this comment form are awesome. Kudos!

p.s. Most posters here didn't escape the "less than" (<) in their for loops, so half of them are cut off (which is why I had to go to another site to see if these same functions were copied elsewhere).

Jonathan Snook said on July 28, 2005

Max: thanks for pointing out the escaping issue... I've gone over the previous comments and encoded them.

Johan Sundstr?m said on October 09, 2005

The version I use (actually hauntingly similar to some of these), working fine in at least Opera, Mozilla and IE:

if( document.all && !document.getElementsByTagName )
  document.getElementsByTagName = function( nodeName )
  {
    if( nodeName == '*' ) return document.all;
    var result = [], rightName = new RegExp( nodeName, 'i' ), i;
    for( i=0; i<document.all.length; i++ )
      if( rightName.test( document.all[i].nodeName ) )
	result.push( document.all[i] );
    return result;
  };

document.getElementsByClassName = function( className, nodeName )
{
  var result = [], tag = nodeName||'*', node, seek, i;
  if( document.evaluate )
  {
    seek = '//'+ tag +'[@class="'+ className +'"]';
    seek = document.evaluate( seek, document, null, 0, null );
    while( (node = seek.iterateNext()) )
      result.push( node );
  }
  else
  {
    var rightClass = new RegExp( '(^| )'+ className +'( |$)' );
    seek = document.getElementsByTagName( tag );
    for( i=0; i<seek.length; i++ )
      if( rightClass.test( (node = seek[i]).className ) )
	result.push( seek[i] );
  }
  return result;
};
Ron said on October 21, 2005

I got a version working that is both a hybrid of the XPATH version by Glazman and a version i cooked up that works in IE as well.

NOTE: It matches only if it has 1 class name in Gecko, but matches multiple classes in IE. How can we change the XPATH to do better multi-selector matching?


document.getElementsByClassName = function(className)
{   var outArray = new Array();
    var item;
    try {
        var xpathResult = document.evaluate('//*[@class = "' + className + '"]', document, null, 0, null);
        while (item = xpathResult.iterateNext())
            outArray[outArray.length] = item;
    }
    catch(err) {
        // ie fix
        var currentIndex = 0;
        var allElements = document.getElementsByTagName('*');
        for(var i=0; i < allElements.length; i++)
        {   if(allElements[i].className.match(className))
            {   outArray[currentIndex] = allElements[i];
                currentIndex++;
            }
        }
    }
    return outArray;
}
Ron said on October 21, 2005

Sorry, the less than symbol messed things up a bit.


document.getElementsByClassName = function(className)
{   var outArray = new Array();
    var item;
    try {
        var xpathResult = document.evaluate('//*[@class = "' + className + '"]', document, null, 0, null);
        while (item = xpathResult.iterateNext())
            outArray[outArray.length] = item;
    }
    catch(err) {
        // ie fix
        var currentIndex = 0;
        var allElements = document.getElementsByTagName('*');
        for(var i=0; i < allElements.length; i++)
        {   if(allElements[i].className.match(className))
            {   outArray[currentIndex] = allElements[i];
                currentIndex++;
            }
        }
    }
    return outArray;
}
Ron said on October 21, 2005

This will also do matching, but its more inclusive that I'd like it to be. If we're looking for a className of "vButton" it would also match "uvButton" and vButtonz as well.


var xpathResult = document.evaluate('contains("//*[@class]",'+className+')', document, null, 0, null);
Ian said on November 08, 2005

Hi there; your site design rocks.

Why getElementsByClassName when you can getElementsBySelector? See Behaviour:

http://bennolan.com/behaviour/

- Ian

Stuart Grimshaw said on March 13, 2007

@Ron: Wouldn't adding a space either side of the classname give you the desired effect?

var xpathResult = document.evaluate('contains("//*[@class]", '+className+' )', document, null, 0, null);

Might need some quotes to get it to honour the spaces.

Vikram Kriplaney said on August 27, 2008

Mats' non-regex proposal can easily be made to work by padding with spaces:

function getElementsByClassName(className) {
  className = " " + className + " ";
  var a = [];
  var els = document.getElementsByTagName("*");
  for ( var i = 0, j = els.length ; i < j ; i++ ) {
    if ((" "+els.item(i).className+" ").indexOf(className) != -1)
      a.push(els.item(i));
  }
  return a;
}
Sorry, comments are closed for this post. If you have any further questions or comments, feel free to send them to me directly.