EvilGenius82
EvilGenius82

Reputation: 281

className only changing every other class

I'm performing a small text with JavaScript with the getElementsByClassName() and I am getting some unwanted results. I would like the script to change each CSS class to a new class. The issue is that every other class is only changing...

I would like to use pure js how this issue as it is for practice purposes.

The first thing that came to mind was white spaces, although when removing the this did not make any differnce.

Can anyone point our what I am doing wrong?

<html>
    <head>
       <link rel="stylesheet" type="text/css" href="default.css">
    </head>
    <body>
        <div class="block-default">BLOCK1</div>
        <div class="block-default">BLOCK2</div>
        <div class="block-default">BLOCK3</div>
        <div class="block-default">BLOCK4</div>
        <div class="block-default">BLOCK5</div>
        <div class="block-default">BLOCK6</div>
        <div class="block-default">BLOCK7</div>
        <div class="block-default">BLOCK8</div>
        <script>

    var blockSet = document.getElementsByClassName("block-default");
    var blockSetLength = blockSet.length;

    blockSet[0].className = "block-selected";
    blockSet[1].className = "block-selected";
    blockSet[2].className = "block-selected";
    blockSet[3].className = "block-selected";
    blockSet[4].className = "block-selected";
    blockSet[5].className = "block-selected";
    blockSet[6].className = "block-selected";
    blockSet[7].className = "block-selected";   

        </script>
    </body>
</html>

CSS Classes:

.block-default {
    width: 100px;
    height:50px;
    background-color: green;
    border: 1px solid red;
    padding:10px;
}

.block-selected {
    width: 100px;
    height:50px;
    background-color: blue;
    border: 1px solid white;
    padding:10px;
 }

Upvotes: 25

Views: 2149

Answers (16)

HeartOfGermany
HeartOfGermany

Reputation: 41

I came across an issue, where I wanted to identify HTML objects by the class and than change the class. However getElementsByClassName(string str) returned a live HTML nodeList, which also resulted in 50% loss, where the loop stopped to early.

My solution was simple and should work in all browsers. It also should be fast, since it does not use any exotic or complex logic.

So the solution was, to clone the list as an array and than loop over the array:

  let messagenodes = document.getElementsByClassName("WidgetAction Toggle");
  let messages = [];
  for (let i = 0; i < messagenodes.length; i++)
  {
    let self = messagenodes[i];
    messages.push(self);
  }
  
  for (let i = 0; i < messages.length; i++)
  {
    window.alert(messages.length);       // Debugging output to verify result
    window.alert(messages[i].innerHTML); // Debugging output to verify result
    messages[i].className = 'ToggleTicketMessageVisibility';
  }

Upvotes: 0

adricadar
adricadar

Reputation: 10209

Because you change the .className of the blockSet which is an HTMLCollection. The collection that have elements with same class (block-default) will change when the elements suffers some updates.

In other words when you change the .className of an element the collection will exclude that element. This means that the size of the HTMLCollection will decrease . Also the size will increase if an element with that class has beed added to the DOM.

To solve this you can always change only the first element .className.

for(var i = 0; i<blockSetLength; i++)
{
    blockSet[0].className = "block-selected";
}

Notes: Intead of changing class element by element, you can iterate through elements with for and change .className.

var blockSet = document.getElementsByClassName("block-default");
var blockSetLength = blockSet.length;

console.log(blockSet);

for(var i = 0; i<blockSetLength; i++)
{
    blockSet[0].className = "block-selected";
}
.block-default {
    width: 100px;
    height:50px;
    background-color: green;
    border: 1px solid red;
    padding:10px;
}

.block-selected {
    width: 100px;
    height:50px;
    background-color: blue;
    border: 1px solid white;
    padding:10px;
 }
<div class="block-default">BLOCK1</div>
<div class="block-default">BLOCK2</div>
<div class="block-default">BLOCK3</div>
<div class="block-default">BLOCK4</div>
<div class="block-default">BLOCK5</div>
<div class="block-default">BLOCK6</div>
<div class="block-default">BLOCK7</div>
<div class="block-default">BLOCK8</div>

If you add a new item in DOM (not collection) the size will increase as presented in the example below.

var blockSet = document.getElementsByClassName("block-default");
var blockSetLength = blockSet.length;

alert("Current size: " + blockSet.length);
document.body.innerHTML += '<div class="block-default">BLOCK9</div>';
alert("After adding an element in DOM size: " + blockSet.length);
.block-default {
    width: 100px;
    height:50px;
    background-color: green;
    border: 1px solid red;
    padding:10px;
}

.block-selected {
    width: 100px;
    height:50px;
    background-color: blue;
    border: 1px solid white;
    padding:10px;
 }
