0

I am working on a simple calculator project

I am attempting to automate adding event listeners to the various numeric buttons (1-9).

The event listeners will listen to click events on the buttons leading to a change in the display section of the HTML (class = .display)

key value pairs being b1-b9 containing the various corresponding values.

I have come up with the below FOR EACH loop. For some reason it causes all numerical buttons to apply the number 9; which i believe is the cause of the for each loop.

I am unsure how to quite fix it. I have also come up with an alternative FOR loop that leads to another problem. pairs[Properties[i]].toString() returns undefined.

interestingly if i swap pairs[Properties[i]].toString() out to just i then the SAME issue occurs

Help really appreciated and thank you..

const pairs = {
    b1: 1,
    b2: 2,
    b3: 3,
    b4: 4,
    b5: 5,
    b6: 6,
    b7: 7,
    b8: 8,
    b9: 9,
};
var Properties = Object.keys(pairs);
function loadButtons () {
    for (var item in pairs) {
        //for each key property in pairs

        console.log(item);
        let targetCell = document.querySelector("." + item.toString())
        // querySelector only targets the FIRST element found
        // in this case only 1 for that name
        console.log(targetCell);
        targetCell.addEventListener('click', () => {
            // you want it to manipulate the display as and when clicked
            var currentDisplay = document.querySelector(".display").innerHTML.toString();
            newDisplay = currentDisplay + pairs[item].toString();
            document.querySelector(".display").innerHTML = newDisplay;

        })


        // console.log(pairs[item]);
        // // pairs[item] retrieves the value to that "key"        
    }
};
function alternative() {
    var i;
    var Properties = Object.keys(pairs);
    for (i = 0; i < Properties.length; i++) {
        let targetCell = document.querySelector("." + Properties[i].toString())
        // querySelector only targets the FIRST element found
        // in this case only 1 for that name
        console.log(targetCell);
        targetCell.addEventListener('click', () => {
            // you want it to manipulate the display as and when clicked
            var currentDisplay = document.querySelector(".display").innerHTML.toString();
            newDisplay = currentDisplay + pairs[Properties[i]].toString();
            document.querySelector(".display").innerHTML = newDisplay;

        })
    };
};

Expected should be clicking of 1 to add a string "1" to the current string of the calculator, so on .

  • I guess you're experimenting closure issue: https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – Gaël S Apr 21 '19 at 09:53
  • I think this might be the exact problem I am facing but I cant figure how to implement the solution – youcantcme Apr 21 '19 at 10:37

3 Answers3

0

You should use event delegation instead of looping over and attaching events to every button. Here's an example:

var keyboard = document.getElementById('keyboard');
var display = document.getElementById('display');

keyboard.addEventListener('click', function(ev) {
  var val = ev.target.id;
  if (ev.target.localName === 'button') {
    display.innerText += val;
  }
});
.calculator {
  width: 300px;
  
  background: whitesmoke;
}

#display {
  height: 50px;
  background: #d2d2d2;
  border: 1px solid #9c9c9c;
  margin: 10px auto 10px;
  font-size: 20px;
  line-height: 50px;
  padding: 0 10px;
}

#keyboard {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
}

button {
  font-size: 20px;
  
  padding: 20px;
  margin: 5px;
  
  cursor: pointer;
}
<div class="calculator">
  <div id="display" contenteditable="true" >
  
  </div>
  <div id="keyboard">
    <button id="0">0</button>
    <button id="1">1</button>
    <button id="2">2</button>
    <button id="3">3</button>
    <button id="4">4</button>
    <button id="5">5</button>
    <button id="6">6</button>
    <button id="7">7</button>
    <button id="8">8</button>
    <button id="9">9</button>
  </div>
</div>
iacobalin
  • 523
  • 2
  • 9
0

i will do it this way but that not the only one i guess and there could be better ways.

   const BUTTONS_NAMESVALUES={
//-- sound awful when a loop can do that!
bt0:0,bt1:1,bt2:2,bt3:3,bt4:4,bt5:5,bt6:6,bt7:7,bt8:8,bt9:9
}
function checkButtonValue(bt){
if(BUTTONS_NAMESVALUES[bt.id] !=null && BUTTONS_NAMESVALUES[bt.id] !='undefined'){
 return bt.innerHTML;
}return;
}

//a button may look like that
<button id="bt1">1</button>

//-- with listener:

document.getElementById('bt1').addEventListener('click', function(e){
let chk=checkButtonValue(this);
if(chk!=null && chk!='undefined' && chk!=''){
document.getElementById('calculatorScreen').innerHTML=''+document.getElementById('calculatorScreen').innerHTML+chk;
}

});

I hope that help. I just replace the class name '.display' who can easily be a source of error(because it's the name of a CSS property and anything is display in HTML+ using an id better in that case because it's a unique element and can't be mistaken, classes aren't) and is not very accurate(as i write a correct constante name who has some meaning instead of pairs who means really nothing ^^).

Neither i 've automated the code into a loop but that's the easy part who is ever in your script.

jean3xw
  • 121
  • 7
  • Thank you for your time on this. Good point on the .display naming. I will attempt to play with this as well – youcantcme Apr 21 '19 at 10:22
0
function onClick(item, pairs) {
return () => {
            // you want it to manipulate the display as and when clicked
            var currentDisplay = document.querySelector(".display").innerHTML.toString();
            var newDisplay = currentDisplay + pairs[item].toString();
            document.querySelector(".display").innerHTML = newDisplay;
}
} 
var Properties = Object.keys(pairs);
function loadButtons () {
    for (var item in pairs) {
        //for each key property in pairs

        console.log(item);
        let targetCell = document.querySelector("." + item.toString())
        // querySelector only targets the FIRST element found
        // in this case only 1 for that name
        console.log(targetCell);
        targetCell.addEventListener('click', onClick(item, pairs)) 


        // console.log(pairs[item]);
        // // pairs[item] retrieves the value to that "key"        
    }
};

Gaël S
  • 1,598
  • 6
  • 15
  • gael , out of curiosity is this the proposed solution off the Closure link you had shared? This is great by the way, I think the closest solution to the original loop question [though iacobalin has a great alternative as well] – youcantcme Apr 21 '19 at 10:58
  • minor typo @ function onClick(event, pairs) { change event to item :) – youcantcme Apr 21 '19 at 11:05
  • Fixed And yes and no, it's the classical way to handle this kind of "problem" – Gaël S Apr 21 '19 at 13:40
  • the event delegation solution is the recommended one. just imagine having some dynamic content in there. you would have to repeat your process every time for the newly inserted elements while the event delegation is a one off process. also it's less performant and more code to write for no reason. – iacobalin Apr 24 '19 at 06:38