Recently, I needed a cross-browser implementation of the getElementsByClassName Javascript DOM method. I couldn’t use a JS library like jQuery because code size had to be as small as possible.

Fortunately, Robert Nyman developed back in 2008 a very good function to handle this issue.

I have refactored the function definition logic, optimized code as much as possible, fixed some bugs and added the missing querySelectorAll DOM method.

I’ve written some QUnit tests if you want to test wether it works as it should or not.

Parameters:

className One or several class names, separated by space. Multiple class names demands that each match have all of the classes specified. Mandatory.

tag Tag name of the elements to match. Optional.

elm Reference to a DOM element to look amongst its children for matches. Recommended for better performance in larger documents. Optional.

ChangeLog:

7 Dic 2011

  • Added a refactored and bugfixed XPath method. Robert Nyman’s version did not add normalize-space(@class) (space normalization) so his method wouldn’t return elements with a class attribute containing space characters other than whitespace (newlines for example). Also, namespace try/catch was removed from his version since we’re dealing with HTML documents and therefore it’s not necessary.
  • BugFix: Some elements (nodes) may not have a getElementsByClassName method, so we test for this. As a fallback document will be used. See this issue for more info.
  • BugFix for IE: Array.slice cannot be called on StaticNodeLists in IE. Instead, a regular loop is used to populate the array.

So here it is my 2011 version of the getElementsByClassName function:

        
/*
 *  Refactored & improved in 2011 by Tubal Martin (http://www.margenn.com)
 *  Unit tests: http://www.margenn.com/tubal/javascript/getelementsbyclassname.html
 *  Original version developed by Robert Nyman in 2008 http://code.google.com/p/getelementsbyclassname/
 */	
var getElementsByClassName = document.getElementsByClassName ?
    function (className, tag, elm) {
        var nodeElm = elm && elm.getElementsByClassName ? elm : document,
            elements = nodeElm.getElementsByClassName(className),
            nodeName = tag ? new RegExp("\\b" + tag + "\\b", "i") : null,
            rElements = [],
            i = 0, l = elements.length,
            current;
        for (; i < l; i++) {
            current = elements[i];
            if (!nodeName || nodeName.test(current.nodeName)) {
                rElements.push(current);
            }
        }
        return rElements;
    } : document.querySelectorAll ?
    function (className, tag, elm) {
        var elements = (elm || document).querySelectorAll((tag || "") + "." + className.split(" ").join(".")),
            rElements = [], 
            i = 0, l = elements.length;
        for (; i < l; i++) {
            rElements.push(elements[i]);
        }
        return rElements;
    } : document.evaluate ?
    function (className, tag, elm) {
        var classes = className.split(" "),
            classesToCheck = "",
            rElements = [],
            i = 0, l = classes.length, 
            elements, node;
        for(; i < l; i++){
            classesToCheck += "[contains(concat(' ', normalize-space(@class), ' '), ' " + classes[i] + " ')]";
        }
        elements = document.evaluate(".//" + (tag || "*") + classesToCheck, (elm || document), null, 0, null);
        while (node = elements.iterateNext()) {
            rElements.push(node);
        }
        return rElements;
    } :
    function (className, tag, elm) {
        tag = tag || "*";
        elm = elm || document;
        var classes = className.split(" "),
            classesToCheck = [],
            elements = tag == "*" && elm.all ? elm.all : elm.getElementsByTagName(tag),
            rElements = [],
            i = j = 0, 
            il = classes.length, 
            jl = elements.length,
            current, match, k, kl;
        for (; i < il; i++) {
            classesToCheck.push(new RegExp("(^|\\s)" + classes[i] + "(\\s|$)"));
        }
        kl = classesToCheck.length;
        for (; j < jl; j++) {
            current = elements[j];
            match = false;
            for (k = 0; k < kl; k++) {
                match = classesToCheck[k].test(current.className);
                if (!match) {
                    break;
                }
            }
            if (match) {
                rElements.push(current);
            }
        }
        return rElements;
    };