0

I have the following markups and scripts to simulate a simple calculator

HTML:

<input id="txtResult" type="text" readonly="readonly" /><br />
    <input id="txtInput" type="text" /><br />
    <button id="btn7" class="number">7</button>
    <button id="btn8" class="number">8</button>
    <button id="btn9" class="number">9</button><br />
    <button id="btn4" class="number">4</button>
    <button id="btn5" class="number">5</button>
    <button id="btn6" class="number">6</button><br />
    <button id="btn1" class="number">1</button>
    <button id="btn2" class="number">2</button>
    <button id="btn3" class="number">3</button><br />
    <button id="btnClear">C</button>
    <button id="btn0" class="number">0</button>
    <button id="btnClearEntry">CE</button><br />
    <button id="btnPlus">+</button>
    <button id="btnMinus">-</button>

Javascript:

The scripts tried to attach a click event handler to the buttons.

window.onload = function()
{
    var buttons = document.getElementsByClassName("number");

    for (var btn in buttons)
    {
        console.log(btn); //print the value of "buttons", i.e 1, 2 ,3

        btn.addEventListener("click", numberClick, false);  // JavaScript runtime error: Object doesn't support property or method 'addEventListener'
    }


    //However accessing an element directly works !!!
    var btn5 = document.getElementById("btn5");

    btn5.addEventListener("click", numberClick, false);
}

function numberClick()
{
    var input = document.getElementById("txtInput");
    input.value = input.value == "0" ? this.innerText : input.value + this.innerText;
}

The problem here is when I looped through the buttons and tried to attach the event handler, it threw an exception. However, when a button was retrieved directly by using getElementById, the code worked.

Could you please explain why?

Toan Nguyen
  • 11,263
  • 5
  • 43
  • 59

2 Answers2

2

document.getElementsByClassName() returns an array like object that is better iterated like this:

var buttons = document.getElementsByClassName("number");
for (var i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener("click", numberClick, false);
}

You can iterate arrays the way you were (not recommended though because it iterates all enumerable properties not just array elements which can sometimes mess up the code), but if you did, then buttons[btn] would be the object, not btn like you were trying to use.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Nobody cares about IE. Too terrible to support. – bjb568 Mar 18 '14 at 21:49
  • @bjb568 - actually, I don't think the return value from `getElementsByClassName()` supports `.forEach()` because it is a nodeList or HTMLCollection, not an array. – jfriend00 Mar 18 '14 at 21:51
  • You could convert it. http://stackoverflow.com/questions/3199588/fastest-way-to-convert-javascript-nodelist-to-array – bjb568 Mar 18 '14 at 21:53
  • nodeLists generally don't support array methods. every, some, forEach etc. doesn't work. – adeneo Mar 18 '14 at 21:53
  • @bjb568 - yes you could convert it, but then why bother using `.forEach()`. It doesn't buy you anything over a `for` loop if you have to add extra steps just to convert it into an array. – jfriend00 Mar 18 '14 at 21:53
  • I didn't say you should do it. I just said it was an option. – bjb568 Mar 18 '14 at 21:55
2

A nodeList is array-like, so a regular for loop should be used, and querySelectorAll has better support

window.onload = function() {

    var buttons = document.querySelectorAll("number");

    for (var i=buttons.length; i--;) {
        buttons[i].addEventListener("click", numberClick, false);
    }
}

When using for-in loops, it's

for ( key in object )

so it would be

for (var btn in buttons) {
    buttons[btn].addEventListener
}
adeneo
  • 312,895
  • 29
  • 395
  • 388