global custom events in jQuery - javascript

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.

Related

Multiple Components listening via eventlistener

I was wondering why you're not allowed to have multiple similar eventListeners on a single node? I imagine the case where I have multiple partials of the same type that use a single node to communicate with eachother through CustomEvents. But that doesnt seem to be working because they do all share the same EventListener and thus only one of them is able to listen and process the event.
Why is that?
Thanks!
EDIT: little code snippet:
I have
node.addEventListener("customEvent", this.func, false);
and
node.addEventListener("customEvent", this.func, false);
in two different places and this.func points to the same function but in different contextes and would eventually trigger different things. But the second listener never gets called because for some reason this something seems to assumes that their the same probably because the signature is alike or whatever.
EDIT2: I'm basically referring to this.
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener#Multiple_identical_event_listeners
You can attach multiple similar event handlers to a node by using addEventListener.
node.addEventListener("click", function(){alert(1)}, false);
node.addEventListener("click", function(){alert(2)}, false);
Should give an alert twice.
When you assign anonymous function this works.
However you can assign multiple listeners referring to the same function and event by using bind.
var a = this.func.bind();
document.getElementById("start").addEventListener("click", a, false);
var b = this.func.bind();
document.getElementById("start").addEventListener("click", b, false);
This will alert twice. Since the function wrapper is different, it's being treated as an unique event.
You can't assign an event handler twice through node.onclick, because it's a property. When you assign it twice it will just overwrite the first handler. Event listeners were invented to cope with this.
In Java you can add multiple listeners. But, honestly, I dont like this approach. In most of the cases, you only need to trigger 1 interface/object (you are adding a overhead, logic for multiple interfaces where only one is used).
I like more the Android approach: setOn....Event (looks like JS is this way) instead of addActionListener(...) of Java.
When you need multiple triggers, you could override like:
obj.setOnEvent(function() {
doTheOtherEventTrigger();
doOneMoreEventTrigger();
...
}

Is there actually a good reason for jQuery to manipulate 'this' keyword in event handlers?

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.

Events - inversion of control methods

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...

How to bind to all custom events in jQuery

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
});

Javascript event handler order

I have an input field, which has two event handlers bound to it.
Validate & AutoSave
Obviously I want to validate before I save. If validation fails, the "invalid" class is added to the input and autosave will check for that class before it proceeds.
This works well enough, but is there a way to guarantee Validate runs before Autosave in all cases?
If you use JQuery to bind your events, it guarantees that handlers are fired in the same order that they were bound. Otherwise the order is officially undefined.
If you cannot use JQuery or a similar framework you can easily simulate this by using your own custom even binding, where your generic handler is a function which keeps an array of functions and calls them in order.
Normally you'd have the Save event handler call Validate() which will return true if everything is fine and ready to be saved.
function onSaved() {
if (!validate()) {
// set class
return;
}
// do the save
}
Why not attach just one handler -- Validate -- and call AutoSave from inside it?
For an answer to your question that isn't also a question, see this post or this one or this one.
Already answered - but just to add this piece of knowledge, the order of event handlers can not be relied upon. It may in any given implementation be predictable, but this can change from one (Javascript) implementation to the next and/or over time. The only thing certain is that they all will be executed - but not in what order.
Note that the situation is similar when there is an event handler for a DOM object and another one for the same event for a child or parent - which of those is executed first is not always clear as well. See http://www.quirksmode.org/js/events_order.html

Categories