javascriptjqueryjquery-eventsjquery-triggerjquery-triggerhandler

Why jQuery triggerHandler() works but trigger() doesn't?


Click an element using triggerHandler() works:

$element = $('#gwt-debug-location-pill-edit-div:visible');
$element.triggerHandler('click');

But, clicking it using trigger(), doesn't:

$element.trigger('click');

Why is that?


To replicate this (in Firefox and Chrome):


enter image description here


Solution

  • Let's compare jQuery.fn.trigger and jQuery.fn.triggerHandler:

    trigger: function( type, data ) {
        return this.each(function() {
            jQuery.event.trigger( type, data, this );
        });
    },
    triggerHandler: function( type, data ) {
        var elem = this[0];
        if ( elem ) {
            return jQuery.event.trigger( type, data, elem, true );
        }
    }
    

    The only real difference is the fourth argument, true, given to jQuery.event.trigger with triggerHandler.

    Looking at jQuery.event.trigger, the argument is called onlyHandlers, and among other things, the documentation of triggerHandler notes that:

    • The .triggerHandler() method does not cause the default behavior of an event to occur (such as a form submission).

    We can see where the default behaviour is actually triggered:

    // If nobody prevented the default action, do it now
    if ( !onlyHandlers && !event.isDefaultPrevented() ) {
    

    In the case that onlyHandlers is false (trigger()), and no event handler stopped the default event from executing, the default action will then be executed.

    With onlyHandlers being true (triggerHandler()), this will never happen.

    So, for the trigger() case, it ends up executing click() on the target element, which triggers the state change correctly—but it turns out that, in both the trigger() and triggerHandler() cases, the click was already correctly fired up in the loop through the eventPath above:

    // Native handler
    handle = ontype && cur[ ontype ];
    if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
    

    So trigger('click') ends up clicking the element twice (!) — presumably because click() doesn't return false, so the event default action is never prevented — whereas triggerHandler('click') only does it once.

    This can be verified by stepping through the jQuery.event.trigger method with the inspector and seeing the selector open, then close again.

    The question is if this what we should generally expect; it seems strange that an otherwise-working event trigger would cause double triggers in the case of a DOM-only reaction.