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:
parent
a75482fd30
commit
ea79a197e0
76
src/js/select2/core.js
vendored
76
src/js/select2/core.js
vendored
@ -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'));
|
||||||
|
Loading…
Reference in New Issue
Block a user