Javascript event handler with parameters
Asked Answered
E

9

128

I want to make an eventHandler that passes the event and some parameters. The problem is that the function doesn't get the element. Here is an example:

doClick = function(func){
    var elem = .. // the element where it is all about
    elem.onclick = function(e){
        func(e, elem);
    }
}
doClick(function(e, element){
    // do stuff with element and the event
});

The elem must be defined outside of anonymous function. How can i get the passed element to use within the anonymous function? Is there a way to do this?

And what about addEventListener? I don't seem to be able to pass the event through an addEventListener at all do I ?

Update

I seemed to fix the problem with 'this'

doClick = function(func){
    var that = this;
    this.element.onclick = function(e){
        func(e, that);
    }
}

Where this contains this.element that i can access in the function.

The addEventListener

But i'm wondering about the addEventListener:

function doClick(elem, func){
    element.addEventListener('click', func(event, elem), false);
}
Enyedy answered 3/4, 2012 at 19:32 Comment(3)
I'm sorry, i was trying to make an example of my situation but this didn't went well, i updated the code.Enyedy
Would you be looking for e.target / e.currentTarget?Stoicism
how to send a parameter if im using this : ok.addEventListener("click", this.changeText.bind(this) );Goodspeed
B
136

I don't understand exactly what your code is trying to do, but you can make variables available in any event handler using the advantages of function closures:

function addClickHandler(elem, arg1, arg2) {
    elem.addEventListener('click', function(e) {
        // in the event handler function here, you can directly refer
        // to arg1 and arg2 from the parent function arguments
    }, false);
}

Depending upon your exact coding situation, you can pretty much always make some sort of closure preserve access to the variables for you.

From your comments, if what you're trying to accomplish is this:

element.addEventListener('click', func(event, this.elements[i]))

Then, you could do this with a self executing function (IIFE) that captures the arguments you want in a closure as it executes and returns the actual event handler function:

element.addEventListener('click', (function(passedInElement) {
    return function(e) {func(e, passedInElement); };
}) (this.elements[i]), false);

For more info on how an IIFE works, see these other references:

Javascript wrapping code inside anonymous function

Immediately-Invoked Function Expression (IIFE) In JavaScript - Passing jQuery

What are good use cases for JavaScript self executing anonymous functions?

This last version is perhaps easier to see what it's doing like this:

// return our event handler while capturing an argument in the closure
function handleEvent(passedInElement) {
    return function(e) {
        func(e, passedInElement); 
    };
}

element.addEventListener('click', handleEvent(this.elements[i]));

It is also possible to use .bind() to add arguments to a callback. Any arguments you pass to .bind() will be prepended to the arguments that the callback itself will have. So, you could do this:

elem.addEventListener('click', function(a1, a2, e) {
    // inside the event handler, you have access to both your arguments
    // and the event object that the event handler passes
}.bind(elem, arg1, arg2));
Bylaw answered 3/4, 2012 at 19:41 Comment(8)
yes this almost what i'm trying to do, but what if the anonymous function get passed as parameter by the addClickHandler and you want to give this function some parameters with the event like: element.addEventListener('click', func(event, this.elements[i]));Enyedy
@Enyedy - You can't do element.addEventListener('click', func(event, this.elements[i])). Javascript just doesn't work that way. There are other ways around that, using closures, but you can't do it the way you wrote. addEventListener calls it's callback with exactly one argument (the event) and you can't change that in any way.Bylaw
I added another example of doing this to the end of my answer.Bylaw
@Bylaw What you could do is use the bind method. function eventFunction (arg, evt) { console.log(arg[0],arg[1],arg[2]) console.log(evt) } var el = document.getElementById('elementID'); el.addEventListener('click', eventFunction.bind(el,[1,2,3])) Note that the event argument is always last in the arguments for the function, and is not explicitly declared in the bind method.Gourmandise
@Bylaw I know it has been 3 years, but could you (or someone) explain the last line? "} (this.elements[i]) , false);" How does the parameter "this.elements[i]" gets passed? I know it works but I haven't found any information on why it works, not even how is this JavaScript capability of passing a value after the function definition called.Sonny
@Bosh19 - see what I've added to the end of my answer to explain the construct you asked about. It's an immediately invoked function expression (IIFE) which is essentially an anonymously declared function that is immediately executed. It's a shortcut for defining a function and then calling it - useful when you want the body inline and it will not be called anywhere else so it doesn't need a name.Bylaw
Very clever and excellent example and usage of closures. Thanks! Works like a charm.Nyssa
Wow, I know a fair amount of JavaScript but you just doubled my knowledge in one post lol.Reine
V
58