<div class="block-default">BLOCK1</div>
<div class="block-default">BLOCK2</div>
<div class="block-default">BLOCK3</div>
<div class="block-default">BLOCK4</div>
<div class="block-default">BLOCK5</div>
<div class="block-default">BLOCK6</div>
<div class="block-default">BLOCK7</div>
<div class="block-default">BLOCK8</div>

Upvotes: 23

Tharaka Arachchige
Tharaka Arachchige

Reputation: 807

Use this to your javascript code. this will fix your error

<script>
    var blockSet = document.getElementsByClassName("block-default");
    var blockSetLength = blockSet.length;

    for (var i = blockSet.length - 1; i >= 0; i--) {
        blockSet[i].className = "block-selected";
    };
</script>

Upvotes: -1

Mitul
Mitul

Reputation: 3427

You can find the working code over here

<div class="block-default">BLOCK1</div>
<div class="block-default">BLOCK2</div>
<div class="block-default">BLOCK3</div>
<div class="block-default">BLOCK4</div>
<div class="block-default">BLOCK5</div>
<div class="block-default">BLOCK6</div>
<div class="block-default">BLOCK7</div>
<div class="block-default">BLOCK8</div>


var blockSet = document.getElementsByClassName("block-default");
var blockSetLength = blockSet.length;

for (i = 0; i < blockSetLength; i++) {
    blockSet[0].className = "block-selected";
}

Demo link http://jsfiddle.net/patelmit69/9koxfaLq/1/

Upvotes: -1

Mohammed Moustafa
Mohammed Moustafa

Reputation: 630

Definition and Usage

  1. The getElementsByClassName() method returns a collection of all elements in the document with the specified class name, as a NodeList object.

  2. The NodeList object represents a collection of nodes. The nodes can be accessed by index numbers. The index starts at 0.

The use of this property is discouraged because of performance implications (due to the live DOMCollection where any changes to the document must be reflected on the returned object immediately) and complexity (the removal of an element from the document will result in immediate changes to the collection).

And by just adding only blockSet[0].className = "block-selected"; and by clicking on the button it colord each div by each click so we need to click 8 times in order to color all the divs and see the live example below

function myFunction() {
  var blockSet = document.getElementsByClassName('block-default');

  blockSet[0].className = "block-selected";

}
.block-default {
  width: 100px;
  height: 50px;
  background-color: green;
  border: 1px solid red;
  padding: 10px;
}
.block-selected {
  width: 100px;
  height: 50px;
  background-color: blue;
  border: 1px solid white;
  padding: 10px;
}
<button onclick="myFunction()">change</button>

<div class="block-default">BLOCK1</div>
<div class="block-default">BLOCK2</div>
<div class="block-default">BLOCK3</div>
<div class="block-default">BLOCK4</div>
<div class="block-default">BLOCK5</div>
<div class="block-default">BLOCK6</div>
<div class="block-default">BLOCK7</div>
<div class="block-default">BLOCK8</div>

And by adding only var blockSet = document.getElementsByClassName('block-default'); alert("Length are: " + blockSet.length + "\nFirst Item is: " + blockSet[0].childNodes[0].nodeValue); without the rest it will alert

  • Length are: 8
  • First Item is: block1

As in the below live example:

function myFunction() {
  var blockSet = document.getElementsByClassName('block-default');

  /*
      blockSet[0].className = "block-selected";
      blockSet[1].className = "block-selected";
      blockSet[2].className = "block-selected";
      blockSet[3].className = "block-selected";
      blockSet[4].className = "block-selected";
      blockSet[5].className = "block-selected";
      blockSet[6].className = "block-selected";
      blockSet[7].className = "block-selected";*/

  alert("Length are: " + blockSet.length + "\nFirst Item is: " + blockSet[0].childNodes[0].nodeValue);
}
.block-default {

  width: 100px;

  height: 50px;

  background-color: green;

  border: 1px solid red;

  padding: 10px;

}

.block-selected {

  width: 100px;

  height: 50px;

  background-color: blue;

  border: 1px solid white;

  padding: 10px;

}
<button onclick="myFunction()">change</button>

<div class="block-default">BLOCK1</div>
<div class="block-default">BLOCK2</div>
<div class="block-default">BLOCK3</div>
<div class="block-default">BLOCK4</div>
<div class="block-default">BLOCK5</div>
<div class="block-default">BLOCK6</div>
<div class="block-default">BLOCK7</div>
<div class="block-default">BLOCK8</div>

Or we can use it use document.getElementsByClassNamewith for loopso a close alternative is querySelectorAll as Rick Hitchcock answered it.

