Custom events are an underappreciated feature of jQuery that make it easy to graft a powerful distributed event system onto any web app, with no additional libraries. You can emit any event you want from any DOM element from jQuery using trigger.
| $('#tabby, #socks').on('meow', function() { |
| console.log(this.id + ' meowed'); |
| }); |
| $('#tabby').trigger('meow'); // "tabby meowed" |
| $('#socks').trigger('meow'); // "socks meowed" |
If you’ve worked with DOM events before, you’re no doubt familiar with bubbling. Whenever an element emits an event (such as a ’click’), its parent then emits the event, then its grandparent, and so on, up to the root element, document—unless the event’s stopPropagation method is called at some point along the way. (jQuery does this for us automatically when we return false from a handler.) But did you know that jQuery’s custom events bubble as well? For instance, if we have a span named “soda” nested in a div named “bottle,” the code
| $('#soda, #bottle').on('fizz', function() { |
| console.log(this.id + ' emitted fizz'); |
| }); |
| $('#soda').trigger('fizz'); |
will emit the following output:
<= | soda emitted fizz |
| bottle emitted fizz |
This bubbling isn’t always desirable, as we’ll see in the following tooltips example. Fortunately, jQuery offers the nonbubbling triggerHandler method as well.
When events can be mapped intuitively to page elements, jQuery is an ideal way of distributing them. For instance, suppose you’re writing a tooltip library and you want only one tooltip to be visible at a time. You might simply add the line
| $('.tooltip').remove(); |
to the start of the function that adds new tooltips. But what if we decide later that we want certain containers to be isolated, such as when a new tooltip is shown in the sidebar, tooltips everywhere else are unaffected, and vice versa? Writing a selector for “elements with class tooltip that are not descendants of sidebar” is tricky and not very efficient. The problem would get exponentially harder if we decided to allow isolated containers to be nested to an arbitrary depth.
But implementing this behavior with event logic rather than selector logic is easy.
| // $container could be $('#sidebar') or $(document) |
| $container.triggerHandler('newTooltip'); |
| $container.one('newTooltip', function() { |
| $tooltip.remove(); |
| }); |
(Notice the use of jQuery’s one instead of on. The difference is that one automatically removes the handler after it fires.)
With these two lines of code, each tooltip will listen to its container and remove itself when the container gets a new tooltip. It’s a beautifully direct, efficient approach that saves us from having to store any state or engineer complex selectors (which would slow older browsers to a crawl—IE7 and older don’t even have a way of selecting all elements with the tooltip class without traversing the whole document!).
Note that event bubbling would actually have defeated our intent in this case: when we create a new tooltip in the sidebar, we want only tooltips listening for the ’newTooltip’ event on the sidebar itself to go away, not those on the surrounding document. Always think carefully about whether trigger or triggerHandler is the right tool for the job.
Custom jQuery events are an unusual twist on PubSub, since the events are emitted by selectable elements rather than by objects in our script. Much as evented models are an intuitive way of expressing state-related events, jQuery events let us express DOM-related events directly through the DOM, saving us from having to duplicate that state elsewhere in our application. Use them liberally, but try to avoid relying on the structure of your application’s markup—you don’t want your next redesign to break your script.
3.149.242.9