In my Backbone Marionette App I'm filtering things in the router using 'before' and 'after' (backbone.routefilter). In order to avoid doing this in the router, I'm using App's vent to handle it somewhere else.
I have a method that add events to these handlers and everything work just fine, except if I need to remove some callbacks registered to run in the vent handler, for example:
// Some callback is registered to the router:before event.
App.registerRouterEventCallback.add({type: 'before', 'callback': function () { console.log("I'm Doe, John.") }});
// The URL changes and the callback is run.
App.vent.on('route:before', callback);
// In another site location, another callback is added to the events stack
// and attached to the route:before callback: there are 2 functions registered
// to this event now.
App.registerRouterEventCallback.add({type: 'before', 'callback': function () { console.log("I'm Doe, Josh.") }});
But what if the first event need to be removed? A call to App.vent.off('route:before') can't be done as it would wipe all the functions attached to this event.
The first thing I came up with is to namespace the events, so I can remove it without mess up with the other ones. But I want them to be added dynamically, not hardcoded in the router. In fact I would like to leave the router as clean as possible.
I wonder if there is something out of the box like the following:
// Listener for all route:before events
App.vent.on('route:before', fn);
// Listener for route:before:foo event
App.vent.on('route:before:foo', foo);
The idea here is to deal with it in a more jQuery-ish way, where you assign $('#foo').on('click.foo', fn) and have this listener removed by calling $('#foo').off('click.foo').
Any ideas would be great.
Related
I am trying run custom code whenever a click event is triggered. This is what I have so far:
const origHandler = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (eventName, eventHandler, options) {
let handler = eventHandler;
const target = this;
origHandler.call(this, eventName, function (e) {
// Do something with e
doSomething(e);
// Run original function
handler.call(target, e);
}, options);
};
I am also using this multi-select dropdown plugin. With the code above, clicking on the dropdown element doesn't do anything until I click it a couple of times.
It works fine if I just do the following:
origHandler.call(this, eventName, handler, options);
However, the above doesn't allow me to run custom code whenever the handler is called. Is there anything I can do to create a wrapper that also works with these types of plugins?
This problem is not specific to this plugin, as I have seen a few other plugins in the application also breaking as a result of this code.
Incomplete Algorithm
The posted code calls (the original) addEventListener with an anonymous function argument. This means any removeEventListener in calling code which supplies handler as its function argument will fail - it never matches the anonymous function.
To successfully add a hook into addEventListener would require implementing a complementary hook into removeEventListener and additional logic to achieve correct removal of added listeners.
This doesn't mean the particular problems encountered are specifically caused by only patching addEventListener, but doing so is guaranteed to produce code failure.
In general patching prototype object properties of global functions is probably best avoided if at all possible.
Alternative using Capture
Adding a document click event listener that uses event capture, before including any library scripts, should allow inspection of every click event before being handled by anything else:
document.addEventListener("click", function(e){
// do something with event
console.log("click event type: %s on %s", e.type, e.target.tagName);
}, {capture:true});
body {background-color: white}
html {background-color: grey}
Click me!
I have a chunk of markup in my page that represents a view, and a JS controller function which is associated with that view. (These are Angular, but I don't believe that matters.) The controller code listens for a custom event fired from elsewhere in the app, and handles that event with some controller-specific logic.
My problem is that the controller's event handler is getting attached too many times: it gets attached every time the view is re-activated, resulting in the handler being run multiple times every time the custom event is fired. I only want the handler to run once per event.
I've tried using .off() to unbind the handler before binding it; I've tried .one() to ensure that the handler is only run once; and I've tried $.proxy() after reading about its interaction with .off() here.
Here's a sketch of my code:
// the code inside this controller is re-run every time its associated view is activated
function MyViewController() {
/* SNIP (lots of other controller code) */
function myCustomEventHandler() {
console.log('myCustomEventHandler has run');
// the code inside this handler requires the controller's scope
}
// Three variants of the same misbehaving event attachment logic follow:
// first attempt
$('body').off('myCustomEvent', myCustomEventHandler);
$('body').on('myCustomEvent', myCustomEventHandler);
// second attempt
$('body').one('myCustomEvent', myCustomEventHandler);
// third attempt
$('body').off('myCustomEvent', $.proxy(myCustomEventHandler, this));
$('body').on('myCustomEvent', $.proxy(myCustomEventHandler, this));
// all of these result in too many event attachments
};
// ...meanwhile, elsewhere in the app, this function is run after a certain user action
function MyEventSender() {
$('body').trigger('myCustomEvent');
console.log('myCustomEvent has been triggered');
};
After clicking around in my app and switching to the troublesome view five times, then doing the action which runs MyEventSender, my console will look like this:
myCustomEvent has been triggered
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run
myCustomEventHandler has run
How can I get it to look like this:
myCustomEvent has been triggered
myCustomEventHandler has run
???
Give your events a namespace, then simply remove all events with said namespace when you re-run the controller.
jsbin
$('body').off('.controller');
$('body').on('myCustomEvent.controller', myCustomEventHandler);
You could listen in on the scope destroy event in your Main controller
function MyViewController($scope) {
function myCustomEventHandler() {
console.log('myCustomEventHandler has run');
// the code inside this handler requires the controller's scope
}
$('body').on('myCustomEvent', myCustomEventHandler);
$scope.$on("$destroy", function(){
$('body').off('myCustomEvent', myCustomEventHandler);
//scope destroyed, no longer in ng view
});
}
edit This is an angularJS solution. The ngview is constantly being loaded as you move from page to page. It will attach the event over and over again as the function is repeatedly called. What you want to do is unbind/remove the event when someone leaves the view. You can do this by hooking into a scopes $destroy (with the dollar sign) event. You can read up more on that here: $destroy docs
The problem is that when function MyViewController(){} is called multiple times, you get a separate instance of myCustomEventHandler (attached to the current closure), so passing that to $.off doesn't unregister the previous handler.
KevinB's answer, event namespaces, is what I suggest for removing specific handlers without requiring knowledge of which handler was installed. It'd be nicer if you could unregister the events when the element is removed/hidden, then you would have the reference to the function you want to unregister, without risking removing handlers that other code may have added to the same event namespace. After all, event namespace is just a global pool of string and is susceptible to name collision.
If you make your function global, it will also work (except that it looks like you need the closure), but I'm just showing it to explain the problem, use namespaces
function myCustomEventHandler() {
console.log('myCustomEventHandler has run');
// the code inside this handler requires the controller's scope
}
function MyViewController() {
// first attempt
$('body').off('myCustomEvent', myCustomEventHandler);
$('body').on('myCustomEvent', myCustomEventHandler);
// second attempt
$('body').one('myCustomEvent', myCustomEventHandler);
// third attempt
$('body').off('myCustomEvent', $.proxy(myCustomEventHandler, this));
$('body').on('myCustomEvent', $.proxy(myCustomEventHandler, this));
}
// ...meanwhile, elsewhere in the app, this function is run after a certain user action
function MyEventSender() {
$('body').trigger('myCustomEvent');
console.log('myCustomEvent has been triggered');
}
MyViewController();
MyViewController();
MyEventSender();
Previous Idea
One of the problems is that you're not passing the same function to $.on and $.off, so off is not unregistering anything in this case
Not the problem, leaving the answer up for reference since it's not exactly intuitive. $.proxy seems to return a reference to the same bound function if passed the same function and context. http://jsbin.com/adecul/9/edit
What is the difference between event handlers and event listeners in JavaScript? They both execute a function when the event appears.
I don't really get when to use event handlers and when to use event listeners.
A handler and a listener are one in the same - just synonyms for the function that will handle an event. "Handler" is probably the more accepted term, and is certainly more semantically correct to me. The term "listener" is derived from the code used to add an event to an element:
element.addEventListener('click', function() { /* do stuff here*/ }, false);
You could, however, get really nitpicky and break the two down into separate meanings. If you're so inclined, "handler" could be the term for the function that is going to handle an event when you add a "listener", thus one can have several "listeners" that utilize a single "handler". Consider:
// handler is synonymous with function
function someFunction(e) {
if (typeof e == 'undefined')
alert('called as a function');
else
alert('called as a handler');
}
// use someFunction as a handler for a
// click event on element1 -- add a "listener"
element.addEventListener('click', someFunction, false);
// use an anonymous function as a handler for a
// click event on element1 -- add another "listener"
element.addEventListener('click', function () { alert('anonymoose'); }, false);
// use someFunction as a handler for a
// click event on element2 -- add a "listener"
element2.addEventListener('click', someFunction, false);
// call someFunction right now
someFunction();
So in the above code, I have 2 "handlers" (someFunction and an anonymous function) and 3 "listeners".
Again, this is all semantics - for all practical purposes the terms listener and handler are used interchangeably. If a distinction need be made then a listener is a subscription to an event that will trigger a call to a handler (which is a function).
Clear as mud?
There's no difference; it's just different terminology for the same thing.
There are different ways of associating functions with DOM elements for the purpose of event handling, that's all. The differences emerged back when standards were in flux (or just because implementors were ornery or difficult) but ultimately the mechanisms are essentially the same.
If you're confused about what sort of event handler registration to use, you can:
Read more about the topic and choose an approach to use, perhaps on a browser-by-browser basis;
Choose one of the popular JavaScript frameworks and use its mechanism for attaching handlers
This site, (which funnily enough has a cyclical reference to here by one of the comments) states otherwise, to what people have answered here (stating they are same); pasting one of the answers:
One difference is that if you add two event handlers for the same button click, the second event handler will overwrite the first and only that event will trigger. For example:
document.querySelector('.btn').onclick = function() {
console.log('Hello ');
};
document.querySelector('.btn').onclick = function() {
console.log('World!');
};
// This logs "World!" out to the console.
But if you use addEventListener instead, then both of the triggers will run.
document.querySelector('.btn').addEventListener('click', function() {
console.log('Hello ');
});
document.querySelector('.btn').addEventListener('click', function() {
console.log('World!');
});
// This logs "Hello" and "World!" out to the console.
I find this explanation particularly hands-on:
Event handlers are comprised of an event listener and a callback function.
An event listener specifies the type of event that will be detected.
The callback function executes when the event happens.
Everything together is the event handler.
both of them used for associating a function when an event occurs, if using the event listener's you can listen more than once in A specified event (duplicate) for example listen to tow 'click' event into independent event listener's, but when using the handler it's impossible because handler is a property of your dom object and if Assign more than once a function in same event handler, for example, when set to the a element tow handler for onClick event, the last event handler assignment is work.
myElement= document.querySelector('#btn');
myElement.onClick = function(){
alert('first event handler');
}
myElement.onClick = function(){
alert('second event handler');
}
// result : occur last handler >> alert('second event handler');
but if using the event listeners you can listen to how many times listen to the same
event.
myElement.addEventListener('click',()=>{
alert('first listener')
})
myElement.addEventListener('click',()=>{
alert('second listener')
})
/* result : occur both listeners - alert('firstlistener') >> and next >> alert('second
listener'); */
there's no big difference.
we can say they are almost the same things except for three subtle things that are:
you can use an event handler once. if you use a handler to an element twice or more, then the last handler will overwrite all those previous handlers.
on the other hand, if you use event listeners more then once, there won't be such a thing like this.
you can use many event listeners, but not just one.
For some events, handlers only work with addEventListener. like the DOMContentLoaded event, which triggers when the document is loaded and DOM is built.
whit using event listeners, you can pass an object or class by using handleEvent on them instead of a function to the handler.
except for these subtle things, I don't think it exists any difference.
For more information see
https://javascript.info/introduction-browser-events#object-handlers-handleevent
I understand the difference between an addEventListener and the onclick property and know how to use both. I am wondering if there is a draw back to always using EventListener's instead of using the onclick property. The EventListener appears to be much more powerful than just using the onclick atleast when dynamically generating the HTML from javascript.
Is there a memory/cpu drawback or am I safe to only use EventListeners?
This probably isn't the direction you were going in, but there are a few instances where you would be unable to remove an event listener.
Event handlers are completely public, and can be modified (to a certain extent) by anyone:
// You do this
myLink.onclick = function () {
alert('hello, world');
};
// Another developer who hates you because
// he thinks that you're hitting on his girlfriend
// but you're not, you're just friends, but
// he's jealous so he doesn't understand
// does this
myLink.onclick = function () {
alert('muahahahaha');
};
// Someone else could even get rid of
// the handler entirely:
myLink.onclick = null;
But there is no publically accessible list of event listeners. The only way to remove an event listener is if you still have access to the original function:
myLink.addEventListener('click', function () {
alert('hello, world');
}, false);
There is now no way to remove that event listener. You gave it an anonymous function, so even you wouldn't be able to remove it if you wanted to.
I have a Javascript module the following Javascript:
EntryController = function$entry(args) {
MainView();
$('#target').click(function() {
alert('Handler called!');
});
}
MainView() has a callback that creates the #target button. Because of the callback the code will pick up and run through the rest of the code $('#target') ... before #target is created. If this is the case the event is never hooked up to the #target. If I put a breakpoint at $('#target') that'll give the callback enough time to return and build the #target, when I press play everything works as expected.
What's the best way to deal with this? I would like all events to take place in the controller so it can choose which view to send it to.
I was thinking about placing the entire $('#target').click ... inside MainView() and instead of alert('Handler called!'); I'd put a references to EntryController.TargetEventRaise(), but that started to look a bit like steady code. What's the best way to approach this?
You're looking for jQuery's live event handlers, which will handle an event on every element that matches the selector, no matter when the element was created.
For example:
$('#target').live('click', function() {
alert('Handler called!');
});
Alternatively, you could make the MainView function itself take a callback, and add the handler in the callback. You could then call the callback in MainView inside of its callback.