Skip to content

Commit f34c84b

Browse files
mcountiskevin-brown
andcommitted
Prevent opening of disabled elements (select2#5751)
* Prevent a disabled element from opening via keydown/keypress events Gist: return early from the Select2#open method if the object is disabled. Add helper #isEnabled and #isDisabled methods to Select2 and selector classes. Add test coverage for the original, expected behavior and new preventative behavior. * Remove single-use method from tests Co-authored-by: Kevin Brown <[email protected]>
1 parent e0855a2 commit f34c84b

File tree

8 files changed

+241
-4
lines changed

8 files changed

+241
-4
lines changed

src/js/select2/core.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ define([
365365
Select2.prototype._syncAttributes = function () {
366366
this.options.set('disabled', this.$element.prop('disabled'));
367367

368-
if (this.options.get('disabled')) {
368+
if (this.isDisabled()) {
369369
if (this.isOpen()) {
370370
this.close();
371371
}
@@ -470,7 +470,7 @@ define([
470470
};
471471

472472
Select2.prototype.toggleDropdown = function () {
473-
if (this.options.get('disabled')) {
473+
if (this.isDisabled()) {
474474
return;
475475
}
476476

@@ -486,6 +486,10 @@ define([
486486
return;
487487
}
488488

489+
if (this.isDisabled()) {
490+
return;
491+
}
492+
489493
this.trigger('query', {});
490494
};
491495

@@ -497,6 +501,27 @@ define([
497501
this.trigger('close', { originalEvent : evt });
498502
};
499503

504+
/**
505+
* Helper method to abstract the "enabled" (not "disabled") state of this
506+
* object.
507+
*
508+
* @return {true} if the instance is not disabled.
509+
* @return {false} if the instance is disabled.
510+
*/
511+
Select2.prototype.isEnabled = function () {
512+
return !this.isDisabled();
513+
};
514+
515+
/**
516+
* Helper method to abstract the "disabled" state of this object.
517+
*
518+
* @return {true} if the disabled option is true.
519+
* @return {false} if the disabled option is false.
520+
*/
521+
Select2.prototype.isDisabled = function () {
522+
return this.options.get('disabled');
523+
};
524+
500525
Select2.prototype.isOpen = function () {
501526
return this.$container.hasClass('select2-container--open');
502527
};

src/js/select2/selection/allowClear.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ define([
3131

3232
AllowClear.prototype._handleClear = function (_, evt) {
3333
// Ignore the event if it is disabled
34-
if (this.options.get('disabled')) {
34+
if (this.isDisabled()) {
3535
return;
3636
}
3737

src/js/select2/selection/base.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,26 @@ define([
153153
throw new Error('The `update` method must be defined in child classes.');
154154
};
155155

156+
/**
157+
* Helper method to abstract the "enabled" (not "disabled") state of this
158+
* object.
159+
*
160+
* @return {true} if the instance is not disabled.
161+
* @return {false} if the instance is disabled.
162+
*/
163+
BaseSelection.prototype.isEnabled = function () {
164+
return !this.isDisabled();
165+
};
166+
167+
/**
168+
* Helper method to abstract the "disabled" state of this object.
169+
*
170+
* @return {true} if the disabled option is true.
171+
* @return {false} if the disabled option is false.
172+
*/
173+
BaseSelection.prototype.isDisabled = function () {
174+
return this.options.get('disabled');
175+
};
176+
156177
return BaseSelection;
157178
});

src/js/select2/selection/multiple.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ define([
3737
'.select2-selection__choice__remove',
3838
function (evt) {
3939
// Ignore the event if it is disabled
40-
if (self.options.get('disabled')) {
40+
if (self.isDisabled()) {
4141
return;
4242
}
4343

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
module('Selection containers - Open On Key Down');
2+
3+
var KEYS = require('select2/keys');
4+
var $ = require('jquery');
5+
6+
/**
7+
* Build a keydown event with the given key code and extra options.
8+
*
9+
* @param {Number} keyCode the keyboard code to be used for the 'which'
10+
* attribute of the keydown event.
11+
* @param {Object} eventProps extra properties to build the keydown event.
12+
*
13+
* @return {jQuery.Event} a 'keydown' type event.
14+
*/
15+
function buildKeyDownEvent (keyCode, eventProps) {
16+
return $.Event('keydown', $.extend({}, { which: keyCode }, eventProps));
17+
}
18+
19+
/**
20+
* Wrapper function providing a select2 element with a given enabled/disabled
21+
* state that will get a given keydown event triggered on it. Provide an
22+
* assertion callback function to test the results of the triggered event.
23+
*
24+
* @param {Boolean} isEnabled the enabled state of the desired select2
25+
* element.
26+
* @param {String} testName name for the test.
27+
* @param {Number} keyCode used to set the 'which' attribute of the
28+
* keydown event.
29+
* @param {Object} eventProps attributes to be used to build the keydown
30+
* event.
31+
* @param {Function} fn assertion callback to perform checks on the
32+
* result of triggering the event, receives the
33+
* 'assert' variable for the test and the select2
34+
* instance behind the built <select> element.
35+
* @return {null}
36+
*/
37+
function testAbled(isEnabled, testName, keyCode, eventProps, fn) {
38+
test(testName, function (assert) {
39+
var $element = $(
40+
'<select>' +
41+
'<option>one</option>' +
42+
'<option>two</option>' +
43+
'</select>'
44+
);
45+
$('#qunit-fixture').append($element);
46+
$element.select2({ disabled: !isEnabled });
47+
48+
var select2 = $element.data('select2');
49+
var $selection = select2.$selection;
50+
51+
assert.notOk(select2.isOpen(), 'The instance should not be open');
52+
assert.equal(select2.isEnabled(), isEnabled);
53+
54+
var event = buildKeyDownEvent(keyCode, eventProps);
55+
assert.ok(event.which, 'The event\'s key code (.which) should be set');
56+
57+
$selection.trigger(event);
58+
59+
fn(assert, select2);
60+
});
61+
}
62+
63+
/**
64+
* Test the given keydown event on an enabled element. See #testAbled for
65+
* params.
66+
*/
67+
function testEnabled (testName, keyCode, eventProps, fn) {
68+
testAbled(true, testName, keyCode, eventProps, fn);
69+
}
70+
71+
/**
72+
* Test the given keydown event on a disabled element. See #testAbled for
73+
* params.
74+
*/
75+
function testDisabled (testName, keyCode, eventProps, fn) {
76+
testAbled(false, testName, keyCode, eventProps, fn);
77+
}
78+
79+
/**
80+
* Assertion function used by the above test* wrappers. Asserts that the given
81+
* select2 instance is open.
82+
*
83+
* @param {Assert} assert
84+
* @param {Select2} select
85+
* @return {null}
86+
*/
87+
function assertOpened (assert, select2) {
88+
assert.ok(select2.isOpen(), 'The element should be open');
89+
}
90+
91+
/**
92+
* Assertion function used by the above test* wrappers. Asserts that the given
93+
* select2 instance is not open.
94+
*
95+
* @param {Assert} assert
96+
* @param {Select2} select
97+
* @return {null}
98+
*/
99+
function assertNotOpened (assert, select2) {
100+
assert.notOk(select2.isOpen(), 'The element should not be open');
101+
}
102+
103+
/**
104+
* ENTER, SPACE, and ALT+DOWN should all open an enabled select2 element.
105+
*/
106+
testEnabled(
107+
'enabled element will open on ENTER',
108+
KEYS.ENTER, {},
109+
assertOpened
110+
);
111+
testEnabled(
112+
'enabled element will open on SPACE',
113+
KEYS.SPACE, {},
114+
assertOpened
115+
);
116+
testEnabled(
117+
'enabled element will open on ALT+DOWN',
118+
KEYS.DOWN, { altKey: true },
119+
assertOpened
120+
);
121+
122+
/**
123+
* Some other keys triggered on an enabled select2 element should not open it.
124+
*/
125+
testEnabled(
126+
'enabled element will not open on UP',
127+
KEYS.UP, {},
128+
assertNotOpened
129+
);
130+
testEnabled(
131+
'enabled element will not open on DOWN',
132+
KEYS.UP, {},
133+
assertNotOpened
134+
);
135+
testEnabled(
136+
'enabled element will not open on LEFT',
137+
KEYS.UP, {},
138+
assertNotOpened
139+
);
140+
testEnabled(
141+
'enabled element will not open on RIGHT',
142+
KEYS.UP, {},
143+
assertNotOpened
144+
);
145+
146+
/*
147+
* The keys that will open an enabled select2 element should not open a disabled
148+
* one.
149+
*/
150+
testDisabled(
151+
'disabled element will not open on ENTER',
152+
KEYS.ENTER, {},
153+
assertNotOpened
154+
);
155+
testDisabled(
156+
'disabled element will not open on SPACE',
157+
KEYS.SPACE, {},
158+
assertNotOpened
159+
);
160+
testDisabled(
161+
'disabled element will not open on ALT+DOWN',
162+
KEYS.DOWN, { altKey: true },
163+
assertNotOpened
164+
);
165+
166+
/**
167+
* Other keys should continue to not open a disabled select2 element.
168+
*/
169+
testDisabled(
170+
'disabled element will not open on UP',
171+
KEYS.UP, {},
172+
assertNotOpened
173+
);
174+
testDisabled(
175+
'disabled element will not open on DOWN',
176+
KEYS.UP, {},
177+
assertNotOpened
178+
);
179+
testDisabled(
180+
'disabled element will not open on LEFT',
181+
KEYS.UP, {},
182+
assertNotOpened
183+
);
184+
testDisabled(
185+
'disabled element will not open on RIGHT',
186+
KEYS.UP, {},
187+
assertNotOpened
188+
);

tests/unit-jq1.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
<script src="selection/search-placeholder-tests.js" type="text/javascript"></script>
9898
<script src="selection/single-tests.js" type="text/javascript"></script>
9999
<script src="selection/stopPropagation-tests.js" type="text/javascript"></script>
100+
<script src="selection/openOnKeyDown-tests.js" type="text/javascript"></script>
100101

101102
<script src="utils/data-tests.js" type="text/javascript"></script>
102103
<script src="utils/decorator-tests.js" type="text/javascript"></script>

tests/unit-jq2.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
<script src="selection/search-placeholder-tests.js" type="text/javascript"></script>
9898
<script src="selection/single-tests.js" type="text/javascript"></script>
9999
<script src="selection/stopPropagation-tests.js" type="text/javascript"></script>
100+
<script src="selection/openOnKeyDown-tests.js" type="text/javascript"></script>
100101

101102
<script src="utils/data-tests.js" type="text/javascript"></script>
102103
<script src="utils/decorator-tests.js" type="text/javascript"></script>

tests/unit-jq3.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
<script src="selection/search-placeholder-tests.js" type="text/javascript"></script>
9898
<script src="selection/single-tests.js" type="text/javascript"></script>
9999
<script src="selection/stopPropagation-tests.js" type="text/javascript"></script>
100+
<script src="selection/openOnKeyDown-tests.js" type="text/javascript"></script>
100101

101102
<script src="utils/data-tests.js" type="text/javascript"></script>
102103
<script src="utils/decorator-tests.js" type="text/javascript"></script>

0 commit comments

Comments
 (0)