It is an old question but a common one. So let me add this one here.

With arrow function syntax you can achieve it more succinct way since it is lexically bound and can be chained.

An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords.

const event_handler = (event, arg) => console.log(event, arg);
el.addEventListener('click', (event) => event_handler(event, 'An argument'));

If you need to clean up the event listener:

// Let's use use good old function sytax
function event_handler(event, arg) {
  console.log(event, arg);
}

// Assign the listener callback to a variable
var doClick = (event) => event_handler(event, 'An argument'); 

el.addEventListener('click', doClick);

// Do some work...

// Then later in the code, clean up
el.removeEventListener('click', doClick);

Here is crazy one-liner:

// You can replace console.log with some other callback function
el.addEventListener('click', (event) => ((arg) => console.log(event, arg))('An argument'));

More docile version: More appropriate for any sane work.

el.addEventListener('click', (event) => ((arg) => {
  console.log(event, arg);
})('An argument'));
Vault answered 27/2, 2018 at 18:40 Comment(4)
This seems like a reasonable solution but what about when you have to remove the same event listener ?Tahsildar
@SnnSnn: +1. I used this to provide a class property to the event handler. I just passed in this and the handler could access what it needs.Languishment
If my understanding is correct, you'd actually be passing an anonymous function as your event handler : (e)=>{ .... } and not yourEventHandlerFunction(). This will cause problems if you wish to remove the the event handler later, since you passed in an anonymous function to begin with???Tilley
Answer clearly explains how to remove event listener, so no problem there.Vault
G
11

Something you can try is using the bind method, I think this achieves what you were asking for. If nothing else, it's still very useful.

function doClick(elem, func) {
  var diffElem = document.getElementById('some_element'); //could be the same or different element than the element in the doClick argument
  diffElem.addEventListener('click', func.bind(diffElem, elem))
}

function clickEvent(elem, evt) {
  console.log(this);
  console.log(elem); 
  // 'this' and elem can be the same thing if the first parameter 
  // of the bind method is the element the event is being attached to from the argument passed to doClick
  console.log(evt);
}

var elem = document.getElementById('elem_to_do_stuff_with');
doClick(elem, clickEvent);
Gourmandise answered 5/1, 2015 at 13:54 Comment(0)
S
2

Given the update to the original question, it seems like there is trouble with the context ("this") while passing event handlers. The basics are explained e.g. here http://www.w3schools.com/js/js_function_invocation.asp

A simple working version of your example could read

var doClick = function(event, additionalParameter){
    // do stuff with event and this being the triggering event and caller
}

element.addEventListener('click', function(event)
{
  var additionalParameter = ...;
  doClick.call(this, event, additionalParameter );
}, false);

See also Javascript call() & apply() vs bind()?

Shoreless answered 16/6, 2016 at 8:48 Comment(0)
T
2

Short answer:

x.addEventListener("click", function(e){myfunction(e, param1, param2)});

... 

