Why does the default function for my event not execute when I specify it with defaultFn?
var node = Y.one(".foo");
node.publish("myEvent", {
defaultFn: function () {
//I don't understand why this doesn't execute
//I expect it to execute after my on listener
//and before my after listener
Y.log("In default function");
}
});
node.before("myEvent", function () {
Y.log("In before listener");
});
node.on("myEvent", function () {
Y.log("In on listener");
});
node.after("myEvent", function () {
Y.log("In after listener");
});
node.fire("myEvent");
JSFiddle: http://jsfiddle.net/steaks/rUacD/
Your jsfiddle shows you are loading only node. To have events, you have to load one of the event modules. As it happens, your code fails at the call to publish, the first method that event provides that it finds.
In general, check the API docs for the methods that fail and see which module provides them ('Inherited from' right below the heading for the method) and make sure you have the corresponding module loaded.
--
Updated according to newer JSFiddle:
Add emitFacade: true to the configuration of the published event. YUI does this automatically for classes inheriting from Base but not for others. For Node instances, you have to add that option explicitly.
Related
I started web development recently, and i can't seem to wrap my head around event handlers and callback functions. I understand that events handlers are a type of callback, and they respond to events unlike the more general definition of callbacks that get called by some other code when it finishes running. But, I'm looking for an explanation that 'sticks' with me.
Here's an example
<script>
$(document).ready(function(){
$("button").click(function(){
$("p").hide("slow", function(){
alert("The paragraph is now hidden");
});
});
});
</script>
In this code example, i know that ready and click are both events so $("button").click(function(){ is an event handler for the ready event? and $("p").hide("slow", function(){ is an event handler for the click event? How about hide? is it also an event?
Yes, that's correct (took me a second to realize you were just showing the content of the handlers you were referring to). It's clearer if you don't define the handlers/callbacks inline, and you give them descriptive names:
function readyHandler() {
$("button").click(clickHandler);
}
function clickHandler() {
$("p").hide("slow", hideAnimationCompleteCallback);
}
function hideAnimationCompleteCallback() {
alert("The paragraph is now hidden");
}
$(document).ready(readyHandler);
Note that the code above is slightly different from your original, which looks more like this:
function readyHandler() {
function clickHandler() {
function hideAnimationCompleteCallback() {
alert("The paragraph is now hidden");
}
$("p").hide("slow", hideAnimationCompleteCallback);
}
$("button").click(clickHandler);
}
$(document).ready(readyHandler);
...but since none of the handlers/callbacks was relying on the fact it was created inside a handler/callback, it seemed clearer to show them completely independently. But it would matter if they were using something that's only in-scope within the handler they were created in.
Callback functions are what you described. Functions that are passed as parameters to another function and then later "called back".
Example:
file.read(fileName, function (err, data) {
// once file reading has finished, this function body is called,
// so this anonymous function is the callback
});
Event handlers are functions that gets triggered when a specific event occurs. It can be used for synthetic events, websocket events, and more. And its usual syntax is using callbacks.
Examples:
eventBus.on('new_message_arrived', function (err, data) {
// when 'new_message_arrived' event happens, this callback will be called
});
button.click((event) => {
// when button gets clicked, this callback (now used arrow function notaion)
// will be called with the details of the UI event
});
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
How do I clear out anonymous functions that are set to trigger via a jQuery document.ready() call?
For example:
<script type="text/javascript">
//some code sets a doc ready callback
$(document).ready(function ()
{
alert('ready');
});
//my attempt to prevent the callback from happening
window.onload = null;
$(document).unbind("ready");
</script>
The alert happens regardless of my attempts to circumvent it. Is there any way to do this?
You'd probably get the most appropriate answer if you described what problem you're really trying to solve.
jQuery doesn't have a publicly documented way to undo or block document.ready() handlers. If you control the code, you can use a global variable and a conditional like this:
var skipReady = false;
$(document).ready(function ()
{
if (!skipReady) {
alert('ready');
}
});
// skip the document.ready code, if it hasn't already fired
skipReady = true;
Or, if you want to hack into jQuery a bit (beyond the documented interfaces), you can do this:
$(document).ready(function() {
alert("ready");
});
// stop the ready handler
$.isReady = true;
You can see this last one work here: http://jsfiddle.net/jfriend00/ZjH2k/. This works because jQuery uses the property: $.isReady to keep track of whether it has already fired the ready handlers or not. Setting it to true makes it think it has already fired them so it won't every do it again.
This works:
$(document).bind("ready", function () { alert("hey!"); });
$(document).unbind("ready");
Seems like a bug to me - all other events in jQuery are able to be unbound. Omitting this one is inconsistent.
Not a direct answer as to the omission, but here's some related info from jQuery docs:
All three of the following syntaxes are equivalent:
$(document).ready(handler)
$().ready(handler) (this is not recommended)
$(handler)
There is also $(document).bind("ready", handler). This behaves similarly to the ready method but with one exception: If the ready event has already fired and you try to .bind("ready") the bound handler will not be executed. Ready handlers bound this way are executed after any bound by the other three methods above.
$(document).ready() is dependent on the onLoad event which is triggered by the browser meaning you can not prevent it from happening. If the alert() is determined by some condition then I would use an if/else statement to decide whether it is called.
Super old question, but came across the need to do this recently to prevent document.ready code I didn't control from running in certain instances. This can be achieved by proxying jQuery's ready function, rather like a test spy. The following will work:
var ready = $.prototype.ready;
// proxy the ready function
$.prototype.ready = function ( fn, allowed ) {
allowed = allowed || false;
if ( allowed ) {
ready.call( this, fn );
}
};
All calls to $( document ).ready will now be ignored. You can override this behaviour by passing true as the second argument: $( document ).ready( fn, true )
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.