269

I'm trying to get the child span that has a class = 4. Here is an example element:

<div id="test">
 <span class="one"></span>
 <span class="two"></span>
 <span class="three"></span>
 <span class="four"></span>
</div>

The tools I have available are JS and YUI2. I can do something like this:

doc = document.getElementById('test');
notes = doc.getElementsByClassName('four');

//or

doc = YAHOO.util.Dom.get('#test');
notes = doc.getElementsByClassName('four');

These do not work in IE. I get an error that the object (doc) doesn't support this method or property (getElementsByClassName). I've tried a few examples of cross browser implementations of getElementsByClassName but I could not get them to work and still got that error.

I think what I need is a cross browser getElementsByClassName or I need to use doc.getElementsByTagName('span') and loop through until I find class 4. I'm not sure how to do that though.

Joshua
  • 3,615
  • 1
  • 26
  • 32
spyderman4g63
  • 4,087
  • 4
  • 22
  • 31
  • Here it is: http://stackoverflow.com/questions/3808808/how-to-get-element-by-class-in-javascript – mostar Aug 28 '12 at 20:11
  • 2
    Funny enough, the more powerful `querySelectorAll` is supported by IE 8+ whereas `getElementsByClassName` is only supported by IE 9+. If you can drop IE 7, you are safe to use `querySelectorAll('.4')`. By the way, `4` is an invalid class name. – Prinzhorn Aug 28 '12 at 20:12
  • @paritybit that question doesn't work because it still utilizes getElementsByClassName and older version of IE don't seem to support that. edit: I'm sorry it uses tag name. This may work. – spyderman4g63 Aug 28 '12 at 20:32
  • @Prinzhorn I do not have the YUI2 selector utility available in this product for some reason. – spyderman4g63 Aug 28 '12 at 20:41
  • @spyderman4g63 I did not talk about anything YUI2 specific. `document.querySelectorAll` is DOM and has nothing to do with YUI – Prinzhorn Aug 29 '12 at 07:48

22 Answers22

338

Use querySelector and querySelectorAll

var testContainer = document.querySelector('#test');
var fourChildNode = testContainer.querySelector('.four');

IE9 and upper

Strider
  • 3,539
  • 5
  • 32
  • 60
