瀏覽代碼

Remove support for legacy IE (#5834)

* Replace usage of $.trim with String.prototype.trim

This officially drops support for Internet Explorer 8, since this
method is not provided by that browser. This is one more step closer
to removing the dependency on jQuery itself.

* Replace $.isArray with Array.isArray

Now that we no longer support IE8, we can rely on this method
actually existing now.

* Remove old getAttribute workaround for IE7

This has long been unsupported, and now that we don't care about
IE8 with quirks mode, we can go back to expecting that `getAttribute`
actually returns the string like it is supposed to.

* Remove jQuery from Utils.copyNonInternalCssClasses

This also simplifies the logic used to use more modern array methods
to calculate the list of classes which need to be copied over to
the destination object.

* Remove unused imports

* Remove use of jQuery.fn.is()

* Replace $.inArray with Array.prototype.indexOf

* Replace addClass, hasClass, removeClass with classList calls

This replaces calls to the jQuery `getClass`, `hasClass`, and
`removeClass` methods with the corresponding calls to `classList`
methods on the elements that are referenced. There is one exception
to this, specifically where results remove a class from any elements
which may contain it, since that cannot be easily translated to use
the `classList` syntax.

* Remove legacy DOM modification tracking

This removes the legacy tracking for `onpropertychange`, which was
used by IE 8, to synchronize attribute changes from the `<select>`
element back to Select2.

This removes the legacy tracking of `DOMNodeInserted`,
`DOMNodeRemoved`, and `DOMAttrModified` which was used by IE 9 and
IE 10 for tracking when `<option>` elements were added and removed,
as well as when attributes on the `<select>` element were
synchronized.

Now only the `MutationObserver` is in use for synchronizing changes
to the `<select>` as well as changes to the `<option>` elements that
it contains.

* Replace $.map with Array.prototype.map
Kevin Brown 5 年之前
父節點
當前提交
f7cbd2c930

+ 1 - 1
src/js/jquery.select2.js

@@ -39,7 +39,7 @@ define([
         });
 
         // Check if we should be returning `this`
-        if ($.inArray(options, thisMethods) > -1) {
+        if (thisMethods.indexOf(options) > -1) {
           return this;
         }
 

+ 37 - 95
src/js/select2/core.js

@@ -77,7 +77,7 @@ define([
     });
 
     // Hide the original select
-    $element.addClass('select2-hidden-accessible');
+    $element[0].classList.add('select2-hidden-accessible');
     $element.attr('aria-hidden', 'true');
 
     // Synchronize any monitored attributes
@@ -197,42 +197,15 @@ define([
     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._syncA);
-    }
-
-    var observer = window.MutationObserver ||
-      window.WebKitMutationObserver ||
-      window.MozMutationObserver
-    ;
-
-    if (observer != null) {
-      this._observer = new observer(function (mutations) {
-        self._syncA();
-        self._syncS(null, mutations);
-      });
-      this._observer.observe(this.$element[0], {
-        attributes: true,
-        childList: true,
-        subtree: false
-      });
-    } else if (this.$element[0].addEventListener) {
-      this.$element[0].addEventListener(
-        'DOMAttrModified',
-        self._syncA,
-        false
-      );
-      this.$element[0].addEventListener(
-        'DOMNodeInserted',
-        self._syncS,
-        false
-      );
-      this.$element[0].addEventListener(
-        'DOMNodeRemoved',
-        self._syncS,
-        false
-      );
-    }
+    this._observer = new window.MutationObserver(function (mutations) {
+      self._syncA();
+      self._syncS(mutations);
+    });
+    this._observer.observe(this.$element[0], {
+      attributes: true,
+      childList: true,
+      subtree: false
+    });
   };
 
   Select2.prototype._registerDataEvents = function () {
@@ -256,7 +229,7 @@ define([
     });
 
     this.selection.on('*', function (name, params) {
-      if ($.inArray(name, nonRelayEvents) !== -1) {
+      if (nonRelayEvents.indexOf(name) !== -1) {
         return;
       }
 
@@ -284,23 +257,23 @@ define([
     var self = this;
 
     this.on('open', function () {
-      self.$container.addClass('select2-container--open');
+      self.$container[0].classList.add('select2-container--open');
     });
 
     this.on('close', function () {
-      self.$container.removeClass('select2-container--open');
+      self.$container[0].classList.remove('select2-container--open');
     });
 
     this.on('enable', function () {
-      self.$container.removeClass('select2-container--disabled');
+      self.$container[0].classList.remove('select2-container--disabled');
     });
 
     this.on('disable', function () {
-      self.$container.addClass('select2-container--disabled');
+      self.$container[0].classList.add('select2-container--disabled');
     });
 
     this.on('blur', function () {
-      self.$container.removeClass('select2-container--focus');
+      self.$container[0].classList.remove('select2-container--focus');
     });
 
     this.on('query', function (params) {
@@ -376,49 +349,30 @@ define([
     }
   };
 
-  Select2.prototype._isChangeMutation = function (evt, mutations) {
-    var changed = false;
+  Select2.prototype._isChangeMutation = function (mutations) {
     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) {
+    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;
+          return true;
         }
       }
     } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
-      changed = true;
-    } else if ($.isArray(mutations)) {
-      $.each(mutations, function(evt, mutation) {
-        if (self._isChangeMutation(evt, mutation)) {
-          // We've found a change mutation.
-          // Let's escape from the loop and continue
-          changed = true;
-          return false;
-        }
+      return true;
+    } else if (Array.isArray(mutations)) {
+      return mutations.some(function (mutation) {
+        return self._isChangeMutation(mutation);
       });
     }
-    return changed;
+
+    return false;
   };
 
-  Select2.prototype._syncSubtree = function (evt, mutations) {
-    var changed = this._isChangeMutation(evt, mutations);
+  Select2.prototype._syncSubtree = function (mutations) {
+    var changed = this._isChangeMutation(mutations);
     var self = this;
 
     // Only re-pull the data if we think there is a change
@@ -523,11 +477,11 @@ define([
   };
 
   Select2.prototype.isOpen = function () {
-    return this.$container.hasClass('select2-container--open');
+    return this.$container[0].classList.contains('select2-container--open');
   };
 
   Select2.prototype.hasFocus = function () {
-    return this.$container.hasClass('select2-container--focus');
+    return this.$container[0].classList.contains('select2-container--focus');
   };
 
   Select2.prototype.focus = function (data) {
@@ -536,7 +490,7 @@ define([
       return;
     }
 
-    this.$container.addClass('select2-container--focus');
+    this.$container[0].classList.add('select2-container--focus');
     this.trigger('focus', {});
   };
 
@@ -590,8 +544,8 @@ define([
 
     var newVal = args[0];
 
-    if ($.isArray(newVal)) {
-      newVal = $.map(newVal, function (obj) {
+    if (Array.isArray(newVal)) {
+      newVal = newVal.map(function (obj) {
         return obj.toString();
       });
     }
@@ -602,21 +556,8 @@ define([
   Select2.prototype.destroy = function () {
     this.$container.remove();
 
-    if (this.$element[0].detachEvent) {
-      this.$element[0].detachEvent('onpropertychange', this._syncA);
-    }
-
-    if (this._observer != null) {
-      this._observer.disconnect();
-      this._observer = null;
-    } else if (this.$element[0].removeEventListener) {
-      this.$element[0]
-        .removeEventListener('DOMAttrModified', this._syncA, false);
-      this.$element[0]
-        .removeEventListener('DOMNodeInserted', this._syncS, false);
-      this.$element[0]
-        .removeEventListener('DOMNodeRemoved', this._syncS, false);
-    }
+    this._observer.disconnect();
+    this._observer = null;
 
     this._syncA = null;
     this._syncS = null;
@@ -625,7 +566,7 @@ define([
     this.$element.attr('tabindex',
     Utils.GetData(this.$element[0], 'old-tabindex'));
 
-    this.$element.removeClass('select2-hidden-accessible');
+    this.$element[0].classList.remove('select2-hidden-accessible');
     this.$element.attr('aria-hidden', 'false');
     Utils.RemoveData(this.$element[0]);
     this.$element.removeData('select2');
@@ -653,7 +594,8 @@ define([
 
     this.$container = $container;
 
-    this.$container.addClass('select2-container--' + this.options.get('theme'));
+    this.$container[0].classList
+      .add('select2-container--' + this.options.get('theme'));
 
     Utils.StoreData($container[0], 'element', this.$element);
 

+ 1 - 1
src/js/select2/data/ajax.js

@@ -70,7 +70,7 @@ define([
 
         if (self.options.get('debug') && window.console && console.error) {
           // Check to make sure that the response included a `results` key.
-          if (!results || !results.results || !$.isArray(results.results)) {
+          if (!results || !results.results || !Array.isArray(results.results)) {
             console.error(
               'Select2: The AJAX results did not return an array in the ' +
               '`results` key of the response.'

+ 1 - 1
src/js/select2/data/array.js

@@ -52,7 +52,7 @@ define([
       var item = this._normalizeItem(data[d]);
 
       // Skip items which were pre-loaded, only merge the data
-      if ($.inArray(item.id, existingIds) >= 0) {
+      if (existingIds.indexOf(item.id) >= 0) {
         var $existingOption = $existing.filter(onlyItem(item));
 
         var existingData = this.item($existingOption);

+ 20 - 12
src/js/select2/data/select.js

@@ -33,7 +33,9 @@ define([
     data.selected = true;
 
     // If data.element is a DOM node, use it instead
-    if ($(data.element).is('option')) {
+    if (
+      data.element != null && data.element.tagName.toLowerCase() === 'option'
+    ) {
       data.element.selected = true;
 
       this.$element.trigger('input').trigger('change');
@@ -51,7 +53,7 @@ define([
         for (var d = 0; d < data.length; d++) {
           var id = data[d].id;
 
-          if ($.inArray(id, val) === -1) {
+          if (val.indexOf(id) === -1) {
             val.push(id);
           }
         }
@@ -76,7 +78,10 @@ define([
 
     data.selected = false;
 
-    if ($(data.element).is('option')) {
+    if (
+      data.element != null &&
+      data.element.tagName.toLowerCase() === 'option'
+    ) {
       data.element.selected = false;
 
       this.$element.trigger('input').trigger('change');
@@ -90,7 +95,7 @@ define([
       for (var d = 0; d < currentData.length; d++) {
         var id = currentData[d].id;
 
-        if (id !== data.id && $.inArray(id, val) === -1) {
+        if (id !== data.id && val.indexOf(id) === -1) {
           val.push(id);
         }
       }
@@ -130,12 +135,15 @@ define([
     var $options = this.$element.children();
 
     $options.each(function () {
-      var $option = $(this);
-
-      if (!$option.is('option') && !$option.is('optgroup')) {
+      if (
+        this.tagName.toLowerCase() !== 'option' &&
+        this.tagName.toLowerCase() !== 'optgroup'
+      ) {
         return;
       }
 
+      var $option = $(this);
+
       var option = self.item($option);
 
       var matches = self.matches(params, option);
@@ -186,15 +194,13 @@ define([
       option.title = data.title;
     }
 
-    var $option = $(option);
-
     var normalizedData = this._normalizeItem(data);
     normalizedData.element = option;
 
     // Override the option's data with the combined data
     Utils.StoreData(option, 'data', normalizedData);
 
-    return $option;
+    return $(option);
   };
 
   SelectAdapter.prototype.item = function ($option) {
@@ -206,7 +212,9 @@ define([
       return data;
     }
 
-    if ($option.is('option')) {
+    var option = $option[0];
+
+    if (option.tagName.toLowerCase() === 'option') {
       data = {
         id: $option.val(),
         text: $option.text(),
@@ -214,7 +222,7 @@ define([
         selected: $option.prop('selected'),
         title: $option.prop('title')
       };
-    } else if ($option.is('optgroup')) {
+    } else if (option.tagName.toLowerCase() === 'optgroup') {
       data = {
         text: $option.prop('label'),
         children: [],

+ 6 - 2
src/js/select2/data/tags.js

@@ -18,7 +18,7 @@ define([
 
     decorated.call(this, $element, options);
 
-    if ($.isArray(tags)) {
+    if (Array.isArray(tags)) {
       for (var t = 0; t < tags.length; t++) {
         var tag = tags[t];
         var item = this._normalizeItem(tag);
@@ -94,7 +94,11 @@ define([
   };
 
   Tags.prototype.createTag = function (decorated, params) {
-    var term = $.trim(params.term);
+    if (params.term == null) {
+      return null;
+    }
+
+    var term = params.term.trim();
 
     if (term === '') {
       return null;

+ 1 - 1
src/js/select2/data/tokenizer.js

@@ -82,7 +82,7 @@ define([
     while (i < term.length) {
       var termChar = term[i];
 
-      if ($.inArray(termChar, separators) === -1) {
+      if (separators.indexOf(termChar) === -1) {
         i++;
 
         continue;

+ 2 - 2
src/js/select2/defaults.js

@@ -251,7 +251,7 @@ define([
 
     function matcher (params, data) {
       // Always return the object if there is nothing to compare
-      if ($.trim(params.term) === '') {
+      if (params.term == null || params.term.trim() === '') {
         return data;
       }
 
@@ -355,7 +355,7 @@ define([
 
     var languages;
 
-    if (!$.isArray(language)) {
+    if (!Array.isArray(language)) {
       languages = [language];
     } else {
       languages = language;

+ 13 - 10
src/js/select2/dropdown/attachBody.js

@@ -41,8 +41,8 @@ define([
     // Clone all of the container classes
     $dropdown.attr('class', $container.attr('class'));
 
-    $dropdown.removeClass('select2');
-    $dropdown.addClass('select2-container--open');
+    $dropdown[0].classList.remove('select2');
+    $dropdown[0].classList.add('select2-container--open');
 
     $dropdown.css({
       position: 'absolute',
@@ -148,8 +148,10 @@ define([
   AttachBody.prototype._positionDropdown = function () {
     var $window = $(window);
 
-    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
-    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
+    var isCurrentlyAbove = this.$dropdown[0].classList
+      .contains('select2-dropdown--above');
+    var isCurrentlyBelow = this.$dropdown[0].classList
+      .contains('select2-dropdown--below');
 
     var newDirection = null;
 
@@ -221,12 +223,13 @@ define([
     }
 
     if (newDirection != null) {
-      this.$dropdown
-        .removeClass('select2-dropdown--below select2-dropdown--above')
-        .addClass('select2-dropdown--' + newDirection);
-      this.$container
-        .removeClass('select2-container--below select2-container--above')
-        .addClass('select2-container--' + newDirection);
+      this.$dropdown[0].classList.remove('select2-dropdown--below');
+      this.$dropdown[0].classList.remove('select2-dropdown--above');
+      this.$dropdown[0].classList.add('select2-dropdown--' + newDirection);
+
+      this.$container[0].classList.remove('select2-container--below');
+      this.$container[0].classList.remove('select2-container--above');
+      this.$container[0].classList.add('select2-container--' + newDirection);
     }
 
     this.$dropdownContainer.css(css);

+ 2 - 2
src/js/select2/dropdown/attachContainer.js

@@ -10,8 +10,8 @@ define([
     var $dropdownContainer = $container.find('.dropdown-wrapper');
     $dropdownContainer.append($dropdown);
 
-    $dropdown.addClass('select2-dropdown--below');
-    $container.addClass('select2-container--below');
+    $dropdown[0].classList.add('select2-dropdown--below');
+    $container[0].classList.add('select2-container--below');
   };
 
   return AttachContainer;

+ 2 - 3
src/js/select2/dropdown/dropdownCss.js

@@ -1,7 +1,6 @@
 define([
-  'jquery',
   '../utils'
-], function ($, Utils) {
+], function (Utils) {
   function DropdownCSS () { }
 
   DropdownCSS.prototype.render = function (decorated) {
@@ -12,7 +11,7 @@ define([
     if (dropdownCssClass.indexOf(':all:') !== -1) {
       dropdownCssClass = dropdownCssClass.replace(':all:', '');
 
-      Utils.copyNonInternalCssClasses($dropdown, this.$element);
+      Utils.copyNonInternalCssClasses($dropdown[0], this.$element[0]);
     }
 
     $dropdown.addClass(dropdownCssClass);

+ 4 - 5
src/js/select2/dropdown/search.js

@@ -1,7 +1,6 @@
 define([
-  'jquery',
-  '../utils'
-], function ($, Utils) {
+  'jquery'
+], function ($) {
   function Search () { }
 
   Search.prototype.render = function (decorated) {
@@ -79,9 +78,9 @@ define([
         var showSearch = self.showSearch(params);
 
         if (showSearch) {
-          self.$searchContainer.removeClass('select2-search--hide');
+          self.$searchContainer[0].classList.remove('select2-search--hide');
         } else {
-          self.$searchContainer.addClass('select2-search--hide');
+          self.$searchContainer[0].classList.add('select2-search--hide');
         }
       }
     });

+ 1 - 1
src/js/select2/options.js

@@ -106,7 +106,7 @@ define([
     data = Utils._convertData(data);
 
     for (var key in data) {
-      if ($.inArray(key, excludedData) > -1) {
+      if (excludedData.indexOf(key) > -1) {
         continue;
       }
 

+ 3 - 3
src/js/select2/results.js

@@ -120,7 +120,7 @@ define([
     var self = this;
 
     this.data.current(function (selected) {
-      var selectedIds = $.map(selected, function (s) {
+      var selectedIds = selected.map(function (s) {
         return s.id.toString();
       });
 
@@ -136,7 +136,7 @@ define([
         var id = '' + item.id;
 
         if ((item.element != null && item.element.selected) ||
-            (item.element == null && $.inArray(id, selectedIds) > -1)) {
+            (item.element == null && selectedIds.indexOf(id) > -1)) {
           $option.attr('aria-selected', 'true');
         } else {
           $option.attr('aria-selected', 'false');
@@ -409,7 +409,7 @@ define([
     });
 
     container.on('results:focus', function (params) {
-      params.element.addClass('select2-results__option--highlighted');
+      params.element[0].classList.add('select2-results__option--highlighted');
     });
 
     container.on('results:message', function (params) {

+ 2 - 2
src/js/select2/selection/eventRelay.js

@@ -21,7 +21,7 @@ define([
 
     container.on('*', function (name, params) {
       // Ignore events that should not be relayed
-      if ($.inArray(name, relayEvents) === -1) {
+      if (relayEvents.indexOf(name) === -1) {
         return;
       }
 
@@ -36,7 +36,7 @@ define([
       self.$element.trigger(evt);
 
       // Only handle preventable events if it was one
-      if ($.inArray(name, preventableEvents) === -1) {
+      if (preventableEvents.indexOf(name) === -1) {
         return;
       }
 

+ 1 - 1
src/js/select2/selection/multiple.js

@@ -12,7 +12,7 @@ define([
   MultipleSelection.prototype.render = function () {
     var $selection = MultipleSelection.__super__.render.call(this);
 
-    $selection.addClass('select2-selection--multiple');
+    $selection[0].classList.add('select2-selection--multiple');
 
     $selection.html(
       '<ul class="select2-selection__rendered"></ul>'

+ 4 - 4
src/js/select2/selection/placeholder.js

@@ -1,6 +1,6 @@
 define([
-  '../utils'
-], function (Utils) {
+
+], function () {
   function Placeholder (decorated, $element, options) {
     this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
 
@@ -22,8 +22,8 @@ define([
     var $placeholder = this.selectionContainer();
 
     $placeholder.html(this.display(placeholder));
-    $placeholder.addClass('select2-selection__placeholder')
-                .removeClass('select2-selection__choice');
+    $placeholder[0].classList.add('select2-selection__placeholder');
+    $placeholder[0].classList.remove('select2-selection__choice');
 
     return $placeholder;
   };

+ 2 - 3
src/js/select2/selection/selectionCss.js

@@ -1,7 +1,6 @@
 define([
-  'jquery',
   '../utils'
-], function ($, Utils) {
+], function (Utils) {
   function SelectionCSS () { }
 
   SelectionCSS.prototype.render = function (decorated) {
@@ -12,7 +11,7 @@ define([
     if (selectionCssClass.indexOf(':all:') !== -1) {
       selectionCssClass = selectionCssClass.replace(':all:', '');
 
-      Utils.copyNonInternalCssClasses($selection, this.$element);
+      Utils.copyNonInternalCssClasses($selection[0], this.$element[0]);
     }
 
     $selection.addClass(selectionCssClass);

+ 1 - 1
src/js/select2/selection/single.js

@@ -13,7 +13,7 @@ define([
   SingleSelection.prototype.render = function () {
     var $selection = SingleSelection.__super__.render.call(this);
 
-    $selection.addClass('select2-selection--single');
+    $selection[0].classList.add('select2-selection--single');
 
     $selection.html(
       '<span class="select2-selection__rendered"></span>' +

+ 14 - 24
src/js/select2/utils.js

@@ -319,36 +319,26 @@ define([
     element.removeAttribute('data-select2-id');
   };
 
-  Utils.copyNonInternalCssClasses = function ($dest, $src) {
-    var classes, replacements = [], adapted;
+  Utils.copyNonInternalCssClasses = function (dest, src) {
+    var classes;
 
-    classes = $.trim($dest.attr('class'));
+    var destinationClasses = dest.getAttribute('class').trim().split(/\s+/);
 
-    if (classes) {
-      classes = '' + classes; // for IE which returns object
-
-      $(classes.split(/\s+/)).each(function () {
-        // Save all Select2 classes
-        if (this.indexOf('select2-') === 0) {
-          replacements.push(this);
-        }
-      });
-    }
+    destinationClasses = destinationClasses.filter(function (clazz) {
+      // Save all Select2 classes
+      return clazz.indexOf('select2-') === 0;
+    });
 
-    classes = $.trim($src.attr('class'));
+    var sourceClasses = src.getAttribute('class').trim().split(/\s+/);
 
-    if (classes) {
-      classes = '' + classes; // for IE which returns object
+    sourceClasses = sourceClasses.filter(function (clazz) {
+      // Only copy non-Select2 classes
+      return clazz.indexOf('select2-') !== 0;
+    });
 
-      $(classes.split(/\s+/)).each(function () {
-        // Only copy non-Select2 classes
-        if (this.indexOf('select2-') !== 0) {
-          replacements.push(this);
-        }
-      });
-    }
+    var replacements = destinationClasses.concat(sourceClasses);
 
-    $dest.attr('class', replacements.join(' '));
+    dest.setAttribute('class', replacements.join(' '));
   };
 
   return Utils;