Skip to content

Commit

Permalink
Event: Leverage native events for focus/blur/click; propagate additio…
Browse files Browse the repository at this point in the history
…nal data

Summary of the changes/fixes:
1. Trigger checkbox and radio click events identically (cherry-picked from
   b442aba that was reverted before).
2. Manually trigger a native event before checkbox/radio handlers.
3. Add test coverage for triggering namespaced native-backed events.
4. Propagate extra parameters passed when triggering the click event to
   the handlers.
5. Intercept and preserve namespaced native-backed events.
6. Leverage native events for focus and blur.
7. Accept that focusin handlers may fire more than once for now.

Fixes jquerygh-1741
Fixes jquerygh-3423
Fixes jquerygh-3751
Fixes jquerygh-4139
Closes jquerygh-4279
Ref jquerygh-1367
Ref jquerygh-3494
  • Loading branch information
gibson042 authored and mgol committed Mar 20, 2019
1 parent a0abd15 commit 669f720
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 54 deletions.
177 changes: 157 additions & 20 deletions src/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ define( [
"./var/documentElement",
"./var/isFunction",
"./var/rnothtmlwhite",
"./var/rcheckableType",
"./var/slice",
"./data/var/dataPriv",
"./core/nodeName",

"./core/init",
"./selector"
], function( jQuery, document, documentElement, isFunction, rnothtmlwhite,
slice, dataPriv, nodeName ) {
rcheckableType, slice, dataPriv, nodeName ) {

"use strict";

Expand Down Expand Up @@ -329,9 +330,10 @@ jQuery.event = {
while ( ( handleObj = matched.handlers[ j++ ] ) &&
!event.isImmediatePropagationStopped() ) {

// Triggered event must either 1) have no namespace, or 2) have namespace(s)
// a subset or equal to those in the bound event (both can have no namespace).
if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
// If the event is namespaced, then each handler is only invoked if it is
// specially universal or its namespaces are a superset of the event's.
if ( !event.rnamespace || handleObj.namespace === false ||
event.rnamespace.test( handleObj.namespace ) ) {

event.handleObj = handleObj;
event.data = handleObj.data;
Expand Down Expand Up @@ -457,37 +459,101 @@ jQuery.event = {
},
focus: {

// Fire native event if possible so blur/focus sequence is correct
// Utilize native event if possible so blur/focus sequence is correct
setup: function() {

// Claim the first handler
// dataPriv.set( this, "focus", ... )
leverageNative( this, "focus", false, function( el ) {
return el !== safeActiveElement();
} );

// Return false to allow normal processing in the caller
return false;
},
trigger: function() {
if ( this !== safeActiveElement() && this.focus ) {
this.focus();
return false;
}

// Force setup before trigger
leverageNative( this, "focus", returnTrue );

// Return non-false to allow normal event-path propagation
return true;
},

delegateType: "focusin"
},
blur: {

// Utilize native event if possible so blur/focus sequence is correct
setup: function() {

// Claim the first handler
// dataPriv.set( this, "blur", ... )
leverageNative( this, "blur", false, function( el ) {
return el === safeActiveElement();
} );

// Return false to allow normal processing in the caller
return false;
},
trigger: function() {
if ( this === safeActiveElement() && this.blur ) {
this.blur();
return false;
}

// Force setup before trigger
leverageNative( this, "blur", returnTrue );

// Return non-false to allow normal event-path propagation
return true;
},

delegateType: "focusout"
},
click: {

// For checkbox, fire native event so checked state will be right
trigger: function() {
if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) {
this.click();
return false;
// Utilize native event to ensure correct state for checkable inputs
setup: function( data ) {

// For mutual compressibility with _default, replace `this` access with a local var.
// `|| data` is dead code meant only to preserve the variable through minification.
var el = this || data;

// Claim the first handler
if ( rcheckableType.test( el.type ) &&
el.click && nodeName( el, "input" ) &&
dataPriv.get( el, "click" ) === undefined ) {

// dataPriv.set( el, "click", ... )
leverageNative( el, "click", false, returnFalse );
}

// Return false to allow normal processing in the caller
return false;
},
trigger: function( data ) {

// For mutual compressibility with _default, replace `this` access with a local var.
// `|| data` is dead code meant only to preserve the variable through minification.
var el = this || data;

// Force setup before triggering a click
if ( rcheckableType.test( el.type ) &&
el.click && nodeName( el, "input" ) &&
dataPriv.get( el, "click" ) === undefined ) {

leverageNative( el, "click", returnTrue );
}

// Return non-false to allow normal event-path propagation
return true;
},

// For cross-browser consistency, don't fire native .click() on links
// For cross-browser consistency, suppress native .click() on links
// Also prevent it if we're currently inside a leveraged native-event stack
_default: function( event ) {
return nodeName( event.target, "a" );
var target = event.target;
return rcheckableType.test( target.type ) &&
target.click && nodeName( target, "input" ) &&
dataPriv.get( target, "click" ) ||
nodeName( target, "a" );
}
},

Expand All @@ -504,6 +570,77 @@ jQuery.event = {
}
};

// Ensure the presence of an event listener that handles manually-triggered
// synthetic events by interrupting progress until reinvoked in response to
// *native* events that it fires directly, ensuring that state changes have
// already occurred before other listeners are invoked.
function leverageNative( el, type, forceAdd, allowAsync ) {

// Setup must go through jQuery.event.add
if ( forceAdd ) {
jQuery.event.add( el, type, forceAdd );
return;
}

// Register the controller as a special universal handler for all event namespaces
dataPriv.set( el, type, forceAdd );
jQuery.event.add( el, type, {
namespace: false,
handler: function( event ) {
var maybeAsync, result,
saved = dataPriv.get( this, type );

// Interrupt processing of the outer synthetic .trigger()ed event
if ( ( event.isTrigger & 1 ) && this[ type ] && !saved ) {

// Store arguments for use when handling the inner native event
saved = slice.call( arguments );
dataPriv.set( this, type, saved );

// Trigger the native event and capture its result
// Support: IE <=9 - 11+
// focus() and blur() are asynchronous
maybeAsync = allowAsync( this, type );
this[ type ]();
result = dataPriv.get( this, type );
if ( result !== saved ) {
dataPriv.set( this, type, false );

// Cancel the outer synthetic event
event.stopImmediatePropagation();
event.preventDefault();
return result;
} else if ( maybeAsync ) {

// Cancel the outer synthetic event in expectation of a followup
event.stopImmediatePropagation();
event.preventDefault();
return;
} else {
dataPriv.set( this, type, false );
}

// If this is a native event triggered above, everything is now in order
// Fire an inner synthetic event with the original arguments
} else if ( !event.isTrigger && saved ) {

// ...and capture the result
dataPriv.set( this, type, jQuery.event.trigger(

// Support: IE <=9 - 11+
// Extend with the prototype to reset the above stopImmediatePropagation()
jQuery.extend( saved.shift(), jQuery.Event.prototype ),
saved,
this
) );

// Abort handling of the native event
event.stopImmediatePropagation();
}
}
} );
}

jQuery.removeEvent = function( elem, type, handle ) {

// This "if" is needed for plain objects
Expand Down
6 changes: 3 additions & 3 deletions src/manipulation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ define( [
"./var/concat",
"./var/isFunction",
"./var/push",
"./var/rcheckableType",
"./core/access",
"./manipulation/var/rcheckableType",
"./manipulation/var/rtagName",
"./manipulation/var/rscriptType",
"./manipulation/wrapMap",
Expand All @@ -24,8 +24,8 @@ define( [
"./traversing",
"./selector",
"./event"
], function( jQuery, isAttached, concat, isFunction, push, access,
rcheckableType, rtagName, rscriptType,
], function( jQuery, isAttached, concat, isFunction, push, rcheckableType,
access, rtagName, rscriptType,
wrapMap, getAll, setGlobalEval, buildFragment, support,
dataPriv, dataUser, acceptData, DOMEval, nodeName ) {

Expand Down
2 changes: 1 addition & 1 deletion src/serialize.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
define( [
"./core",
"./core/toType",
"./manipulation/var/rcheckableType",
"./var/rcheckableType",
"./var/isFunction",
"./core/init",
"./traversing", // filter
Expand Down
File renamed without changes.
Loading

0 comments on commit 669f720

Please sign in to comment.