I know it's not possible to bind to all DOM events and I know you can bind to multiple events by supplying a space-separated list.
But is it possible to bind to all custom events (preferably filtered by a wildcard pattern like 'abc*' or name-space)?
Edit:
To clarify, I have created some custom widgets that respond to some custom events. For example, they all handle an event called stepReset and resets their internal models.
After I've written these, I realized events don't bubble down, so the call $(body).trigger('stepReset') basically does nothing. As a result, I am considering adding an umbrella event handler on all widgets' parent elements to propagate all relevant events down.
(I know this is not an elegant solution, but I forgot to tag elements with handlers with a common class, so there's no easy way to use select them all.)
With regards to your upcoming edit, you can retrieve all bound events by accessing the object's data:
var boundEvents = $.data(document, 'events');
From here, you can iterate over the resulting object and check each property for your chosen wildcard character, or iterate over that property's array elements and check the namespace property of each.
For instance,
$.each(boundEvents, function () {
if (this.indexOf("*")) // Checks each event name for an asterisk *
alert(this);
// alerts the namespace of the first handler bound to this event name
alert(this[0].namespace);
});
If I understood you correctly, you can iterate over the special events object to get a list of custom events (including those specified in the jQuery source code). Here's an ES5 example, you will need to adapt it yourself for older browsers or use a polyfill for Object.keys:
var evts = Object.keys(jQuery.event.special).join(" ");
$("#myDiv").on(evts, function (e) {
// your code here
});
Related
I assume it's well understood in practice that objects which implement EventTarget include all of their supported events as properties with on prefixes in their keys. For example, Window is paired with the WindowEventHandlers mixin, allowing code like:
window.addEventListener('print', foo);
// ...or
window.onprint = foo;
But does a standard mandate that for every possible event name X there must be an associated onX property on that same EventTarget? Or is that just a convention browsers happened to follow as a historical artifact?
The HTML living standard covers this.
Excerpt:
Event handlers are exposed in two ways.
The first way, common to all event handlers, is as an event handler IDL attribute.
The second way is as an event handler content attribute. Event handlers on HTML elements and some of the event handlers on Window objects are exposed in this way.
For both of these two ways, the event handler is exposed through a name, which is a string that always starts with "on" and is followed by the name of the event for which the handler is intended.
The IDL and content attributes do not determine the taxonomy of event names, but since IDL attributes are visible as properties in JS without a setAttribute or getAttribute, then the above passage basically says that every event handler must get exposed as a property with an on-prefixed name.
That being said, the JS object that has these property names might not be the actual target of the event. The living standard cites body as one example of an element with IDL attributes for handlers targeting a corresponding Window instance... Even though the body element itself implements EventTarget.
So in general, it's your responsibility to verify the target, but you can at least count on every standard event N corresponding onN property that you can set on some EventTarget in your JavaScript source.
Given the following, common scenario:
console.log(this); // window or any parent object
$('.selector').on('click', function(event) {
console.log(this); // clicked DOM element
});
var myFunc = function() {
console.log(this); // window or parent object
}
Since version 1.3 jQuery adds the event.currentTarget when binding event handlers for which counts event.currentTarget === this, so is there actually a good reason to manipulate this and switch context? Doesn't this behaviour generally go against the unspoken rule of "don't change keyword values" (like undefined = 'not defined')?
This "feature" of jQuery makes a lot of OOP less efficient and awkward imho, when we need to either cache the original this in a variable like self or use helpers like jQuery.proxy to reassign context to event handlers.
My question: is this just a relic of early jQuery implementations kept alive or is there an actual benefit which I cannot see (except maybe the slightly more convenient way than accessing event.currentTarget to get the element...)?
Let's say you've got an object with some methods on it:
var object = {
click: function() {
alert(this.property);
},
property: "Hello World"
}
You can call object.click() and, as you'd expect, you'll get "Hello World" in the alert.
You'd like to be able to use that "click" function as an event handler:
$("button").on("click", object.click);
However you discover that that doesn't work, because jQuery invokes the "click" function with this set to the DOM node for the clicked button. This is irritating. It's also inevitable because of the semantics of JavaScript function calls.
When you call the "click" function by way of a property reference, the language arranges for this to refer to that object. That's why object.click() works. However, when you fetch the reference to the function and pass it across a function boundary (as in the call to .on()), that relationship is lost. All that the jQuery method gets is a plain unadorned function that has absolutely no inherent relationship to the original object involved in its definition.
Thus, jQuery really has only two choices. The first is that it could make explicit the fact that the function is unconnected by arranging for this to be undefined. That wouldn't be very useful however. The other choice is to pick something interesting for this, and that's what the library does. Note that the native DOM level 0 event dispatch mechanism does the same thing.
The reason is that jQuery wants to mimic how regular event handlers (ones created without jQuery or any other library) works. In regular event handlers the value of this refers to the DOM node that triggers the event if there is one.
One could in fact consider that this is an example of jQuery not manipulating built-in behavior.
How does listenTo / stopListening work with respect to on / off?
In the console, I'm experimenting with the Backbone event system as such ...
// works
Backbone.on('x', function(){console.log('x happened');})
// works
Backbone.trigger('x');
// works
Backbone.off('x');
// works
Backbone.once('x', function(){console.log('x happened');})
I'm trying to extend my example to use listenTo() and stopListening().
These are listed as the inversion of control types. Is there a simple way to show there use as above?
The primary difference, as viewed from the source, is that the first parameter needs to be an object.
It is exactly the same, except it's an inversion of control: listenTo binds the events on the listening object, rather than the triggering object. This is most useful for cleaning up View event handlers, because the view now knows which events it's listening to and can unbind them when it's removed. With the original system, only the triggering object (ie, the model) would have direct knowledge of the bound events.
There's a good explanation of the concept here. It shows how people used to do it before it was added to Backbone.
In your example, you don't really have a "listening" object, since your handler is just an anonymous function. But it would be something like obj.listenTo(Backbone, "x", obj.alert);, where alert would be a handler method on obj.
Because listenTo and stopListening work on a different object you need to create another object that has access to the event system. One easy way is to create a view object as such.
var View1 = new Backbone.View();
Next setup a listener:
Backbone.listenTo(View1, 'x', function(){console.log('I heard x');});
Now trigger the event on View1
View1.trigger('x');
Finally remove the listener
Backbone.stopListening(View1,'x');
That covers the 6 main methods of the event system.
Tested and working...
I want to use custom jQuery events independent of DOM elements, but I'm not sure what the best way is to achieve this.
Here's what I started out with:
// some system component registers an event handler
$().bind("foo.bar", handler); // foo is my app's namespace, bar is event name
// another part of the system fires off the event
$().trigger("foo.bar", { context: "lorem ipsum" });
After looking at jQuery's source, in particular its handling of global AJAX events, I figured this should work:
$.fn.bind("foo.bar", handler);
// ...
$.event.trigger("foo.bar", { context: "lorem ipsum" });
However, it appears that my handler function is never even called.
Am I perhaps going about this the wrong way?
If you're using jQuery >1.4 then $() returns an empty jQuery collection which would mean that no event handler is actually bound to anything. Before 1.4 it would have returned the same as jQuery(document).
It might be better to simply have a global namespace (an actual object) and then add events to that:
var FOO = {};
$(FOO).bind("foo.bar", handler);
$(FOO).trigger("foo.bar", { context: "lorem ipsum" });
I found my way here because I was looking to implement the publisher/subscriber pattern using namespaced custom events using jQuery. While the accepted solution is a way to use $.event.trigger() in a way that is not tied to DOM elements, it won't work well for a true global event implementation in a publisher/subscriber architecture (such as with a complex UI with many asynchronous actions), where you want to have arbitrary objects/elements listen for a custom event.
Through experimentation, I've found that the real answer to why AnC's events were not firing is because jQuery apparently doesn't allow the "." (period) character in custom event names...but underscores seem to be ok.
So, if you name your events something like foo_bar (rather than foo.bar), your code should work as expected. Tested with jQuery 1.4.4.
Edit: Just to be clear - I mean that periods aren't allowed for custom events if you want to use the $.event.trigger() mechanism. In scenarios where events are being triggered by objects or elements, periods seem to be ok...not sure if this is a bug or by design.
If I have the following code in two functions of an object:
add: function()
{
// create trip.
var trip = new Trip();
// add the trip using its id.
this.trips[trip.id] = trip;
},
remove: function(tripId)
{
// remove trip.
delete this.trips[tripId];
}
NOTE: The constructor for the Trip object binds a bunch of custom jQuery event handlers to itself.
Will the event handlers bound to the Trip object be automatically destroyed/cleaned up when the Trip object is deleted?
Would the same occur for a DOM node if it was removed and had event handlers bound to it?
Also I read that objects are not cleaned up by the garbage collector until all references to them no longer exist, so do the event handlers bound to the object by itself count as references and prevent the object from being cleaned up, even when I am no longer referencing it?
The event will not be deleted as jQuery maintains a central repository of all bound event handlers, and does know if or when you deleted an associated object using delete. Try this little test to confirm. (jQuery 1.4.2 only)
jsfiddle link:
// 1. a regular JS object
var root = {};
// Since we can't delete anything created with var (except on Chrome),
// we use an object property here. An array value works just as well,
// which is already the case in your example.
root.o = {};
// 2. at this point, jQuery creates an internal property
// jQuery<UNIQ_ID>, for example jQuery1277242840125 inside object o
$(root.o).bind("myEvent", function() { alert("here"); });
// 3. get the *internal* index jQuery assigned this object:
// there's only 1 property, so we just enumerate and fetch it.
var internalIndex;
for(var prop in root.o) {
internalIndex = root.o[prop];
}
// 4. delete the object
delete root.o;
// 5. query jQuery internal cache with the internal index from step 3
console.log(jQuery.cache[internalIndex].events);
Step 5 should log an array of all event types that were associated with the ex-o object, including "myEvent", and it's associated handler, so no the bound events handlers will not delete automatically. Here's what I see get logged (stripped out irrelevant properties):
▾ Object
▾ myEvent: Array (1)
▾ 0: Object
▸ handler: function () { alert("here"); }
namespace: ""
type: "myEvent"
length: 1
The object deletion, however, is not affected, and that will be deleted as expected. However, it is a hole in the wall kind of a situation since there is associated data somewhere in jQuery's cache that will remain there.
It seems that although you can bind events to plain JavaScript objects, you cannot unbind them. It appears jQuery assumes the object is a DOM node when unbinding, and throws the following error:
Uncaught TypeError: Object #<an Object> has no method 'removeEventListener'
Even the part about being able to bind events to objects is, I believe, undocumented. So you have to be a little careful on this, as the following will not work when trying to clean up the event handler references for that object:
$(object).remove()
$(object).unbind(..)
As a workaround, when cleaning up the Trip object, you can explicitly call removeData to do the job.
$(object).removeData();
As I've already mentioned, it's getting knee-deep with jQuery's internals, so you might want to look at an alternative solution, or be wary that library upgrades can easily break your code, which is not very unlikely.
As far as I know, you can only bind event handlers to nodes, or, in special cases, the window, document, etc. For DOM nodes, the event handlers will be removed. Even if they weren't, they wouldn't be able to be triggered anyway. Deleting the object will remove the event handlers associated with it. The event handlers should not prevent the object from being garbage collected.
Would the same occur for a dom node if
it was removed and had event handlers
bound to it?
this.handlerClick = function () { ... };
$(this.testDomNode).bind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);
Using the above code and testing with FireQuery in FireFox removing the dom node does not unbind the handler from the event,
it seems you have to explicitly unbind the handler before removing the dom node as follows:
$(this.testDomNode).unbind('click', this.handlerClick);
this.testDomNode.parentNode.removeChild(this.testDomNode);