Skip to content

Commit 1f3eceb

Browse files
authored
Fix generated options not receiving result IDs (select2#5586)
In order to enable the ability to uniquely identify a result by an ID in the DOM, we generate a new ID for the result based on a combination of things, including the container ID prefix that is generated and used elsewhere in Select2. This has worked fairly well for use cases including attaching Select2 to an existing `<select>` and loading in options from a remote data set. Unfortunately, because this process relied on the container ID being used as a prefix, this failed for options which were automatically generated on initialization using the `data:` option to Select2. These were not being generated with an ID because at the time that they were being generated, the data adapter was not aware of the container it was being used in. This broke some accessibility features because we had a mix of options in the results list with IDs, and some without, so we fixed the ordering to make this work. Option generation no longer happens when the data adapter is first initialized, which is where it was previously happening, and instead it now occurs when the data adapter is bound to the container. This allows us to ensure that the data adapter is always aware of the container it is being associated with, so now it will be able to generate the result IDs. This also fixes the tests for the array adapter as well as the legacy `<input />` adapter so they properly bind to a container during the test. This was causing test failures becuase the options which would previously be generated during initialization were no longer appearing. Fixes select2#4350
1 parent 2fce8ae commit 1f3eceb

3 files changed

Lines changed: 76 additions & 3 deletions

File tree

src/js/select2/data/array.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ define([
44
'jquery'
55
], function (SelectAdapter, Utils, $) {
66
function ArrayAdapter ($element, options) {
7-
var data = options.get('data') || [];
7+
this._dataToConvert = options.get('data') || [];
88

99
ArrayAdapter.__super__.constructor.call(this, $element, options);
10-
11-
this.addOptions(this.convertToOptions(data));
1210
}
1311

1412
Utils.Extend(ArrayAdapter, SelectAdapter);
1513

14+
ArrayAdapter.prototype.bind = function (container, $container) {
15+
ArrayAdapter.__super__.bind.call(this, container, $container);
16+
17+
this.addOptions(this.convertToOptions(this._dataToConvert));
18+
};
19+
1620
ArrayAdapter.prototype.select = function (data) {
1721
var $option = this.$element.find('option').filter(function (i, elm) {
1822
return elm.value == data.id.toString();

tests/data/array-tests.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ test('current gets default for single', function (assert) {
7171

7272
var data = new ArrayData($select, arrayOptions);
7373

74+
var container = new MockContainer();
75+
data.bind(container, $('<div></div>'));
76+
7477
data.current(function (val) {
7578
assert.equal(
7679
val.length,
@@ -93,6 +96,9 @@ test('current gets default for multiple', function (assert) {
9396

9497
var data = new ArrayData($select, arrayOptions);
9598

99+
var container = new MockContainer();
100+
data.bind(container, $('<div></div>'));
101+
96102
data.current(function (val) {
97103
assert.equal(
98104
val.length,
@@ -107,6 +113,9 @@ test('current works with existing selections', function (assert) {
107113

108114
var data = new ArrayData($select, arrayOptions);
109115

116+
var container = new MockContainer();
117+
data.bind(container, $('<div></div>'));
118+
110119
$select.val(['One']);
111120

112121
data.current(function (val) {
@@ -137,6 +146,9 @@ test('current works with selected data', function (assert) {
137146

138147
var data = new ArrayData($select, arrayOptions);
139148

149+
var container = new MockContainer();
150+
data.bind(container, $('<div></div>'));
151+
140152
data.select({
141153
id: '2',
142154
text: '2'
@@ -170,6 +182,9 @@ test('select works for single', function (assert) {
170182

171183
var data = new ArrayData($select, arrayOptions);
172184

185+
var container = new MockContainer();
186+
data.bind(container, $('<div></div>'));
187+
173188
assert.equal(
174189
$select.val(),
175190
'default',
@@ -193,6 +208,9 @@ test('multiple sets the value', function (assert) {
193208

194209
var data = new ArrayData($select, arrayOptions);
195210

211+
var container = new MockContainer();
212+
data.bind(container, $('<div></div>'));
213+
196214
assert.ok(
197215
$select.val() == null || $select.val().length == 0,
198216
'nothing should be selected'
@@ -211,6 +229,9 @@ test('multiple adds to the old value', function (assert) {
211229

212230
var data = new ArrayData($select, arrayOptions);
213231

232+
var container = new MockContainer();
233+
data.bind(container, $('<div></div>'));
234+
214235
$select.val(['One']);
215236

216237
assert.deepEqual($select.val(), ['One']);
@@ -228,18 +249,42 @@ test('option tags are automatically generated', function (assert) {
228249

229250
var data = new ArrayData($select, arrayOptions);
230251

252+
var container = new MockContainer();
253+
data.bind(container, $('<div></div>'));
254+
231255
assert.equal(
232256
$select.find('option').length,
233257
4,
234258
'An <option> element should be created for each object'
235259
);
236260
});
237261

262+
test('automatically generated option tags have a result id', function (assert) {
263+
var $select = $('#qunit-fixture .single-empty');
264+
265+
var data = new ArrayData($select, arrayOptions);
266+
267+
var container = new MockContainer();
268+
data.bind(container, $('<div></div>'));
269+
270+
data.select({
271+
id: 'default'
272+
});
273+
274+
assert.ok(
275+
Utils.GetData($select.find(':selected')[0], 'data')._resultId,
276+
'<option> default should have a result ID assigned'
277+
);
278+
});
279+
238280
test('option tags can receive new data', function(assert) {
239281
var $select = $('#qunit-fixture .single');
240282

241283
var data = new ArrayData($select, extraOptions);
242284

285+
var container = new MockContainer();
286+
data.bind(container, $('<div></div>'));
287+
243288
assert.equal(
244289
$select.find('option').length,
245290
2,
@@ -270,6 +315,9 @@ test('optgroup tags can also be generated', function (assert) {
270315

271316
var data = new ArrayData($select, nestedOptions);
272317

318+
var container = new MockContainer();
319+
data.bind(container, $('<div></div>'));
320+
273321
assert.equal(
274322
$select.find('option').length,
275323
1,
@@ -288,6 +336,9 @@ test('optgroup tags have the right properties', function (assert) {
288336

289337
var data = new ArrayData($select, nestedOptions);
290338

339+
var container = new MockContainer();
340+
data.bind(container, $('<div></div>'));
341+
291342
var $group = $select.children('optgroup');
292343

293344
assert.equal(
@@ -328,5 +379,8 @@ test('existing selections are respected on initialization', function (assert) {
328379

329380
var data = new ArrayData($select, options);
330381

382+
var container = new MockContainer();
383+
data.bind(container, $('<div></div>'));
384+
331385
assert.equal($select.val(), 'Second');
332386
});

tests/data/inputData-tests.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ test('test that options can be selected', function (assert) {
2323

2424
var adapter = new InputAdapter($element, options);
2525

26+
var container = new MockContainer();
27+
adapter.bind(container, $('<div></div>'));
28+
2629
adapter.select({
2730
id: 'test'
2831
});
@@ -48,6 +51,9 @@ test('unselect the single selected option clears the value', function (assert) {
4851

4952
var adapter = new InputAdapter($element, options);
5053

54+
var container = new MockContainer();
55+
adapter.bind(container, $('<div></div>'));
56+
5157
adapter.unselect({
5258
id: 'test'
5359
});
@@ -81,6 +87,9 @@ test('options can be unselected individually', function (assert) {
8187

8288
var adapter = new InputAdapter($element, options);
8389

90+
var container = new MockContainer();
91+
adapter.bind(container, $('<div></div>'));
92+
8493
adapter.unselect({
8594
id: 'test2'
8695
});
@@ -107,6 +116,9 @@ test('default values can be set', function (assert) {
107116

108117
var adapter = new InputAdapter($element, options);
109118

119+
var container = new MockContainer();
120+
adapter.bind(container, $('<div></div>'));
121+
110122
adapter.current(function (data) {
111123
assert.equal(
112124
data.length,
@@ -142,6 +154,9 @@ test('no default value', function (assert) {
142154

143155
var adapter = new InputAdapter($element, options);
144156

157+
var container = new MockContainer();
158+
adapter.bind(container, $('<div></div>'));
159+
145160
adapter.current(function (data) {
146161
assert.equal(
147162
data.length,

0 commit comments

Comments
 (0)