DO NOT TRIGGER REAL EVENT NAMES WITH JQUERY!

By  on  

Sometimes JavaScript toolkits give us so much functionality that we can get hung by it if we're not careful.  The more functionality that we use within a toolkit, the more opportunity there is to have one set of changes or additions affect another.  That's especially true as you maintain your code over a period of years.  One mistake I often see is the use of jQuery's trigger, a method which allows developers to trigger a registered event.  It's great that you can do that but DO NOT TRIGGER REAL EVENT NAMES!

Note:  I'm aware that other toolkits provide this functionality -- I simply use jQuery as an example because of its popularity and recent problem I saw.  This can happen with any toolkit. MooTools uses fireEvent, etc.

The following is a prime example of using trigger to accomplish a task:

// Set up a click event
jQuery('.tabs a').on('click', function() {
	// Switch to the tab, load some content, whatever	
});

// Trigger a click on the last one
jQuery('.tabs a').last().trigger('click');

The above would open the given tab at that index.  I totally understand why people use trigger for this type of thing; it's usually because the function to trigger said opening is not available globally, and triggering an event is easy and always available.  The problem is that using real event names as the trigger can ...trigger... some crazy shit.  Let's say this is added somewhere else in the site:

// Listen to all clicks in the body
jQuery('body').on('click', 'a', function() {
	// Do some logic

	// Condition met, do something drastic!
	if(conditionMet) {
		// Reload the page?
		// Open a submenu?
		// Submit a form?
		// ... you get the idea
	}
});

Now we have a problem -- the tab trigger click is listened to by something completely separate and now we're in for trouble.  Yikes.  The best solution if you need to use trigger is providing a custom event name along with the native event:

// Set up a click event with an additional custom event
jQuery('.tabs a').on('click tabs-click', function() {
	// Switch to the tab, load some content, whatever	
});

// Trigger a fake click on the last one
jQuery('.tabs a').last().trigger('tabs-click');

Now your event trigger won't cause conflicts with other listeners on the page.  The reserved developer in me says that you may want to avoid trigger (and its other toolkit counterparts) all together, but adding a custom event name is the very least you should do!

Alternate: triggerHandler

If you're using jQuery specifically, you could also use jQuery's triggerHandler method which triggers an event but only those registered through jQuery, and with the following caveats:

  • The .triggerHandler() method does not cause the default behavior of an event to occur (such as a form submission).
  • While .trigger() will operate on all elements matched by the jQuery object, .triggerHandler() only affects the first matched element.
  • Events created with .triggerHandler() do not bubble up the DOM hierarchy; if they are not handled by the target element directly, they do nothing.
  • Instead of returning the jQuery object (to allow chaining), .triggerHandler() returns whatever value was returned by the last handler it caused to be executed. If no handlers are triggered, it returns undefined

The .triggerHandler() method is jQuery's way of preventing the problems that trigger can create.

Recent Features

Incredible Demos

  • By
    CSS calc

    CSS is a complete conundrum; we all appreciate CSS because of its simplicity but always yearn for the language to do just a bit more. CSS has evolved to accommodate placeholders, animations, and even click events. One problem we always thought...

  • By
    Vibration API

    Many of the new APIs provided to us by browser vendors are more targeted toward the mobile user than the desktop user.  One of those simple APIs the Vibration API.  The Vibration API allows developers to direct the device, using JavaScript, to vibrate in...

