1

I've looked through the plethora of articles about making an array from a NodeList (in this case, document.getElementsByClassName()) in order to iterate and modify the className property on each node...however I cannot accomplish any kind of change. Seems a bit ridiculous, but here is the code:

EDIT: The problem appears to be related to creating an Array from document.getElementsByClassName. It's creating something that looks like an array, but isn't truly an Array. See screenshot below.

var cols = document.getElementsByClassName('cell symmetry');
[].forEach.call(cols, (node) => {
    node.className += ' transiting';
    console.log(node.className);
});

cols returns an array of table cells. But using the assignment operator or trying to append another class onto the node does nothing. have tried document.querySelector, while loops, for loops, Array.prototype.slice.call..., but no dice.

Screenshot of the console:

enter image description here

Jose
  • 3,591
  • 6
  • 23
  • 40
  • 1
    Seems to be working here https://jsfiddle.net/qw0fnLhv/ . You could use `classList.add` method which is cleaner – Sushanth -- Jun 20 '17 at 00:44
  • 1
    Have you looked at classList? Something like classList.add("transiting") https://developer.mozilla.org/en-US/docs/Web/API/Element/classList – Rob Zuber Jun 20 '17 at 00:44
  • That's bizarre. Yeah I tried `classList`. I see it working fine on the fiddle. Must mean there's something else preventing it from working. Thank you both though, this helps. – Jose Jun 20 '17 at 00:48
  • 1
    @Jose `classList.add()` works fine, see my answer. – zer00ne Jun 20 '17 at 01:11
  • @zer00ne You're right, it does. However, the problem runs deeper. It appears that trying to create an array from the NodeList is failing and spitting out an empty array. See the screenshot. When you expand an array in the console, it usually does not look like that^. I don't recall it having a `length` property when you expand it. – Jose Jun 20 '17 at 01:14
  • 1
    `getElementsBy*` methods collect "live" so on each iteration if the NodeList actually changes, then it changes immediately. If that happens in then the `.length` will change on each iteration. You'll end up with half the `.length` and wacky results. `querySelectorAll()` returns a "static" NodeList. `.length` will not change. http://htmlcssjavascript.com/javascript/the-javascript-nodelist-and-you-watch-where-you-point-that-thing-soldier/ – zer00ne Jun 20 '17 at 01:18
  • Have tried `document.querySelectorAll`. Returns empty when logged from the actual file...returns full when logged from the console... Will get back to this later. Some Inception mumbo-jumbo going on here. – Jose Jun 20 '17 at 01:24

2 Answers2

2

Details are commented extensively in the demo

Demo

/* 
|>.querySelectorAll()
|| Collect all .cell into a "static" NodeList

|>Array.from()
|| Convert NodeList into an array

|~.getElementsByClassName() 
|| Does the same as .querySelectorAll({.class})
|| except that gEBCN() returns a "Live Collection"
|| which means that it changes in real time so
|| that there's a good chance you may end up with
|| half the expected length of the collection.
|| 99% of the time .qSA() is the better choice
|| because it returns a "static" NodeList.
*/
var cellArray = Array.from(document.querySelectorAll('.cell'));

/*
|>.forEach()
|| Iterate through array and invoke a function on 
|| each node.
*/
cellArray.forEach(function(node, idx) {

  /*
  |>.classList.add()
  || Adds a class to a node's list of classes
  */
  node.classList.add('transiting');
});
table {
  width: 50%
}

.cell {
  border: 1px solid blue;
  background:blue;
}

.transiting {
  border: 5px solid red;
}
<table>
  <tr>
    <td class='cell'>&nbsp;</td>
    <td class='cell'>&nbsp;</td>
    <td class='cell'>&nbsp;</td>
  </tr>
  <tr>
    <td class='cell'>&nbsp;</td>
    <td class='cell'>&nbsp;</td>
    <td class='cell'>&nbsp;</td>
  </tr>
  <tr>
    <td class='cell'>&nbsp;</td>
    <td class='cell'>&nbsp;</td>
    <td class='cell'>&nbsp;</td>
  </tr>
  <tr>
    <td class='cell'>&nbsp;</td>
    <td class='cell'>&nbsp;</td>
    <td class='cell'>&nbsp;</td>
  </tr>
</table>
Community
  • 1
  • 1
zer00ne
  • 31,838
  • 5
  • 32
  • 53
1

There doesn't seem to be anything wrong with your example as you've described it. Notice below that all of mine turn blue, which means the new d class is getting added.

var cols = document.getElementsByClassName('cell symmetry');
[].forEach.call(cols, (node) => {
    node.className += ' transiting';
    console.log(node.className);
});
.transiting {
  color: #00F;
}
 <p class="cell symmetry">A</p>
 <p class="cell symmetry">B</p>
 <p class="cell symmetry">C</p>

I think something else may be going on beyond your snippet though. Your nodes should not have a className outputting of td.cell.symmetry. The className should be along the lines of cell symmetry transitioning.

Is there any more code, possibly some other place you are setting node.className to something? It's possible it may be getting it in a funky state that is then messing with this bit of code.

You might also try console.log(node.className) before you try to change it.

samanime
  • 21,211
  • 7
  • 69
  • 122
  • Yeah. So `className` I assume is appearing because this is part of a React app. However, I don't see why `classList` does not work either. I'll do some more digging and update this soon. – Jose Jun 20 '17 at 00:53
  • Ah, okay. React makes the situation a little different. You shouldn't use `document.querySelector` and the like. React doesn't like that. Instead you should be setting the className when in your render function for the components, based on some properties you send in. – samanime Jun 20 '17 at 00:58
  • Right, except I'm doing toggling on CSS classes on certain DOM elements. `ReactDOM.findDOMNode` is deprecated and using `this.refs` won't work here because the array of DOM nodes is not all in a single component. So that complicates it a bit. Also staying far away from jQuery. – Jose Jun 20 '17 at 01:00
  • Indeed, but that's kind of the gotcha of React. When using React, you can't really do direct DOM manipulation. You need to approach it from a React way or else weird things will happen. You'll likely need to refactor your code in some way to take care of this. Another possible option is to apply the transition by targeting some parent class with the CSS. – samanime Jun 20 '17 at 01:02
  • Okay update: using debugger, apparently `cols` is an empty array when logged. Makes no sense, until I set another breakpoint inside the loop and notice it never hits the break point. So then the question is **why is the array of nodes empty, yet displays length: 50 after all the page is done rendering?** – Jose Jun 20 '17 at 01:04
  • 1
    We'd need a much large hunk of code for that. It's possible that you're seeing the 50 on a second loop through of the same code, with the first time being empty (possibly pre-render). Remember when React first starts up, there is nothing. – samanime Jun 20 '17 at 01:06
  • You're right. I think I'm going to go on my own from here and update this once I figure this out. – Jose Jun 20 '17 at 01:16