Skip to content

Commit ea79a19

Browse files
committed
Select2 now detects added and removed options
Select2 will now automatically update the selection if there are options added to or removed from the DOM within the `<select>` element. This is supported in all browsers except for Internet Explorer 8. Internet Explorer 8 does not support the DOM mutation events which were added in Internet Explorer 9, and it does not support mutation observers which are the recommended way of handling this in modern browsers. DOM mutation events also trigger for the `<select>` itself when it is pulled from the DOM, so we need to filter these out within the event handler. Despite supporting mutation observers, we cannot accurately detect if the removed option was selected at one time or another, so we need to always re-pull the selection when an element is deleted. This closes select2#4248 This builds upon select2#4249
1 parent a75482f commit ea79a19

1 file changed

Lines changed: 69 additions & 7 deletions

File tree

src/js/select2/core.js

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,11 @@ define([
185185
self.trigger('focus', evt);
186186
});
187187

188-
this._sync = Utils.bind(this._syncAttributes, this);
188+
this._syncA = Utils.bind(this._syncAttributes, this);
189+
this._syncS = Utils.bind(this._syncSubtree, this);
189190

190191
if (this.$element[0].attachEvent) {
191-
this.$element[0].attachEvent('onpropertychange', this._sync);
192+
this.$element[0].attachEvent('onpropertychange', this._syncA);
192193
}
193194

194195
var observer = window.MutationObserver ||
@@ -198,14 +199,30 @@ define([
198199

199200
if (observer != null) {
200201
this._observer = new observer(function (mutations) {
201-
$.each(mutations, self._sync);
202+
$.each(mutations, self._syncA);
203+
$.each(mutations, self._syncS);
202204
});
203205
this._observer.observe(this.$element[0], {
204206
attributes: true,
207+
childList: true,
205208
subtree: false
206209
});
207210
} else if (this.$element[0].addEventListener) {
208-
this.$element[0].addEventListener('DOMAttrModified', self._sync, false);
211+
this.$element[0].addEventListener(
212+
'DOMAttrModified',
213+
self._syncA,
214+
false
215+
);
216+
this.$element[0].addEventListener(
217+
'DOMNodeInserted',
218+
self._syncS,
219+
false
220+
);
221+
this.$element[0].addEventListener(
222+
'DOMNodeRemoved',
223+
self._syncS,
224+
false
225+
);
209226
}
210227
};
211228

@@ -350,6 +367,46 @@ define([
350367
}
351368
};
352369

370+
Select2.prototype._syncSubtree = function (evt, mutations) {
371+
var changed = false;
372+
var self = this;
373+
374+
// Ignore any mutation events raised for elements that aren't options or
375+
// optgroups. This handles the case when the select element is destroyed
376+
if (
377+
evt && evt.target && (
378+
evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
379+
)
380+
) {
381+
return;
382+
}
383+
384+
if (!mutations) {
385+
// If mutation events aren't supported, then we can only assume that the
386+
// change affected the selections
387+
changed = true;
388+
} else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
389+
for (var n = 0; n < mutations.addedNodes.length; n++) {
390+
var node = mutations.addedNodes[n];
391+
392+
if (node.selected) {
393+
changed = true;
394+
}
395+
}
396+
} else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
397+
changed = true;
398+
}
399+
400+
// Only re-pull the data if we think there is a change
401+
if (changed) {
402+
this.dataAdapter.current(function (currentData) {
403+
self.trigger('selection:update', {
404+
data: currentData
405+
});
406+
});
407+
}
408+
};
409+
353410
/**
354411
* Override the trigger method to automatically trigger pre-events when
355412
* there are events that can be prevented.
@@ -496,18 +553,23 @@ define([
496553
this.$container.remove();
497554

498555
if (this.$element[0].detachEvent) {
499-
this.$element[0].detachEvent('onpropertychange', this._sync);
556+
this.$element[0].detachEvent('onpropertychange', this._syncA);
500557
}
501558

502559
if (this._observer != null) {
503560
this._observer.disconnect();
504561
this._observer = null;
505562
} else if (this.$element[0].removeEventListener) {
506563
this.$element[0]
507-
.removeEventListener('DOMAttrModified', this._sync, false);
564+
.removeEventListener('DOMAttrModified', this._syncA, false);
565+
this.$element[0]
566+
.removeEventListener('DOMNodeInserted', this._syncS, false);
567+
this.$element[0]
568+
.removeEventListener('DOMNodeRemoved', this._syncS, false);
508569
}
509570

510-
this._sync = null;
571+
this._syncA = null;
572+
this._syncS = null;
511573

512574
this.$element.off('.select2');
513575
this.$element.attr('tabindex', this.$element.data('old-tabindex'));

0 commit comments

Comments
 (0)