function myFunction() {
  var blockSet = document.querySelectorAll('.block-default');
  blockSet[0].className = "block-selected";
  blockSet[1].className = "block-selected";
  blockSet[2].className = "block-selected";
  blockSet[3].className = "block-selected";
  blockSet[4].className = "block-selected";
  blockSet[5].className = "block-selected";
  blockSet[6].className = "block-selected";
  blockSet[7].className = "block-selected";
}
.block-default {
  width: 100px;
  height: 50px;
  background-color: green;
  border: 1px solid red;
  padding: 10px;
}
.block-selected {
  width: 100px;
  height: 50px;
  background-color: blue;
  border: 1px solid white;
  padding: 10px;
}
<button onclick="myFunction()">change</button>

<div class="block-default">BLOCK1</div>
<div class="block-default">BLOCK2</div>
<div class="block-default">BLOCK3</div>
<div class="block-default">BLOCK4</div>
<div class="block-default">BLOCK5</div>
<div class="block-default">BLOCK6</div>
<div class="block-default">BLOCK7</div>
<div class="block-default">BLOCK8</div>

I hope my post it helps, let me know if you have any question.

Upvotes: 0

Rick Hitchcock
Rick Hitchcock

Reputation: 35670

Instead of using getElementsByClassName(),
which returns a live HTMLCollection that will change as the classNames change,
you can use querySelectorAll(),
which returns a non-live NodeList that will not change.

querySelectorAll() has better IE support than getElementsByClassName() (IE8+ vs. IE9+).
It's also much more flexible since it supports CSS selectors (CSS2 for IE8+ and CSS3 for IE9+).

However, querySelectorAll() is slower than getElementsByClassName().
Keep that in mind if you're processing thousands of DOM elements.

Snippet

var blockSet = document.querySelectorAll(".block-default");
var blockSetLength = blockSet.length;

blockSet[0].className = "block-selected";
blockSet[1].className = "block-selected";
blockSet[2].className = "block-selected";
blockSet[3].className = "block-selected";
blockSet[4].className = "block-selected";
blockSet[5].className = "block-selected";
blockSet[6].className = "block-selected";
blockSet[7].className = "block-selected";
.block-default {
  width: 100px;
  height: 50px;
  background-color: green;
  border: 1px solid red;
  padding: 10px;
}
.block-selected {
  width: 100px;
  height: 50px;
  background-color: blue;
  border: 1px solid white;
  padding: 10px;
}
<div class="block-default">BLOCK1</div>
<div class="block-default">BLOCK2</div>
<div class="block-default">BLOCK3</div>
<div class="block-default">BLOCK4</div>
<div class="block-default">BLOCK5</div>
<div class="block-default">BLOCK6</div>
<div class="block-default">BLOCK7</div>
<div class="block-default">BLOCK8</div>

Upvotes: 13

Maverick
Maverick

Reputation: 131

The simplest way you can do this is by using the below code:

while(blockSetLength--){ 
    //this will change the class of n-1 dom object 
    blockSet[blockSetLength].className='block-selected';
}

Upvotes: 2

Arathi Sreekumar
Arathi Sreekumar

Reputation: 2584

First, the below code should do the trick in the simplest way.

var blockSet = document.getElementsByClassName("block-default").className = "block-selected";

Next what goes wrong with your code, or rather what is the interesting thing that is happening:

blockSet[0].className = 'block-selected';

makes the first block set element no longer a block set element. That leaves you with 7 remaining. Now,

blockSet[1].className = 'block-selected';

Selects the second one of the remaining ones. Which would be the third one of your complete list. Now you have 6 remaining.

blockSet[2].className = 'block-selected';

This makes the third one among the remaining, which would be your BLOCK5 into a block selected. And results in you having 5 remaining.

blockSet[3].className = 'block-selected'; 

This again finds your fourth one which is BLOCK7 when you count to the fourth among your remaining. And now you have 4 remaining.

blockSet[4] onwards find no such element and fail to execute. This is what happens with your code. Quite interesting. :).

Here is a jsfiddle alerting you the values as they run: https://jsfiddle.net/xz7h57jv/

Upvotes: 0

Asheesh Kumar
Asheesh Kumar

Reputation: 161

function change() {
  var blockSet = document.getElementsByClassName("block-default");
  var blockSetLength = blockSet.length;

  for (var i = 0; i < blockSetLength; i++) {

    blockSet[0].className = "block-selected";
  }
}
.block-default {
  width: 100px;
  height: 50px;
  background-color: green;
  border: 1px solid red;
  padding: 10px;
}
.block-selected {
  width: 100px;
  height: 50px;
  background-color: blue;
  border: 1px solid white;
  padding: 10px;
}
<button onclick="change()">change</button>

<div class="block-default">BLOCK1</div>
<div class="block-default">BLOCK2</div>
<div class="block-default">BLOCK3</div>
<div class="block-default">BLOCK4</div>
<div class="block-default">BLOCK5</div>
<div class="block-default">BLOCK6</div>
<div class="block-default">BLOCK7</div>
<div class="block-default">BLOCK8</div>

