0

I am working on a basic JavaScript program that plays sounds when I click buttons. I am using a switch to be able to tell which sound should be played. I am also using event listeners on each button to respond to clicks.

When I do something like:

currentButton.addEventListener("click", function() {
    // code here
})

It works fine. However, I want to try defining the function outside of the event listener, and then passing it to the event listener.

I have a simplified example here:

function setPathAndPlay(currentButton) {
    let audioPath;
    switch (currentButton.innerHTML) {
        case "w": {
            audioPath = "./sounds/crash.mp3";
            break;
        }
    }
    const audio = new Audio(audioPath);
    audio.play();
}

for (let currentButton of drumArray) {

    currentButton.addEventListener("click", setPathAndPlay(currentButton))
}

This does not work. I believe what is happening is setPathAndPlay(currentButton) is directly running as it is a function call, which is causing problems. How can I pass the currentButton to setPathAndPlay though? Since that function needs access to the button.

I'm not sure what to try to fix this. I believe something with an arrow function or some sort of wrapper function may work. I am looking to find a solution as simple as possible to help me understand. Thank you!

Ry-
  • 218,210
  • 55
  • 464
  • 476
713sean
  • 313
  • 11
  • Related: [how to identify button clicked](https://stackoverflow.com/questions/58540273/javascript-how-to-identify-button-clicked). – jarmod Oct 28 '22 at 20:10
  • There’s no reason *not* to use arrow functions, but the same way you’ve been putting arbitrary code in the function expression listener, you can make that arbitrary code a call to your function: `function () { setPathAndPlay(currentButton); }` – Ry- Oct 28 '22 at 20:16
  • And more generally in JavaScript: [function pointer with argument as parameter in a function](https://stackoverflow.com/questions/13638635/javascript-function-pointer-with-argument-as-parameter-in-a-function). – jarmod Oct 28 '22 at 20:23
  • The duplication notice on this question links to an outdated answer. – geoffrey Oct 28 '22 at 20:29
  • @Ry- thank you. That clarifies a lot of things. I understand both how to use arrow functions and normal anon functions in this regard now! – 713sean Oct 28 '22 at 20:57

3 Answers3

0

If you want to pass in an arrow function:

currentButton.addEventListener("click", () => setPathAndPlay(currentButton))

This way, you're passing a pointer to the function (with parameters), instead of executing it right away

Arno Teigseth
  • 199
  • 2
  • 10
0

This is actually a really common pattern in javascript. Basically all you want to do is wrap setPathAndPlay(currentButton) in an arrow function so it looks like () => setPathAndPlay(currentButton) then your code should work as expected.

function setPathAndPlay(currentButton) {
    let audioPath;
    switch (currentButton.innerHTML) {
        case "w": {
            audioPath = "./sounds/crash.mp3";
            break;
        }
    }
    const audio = new Audio(audioPath);
    audio.play();
}

for (let currentButton of drumArray) {
    currentButton.addEventListener("click", () => setPathAndPlay(currentButton))
}

Frequently in javascript applications, we have functions that were not written to be used as event handlers but ended up getting used this way. When this happens you sometimes have to wrap your function in a function whose sole purpose is to transform the function into an event handler. An alternative way of doing this would just be to write the function as a curried function that generates an event handler. This makes sense in some cases but not in others. It all depends on how your application is going to change. If you think that the event handler is going to end up doing things other than seting the pathAndPlaying, then you should write it as below, but if instead you think that the setPathAndPlay function will get used in multiple places not relating to event handlers then you could use the above code.

const generateHandleClick = (currentButton) => (e) => {
    let audioPath;
    switch (currentButton.innerHTML) {
        case "w": {
            audioPath = "./sounds/crash.mp3";
            break;
        }
    }
    const audio = new Audio(audioPath);
    audio.play();
}
for (let currentButton of drumArray) {
    currentButton.addEventListener("click", generateHandleClick(currentButton))
}
Zachiah
  • 1,750
  • 7
  • 28
  • Event listeners *are* normal functions. – Ry- Oct 28 '22 at 20:16
  • You were on the right track mentioning `e` but you did not use it ;) – geoffrey Oct 28 '22 at 20:32
  • @Ry- Added the word `semantically` to make it more clear what I meant, I didn't mean there was an actual difference, but that it isn't a function that takes an event – Zachiah Oct 28 '22 at 20:40
  • @geoffrey It isn't necessary to use it – Zachiah Oct 28 '22 at 20:41
  • Sure but if you can use it, why go through the trouble of passing in `currentButton` and creating a new function at each iteration? I mean, this is not specific to your answer, nearly every other answer ignores the event interface completely, I just found it a little amusing that you mention it. – geoffrey Oct 28 '22 at 20:48
  • Yeah agreed @geoffrey, but I was trying to answer his question rather than restructure his code to a better format. Anyways, I think personally I would use the `currentButton` variable because it feels like it more clearly expresses what I'm trying to do. Also you would probably get better type-checking if you were using typescript that way. – Zachiah Oct 28 '22 at 20:51
  • @Zachiah thank you for your time and answer (as well as everyone else commenting on this thread). I follow the first half of your answer, up to but not including "the reason..." Can you explain this more? "the reason this is conceptually necessary is that setPathAndPlay isn't semantically an event listener it is just a normal function. " I don't really understanding what you mean about semantically being an event listener or not. Additionally, I don't fully see the difference between handleClick and setPathAndPlay. – 713sean Oct 28 '22 at 21:01
  • In that case I guess I would either remove the mention of `e` entirely or call `setPathAndPlay(e.target)` from within `handleclick`: that would separate concerns better than currying if you want to allow using `setPathAndPlay` in other contexts than event handling. It could make for a nice 3rd version. – geoffrey Oct 28 '22 at 21:07
  • Sorry to double comment, I think I am understanding what @Zachiah is saying but I am not fully there. const handleClick = (currentButton) => (e) => { This seems to be the crux of my confusion. Can this line of code be further explained? It seems like the handleClick function returns some event or something which then returns more code – 713sean Oct 28 '22 at 21:17
  • I updated the description to be more clear, sorry for the confusion. @713sean that is called currying. You could think of it like: `const handleClick = (currentButton) { return (e) => {} };` In other words it is a function that returns a function – Zachiah Oct 28 '22 at 21:19
  • Here is an article (didn't read just a quick search) about the syntax https://medium.com/@harouny/currying-in-javascript-arrow-function-sequence-2a510441215a – Zachiah Oct 28 '22 at 21:20
  • 1
    Thank you so so much and no need to apologize. I understand it now fully! Thanks to this thread I know now: 1) How to use anonymous functions 2) How to use arrow functions 3) How to use anonymous functions that can execute other functions 4) How to use arrow functions that execute other functions 5) How to use currying – 713sean Oct 28 '22 at 21:22
0

Waw, it brings me back to the old days :)

We use to do it this way

const setPathAndPlay = (e) => {
    // reference e.target instead of currentButton
}

for (let currentButton of drumArray) {
    currentButton.addEventListener("click", setPathAndPlay)
}
geoffrey
  • 2,080
  • 9
  • 13