Alberto Clar Brines
  • 3,504
  • 2
  • 7
  • 4
  • 13
    This checks for all descendants, not just children. – SUM1 Jun 15 '20 at 02:12
  • Check compatibility at [caniuse.com](https://caniuse.com/queryselector) – SapuSeven Mar 02 '21 at 00:18
  • 7
    @SUM1 [If you don't care about IE](https://caniuse.com/mdn-css_selectors_scope), you can use the CSS pseudo element [`:scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/:scope) to check for direct descendants like this: `testContainer.querySelector(':scope > .four')` – SapuSeven Mar 02 '21 at 00:24
  • 1
    in order to get all of the children we need to use ```var fourChildNode = testContainer.querySelectorAll('.four');``` – kingurr Jun 01 '21 at 17:54
121

Use doc.childNodes to iterate through each span, and then filter the one whose className equals 4:

var doc = document.getElementById("test");
var notes = null;
for (var i = 0; i < doc.childNodes.length; i++) {
    if (doc.childNodes[i].className == "4") {
      notes = doc.childNodes[i];
      break;
    }        
}

João Silva
  • 89,303
  • 29
  • 152
  • 158
  • 1
    The problem is that getElementsByClassName doesn't work in IE8. – spyderman4g63 Aug 28 '12 at 20:27
  • 14
    This code doesn't work if element has more than one class. For example: if you're looking for elements with "one" class, and your elements have "one two three" class, this function will not find them. – Fran Verona Feb 04 '14 at 11:51
  • 4
    @FranVerona Just out of curiosity, wouldn't a simple "indexOf('className') > -1" solve the multiple class issue? – James Poulose Jan 04 '15 at 23:33
  • 3
    @JamesPoulose, it *wouldn't*. Consider having an element set to two classes: small and bigger. thatElement.className would return a String that equals "small bigger". If you're looking for a class called big saying myElement.className.indexOf("big") will produce something unequal to negative 1 despite that not actually being a part of the class. If you have 100% control of your class names, such a fix would work, it's just not guaranteed to, especially when your'e writing code that's going to interact code you don't have total control over. – deadboy Jan 06 '15 at 21:32
  • 1
    You should use a regex match on the `className` like so: `if(doc.childNode[i].className.match("\s*" + search_class + "\s*")` where the variable `search_class` is the class name that you are looking for. – WebWanderer Mar 18 '16 at 20:29
  • The real problem is that Typescript generates their definitions from IE so this solution doesn't work in typescript – Brian McCall Apr 10 '18 at 16:59
  • @WebWanderer That also fails in the same scenario described by Evin. You're thinking of either `\s+` -- which fails if it's the first, last, or only class -- or `\b`, which _would_ work. – Nic May 19 '18 at 23:00
51

The accepted answer only checks immediate children. Often times we're looking for any descendants with that class name.

Also, sometimes we want any child that contains a className.

For example: <div class="img square"></div> should match a search on className "img", even though it's exact className is not "img".

Here's a solution for both of these issues:

Find the first instance of a descendant element with the class className

   function findFirstChildByClass(element, className) {
        var foundElement = null, found;
        function recurse(element, className, found) {
            for (var i = 0; i < element.childNodes.length && !found; i++) {
                var el = element.childNodes[i];
                var classes = el.className != undefined? el.className.split(" ") : [];
                for (var j = 0, jl = classes.length; j < jl; j++) {
                    if (classes[j] == className) {
                        found = true;
                        foundElement = element.childNodes[i];
                        break;
                    }
                }
                if(found)
                    break;
                recurse(element.childNodes[i], className, found);
            }
        }
        recurse(element, className, false);
        return foundElement;
    }
isherwood
  • 58,414
  • 16
  • 114
  • 157
Augie Gardner
  • 2,749
  • 3
  • 25
  • 36
36

Use element.querySelector(). Lets assume: 'myElement' is the parent element you already have. 'sonClassName' is the class of the child you are looking for.

let child = myElement.querySelector('.sonClassName');

For more info, visit: https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector

Hamza Dahmoun
  • 1,204
  • 10
  • 16
  • For some reason I had to use `let child = myElement.querySelector('sonClassName');` in my case – malat Apr 10 '20 at 13:58
  • 2
    But you have to add the dot next to the class name querySelector('.sonClassName'), otherwise it will handle it as sonClassName is a tag name not a class name – Hamza Dahmoun Apr 16 '20 at 15:29
20

Modern solution

const context = document.getElementById('context');
const selected = context.querySelectorAll(':scope > div');

documentation

Billizzard
  • 472
  • 6
  • 15
14

You could try:

notes = doc.querySelectorAll('.4');

or

notes = doc.getElementsByTagName('*');
for (var i = 0; i < notes.length; i++) { 
    if (notes[i].getAttribute('class') == '4') {
    }
}
Korikulum
  • 2,529
  • 21
  • 25
  • 1
    I was wondering if browsers finally had this functionality without requiring something like jQuery, glad to see it. If anyone is curious, looks like it's supported in most modern browsers: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll#Browser_compatibility – Liron Yahdav Mar 16 '17 at 21:51
8

To me it seems like you want the fourth span. If so, you can just do this:

document.getElementById("test").childNodes[3]

or

document.getElementById("test").getElementsByTagName("span")[3]

This last one ensures that there are not any hidden nodes that could mess it up.

  • 1
    Thanks, but there are a variable amount of child elements. – spyderman4g63 Aug 28 '12 at 20:30
  • 1
    To me it seemed like the question was to always get the fourth span child, thus leading to my answer. I do of course agree that a function to get whatever type of element at whatever index within an other element would be better, but I did no interpret the question like that... reading the question again I see that I totally misunderstood though :-) – Christian Jørgensen Aug 11 '13 at 14:58
  • `TagName` that's it! So simple. – Jacksonkr Jan 21 '17 at 20:35
  • I use this technique frequently to get the first element eg document.getElementById("test").childNodes[0] – Louise Eggleton Feb 19 '17 at 12:04
  • @LouiseEggleton there's `.firstChild` and `.firstChildElement` though – YakovL Sep 05 '19 at 18:10
5

Use the name of the id with the getElementById, no # sign before it. Then you can get the span child nodes using getElementsByTagName, and loop through them to find the one with the right class:

var doc = document.getElementById('test');

var c = doc.getElementsByTagName('span');

var e = null;
for (var i = 0; i < c.length; i++) {
    if (c[i].className == '4') {
        e = c[i];
        break;
    }
}

if (e != null) {
    alert(e.innerHTML);
}

Demo: http://jsfiddle.net/Guffa/xB62U/

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • Sorry, that # was a typo. I can select the container div, but cannot select the child elements by tag name because that function is not working in ie8. – spyderman4g63 Aug 28 '12 at 20:29
  • @spyderman4g63: The `getElementsByTagName` method works in IE 8. It is supported as far back as IE 5.5. https://developer.mozilla.org/en-US/docs/DOM/element.getElementsByTagName – Guffa Aug 28 '12 at 20:35
5

But be aware that old browsers doesn't support getElementsByClassName.

Then, you can do

function getElementsByClassName(c,el){
    if(typeof el=='string'){el=document.getElementById(el);}
    if(!el){el=document;}
    if(el.getElementsByClassName){return el.getElementsByClassName(c);}
    var arr=[],
        allEls=el.getElementsByTagName('*');
    for(var i=0;i<allEls.length;i++){
        if(allEls[i].className.split(' ').indexOf(c)>-1){arr.push(allEls[i])}
    }
    return arr;
}
getElementsByClassName('4','test')[0];

It seems it works, but be aware that an HTML class

  • Must begin with a letter: A-Z or a-z
  • Can be followed by letters (A-Za-z), digits (0-9), hyphens ("-"), and underscores ("_")
Oriol
  • 274,082
  • 63
  • 437
  • 513
3

In my opinion, each time you can, you should use Array and its methods. They are much, much faster then looping over the whole DOM / wrapper, or pushing stuff into empty array. Majority of solutions presented here you can call Naive as described here (great article btw):

https://medium.com/@chuckdries/traversing-the-dom-with-filter-map-and-arrow-functions-1417d326d2bc

My solution: (live preview on Codepen: https://codepen.io/Nikolaus91/pen/wEGEYe)

const wrapper = document.getElementById('test') // take a wrapper by ID -> fastest
const itemsArray = Array.from(wrapper.children) // make Array from his children

const pickOne = itemsArray.map(item => { // loop over his children using .map() --> see MDN for more
   if(item.classList.contains('four')) // we place a test where we determine our choice
     item.classList.add('the-chosen-one') // your code here
})
3

using querySelector

var doc=document.getElementById("test");
console.log(doc.querySelector('.two').innerHTML)
<div id="test">
 <span class="one"></span>
 <span class="two">two</span>
 <span class="three"></span>
 <span class="four"></span>
</div>
Using querySelectorAll

var doc=document.getElementById("test");
console.log(doc.querySelectorAll('*')[1].innerHTML)
<div id="test">
 <span class="one"></span>
 <span class="two">two</span>
 <span class="three"></span>
 <span class="four"></span>
</div>

using getElementsByTagNames

var doc=document.getElementById("test");
console.log(doc.getElementsByTagName("SPAN")[1].innerHTML);
<div id="test">
 <span class="one"></span>
 <span class="two">two</span>
 <span class="three"></span>
 <span class="four"></span>
</div>
<span>ss</span>

Using getElementsByClassName

var doc=document.getElementById("test");
console.log(doc.getElementsByClassName('two')[0].innerHTML)
<div id="test">
 <span class="one"></span>
 <span class="two">two</span>
 <span class="three"></span>
 <span class="four"></span>
</div>
Balaji
  • 9,657
  • 5
  • 47
  • 47
3

I believe this would answer your question best

document.querySelector('* > span.four')

This will match the first child element (of any parent) it finds that is a span and also has a class "four" set to it

However since in your example you also had a parent element which you are able to retrieve by id, you could also use this instead

document.querySelector('#test > span.four')

If you have a parent element saved in a variable like in your example, and you wish to search the subtree of that element, using :scope, as Billizzard has mentioned already, is probably your best choice

doc.querySelector(':scope > span.four');

Little extra: If the child element you are looking for isn't a direct child descendent, but somewhere further down the subtree, you can actually just omit the > like so

document.querySelector('#test span.four')
Koyaanis
  • 176
  • 7
3

Another way

const result = [...(parentElement.children)].find(child => {
  return child.classList.contains('some-class-name');
});

First we spread the elements of the NodeList to turn it into an Array so we can make use of the find() method. Lastly, find() will return to us the first element whose classList property contains the given class name.

jSamsa
  • 43
  • 5
2

The way i will do this using jquery is something like this..

var targetedchild = $("#test").children().find("span.four");

Marselus Chia
  • 230
  • 2
  • 3
2

You can fetch the parent class by adding the line below. If you had an id, it would be easier with getElementById. Nonetheless,

var parentNode = document.getElementsByClassName("progress__container")[0];

Then you can use querySelectorAll on the parent <div> to fetch all matching divs with class .progress__marker

var progressNodes = progressContainer.querySelectorAll('.progress__marker');

querySelectorAll will fetch every div with the class of progress__marker

B--rian
  • 5,578
  • 10
  • 38
  • 89
Osama Khawar
  • 74
  • 1
  • 6
1

Here is a relatively simple recursive solution. I think a breadth-first search is appropriate here. This will return the first element matching the class that is found.

function getDescendantWithClass(element, clName) {
    var children = element.childNodes;
    for (var i = 0; i < children.length; i++) {
        if (children[i].className &&
            children[i].className.split(' ').indexOf(clName) >= 0) {
            return children[i];
         }
     }
     for (var i = 0; i < children.length; i++) {
         var match = getDescendantWithClass(children[i], clName);
         if (match !== null) {
             return match;
         }
     }
     return null;
}
ceph3us
  • 7,326
  • 3
  • 36
  • 43
Jer
  • 5,468
  • 8
  • 34
  • 41
1

I know this question is a few years old and there have been a few answers to this but I thought I would add my solution just in case it helps anyone. It's in the same vein as the answer given by user2795540 and involves an array iterator.

If you're just wanting to get the first child that has the four class then you could use the find array iterator. Your browser will need to be able to support ES6 or you can use Babel to compile your JS into something all browsers will support. IE will not support this without a polyfill.

Using the same details you provided in your question it could look something like this:

const parentNode = document.getElementById('test');
const childNode = Array.from(parentNode.childNodes).find(({ className }) => className === 'four');

The above solution will return the node you want to target and store it in the childNode variable.

You can find out more about the find array iterator at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

samkitson
  • 184
  • 9
  • This a good answer. It will fail if the element has more than one class though. You can use `String.includes('yourclass')` to avoid this though. `const childNode = Array.from(parentNode.childNodes).find(({ className }) => className && className.includes('four'));` – CaseyC Dec 03 '20 at 19:41
0

June 2018 update to ES6

    const doc = document.getElementById('test');
    let notes = null;
    for (const value of doc) {
        if (value.className === '4') {
            notes = value;
            break;
        }    
    }
Christopher Grigg
  • 2,238
  • 27
  • 33
0

let notes = document.querySelector('#test .four')

-2

YUI2 has a cross-browser implementation of getElementsByClassName.

Hank Gay
  • 70,339
  • 36
  • 160
  • 222
  • Hmm. The example does work in IE8, but maybe it doesn't work in the case that you apply it to an object. In my case is set doc as a yui dom object and then use doc.getElementsByClassName. That's when it fails. edit: or maybe I don't understand how that object works and it is just a normal dom object? – spyderman4g63 Aug 28 '12 at 20:59
  • @spyderman4g63 the YUI version of `getElementsByClassName` is a method of `YAHOO.util.Dom`. In your case, the call would look something like `YAHOO.util.Dom.getElementsByClassName('four', 'span', 'test')`. You should probably read the docs to get a more detailed understanding of what that call is doing. The short version is that it will now look for `span` elements with the class `four` underneath the DOM element with `test` as its `id`. – Hank Gay Aug 29 '12 at 14:58
  • @spyderman4g63 Yes, the result of the `get` call is just a DOM element. YUI doesn't take the sort of whole-sale 'wrap everything in a framework-specific object' approach that something like jQuery does, nor does it monkey-patch native objects like Prototype does (did? I haven't messed w/ Protoype in forever). In this respect, YUI is more like Dojo. – Hank Gay Aug 29 '12 at 15:07
-3

Here is how I did it using the YUI selectors. Thanks to Hank Gay's suggestion.

notes = YAHOO.util.Dom.getElementsByClassName('four','span','test');

where four = classname, span = the element type/tag name, and test = the parent id.

spyderman4g63
  • 4,087
  • 4
  • 22
  • 31
-4

Use YAHOO.util.Dom.getElementsByClassName() from here.

Mihai Iorga
  • 39,330
  • 16
  • 106
  • 107