what you were doing wrong is every time you change the class the colllection re-evaluates and decreases in size.

Upvotes: 0

vals
vals

Reputation: 64164

You have already some good solutions.

I think that the best one is the one from Rick Hitchcock.

But a solution that I often use, to be safe when doing things like that, is to travel the collection backwards

var nmax = blockSet.length - 1;
for (var n=nmax; n>=0; n--) {
    blockSet[n].className = 'block-selected';
}

That isolates you from changes in the collection

Upvotes: 5

clickbait
clickbait

Reputation: 2998

This error occurs because blockSet is an HTMLCollection, which is "live." HTMLCollections update as the elements on the page update.

Every time you swap a className, you're making blockSet shorter one by one.

To solve this problem, just do this instead:

for (var i = 0; i < 8; i += 1) {
    blockSet[ 0 ].className = "block-selected";
}

That way you chunk your HTMLCollection off one by one.

Iteration 1: [ div1, div2, div3, div4, div5, div6, div7, div8 ]

Iteration 2: [ div2, div3, div4, div5, div6, div7, div8 ]

Iteration 3: [ div3, div4, div5, div6, div7, div8 ]

Iteration 4: [ div4, div5, div6, div7, div8 ]

Iteration 5: [ div5, div6, div7, div8 ]

Iteration 6: [ div6, div7, div8 ]

Iteration 7: [ div7, div8 ]

Iteration 8: [ div8 ]

Hope that helps!

Upvotes: 2

Maltir
Maltir

Reputation: 64

Your error occur because .className return a live HTMLCollection. So when you do something like this:

blockSet[0].className = "block-selected";

Your collection blockSet[0] will become the blockSet[1]. So when you execute the line :

blockSet[1].className = "block-selected";

You not change the blockSet[1] you think but you change the starting blockSet[2].

So you can do this:

var blockSet = document.getElementsByClassName("block-default");
var blockSetLength = blockSet.length;
for(var i=0;i<blockSetLength;i++){
    blockSet[0].classList.add('block-selected'); //add the new class first
    blockSet[0].classList.remove('block-default'); //delete the old one
}

http://jsfiddle.net/94dqffa7/

I think it's the best way to do it. Because the classList.add() and classList.remove() will help you to change your class without eleminate the others class you have on your div (if you have some). Also, you need to add the new one before remove the old one or you will have the same problem as before.

Upvotes: 0

OBezzad
OBezzad

Reputation: 144

This error is because you need to do

blockSet[0].className = 'block-selected'

You don't to write blockSet[1] , blockSet[2] ...

You can do:

for (var s=0;s<blockSetLength;s++) {
  blockSet[0].className = 'block-selected';
}

Upvotes: 0

Jan Turoň
Jan Turoň

Reputation: 32912

document.getElementsByClassName returns a HTMLCollection object, which is live

An HTMLCollection in the HTML DOM is live; it is automatically updated when the underlying document is changed.

So when you call

blockSet[0].className = "block-selected";

You changed the underlying document and that item is not in the collection anymore (the blockSet[0] is now the second item in your original selection).

Upvotes: 3

xsh.7
xsh.7

Reputation: 6250

By assigning a value to .className you overwrite every class on that element. What you might want to take a look at is the .classList attribute.

Remove a class:

blockSet[0].classList.remove('block-default');

Add the new class:

blockSet[0].classList.add('block-selected');

A good point to start with, when your trying to do stuff, jQuery usually did for you, is http://youmightnotneedjquery.com/

Upvotes: 6

Akshay
Akshay

Reputation: 14348

This worked for me

var blockSet = document.getElementsByClassName("block-default");
    var blockSetLength = blockSet.length;
 blockSet[0].className = "block-selected";
 blockSet[0].className = "block-selected";
 blockSet[0].className = "block-selected";
 blockSet[0].className = "block-selected";
 blockSet[0].className = "block-selected";
 blockSet[0].className = "block-selected";
 blockSet[0].className = "block-selected";
 blockSet[0].className = "block-selected";

   
.block-default {
    width: 100px;
    height:50px;
    background-color: green;
    border: 1px solid red;
    padding:10px;
}

.block-selected {
    width: 100px;
    height:50px;
    background-color: blue;
    border: 1px solid white;
    padding:10px;
 }
<div class ="block-default">BLOCK1</div>
<div class ="block-default">BLOCK2</div>
<div class ="block-default">BLOCK3</div>
<div class ="block-default">BLOCK4</div>
<div class ="block-default">BLOCK5</div>
<div class ="block-default">BLOCK6</div>
<div class ="block-default">BLOCK7</div>
<div class ="block-default">BLOCK8</div>

Upvotes: 1

Related Questions