Skip to content

Commit d327be4

Browse files
authored
Datepicker: Support init inside a delegated focus handler in jQuery 4
jQuery 4.0+ follows native focus events order, meaning that `focusin` is fired after `focus`. As delegated `focus` is implemented in jQuery via `focusin`, `focus` handlers attached during a delegated `focus` handler will not fire until the second time the field receives focus. This is what the `_attachments` method does. To account for that, show the datepicker if input is already focused. `_showDatepicker` checks if the datepicker is already open, so it's not a problem that it fires again as a `focus` handler in jQuery <4. Note that the fact such an initialization worked inside of delegated focus handlers was a result of an implementation detail in jQuery. If a regular `focus` handler was used to initialize the datepicker, neither jQuery 4.0 nor 3.x would show the datepicker. This issue is now fixed as well. Fixes gh-2385 Closes gh-2390
1 parent f10169e commit d327be4

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

tests/unit/datepicker/core.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ QUnit.test( "initialization - Reinitialization after body had been emptied.", fu
2929
QUnit.test( "widget method - empty collection", function( assert ) {
3030
assert.expect( 1 );
3131
$( "#nonExist" ).datepicker(); // Should create nothing
32-
assert.ok( !$( "#ui-datepicker-div" ).length, "Non init on empty collection" );
32+
assert.strictEqual( $( "#ui-datepicker-div" ).length, 0, "Non init on empty collection" );
3333
} );
3434

3535
QUnit.test( "widget method", function( assert ) {
@@ -540,4 +540,40 @@ QUnit.test( "mouse", function( assert ) {
540540
"Mouse click inline - next" );
541541
} );
542542

543+
QUnit.test( "initialized on focus is immediately shown (gh-2385)", function( assert ) {
544+
assert.expect( 2 );
545+
546+
var dp, dp2, inp, inp2, parent;
547+
548+
try {
549+
inp = $( "#inp" );
550+
parent = inp.parent();
551+
parent.on( "focus", "#inp:not(.hasDatepicker)", function() {
552+
testHelper.init( "#inp" );
553+
dp = $( "#ui-datepicker-div" );
554+
} );
555+
inp.trigger( "focus" );
556+
assert.equal( dp.css( "display" ), "block",
557+
"Datepicker - visible (delegated focus)" );
558+
} finally {
559+
inp.datepicker( "destroy" );
560+
}
561+
562+
try {
563+
inp2 = $( "#inp2" );
564+
inp2.on( "focus", function() {
565+
if ( $( this ).hasClass( "hasDatepicker" ) ) {
566+
return;
567+
}
568+
testHelper.init( "#inp2" );
569+
dp2 = $( "#ui-datepicker-div" );
570+
} );
571+
inp2.trigger( "focus" );
572+
assert.equal( dp2.css( "display" ), "block",
573+
"Datepicker - visible (regular focus)" );
574+
} finally {
575+
inp2.datepicker( "destroy" );
576+
}
577+
} );
578+
543579
} );

ui/widgets/datepicker.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,22 @@ $.extend( Datepicker.prototype, {
235235
if ( inst.settings.disabled ) {
236236
this._disableDatepicker( target );
237237
}
238+
239+
// Support: jQuery 4.0.0+
240+
// jQuery 4.0+ follows native focus events order, meaning that `focusin`
241+
// is fired after `focus`. As delegated `focus` is implemented in jQuery
242+
// via `focusin`, `focus` handlers attached during a delegated `focus`
243+
// handler will not fire until the second time the field receives focus.
244+
// This is what the `_attachments` method does. To account for that, show
245+
// the datepicker if input is already focused. `_showDatepicker` checks
246+
// if the datepicker is already open, so it's not a problem that it
247+
// fires again as a `focus` handler in jQuery <4.
248+
//
249+
// Note that the fact such an initialization worked inside of delegated
250+
// focus handlers was a result of an implementation detail in jQuery. If
251+
// a regular `focus` handler was used to initialize the datepicker, neither
252+
// jQuery 4.0 nor 3.x would show the datepicker without the call below.
253+
this._showDatepickerIfFocused( input );
238254
},
239255

240256
/* Make attachments based on settings. */
@@ -594,6 +610,7 @@ $.extend( Datepicker.prototype, {
594610
this._setDate( inst, date );
595611
this._updateAlternate( inst );
596612
this._updateDatepicker( inst );
613+
this._showDatepickerIfFocused( target );
597614
}
598615
},
599616

@@ -862,6 +879,12 @@ $.extend( Datepicker.prototype, {
862879
}
863880
},
864881

882+
_showDatepickerIfFocused: function( input ) {
883+
if ( input.length && input.is( ":focus" ) ) {
884+
this._showDatepicker( input[ 0 ] );
885+
}
886+
},
887+
865888
/* Generate the date picker content. */
866889
_updateDatepicker: function( inst ) {
867890
this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)

0 commit comments

Comments
 (0)