function myfunction(e, param1, param1) {
    ... 
} 
Tinge answered 31/12, 2018 at 9:57 Comment(13)
This doesn't work correctly. If the value of your params change each time the event handler is set, it will produce unexpected results because it doesn't close over the value of param1 or param2. If those values change, they will not be set to the value which they were during the time the event handler was created. They will be set to the current value only. In addition, this is no different from setting a variable outside the scope of the event listener function and referencing that variable in the event handler, so it doesn't accomplish passing in parameters at all.Nyssa
I didn't really get your point so my response might be off. Anyway, the event handler is set once (not always I guess but usually that's what someone wants). Secondly, this pretty much says that the handler function is myfunction and that it has 2 parameters plus the "event" variable. Thirdly, yes it does work and the results are exactly as expected.Tinge
No, it does not work. I tested it exactly as you did and my point remains valid. If param1 is noodle the first time the addEventListener is called, and the second time you call it, param is cheese then whenever the event listener click fires, param1 will be set to cheese and not noodle. Therefore, it does not use a "closure" to remember the value that existed at the time of the event listener being added. There are scenarios where the event listener must be added multiple times which need not be illustrated for my point to be valid.Nyssa
I'm not an expert and I haven't heard of "closure" in programming before, so I don't get what you mean by that.Tinge
You said "if param1 is noodle the first time the addEventListener...". Param1 is a function parameter and it doesn’t matter what it is when you connect the event with your function. It only matters every time the event actually occurs and it does change since that's the point of passing parameters to functions, otherwise you just use constants. Ofcourse it won't remember the value of param1 when the even listener was added, why would it?Tinge
Lastly, you said "there are scenarios...". Yes, there are and that's pretty much what I said by saying "the event handler is set once (not always I guess but usually that's what someone wants)".Tinge
You are refering to your point but seriously I have no idea what you are talking about man.Tinge
Regarding closures, take a few minutes to read this, it will make you a much more proficient JavasScript programmer: developer.mozilla.org/en-US/docs/Web/JavaScript/Closures "and it does change since that's the point of passing parameters to functions" This isn't true for an event handler. A "click" event handler has no function params passed to it upon the click occurring. Therefore the value at the declaration time matters. I will post a js snippet for you to see what I mean.Nyssa
Ok, actually, after recreating my scenario in codesandbox, you are right, the method you described does in fact work, as does the "closure" method I referred to. See this code sandbox: codesandbox.io/s/6vpm3q95z3 Note that it will immediately open 3 popup windows with a single button in it. Clicking that button will print into the codesandbox console with the name of the popup instance and some params passed to it. Click the "Console" button on the bottom of codesandbox to see. Also, clicking the 6 paragraphs of text will also print some params. It appears both methods work fine.Nyssa
Something must be causing it to not work in my application, so I will investigate further into that. Checkout that codesandbox to see an example of a "closure". Each popup window's button should print a different console message with an incremented number in it, 0,1, 1,2, and 2,3 for each instance respectively. Thanks for hearing me out on this and motivating me to check further into this. I gave you an upvote since your answer is in fact correct and a valid solution. Lastly there is no need to make the function named, the body can be directly in the event handler callback and still workNyssa
P.S. you have to unblock the popups for the codesandbox to workNyssa
Thank you for checking it out, I will definitely visit the links you posted, as well. Also, I have to say you are very polite and honest sir, keep it up and have a good day :)Tinge
A danger to using anonymous functions with addEventListener, you cannot remove them. So if they are added in a function, every time that function is called will add a new event listener, leading to a memory leak.Grilse
S
1

Try,

const handleOnChange = (event, otherParameter) => {
    console.log(`${event.target.value}`);
    console.log(`${otherParameter}`);
}

<Select onchange= (event) => {
    handleOnChange(event, otherParameter);
}>
Shakiashaking answered 4/7, 2021 at 9:27 Comment(0)
U
0

this inside of doThings is the window object. Try this instead:

var doThings = function (element) {
    var eventHandler = function(ev, func){
        if (element[ev] == undefined) {
            return;
        }

        element[ev] = function(e){
            func(e, element);
        }
    };

    return {
        eventHandler: eventHandler
    };
};
Ultimo answered 3/4, 2012 at 19:50 Comment(0)
F
0

This is an old topic and @jfriend00 did a really nice work explaining the basics behind closure function calls.

Lets update the snippets to ES6 and also use another technique that might help us to overcome this kind of scenarios.

First lets define a function handler this function will receive two parameters fnc and val where the fnc is the custom function that will handle the event usually is names on + theEventName in this case onClickEvent. There is nothing new here. Now let's define the onclick listener on the element red and it will be called clickEventHandler like this:

