views:

9058

answers:

8

What's the best way to get an array of all elements in an html document with a specific CSS class using javascript?

No javascript frameworks like jQuery allowed here right now, and I could loop all the elements and check them manually myself. I'm hoping for something a little more elegant.

+3  A: 

You can include a getElementsByClass function, or you can use a jQuery selector.

UPDATE: The implementation mentioned by @Shog9 is probably better than that above.

bdukes
A: 

There is no such thing as a CSS class. CSS has rule-sets and selectors (including the class selector).

Do you mean an HTML class? The usual way is to loop over every element in the document (using document.getElementsByTagName('*') (for efficiency, use a specific tag name if you know the class will only be applied to elements of a certain type) and test the className property of each (noting that the property contains a space separated list of class names, not a single class name).

A number of libraries (such as jQuery or YUI) have functions to simply this.

Do you mean a CSS selector? This gets more complex, and turning to a library is almost certainly the right thing to do here. Again, jQuery or YUI are decent choices.

David Dorward
+6  A: 

1) Get all elements in the document (document.getElementsByTagName('*'))
2) Do a regular expression match on the element's className attribute for each element

Chris MacDonald
Why did this get downvoted? This is perfectly fine for an all-JavaScript solution.
Jason Bunting
Agreed. There does seem to be a JS framework bias building in SO to the point where pure Javascript suggestions are looked down upon. Recommending and pointing to solutions in frameworks are fine but so are standalone Javascript solutions.
AnthonyWJones
Preaching to the choir, for sure - I love using a framework, but the fact is, not everyone wants to use one or can. I think some of those using frameworks don't know enough to know how to do it without one, so they see these answers and downvote them out of ignorance or pride or something.
Jason Bunting
@Jason: Most of the time, i'd agree - but in this case, there are plenty of existing implementations out there, tweaked and honed over the last few years, and so writing your own just seems masochistic - like writing your own sort routine when qsort() works just fine. (I didn't down-vote it though)
Shog9
+15  A: 

Use one that's already been written. Most major JS libraries include one in some form or another, but if you aren't using one of them then i can recommend Robert Nyman's excellent implementation:

http://code.google.com/p/getelementsbyclassname/
http://www.robertnyman.com/2008/05/27/the-ultimate-getelementsbyclassname-anno-2008/

There are just too many ways to make this (conceptually-simple) routine slow and buggy to justify writing your own implementation at this point.

Shog9
A: 

Use jquery, it can't be more convenient.

$(".theclass") or $(".theclass"),makeArray() if you want a native JS array

+3  A: 

Just to do some follow up, I based my code on the the Robert Nyman implementation posted by Shog9, but departed a little from his exact version, for three reasons:

  1. He allowed you to select a root element and tag type to filter your results. I don't need that functionality and so by removing it I was able to simplify the code significantly.
  2. The first thing his code does is see if the function in question already exists, and if it does he provides his own implementation anyway. That just seemed... odd. I understand he was adding functionality to the original, but again: I'm not using those features.
  3. I wanted an additional bit of syntactic sugar- to be able to call it like I would call document.getElementById() or document.getElementsByTagName().

Note that I still relied mostly on his code. His javascript skills are obviously far beyond my own. I did try to factor out some redundant variables, but that's about it.

With that in mind, here is what I ended up with (seems to work in IE6, IE7, Firefox 3, and Chrome see new note at the end):

 if (!document.getElementsByClassName)
    document.getElementsByClassName = function (className)
{
    var classes = className.split(" ");
    var classesToCheck = "";
    var returnElements = [];
    var match, node, elements;

    if (document.evaluate)
    {    
        var xhtmlNamespace = "http://www.w3.org/1999/xhtml";
        var namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace:null;

        for(var j=0, jl=classes.length; j<jl;j+=1)
            classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]"; 

        try
        {
            elements = document.evaluate(".//*" + classesToCheck, document, namespaceResolver, 0, null);
        }
        catch(e)
        {
            elements = document.evaluate(".//*" + classesToCheck, document, null, 0, null);
        }

        while ((match = elements.iterateNext()))
            returnElements.push(match);
    }
    else
    {
        classesToCheck = [];
        elements = (document.all) ? document.all : document.getElementsByTagName("*");

        for (var k=0, kl=classes.length; k<kl; k+=1)
            classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));

        for (var l=0, ll=elements.length; l<ll;l+=1)
        {
            node = elements[l];
            match = false;
            for (var m=0, ml=classesToCheck.length; m<ml; m+=1)
            {
                match = classesToCheck[m].test(node.className);
                if (!match) break;
            }
            if (match) returnElements.push(node);
        } 
    }
    return returnElements;
}

Update:
One new note on this. I've since read the notes on the original implementation, and I understand now that my code could fall down in the case where the existing browser has it's own implementation, because the default implementations return a nodelist where this returns an array. This includes the more recent firefox and safari, and opera browsers. Most of the time that won't matter, but in some situations it could.

So this technically does work everywhere, but it could result in different behavior in different places, and that's not good. I should fix this to either also return a nodelist or override the supplied method to return an array (which is what the original did). Probably the former would be simpler, but that latter would be better.

However, it's working at the moment in the local intranet environment (pretty much all IE), so for the time being I'll leave the fix as an exercise for the reader.

Joel Coehoorn
+1  A: 

Keep in mind that atleast FF3 already has a native implementation of getElementsByClassName afaik.

If you're going to implement your own solution, maybe you should try to find a xpath-solution since all modern browser have native support for xpath.

jishi
Yep: I'm already in the process of updating my post, but it's gonna take a bit, spread over several edits.
Joel Coehoorn
+1  A: 

If using a framework, they all have selections using CSS Selectors. Otherwise.

var getElementsByClassName = function(class, sc){
    //Init
    var elements, i, results = [], curClass;  

    //Default scope is document
    sc = sc || document;

    //Get all children of the scope node
    elements = sc.getElementsByTagName('*');
    for( i=0; i < elements.length; i++ ){
        curClass = elements[i].getAttribute('class');
        if(curClass != null){
            curClass = curClass.split(" ");
            for( j=0; j < curClass.length; j++){
                if(curClass[j] === class){
                    results.push( elements[i] );
                    break;
                }
            }
        }
    }

    return results;
};

Just wrote it right now, just for you. :) Feel free to use.

[edit: ] As suggested in the comments, there was some misfunctionality, so fixed that.

Dmitri Farkov
Old question, but that's okay. Only problem here is that if you have a class like 'navarea-top` and 'navarea` and are just searching for 'navarea', you'll also find all the 'navarea-top' items.
Joel Coehoorn
True. To expand on it though, one may get the class attribute and use regexp to check instead of indexOf.
Dmitri Farkov
Fixed. :) Thank you for the feedback
Dmitri Farkov