Search code examples
javascriptevent-handlingdom-eventsclosurestoggle

Closures and Events


I am learning JavaScript. I was practicing applying closures but I got stuck and am not able to find the issue with my code.

function change_greeting() {
  let on_or_off = 1;
  return function button() {
    if (on_or_off) {
      document.getElementById("demo") = "Hello";
    } else {
      document.getElementById("demo") = "Goodbye";
    }
  }
}
const click = change_greeting();
<button onclick=click()>Click Me</button>
<p id="demo"></p>

I tried a similar example without mixing it up with events, at that time it succeeded. I am not sure as to what change I must bring to my code.


Solution

    • to toggle on_or_off 1,0,1,0,etc... use the Remainder operator % after an increment: on_or_off = ++on_or_off % 2 or simply using XOR assignment on_or_off ^= 1;
    • avoid the use of on* inline attribute handlers. Use addEventListener() instead
    • use Node.textContent when you want to output a string to an Element's content

    const elDemo = document.getElementById("demo");
    const elBtn = document.getElementById("btn");
    
    function change_greeting() {
    
      let on_or_off = 1;
      
      return function () {
        if (on_or_off) {
          elDemo.textContent = "Hello";
        } else {
          elDemo.textContent = "Goodbye";
        }
        
        on_or_off = ++on_or_off % 2; // loop 0,1,0,1,...
      }
    }
    
    const click = change_greeting(); // call and return closure fn
    elBtn.addEventListener("click", click);
    <button id="btn">Click Me</button>
    <span id="demo"></span>

    And here's the example using XOR assignment:

    const elDemo = document.getElementById("demo");
    const elBtn = document.getElementById("btn");
    
    const change_greeting = ()  => {
      let isOn = 1;
      return () => {
        elDemo.textContent = isOn ? "Hello" : "Goodbye";
        isOn ^= 1; // toggle 0,1,0,1...
      }
    };
    
    const click = change_greeting();
    elBtn.addEventListener("click", click);
    <button id="btn">Click Me</button>
    <span id="demo"></span>

    For completeness, instead of using integers 1,0,1,0... you could instead use a boolean variable and switch it using isOn = !isOn

    const elDemo = document.querySelector("#demo");
    const elBtn = document.querySelector("#btn");
    
    const change_greeting = () => {
      let isOn = true;
      return () => {
        elDemo.textContent = isOn ? "Hello" : "Goodbye";
        isOn = !isOn; // toggle boolean
      }
    };
    
    elBtn.addEventListener("click", change_greeting());
    <button id="btn">Click Me</button>
    <span id="demo"></span>

    and, as you can see in the latest example, there's no need to store the closure in a click variable. If not used elsewhere you can simply call it within the click handler .addEventListener("click", change_greeting());

    Additionally (as seen in this examples), you can replace the 5 lines of if/else with a readable, well known and loved Ternary (conditional) operator statement ? truthy : falsy