Discussion

  1. I do this:

    $(window).resize(function(){
        // some responsiveness
    });
    
    $(window).resize();
    

    Where $(window).resize() is like saying $(window).trigger('resize'), I think.

    To your point: This is a good word of caution, but I think sometimes its ok?

    • The point is that if someone else also calls $(window).resize(function() {something()}), that other handler will also be called when window.resize() is called

  2. I definitely agree that custom event names are awesome for this sort of use case, but I think you can also help yourself out further by not using generic selectors when binding events, use specific selectors such as classes, maybe try prefix classes with are javascript related with ‘js-‘ as I sometimes do.

    Obviously jQuery("body").on("click","a", ...) is a pretty extreme example, but you’re less likely to shoot yourself in the foot if you are as specific as you sensibly can be

  3. I definitely agree with this logic, but I don’t think this practice is fundamentally flawed. Basically if what you want is a click (along with any potential handlers), then triggering it is best. Better even than calling the handler directly, for the same reasons the practice can be bad (e.g. in this case you actually want any extra handlers)

  4. Jason Delia

    Same idea, but you can also namespace your events. So instead of:

    jQuery('.tabs a').on('click tabs-click', function() {
     ....
    }
    

    You could have:

    jQuery('.tabs a').on('click.tabs', function() {
     ....
    }
    

    And trigger it with:

    jQuery('.tabs a').last().trigger('click.tabs');
    

    It accomplishes the same thing, but you don’t have to listen for both the real click and fake tabs-click.
    http://codepen.io/jasonadelia/pen/dfblu

  5. Ideally we can separate event handling logic from application logic. So that that none of our application code relies on any event objects, that was we can call those functions manually whenever we want!

        function applicationLogic(args) {
            // application logic should not depend on an event!!
        }
    
        function handleEvent(e) {
            var args = {};
            args.text = $(e.target).text(); // or whatever you need from the event
            applicationLogic(args);
        }
    
        var element = $('#bar');
        element.on('click', handleEvent);
    
        //manually call application code
        applicationLogic({
            text: 'foo'
        });
    
  6. Tim Ostendorf

    Great post and excellent tip, David. I’ve used trigger only on rare occasion when there were no alternatives. I’ll definitely use this methodology if I ever need it in the future!

  7. Not that I am an expert but I think there is a typo in the second line:
    “the more opportunity to there is to have”
    P.S. Huge fan of your blog btw!!!

  8. GREAT ARTICLE. WHY ARE WE YELLING!?!?!?!

    A better understanding of how event bubbling works is necessary to understand the full scope and impact of triggering custom events. I’d recommend anybody who’s attempting to trigger a native event understand this concept. The results should be expected considering this would actually happen if a user physically clicked the link.

    I agree that custom events make far more sense though. Why would someone be simulating a real click in the first place? It’s unlikely the cleanest solution.

    • *”full scope and impact of triggering native events”

  9. Sylvain Pollet-Villard

    I do not understand. A guy call .trigger("click") and complain after “So it actually really triggered a click, with the bubbling stuff and everything !”. Hey, what did you expect ? Create and send an event to the DOM just to call a function sounds like shit anyway. Just stop using anonymous functions.

    function onTabClick(){ 
    	// Switch to the tab, load some content, whatever	
    }
    jQuery('.tabs a').on('click', onTabClick);
    onTabClick.call(jQuery('.tabs a').get(-1));
    
    • Rolf

      I’m not sure I get it either.
      I read this article thinking that I was going to learn about some weird thing happening when you trigger native events through jQuery (as the title suggests).
      But basically what you’re saying is “watch out, events bubble and may trigger other handlers up in the DOM”, or did I miss something?
      Then just do e.stopPropagation() when you catch and process the event on the tab event handler and it will die right there.
      And/or test for the event target on your event listeners to make sure you’re catching the “right” events.
      That or one of the solutions that you proposed.
      Hey, mistakes happen.
      Thanks for the article.

  10. I agree that custom events should be used. In fact, i’d argue that *only* custom events should be used for custom components and application logic. click is meaningless to applications, and while it may mean something for components, if you are feeling the need to trigger it programmatically, there is usually something else going on. You are doing “init” or “setState” or something besides imitating a click. Even tabs-click and click.tabs are pretty awful if you take the time to think about what you are doing and why you are triggering a click event.

    Personally, i use the declarative-friendly event libraries that i’ve written to avoid even listening for click events in my javascript. I declare what event click actually means right on my elements so that my application logic (or custom component logic) listen for the appropriate events. It makes things more readable, more maintainable, and less prone to surprises like the one described here.

  11. I think in cases where you have to programmatically click on an element, $('elm').click() / .trigger() is acceptable.

  12. Ian Bijlovski

    I’m not saying that triggering native events is good, nor that your suggestion is bad… But what is the trouble you’re referring to? Are you trying to suggest that the trigger called programmatically will now reload the page (or submit a form, or whatever happens in the $(body) listener)? But then, so would clicking on a tab – instead of actually opening the tab, it’ll submit the form… So that is the expected behavior… Am I missing something obvious?

  13. Isn’t this why the library includes a .triggerHandler() method? When you just want to fire the event handlers without default browser behavior or event bubbling?

  14. You also need to be careful with how you name your custom events. I found this out the hard way by creating a custom event called remove on an older project. Even though it was name spaced remove.foo it still still caused issues and actually removed the element from the DOM. Turns out “remove” is a newer event type that did not exist when I created the custom name. To prevent this, you should use colons when creating custom names:

    http://bugs.jquery.com/ticket/14600

    • Dave

      Foo looks namespaced but remove does not. If your original JavaScript was an object named pete and remove was a member of it, then you could call pete.remove without running a built in function.

  15. Your example would also “do something drastic!” if the user just clicked on the tab as Ian Bijlovski also mentioned. Triggering “real” events may not be the most sensible way to code, but as others have mentioned, class namespacing is your friend.

  16. Dave Methvin

    Also note that there is DOM semantic magic associated with several of the standard event names like load or change. For example, an event named “load” does not bubble even if you .trigger() it, for example on an image element that has a load event. If it did bubble, it would eventually reach the window and trigger any window.onload handlers. You don’t want that.

  17. I think the biggest problem with calling trigger is that the code that is firing the event now knows about what is supposed to happen when an event happens. Events are supposed to let the system know something happened, not tell the system what to do in response. Therefore, I agree that firing a custom event is much better design.

  18. Alex Babis

    I wouldn’t even use custom events. Using trigger breaks encapsulation

    Remember that DOM is global.

  19. Alex Babis

    I think the reserved developer in you is right. I avoid trigger entirely. Using trigger breaks encapsulation and adds an unnecessary level of indirection to your code. I find using a custom event only marginally preferable; it still has the same code smell.

    In the example you gave, your tabs could have a public API for selecting them programmatically, eg: Tabs.selectLast();

    The only time I’ve ever *needed* trigger was for automated UI testing. Every other time I’ve used it, I’ve regretted not refactoring sooner.

  20. Phiggins

    Speaks to the case where triggering is outright lazy. Define a function, attach it as a handler. If you need the function to execute manually just call it.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!