1
0
mirror of synced 2024-11-25 14:26:03 +03:00

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 https://github.com/select2/select2/issues/4248
This builds upon https://github.com/select2/select2/pull/4249
This commit is contained in:
Kevin Brown 2016-05-23 23:31:17 -04:00
parent a75482fd30
commit ea79a197e0

View File

@ -185,10 +185,11 @@ define([
self.trigger('focus', evt); self.trigger('focus', evt);
}); });
this._sync = Utils.bind(this._syncAttributes, this); this._syncA = Utils.bind(this._syncAttributes, this);
this._syncS = Utils.bind(this._syncSubtree, this);
if (this.$element[0].attachEvent) { if (this.$element[0].attachEvent) {
this.$element[0].attachEvent('onpropertychange', this._sync); this.$element[0].attachEvent('onpropertychange', this._syncA);
} }
var observer = window.MutationObserver || var observer = window.MutationObserver ||
@ -198,14 +199,30 @@ define([
if (observer != null) { if (observer != null) {
this._observer = new observer(function (mutations) { this._observer = new observer(function (mutations) {
$.each(mutations, self._sync); $.each(mutations, self._syncA);
$.each(mutations, self._syncS);
}); });
this._observer.observe(this.$element[0], { this._observer.observe(this.$element[0], {
attributes: true, attributes: true,
childList: true,
subtree: false subtree: false
}); });
} else if (this.$element[0].addEventListener) { } else if (this.$element[0].addEventListener) {
this.$element[0].addEventListener('DOMAttrModified', self._sync, false); this.$element[0].addEventListener(
'DOMAttrModified',
self._syncA,
false
);
this.$element[0].addEventListener(
'DOMNodeInserted',
self._syncS,
false
);
this.$element[0].addEventListener(
'DOMNodeRemoved',
self._syncS,
false
);
} }
}; };
@ -350,6 +367,46 @@ define([
} }
}; };
Select2.prototype._syncSubtree = function (evt, mutations) {
var changed = false;
var self = this;
// Ignore any mutation events raised for elements that aren't options or
// optgroups. This handles the case when the select element is destroyed
if (
evt && evt.target && (
evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
)
) {
return;
}
if (!mutations) {
// If mutation events aren't supported, then we can only assume that the
// change affected the selections
changed = true;
} else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
for (var n = 0; n < mutations.addedNodes.length; n++) {
var node = mutations.addedNodes[n];
if (node.selected) {
changed = true;
}
}
} else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
changed = true;
}
// Only re-pull the data if we think there is a change
if (changed) {
this.dataAdapter.current(function (currentData) {
self.trigger('selection:update', {
data: currentData
});
});
}
};
/** /**
* Override the trigger method to automatically trigger pre-events when * Override the trigger method to automatically trigger pre-events when
* there are events that can be prevented. * there are events that can be prevented.
@ -496,7 +553,7 @@ define([
this.$container.remove(); this.$container.remove();
if (this.$element[0].detachEvent) { if (this.$element[0].detachEvent) {
this.$element[0].detachEvent('onpropertychange', this._sync); this.$element[0].detachEvent('onpropertychange', this._syncA);
} }
if (this._observer != null) { if (this._observer != null) {
@ -504,10 +561,15 @@ define([
this._observer = null; this._observer = null;
} else if (this.$element[0].removeEventListener) { } else if (this.$element[0].removeEventListener) {
this.$element[0] this.$element[0]
.removeEventListener('DOMAttrModified', this._sync, false); .removeEventListener('DOMAttrModified', this._syncA, false);
this.$element[0]
.removeEventListener('DOMNodeInserted', this._syncS, false);
this.$element[0]
.removeEventListener('DOMNodeRemoved', this._syncS, false);
} }
this._sync = null; this._syncA = null;
this._syncS = null;
this.$element.off('.select2'); this.$element.off('.select2');
this.$element.attr('tabindex', this.$element.data('old-tabindex')); this.$element.attr('tabindex', this.$element.data('old-tabindex'));