Skip to content

Commit 669f720

Browse files
gibson042mgol
authored andcommitted
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 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 gh-1741 Fixes gh-3423 Fixes gh-3751 Fixes gh-4139 Closes gh-4279 Ref gh-1367 Ref gh-3494
1 parent a0abd15 commit 669f720

File tree

5 files changed

+294
-54
lines changed

5 files changed

+294
-54
lines changed

src/event.js

Lines changed: 157 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ define( [
44
"./var/documentElement",
55
"./var/isFunction",
66
"./var/rnothtmlwhite",
7+
"./var/rcheckableType",
78
"./var/slice",
89
"./data/var/dataPriv",
910
"./core/nodeName",
1011

1112
"./core/init",
1213
"./selector"
1314
], function( jQuery, document, documentElement, isFunction, rnothtmlwhite,
14-
slice, dataPriv, nodeName ) {
15+
rcheckableType, slice, dataPriv, nodeName ) {
1516

1617
"use strict";
1718

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

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

336338
event.handleObj = handleObj;
337339
event.data = handleObj.data;
@@ -457,37 +459,101 @@ jQuery.event = {
457459
},
458460
focus: {
459461

460-
// Fire native event if possible so blur/focus sequence is correct
462+
// Utilize native event if possible so blur/focus sequence is correct
463+
setup: function() {
464+
465+
// Claim the first handler
466+
// dataPriv.set( this, "focus", ... )
467+
leverageNative( this, "focus", false, function( el ) {
468+
return el !== safeActiveElement();
469+
} );
470+
471+
// Return false to allow normal processing in the caller
472+
return false;
473+
},
461474
trigger: function() {
462-
if ( this !== safeActiveElement() && this.focus ) {
463-
this.focus();
464-
return false;
465-
}
475+
476+
// Force setup before trigger
477+
leverageNative( this, "focus", returnTrue );
478+
479+
// Return non-false to allow normal event-path propagation
480+
return true;
466481
},
482+
467483
delegateType: "focusin"
468484
},
469485
blur: {
486+
487+
// Utilize native event if possible so blur/focus sequence is correct
488+
setup: function() {
489+
490+
// Claim the first handler
491+
// dataPriv.set( this, "blur", ... )
492+
leverageNative( this, "blur", false, function( el ) {
493+
return el === safeActiveElement();
494+
} );
495+
496+
// Return false to allow normal processing in the caller
497+
return false;
498+
},
470499
trigger: function() {
471-
if ( this === safeActiveElement() && this.blur ) {
472-
this.blur();
473-
return false;
474-
}
500+
501+
// Force setup before trigger
502+
leverageNative( this, "blur", returnTrue );
503+
504+
// Return non-false to allow normal event-path propagation
505+
return true;
475506
},
507+
476508
delegateType: "focusout"
477509
},
478510
click: {
479511

480-
// For checkbox, fire native event so checked state will be right
481-
trigger: function() {
482-
if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) {
483-
this.click();
484-
return false;
512+
// Utilize native event to ensure correct state for checkable inputs
513+
setup: function( data ) {
514+
515+
// For mutual compressibility with _default, replace `this` access with a local var.
516+
// `|| data` is dead code meant only to preserve the variable through minification.
517+
var el = this || data;
518+
519+
// Claim the first handler
520+
if ( rcheckableType.test( el.type ) &&
521+
el.click && nodeName( el, "input" ) &&
522+
dataPriv.get( el, "click" ) === undefined ) {
523+
524+
// dataPriv.set( el, "click", ... )
525+
leverageNative( el, "click", false, returnFalse );
485526
}
527+
528+
// Return false to allow normal processing in the caller
529+
return false;
530+
},
531+
trigger: function( data ) {
532+
533+
// For mutual compressibility with _default, replace `this` access with a local var.
534+
// `|| data` is dead code meant only to preserve the variable through minification.
535+
var el = this || data;
536+
537+
// Force setup before triggering a click
538+
if ( rcheckableType.test( el.type ) &&
539+
el.click && nodeName( el, "input" ) &&
540+
dataPriv.get( el, "click" ) === undefined ) {
541+
542+
leverageNative( el, "click", returnTrue );
543+
}
544+
545+
// Return non-false to allow normal event-path propagation
546+
return true;
486547
},
487548

488-
// For cross-browser consistency, don't fire native .click() on links
549+
// For cross-browser consistency, suppress native .click() on links
550+
// Also prevent it if we're currently inside a leveraged native-event stack
489551
_default: function( event ) {
490-
return nodeName( event.target, "a" );
552+
var target = event.target;
553+
return rcheckableType.test( target.type ) &&
554+
target.click && nodeName( target, "input" ) &&
555+
dataPriv.get( target, "click" ) ||
556+
nodeName( target, "a" );
491557
}
492558
},
493559

@@ -504,6 +570,77 @@ jQuery.event = {
504570
}
505571
};
506572

573+
// Ensure the presence of an event listener that handles manually-triggered
574+
// synthetic events by interrupting progress until reinvoked in response to
575+
// *native* events that it fires directly, ensuring that state changes have
576+
// already occurred before other listeners are invoked.
577+
function leverageNative( el, type, forceAdd, allowAsync ) {
578+
579+
// Setup must go through jQuery.event.add
580+
if ( forceAdd ) {
581+
jQuery.event.add( el, type, forceAdd );
582+
return;
583+
}
584+
585+
// Register the controller as a special universal handler for all event namespaces
586+
dataPriv.set( el, type, forceAdd );
587+
jQuery.event.add( el, type, {
588+
namespace: false,
589+
handler: function( event ) {
590+
var maybeAsync, result,
591+
saved = dataPriv.get( this, type );
592+
593+
// Interrupt processing of the outer synthetic .trigger()ed event
594+
if ( ( event.isTrigger & 1 ) && this[ type ] && !saved ) {
595+
596+
// Store arguments for use when handling the inner native event
597+
saved = slice.call( arguments );
598+
dataPriv.set( this, type, saved );
599+
600+
// Trigger the native event and capture its result
601+
// Support: IE <=9 - 11+
602+
// focus() and blur() are asynchronous
603+
maybeAsync = allowAsync( this, type );
604+
this[ type ]();
605+
result = dataPriv.get( this, type );
606+
if ( result !== saved ) {
607+
dataPriv.set( this, type, false );
608+
609+
// Cancel the outer synthetic event
610+
event.stopImmediatePropagation();
611+
event.preventDefault();
612+
return result;
613+
} else if ( maybeAsync ) {
614+
615+
// Cancel the outer synthetic event in expectation of a followup
616+
event.stopImmediatePropagation();
617+
event.preventDefault();
618+
return;
619+
} else {
620+
dataPriv.set( this, type, false );
621+
}
622+
623+
// If this is a native event triggered above, everything is now in order
624+
// Fire an inner synthetic event with the original arguments
625+
} else if ( !event.isTrigger && saved ) {
626+
627+
// ...and capture the result
628+
dataPriv.set( this, type, jQuery.event.trigger(
629+
630+
// Support: IE <=9 - 11+
631+
// Extend with the prototype to reset the above stopImmediatePropagation()
632+
jQuery.extend( saved.shift(), jQuery.Event.prototype ),
633+
saved,
634+
this
635+
) );
636+
637+
// Abort handling of the native event
638+
event.stopImmediatePropagation();
639+
}
640+
}
641+
} );
642+
}
643+
507644
jQuery.removeEvent = function( elem, type, handle ) {
508645

509646
// This "if" is needed for plain objects

src/manipulation.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ define( [
44
"./var/concat",
55
"./var/isFunction",
66
"./var/push",
7+
"./var/rcheckableType",
78
"./core/access",
8-
"./manipulation/var/rcheckableType",
99
"./manipulation/var/rtagName",
1010
"./manipulation/var/rscriptType",
1111
"./manipulation/wrapMap",
@@ -24,8 +24,8 @@ define( [
2424
"./traversing",
2525
"./selector",
2626
"./event"
27-
], function( jQuery, isAttached, concat, isFunction, push, access,
28-
rcheckableType, rtagName, rscriptType,
27+
], function( jQuery, isAttached, concat, isFunction, push, rcheckableType,
28+
access, rtagName, rscriptType,
2929
wrapMap, getAll, setGlobalEval, buildFragment, support,
3030
dataPriv, dataUser, acceptData, DOMEval, nodeName ) {
3131

src/serialize.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
define( [
22
"./core",
33
"./core/toType",
4-
"./manipulation/var/rcheckableType",
4+
"./var/rcheckableType",
55
"./var/isFunction",
66
"./core/init",
77
"./traversing", // filter
File renamed without changes.

0 commit comments

Comments
 (0)