This question is to see if I can get some input on the "design pattern" I tried to implement here. I'm just learning about closure in JavaScript and I think I'm starting to get it. I'm wondering if the way I wrote this code is or is not terrible stylistically.
The code in question is attempting to answer a challenge given in the JavaScript: The Hard Parts (by Will Sentance) on Frontend Masters. Based on the challenge, it seems I went above and beyond as the solution provided, when run, doesn't work properly.
The challenge:
Write a function changeColor that when invoked will first check if the current page background color is "rgb(221, 238, 255)". If it is, it changes the color to "rgb(255, 238, 221)". If it isn't, it sets the color to "rgb(221, 238, 255)".
Add a click event listener to button #1 above (it has an id of "activate"). On click, the button should log to the console "clicked 1". It should also set up a click event listener on button #2 (id of "color"). That listener should log to console "clicked 2" and then call the changeColor function you just created.
Clear the console and hit the 'Run with JS' button. Look at what code has run by analyzing the console. Then try to change the background color by clicking button #2. What needs to happen for the button to work?
Based on the wording of the challenge it seemed like the solution that was hinted at was (in pseudo-ish code):
// Provided solution to the question above
// Assume all necessary HTML is in place
activationButton.addEventListener("click", () => {
console.log("clicked activation button")
colorChangerButton.addEventListener("click", () => {
console.log("clicked color changer button")
changeTheColor()
})
})
The obvious problem with the code above is that a new event listener will be added to the colorChangerButton every time you click the activationButton. This was where I started thinking about trying to use a closure to keep track of a state variable so that I could make sure that the event listener only gets added to the second button once. Also, this is the solution provided in the solution set for the challenge.
To that end, this is what I came up with (please assume that the correct HTML is there, etc):
(function () {
let clickCount = 0
function changeColor() {
if (document.body.style.backgroundColor === "rgb(221, 238, 255)") {
document.body.style.backgroundColor = "rgb(255, 238, 221)"
} else {
document.body.style.backgroundColor = "rgb(221, 238, 255)"
}
}
function activateButton2() {
const btn2 = document.querySelector('#color') // Appropriate to grab button here?
btn2.addEventListener("click", () => {
console.log("clicked #2")
changeColor()
})
}
const btn1 = document.querySelector('#activate') // Appropriate to have variable declaration here?
btn1.addEventListener("click", () => {
clickCount++
if (clickCount === 1) {
activateButton2()
}
console.log("clicked #1")
return
})
// Initially had an un-necessary return statement here
})()
The code above - that I wrote - is working how I'd expect it to - the color change functionality and the console logs are firing as I would expect.
One question I had about my implementation was about containing the button variables inside the IIFE. I feel like I see a lot of front-end scripts putting all the query selectors at the top of the script - is there a reason to do that vs. including them within a function like this? If I understand closure correctly, once the event handlers are created there is a persistent reference to everything in the IIFE, so I can fire this function and the buttons and their handlers persist and behave as expected.
It also seems like I could probably use a boolean to track the "clicked" status of the first button, and a switch statement for checking the background color of the page.
Any other pointers anyone wants to offer?