diff options
author | Richard Gibson <richard.gibson@gmail.com> | 2017-01-11 15:19:30 -0700 |
---|---|---|
committer | Michał Gołębiowski-Owczarek <m.goleb@gmail.com> | 2019-03-20 16:40:16 +0100 |
commit | 669f720edc4f557dfef986db747c09ebfaa16ef5 (patch) | |
tree | dba33f1b3713faa4d2e6e3cfedbd0795a28261cb /src | |
parent | a0abd15b9e5aa9c1f36a9599e6095304825a7b9f (diff) | |
download | jquery-669f720edc4f557dfef986db747c09ebfaa16ef5.tar.gz jquery-669f720edc4f557dfef986db747c09ebfaa16ef5.zip |
Event: Leverage native events for focus/blur/click; propagate additional data
Summary of the changes/fixes:
1. Trigger checkbox and radio click events identically (cherry-picked from
b442abacbb8464f0165059e8da734e3143d0721f 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 gh-1741
Fixes gh-3423
Fixes gh-3751
Fixes gh-4139
Closes gh-4279
Ref gh-1367
Ref gh-3494
Diffstat (limited to 'src')
-rw-r--r-- | src/event.js | 177 | ||||
-rw-r--r-- | src/manipulation.js | 6 | ||||
-rw-r--r-- | src/serialize.js | 2 | ||||
-rw-r--r-- | src/var/rcheckableType.js (renamed from src/manipulation/var/rcheckableType.js) | 0 |
4 files changed, 161 insertions, 24 deletions
diff --git a/src/event.js b/src/event.js index 0bf481b16..cb70e8ebe 100644 --- a/src/event.js +++ b/src/event.js @@ -4,6 +4,7 @@ define( [ "./var/documentElement", "./var/isFunction", "./var/rnothtmlwhite", + "./var/rcheckableType", "./var/slice", "./data/var/dataPriv", "./core/nodeName", @@ -11,7 +12,7 @@ define( [ "./core/init", "./selector" ], function( jQuery, document, documentElement, isFunction, rnothtmlwhite, - slice, dataPriv, nodeName ) { + rcheckableType, slice, dataPriv, nodeName ) { "use strict"; @@ -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; @@ -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" ); } }, @@ -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 diff --git a/src/manipulation.js b/src/manipulation.js index a24a5cc0c..7dbc92689 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -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", @@ -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 ) { diff --git a/src/serialize.js b/src/serialize.js index 44d3606b3..d8a9a36a4 100644 --- a/src/serialize.js +++ b/src/serialize.js @@ -1,7 +1,7 @@ define( [ "./core", "./core/toType", - "./manipulation/var/rcheckableType", + "./var/rcheckableType", "./var/isFunction", "./core/init", "./traversing", // filter diff --git a/src/manipulation/var/rcheckableType.js b/src/var/rcheckableType.js index 25bbcb418..25bbcb418 100644 --- a/src/manipulation/var/rcheckableType.js +++ b/src/var/rcheckableType.js |