Typically, when needing to access an event, you do so via the parameter specified in the callback function:
$button.live("click", function(ev) {
// do something with ev here, like check 'ev.target'
}
But instead (for reasons too complicated to get into here), I do not want to use an anonymous callback function, but instead specify a function to call, like this:
$button.live("click", functionToCall(ev, $(this));
So you'll notice that I included 'ev' as a parameter to functionToCall(), but this obviously won't work because I'm not using the anonymous callback function. But I do still need to access that click event (to check ev.target) within functionToCall(). My question is, how do I access this event? It would be nice if I could do something like this:
$button.live("click", functionToCall($(this));
and
function functionToCall($item) {
var target = $item.event("click").target;
// do something with target
}
Any ideas would be very much appreciated. Thanks.
Original answer
function test(eve) {
alert(eve.type);
alert(this);
//$(this) if you need it as jQuery object
}
$([yourselector]).live("click", test);
You will automatically get the event in the eve parameter.
Answer to extended question in comment
Passing in a parameter makes it a little more difficult. If you need an explanation why I did it like this: Ask.
function helper(customparam) {
return function(eve, selector) { actualFunction(eve, selector, customparam, this) };
}
function actualFunction(eve, selector, customparam, self) {
alert(eve.type);
alert(selector);
alert(customparam);
alert(self); //self is now the element we clicked on
//$(self) if you need it as jQuery object
//using this won't work anymore as this is now window
}
$([yourselector]).live("click", helper([yourparameter]));
You could call a function within the anonymous callback function:
$button.live("click", function(ev) {
functionToCall(ev, $(this));
}
EDIT: I think this may be what you're looking to do (untested):
function handleClick(ev) {
$(this).die("click");
// ...whatever processing to do...
$(this).live("click", handleClick);
}
$button.live("click", handleClick);
I believe the $(this) will refer to the button object in which the function was called.
Remember that jQuery re-assigns this when it calls event handlers, by using the Function methods call or apply. So when functionToCall is invoked, this is the DOM element of $button.
var functionToCall(ev) {
var $this = $(this);
$this.die("click", functionToCall);
// stuff
$this.live("click", functionToCall);
}
$button.live("click", functionToCall);
var mythis = $(this);
var callback = function(ev) {
var target = mythis.event("click").target;
}
$button.live("click", callback);
Related
I am trying to generalize a single function for handling different events in JavaScript. Meaning that depending on the event listener that was called my function needs to behave in a different way. However, I can only pass the reference to my function, which is where my problem arises.
In order for my function to behave properly I need to know which event handler called it. But if I can only pass by reference, I can't pass a parameter which tells me who the function caller was. So is there a way to pass a functions reference but also allow it to accept a parameter?
Here is some general code that hopefully illustrates my question.
btn1.AddEventListener("Click", myFunction);
btn2.AddEventListener("Click", myFunction);
function myFunction(caller){
if(caller == someObject)
{
//do something
}
else
{
//do something
}
}
Inside the function, you can either examine the this, which will refer to the element the listener was added to, or the first parameter (the event)'s currentTarget, which will refer to the same thing:
function myFunction(event) {
if (event.currentTarget === btn1) {
// btn1 was clicked
} else {
// btn2 was clicked
}
}
Closure is useful for this kind of event handlers. To create a closure wrap the function call with another anonymous function like:
btn1.AddEventListener("Click", () => myFunction(btn1));
btn2.AddEventListener("Click", () => myFunction(btn2));
Also, you can pass additional arguments as much as you want like:
btn1.AddEventListener("Click", () => myFunction(btn1, another_param));
function myFunction(btn, param){
// ... do something with param.
}
With the traditional notation, this is equivalent to following:
btn1.AddEventListener("Click", function () {
return myFunction(btn1);
});
I want to call a custom function on mouse enter.
My current code looks like this:
var myFunction = function(element)
{
//do something with element
}
$( selector ).on({
mouseenter: myFunction($(this)),
mouseleave: myFunction($(this))
}, ' selector ');
My problem is that it seems like myFunction() isn't even called. Does this even work or did I mess up something else?
You aren't passing a function. You are calling the function immediately and passing its return value.
Since you want to call the function, with an argument, and the value of that argument isn't determined until the event fires, you need to create a new function.
function use_myFunction_as_event_handler(event) {
myFunction($(this));
}
$('selector').on({
mouseenter: use_myFunction_as_event_handler,
mouseleave: use_myFunction_as_event_handler
}, 'selector');
You are misunderstanding how functions work in javascript. Here's a simple way to make mouseenter call a function.
$(selector).on({
mouseenter: function(event) {
console.log(event);
}
});
This makes sense. When the mouseenter event gets called, the function executes. Great.
Your problem is that this code:
var functionToCall = function(event) {
console.log(event);
}
$(selector).on({
mouseenter: functionToCall(event);
});
does not actually do the same thing as the previous example.
When you put brackets after a function name, you are calling the function. What mouseenter wants is a function reference - javascript will call the function reference you give it for you every time the event occurs.
What we assigned mouseenter to in the first example was an anonymous function reference - we didn't actually call the function we created. Javascript was doing that for us. In the second example, we called the function, so mouseenter was actually set to the return value of the function, not to the function itself.
To fix this, you just need to pass a function reference to the event instead of calling the function. Note, you can't directly pass parameters this way (it's not impossible, but you shouldn't need to), but $(this) will still be the same.
var myFunction = function() {
var element = $(this);
// code...
}
$(selector).on({
mouseenter: myFunction,
mouseleave: myFunction
});
You should call the function as
var myFunction = function(element)
{
//do something with element
}
$('selector').on({
mouseenter: function(){
myFunction($(this));
},
mouseleave: function(){
myFunction($(this));
}
}, 'selector');
Since you want to pass the argument in myFunction, you have do define it like above
Subsequent to removeEventListener in bootstrapped addon not working when addon disabled, I am exploring other possibilities.
Beside using bind() and caching the bound function, is there a way to use 'this' and pass argument?
// works fine but can't pass argeement
contextMenu.addEventListener('popupshowing',
this.contextPopupShowing, false);
// passes the argument but 'this' is no longer available
contextMenu.addEventListener('popupshowing',
function(){this.contextPopupShowing(window);}, false);
I have been using a number of event listeners with bind() and I am looking for alternative methods without using bind()
I even tried to grab window with a recursive function from <menupopup id="contentAreaContextMenu" ...>
Update: bind() interferes with removeEventListener
Since we're talking restartless add-ons... A lot of restartless add-ons use unload and unloadWindow helper functions, to make it easier to implement shutdown properly and also help with stuff like addEventListener, so bear with me for a bit.
The helpers - unload
First, unload is a helper function that you pass another function to, that will be run upon shutdown (or can be called manually). Most implementations are extremely similar to this:
var unloaders = []; // Keeps track of unloader functions.
function unload(fn) {
if (typeof(fn) != "function") {
throw new Error("unloader is not a function");
}
unloaders.push(fn);
return function() {
try {
fn();
}
catch (ex) {
Cu.reportError("unloader threw " + fn.toSource());
Cu.reportError(ex);
}
unloaders = unloaders.filter(function(c) { return c != fn; });
};
}
You'd then hook up shutdown to do the right thing:
function shutdown() {
...
for (let i = unloaders.length - 1; i >= 0; --i) {
try {
unloaders[i]();
}
catch (ex) {
Cu.reportError("unloader threw on shutdown " + fn.toSource());
Cu.reportError(ex);
}
}
unloaders.length = 0;
}
Using unload
Now you can do stuff like:
function startup() {
setupSomething();
unload(removeSomething);
setupSomethingElse();
var manualRemove = unload(removeSomethingElse);
...
if (condition) {
manualRemove();
}
}
The helpers - unloadWindow
You'll usually want to create a second function unloadWindow to unload stuff when either your add-on is shut down or the window gets closed, whatever happens first. Not removing stuff when the window gets closed can be very tricky, and create Zombie compartments of your bootstrap.js and/or code modules very easily (this is from experience writing and reviewing restartless add-ons).
function unloadWindow(window, fn) {
let handler = unload(function() {
window.removeEventListener('unload', handler, false);
try {
fn();
}
catch (ex) {
Cu.reportError("window unloader threw " + fn.toSource());
Cu.reportError(ex);
}
});
window.addEventListener('unload', handler, false);
};
(Some people might want to "optimize" this, as to have only one "unload" handler, but usually you only have so unloadWindow calls that it won't matter.)
Putting it all together
Now you can .bind stuff and do whatever and let the the unloader closures keep track of it. Also, you can use this to keep your shut down code next to your initialization code, which might increase readability.
function setupWindow(window, document) {
var bound = this.contextPopupShowing.bind(this);
contextMenu.addEventListener('popupshowing', bound, false);
unloadWindow(window, function() {
contextMenu.removeEventListener('popupshowing', bound, false);
});
// Or stuff like
var element = document.createElement(...);
contextMenu.appendChild(element);
unloadWindow(window, function() {
contextMenu.removeChild(element);
});
// Or just combine the above into a single unloader
unloadWindow(window, function() {
contextMenu.removeEventListener('popupshowing', bound, false);
contextMenu.removeChild(element);
});
}
Before bind() was supported you had to save a reference to this outside the function. Then pass in a function that can forward the invocation the way you want.
var self = this;
contextMenu.addEventListener('popupshowing', function() {
self.contextPopupShowing.apply(self, arguments);
}, false);
In this case we use apply to set the context to self, our saved version of this, and to send it whatever arguments were passed to the anonymous function via the magic arguments keyword that contains the list of arguments that a function was passed when invoked.
You don't have to use bind for addEventListener. You can use handleEvent. It was in that topic I linked you too:
Removing event listener which was added with bind
MDN :: EventTarget.addEventListener - The value of "this" within the handler
handleEvent is actually a common way the javascript code in the firefox codebase does it.
Copied straight from MDN:
var Something = function(element) {
this.name = 'Something Good';
this.handleEvent = function(event) {
console.log(this.name); // 'Something Good', as this is the Something object
switch(event.type) {
case 'click':
// some code here...
break;
case 'dblclick':
// some code here...
break;
}
};
// Note that the listeners in this case are this, not this.handleEvent
element.addEventListener('click', this, false);
element.addEventListener('dblclick', this, false);
// You can properly remove the listners
element.removeEventListener('click', this, false);
element.removeEventListener('dblclick', this, false);
}
Where I mostly use bind is when doing a for loop and I make anonymous functions with something in the array like arr[i]. If I don't bind it then it always just takes the last element of the array, I have no idea why, and I hate it, so then I go to using [].forEach.call(arr, function(arrI).
http://2ality.com/2013/06/auto-binding.html
var listener = myWidget.handleClick.bind(myWidget);
domElement.addEventListener('click', listener);
...
domElement.removeEventListener(listener);
What is the best approach for passing the this parameter of the init function to the change event handler and why?
Option 1 (using that = this).
SomeObject.prototype.init = function () {
var that = this;
this.$element.change(function () {
//do some some things with that.
that.
});
};
Option 2 (using event.data).
SomeObject.prototype.init = function () {
this.$element.change({object:this }, function (e) {
//do some some things with the event data.
e.data.object.
});
};
Or another (better) one?
Imho the first one is a bit nicer. A 3rd way (if you can use ECMA5) would be
SomeObject.prototype.init = function () {
this.$element.change(function () {
//do some some things with this.
this.
}.bind(this));
};
If you want this inside the event handler to refer to this, of the "parent function", you can use $.proxy [docs]:
this.$element.change($.proxy(function (e) {
//do some some things with the event data.
}, this));
But then you have to access event.currentTarget [docs] to get a reference to the element the event handler is bound to.
Apart from that, choose whatever makes most sense to you / you feel most comfortable with and be consistent.
I tend to wrap the function that needs the reference in a function that holds it, like this:
this.$element.change(function(parent) {
return function() {
// do some things with parent.
}
}(this));
I wouldn't say that one approach is better than the other. It's just that if I use jQuery in my project, I would use the second pattern as it is provided by the framework.
I'm using jQuery and I have a function that serves as an event callback, and so in that function "this" represents the object that that captured the event. However, there's an instance where I want to call the function explicitly from another function - how do I set what "this" will equal within the function in this case?
For example:
function handleEvent(event) {
$(this).removeClass("sad").addClass("happy");
}
$("a.sad").click(handleEvent); // in this case, "this" is the anchor clicked
function differentEvent(event) {
$("input.sad").keydown(e) {
doSomeOtherProcessing();
handleEvent(e); // in this case, "this" will be the window object
// but I'd like to set it to be, say, the input in question
}
}
Use apply call.
handleEvent.call(this, e);
Just parameterize the function you're interested in:
function doStuff(el) {
$(el).removeClass("sad").addClass("happy");
}
function handleEvent(event) {
doStuff(this);
}
$("a.sad").click(handleEvent); // in this case, "this" is the anchor clicked
function differentEvent(event) {
$("input.sad").keydown(e) {
doSomeOtherProcessing();
doStuff(this);
}
}
Use
e.target
I'd advice you re-factoring your function as a jQuery plugin.
But here's a quick Fix:
handleEvent.apply(this,e) //transfers this from one scope, to another
If you're simply looking to call a single event handler as if it were being triggered normally, apply/call will work fine. However, depending on your needs, it may be more robust to use the zero-argument version of jQuery's click() function, which will trigger all click handlers for that element:
function differentEvent(event) {
$("input.sad").keydown(e) {
doSomeOtherProcessing();
$(this).click(); // simulate a click
}
}