red.onclick = clickEventHandler

As we noticed clickEventHandler does not have any parameter Teo, how can I send the event and the index value to the handler?

Good question, first let's call the handler function to create our custom onClickEvent handler like this:

handler(onClickEvent, index)

But Teo, onClickEvent also does NOT have parameters, How we can send the closure function onClickEvent, the event produced by onclick and index value?

Patience my young padawan. First which function will handle the event? In our scenary will be onClickEvent so lets define this function to receive the event e and the index value v:

function onClickEvent(e, v) {
    console.log(`${e.currentTarget.className} -> [x: ${e.x}, y: ${e.y}] | val: ${v}`)
}

Now lets implement our handler function. This function will return a closure function definition that will execute the function defined in fnc, also will merge the parameters received by the closure function and the val paramenter like this:

function handler(fnc, val) {
    return (...params) => {
    const ctx = this
    const arg = [...params, val]
    fnc.apply(ctx, arg)
    }
}

To call the fnc we used apply(). This method calls the specified function with a given a context ctx, and arg as arguments provided as an array. Therefore, the fnc is executed usint the closure function context ctx.

This is a working snippet for this scenario.

const red = document.querySelector('.red')
const blue = document.querySelector('.blue')

function handler(fnc, val) {
  return (...params) => {
    const ctx = this
    const arg = [...params, val]
    fnc.apply(ctx, arg)
  }
}

function onClickEvent(e, v) {
  console.log(`${e.currentTarget.className} -> [x: ${e.x}, y: ${e.y}] | val: ${v}`)
}

const index = 50

// Define the handler function
const clickEventHandler = handler(onClickEvent, index)

// Call the debounced function on every mouse move
red.onclick = clickEventHandler

blue.addEventListener('click', clickEventHandler)
.boxes {
  width: 100%;
  height: 120px;
  display: flex;
  flex-flow: row nowrap;
}

.red {
  background: red;
}

.blue {
  background: blue;
}

.box {
  width: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
}
<div class='boxes'>
  <div class='red box'>Click Me</div>
  <div class='blue box'>Not ready</div>
</div>

Now let's apply this concept to your scenario:

const red = document.querySelector('.red')
const blue = document.querySelector('.blue')
red.clicked = false

let clickEventHandler

function handler(fnc, val) {
  return (...params) => {
    const ctx = this
    const arg = [...params, val]
    fnc.apply(ctx, arg)
  }
}

function onRedClickEvent(e) {
  if (red.clicked) return

  red.clicked = true
  red.textContent = 'Clicked'
  blue.textContent = 'Ready'
  console.log(`${e.currentTarget.className} -> blue is ready`)

  // Define the handler function
  clickEventHandler = handler(onClickEvent, red)

  // Call the debounced function on every mouse move
  blue.addEventListener('click', clickEventHandler)
}

function onClickEvent(e, elem) {
  red.clicked = false
  red.textContent = 'Click Me'
  blue.textContent = 'Not Ready'

  console.log(`${e.currentTarget.className} -> [x: ${e.x}, y: ${e.y}] | elem: ${elem.className}`)

  blue.removeEventListener('click', clickEventHandler)
}



red.onclick = onRedClickEvent
.boxes {
  width: 100%;
  height: 120px;
  display: flex;
  flex-flow: row nowrap;
}

.red {
  background: red;
}

.blue {
  background: blue;
}

.box {
  width: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  user-select: none;
}
<div class='boxes'>
  <div class='red box'>Click Me</div>
  <div class='blue box'>Not ready</div>
</div>
Firebrand answered 28/7, 2022 at 6:47 Comment(0)
B
-2
let obj = MyObject();

elem.someEvent( function(){ obj.func(param) } );

//calls the MyObject.func, passing the param.
Blackjack answered 16/11, 2016 at 13:30 Comment(1)
param can be also a callback function (in the -say- global context) and so can be easily called-back, whith the requested parameters.Blackjack

© 2022 - 2024 — McMap. All rights reserved.