diff --git a/src/js/select2/core.js b/src/js/select2/core.js index cc384b0b..4ba06edb 100644 --- a/src/js/select2/core.js +++ b/src/js/select2/core.js @@ -185,10 +185,11 @@ define([ 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) { - this.$element[0].attachEvent('onpropertychange', this._sync); + this.$element[0].attachEvent('onpropertychange', this._syncA); } var observer = window.MutationObserver || @@ -198,14 +199,30 @@ define([ if (observer != null) { this._observer = new observer(function (mutations) { - $.each(mutations, self._sync); + $.each(mutations, self._syncA); + $.each(mutations, self._syncS); }); this._observer.observe(this.$element[0], { attributes: true, + childList: true, subtree: false }); } 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 * there are events that can be prevented. @@ -496,7 +553,7 @@ define([ this.$container.remove(); if (this.$element[0].detachEvent) { - this.$element[0].detachEvent('onpropertychange', this._sync); + this.$element[0].detachEvent('onpropertychange', this._syncA); } if (this._observer != null) { @@ -504,10 +561,15 @@ define([ this._observer = null; } else if (this.$element[0].removeEventListener) { 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.attr('tabindex', this.$element.data('old-tabindex'));