Get elements by class A or B in JavaScript
Asked Answered
T

2

11

Is it possible to get elements that have one of the specified classes? This is not the same as getting elements that have all of the specified classes.

For example, I want to capture all elements whose class list contains either one, two, or three.

Maybe something like this:

var oneTwoThree = document.getElementsByClassName("one, two, three");

I also do not want to use jQuery. Is the only option to getElementsByClassName for each class and combine them?

Toniatonic answered 18/2, 2014 at 12:32 Comment(0)
R
17

querySelector accepts pretty much any CSS selector:

var oneTwoThree = document.querySelectorAll('.one, .two, .three');
Rind answered 18/2, 2014 at 12:34 Comment(5)
Beat me to it by 40 seconds ;-) Do note if you need to support IE7 or below you're gonna need some kind of a polyfill / selector library.Courtney
Oh wow, I didn't know about this function. Thank you so much. I learned something new. :) Btw, I need only IE10 and above, so this works well.Toniatonic
getElementsByClassName is IE9+, so i doubt old browser support is requiredRind
Just to note for someone in the future. Here is a comparison of performance between jQuery and querySelector jsperf.com/jquery-vs-queryselectorall/42Toniatonic
@Rind Thank you very much for pointing that out... Feels like downvoting myself. I've edited my comment.Toniatonic
M
4

I also do not want to use jQuery. Is the only option to getElementsByClassName for each class and combine them?

By using querySelectorAll (as answered), it will search and do the combining for you. IE9+ {IE8 (CSS2 selectors only}

Otherwise, getElementsByClassName is not the only method available to you, it's not even available on some older browsers (IE9+, FF3+).

But pretty much any other method that you could use would require that you combine the results. So, just as a demonstration I have created a few examples for you.

Define the white spaces as per HTML4.01 class attribute

"This attribute assigns a class name or set of class names to an element. Any number of elements may be assigned the same class name or names. Multiple class names must be separated by white space characters."

var whiteSpaces = '[\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\‌​u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]';

Define the space characters as per HTML5 3.2.5.7 The class attribute

Every HTML element may have a class attribute specified.

The attribute, if specified, must have a value that is a set of space-separated tokens representing the various classes that the element belongs to.

The classes that an HTML element has assigned to it consists of all the classes returned when the value of the class attribute is split on spaces. (Duplicates are ignored.)

Assigning classes to an element affects class matching in selectors in CSS, the getElementsByClassName() method in the DOM, and other such features.

There are no additional restrictions on the tokens authors can use in the class attribute, but authors are encouraged to use values that describe the nature of the content, rather than values that describe the desired presentation of the content.

The className and classList IDL attributes, defined in the DOM specification, reflect the class content attribute. [DOM]"

var whiteSpaces = '[ \n\r\t\f]';

Define start an end echaratcers

var starts = '(^|' + whiteSpaces + ')',
    ends = '(' + whiteSpaces +'|$)';

Cross browser (DOM walker)

function walkTheDOM(node, func) {
    func(node);
    node = node.firstChild;
    while (node) {
        walkTheDOM(node, func);
        node = node.nextSibling;
    }
}

function getElementsByClassName1(node, className) {
    var regex = new RegExp(starts + className + ends),
        results = [];

    walkTheDOM(node, function (currentNode) {
        if (regex.test(currentNode.className)) {
            results.push(currentNode);
        }
    });

    return results;
}

Cross browser (getElementsByClassName)

function getElementsByClassName2(node, className) {
    var array = [],
        regex = new RegExp(starts + className + ends),
        elements = node.getElementsByTagName("*"),
        length = elements.length,
        i = 0,
        element;

    while (i < length) {
        element = elements[i];
        if (regex.test(element.className)) {
            array.push(element);
        }

        i += 1;
    }

    return array;
}

Modern browser (IE9+)

function getElementsByClassName3(node, className) {
    var results = [],
        treeWalker = document.createTreeWalker(
        node,
        NodeFilter.SHOW_ELEMENT, {
            acceptNode: function (thisNode) {
                var accept = NodeFilter.FILTER_SKIP;

                if (thisNode.classList.contains(className)) {
                    accept = NodeFilter.FILTER_ACCEPT;
                }

                return accept;
            }
        }, false);

    while (treeWalker.nextNode()) {
        results.push(treeWalker.currentNode);
    }

    return results;
}

Modern browser (IE9+)

function getElementsByClassName4(node, className) {
    return Array.prototype.slice.call(document.getElementsByClassName(className));
}

Cross browser Array.indexOf

function indexOf(array, searchElement, fromIndex) {
    var length = array.length,
        val = -1,
        index;

    if (length !== 0) {
        if (arguments.length > 2) {
            fromIndex = fromIndex >> 0;
        } else {
            fromIndex = 0;
        }

        if (fromIndex < length) {
            if (fromIndex < 0) {
                fromIndex = length - Math.abs(fromIndex);
            }

            if (fromIndex < 0) {
                fromIndex = 0;
            }

            for (index = fromIndex; index < length; index += 1) {
                if (index in array && searchElement === array[index]) {
                    val = index;
                    break;
                }
            }
        }
    }

    return val;
}

Cross browser Array.filter

function filter(array, fn, thisArg) {
    var length = array.length,
        arr = [],
        index,
        element;

    for (index = 0; index < length; index += 1) {
        if (index in array) {
            element = array[index];
            if (fn.call(thisArg, element, index, array)) {
                arr.push(element);
            }
        }
    }

    return arr;
};

Combining the results and maintaining order

function getElements(node, classes, func) {
    if (typeof classes === 'string') {
        classes = classes.split(/\s*,\s*/);
    }

    var length = classes.length,
        results = [],
        index,
        name;

    for (index = 0; index < length; index += 1) {
        name = classes[index];
        if (name.charAt(0) === '.') {
            name = name.slice(1);
        }

        results = results.concat(func(node, name));
    }

    return filter(results.reverse(), function (element, index, arr) {
        return index <= indexOf(arr, element);
    }).reverse();
}

Test

<div class='A'></div>
<div class='B'></div>
<div class='A B'></div>
<div class='C'></div>

console.log(getElements(document, '.A, .B', getElementsByClassName1));
console.log(getElements(document, '.A, .B', getElementsByClassName2));
console.log(getElements(document, '.A, .B', getElementsByClassName3));
console.log(getElements(document, '.A, .B', getElementsByClassName4));
console.log(document.querySelectorAll('.A, .B'));

On jsFiddle

And finally a performace comparison for you, on jsPerf

Messaline answered 18/2, 2014 at 14:10 Comment(5)
Just for future reference, instead of a single space in the regex you should be using [ \n\r\t\f] because of how the W3C defines space characters. Or you can just use \s to keep things simple.Brocade
Sorry I wasn't clear enough. Class names in an Element may be separated by "space characters" as defined by the W3C. So instead of a space character between 'A' and 'B' in your one test div, you could have a line break and that would still be valid HTML. This means that your regex that uses just the space character would fail in some situations where other whitespace is used in an element's class name.Brocade
Check out the modification I made to your jsFiddle.Brocade
Yup, I've already been through all of this creating a hasClass() function. And yes, technically all of those are whitespace characters, but you only have to take into consideration the ones from the HTML5 spec: w3.org/TR/html5/single-page.html#space-character (as I said earlier).Brocade
Ok, I see that it was the HMT4.01 spec that I looked at which specifies them all, and the HTML5 spec reduces that list to just those you mention. Or so it seems? But have updated with both possibilities.Messaline

© 2022 - 2024 — McMap. All rights reserved.