Browse Source

Merge pull request #3051 from select2/issue_3022

Added compatibility with `<input type="text" />` tags
Kevin Brown 10 years ago
parent
commit
3630385cf7

+ 1 - 0
Gruntfile.js

@@ -14,6 +14,7 @@ module.exports = function (grunt) {
 
     'select2/compat/matcher',
     'select2/compat/initSelection',
+    'select2/compat/inputData',
     'select2/compat/query',
 
     'select2/dropdown/attachContainer',

+ 144 - 4
dist/js/select2.amd.full.js

@@ -2406,7 +2406,6 @@ define('select2/data/select',[
       var val = data.id;
 
       this.$element.val(val);
-
       this.$element.trigger('change');
     }
   };
@@ -2492,6 +2491,10 @@ define('select2/data/select',[
     });
   };
 
+  SelectAdapter.prototype.addOptions = function ($options) {
+    this.$element.append($options);
+  };
+
   SelectAdapter.prototype.option = function (data) {
     var option;
 
@@ -2632,7 +2635,7 @@ define('select2/data/array',[
 
     ArrayAdapter.__super__.constructor.call(this, $element, options);
 
-    $element.append(this.convertToOptions(data));
+    this.addOptions(this.convertToOptions(data));
   }
 
   Utils.Extend(ArrayAdapter, SelectAdapter);
@@ -2643,7 +2646,7 @@ define('select2/data/array',[
     if ($option.length === 0) {
       $option = this.option(data);
 
-      this.$element.append($option);
+      this.addOptions([$option]);
     }
 
     ArrayAdapter.__super__.select.call(this, data);
@@ -2871,7 +2874,7 @@ define('select2/data/tags',[
         var $option = self.option(tag);
         $option.attr('data-select2-tag', true);
 
-        self.$element.append($option);
+        self.addOptions([$option]);
 
         self.insertTag(data, tag);
       }
@@ -4057,6 +4060,15 @@ define('select2/options',[
     }
 
     this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
   }
 
   Options.prototype.fromElement = function ($e) {
@@ -4974,6 +4986,134 @@ define('select2/compat/initSelection',[
   return InitSelection;
 });
 
+define('select2/compat/inputData',[
+  'jquery'
+], function ($) {
+  function InputData (decorated, $element, options) {
+    this._currentData = [];
+    this._valueSeparator = options.get('valueSeparator') || ',';
+
+    if ($element.prop('type') === 'hidden') {
+      if (console && console.warn) {
+        console.warn(
+          'Select2: Using a hidden input with Select2 is no longer ' +
+          'supported and may stop working in the future. It is recommended ' +
+          'to use a `<select>` element instead.'
+        );
+      }
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  InputData.prototype.current = function (_, callback) {
+    function getSelected (data, selectedIds) {
+      var selected = [];
+
+      if (data.selected || $.inArray(data.id, selectedIds) !== -1) {
+        data.selected = true;
+        selected.push(data);
+      } else {
+        data.selected = false;
+      }
+
+      if (data.children) {
+        selected.push.apply(selected, getSelected(data.children, selectedIds));
+      }
+
+      return selected;
+    }
+
+    var selected = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      selected.push.apply(
+        selected,
+        getSelected(
+          data,
+          this.$element.val().split(
+            this._valueSeparator
+          )
+        )
+      );
+    }
+
+    callback(selected);
+  };
+
+  InputData.prototype.select = function (_, data) {
+    if (!this.options.get('multiple')) {
+      this.current(function (allData) {
+        $.map(allData, function (data) {
+          data.selected = false;
+        });
+      });
+
+      this.$element.val(data.id);
+      this.$element.trigger('change');
+    } else {
+      var value = this.$element.val();
+      value += this._valueSeparator + data.id;
+
+      this.$element.val(value);
+      this.$element.trigger('change');
+    }
+  };
+
+  InputData.prototype.unselect = function (_, data) {
+    var self = this;
+
+    data.selected = false;
+
+    this.current(function (allData) {
+      var values = [];
+
+      for (var d = 0; d < allData; d++) {
+        var item = allData[d];
+
+        if (data.id == item.id) {
+          continue;
+        }
+
+        values.push(data.id);
+      }
+
+      self.$element.val(values.join(self._valueSeparator));
+      self.$element.trigger('change');
+    });
+  };
+
+  InputData.prototype.query = function (_, params, callback) {
+    var results = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      var matches = this.matches(params, data);
+
+      if (matches !== null) {
+        results.push(matches);
+      }
+    }
+
+    callback({
+      results: results
+    });
+  };
+
+  InputData.prototype.addOptions = function (_, $options) {
+    var options = $.map($options, function ($option) {
+      return $.data($option[0], 'data');
+    });
+
+    this._currentData.push.apply(this._currentData, options);
+  };
+
+  return InputData;
+});
+
 define('select2/compat/query',[
 
 ], function () {

+ 16 - 4
dist/js/select2.amd.js

@@ -2406,7 +2406,6 @@ define('select2/data/select',[
       var val = data.id;
 
       this.$element.val(val);
-
       this.$element.trigger('change');
     }
   };
@@ -2492,6 +2491,10 @@ define('select2/data/select',[
     });
   };
 
+  SelectAdapter.prototype.addOptions = function ($options) {
+    this.$element.append($options);
+  };
+
   SelectAdapter.prototype.option = function (data) {
     var option;
 
@@ -2632,7 +2635,7 @@ define('select2/data/array',[
 
     ArrayAdapter.__super__.constructor.call(this, $element, options);
 
-    $element.append(this.convertToOptions(data));
+    this.addOptions(this.convertToOptions(data));
   }
 
   Utils.Extend(ArrayAdapter, SelectAdapter);
@@ -2643,7 +2646,7 @@ define('select2/data/array',[
     if ($option.length === 0) {
       $option = this.option(data);
 
-      this.$element.append($option);
+      this.addOptions([$option]);
     }
 
     ArrayAdapter.__super__.select.call(this, data);
@@ -2871,7 +2874,7 @@ define('select2/data/tags',[
         var $option = self.option(tag);
         $option.attr('data-select2-tag', true);
 
-        self.$element.append($option);
+        self.addOptions([$option]);
 
         self.insertTag(data, tag);
       }
@@ -4057,6 +4060,15 @@ define('select2/options',[
     }
 
     this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
   }
 
   Options.prototype.fromElement = function ($e) {

+ 144 - 4
dist/js/select2.full.js

@@ -2845,7 +2845,6 @@ define('select2/data/select',[
       var val = data.id;
 
       this.$element.val(val);
-
       this.$element.trigger('change');
     }
   };
@@ -2931,6 +2930,10 @@ define('select2/data/select',[
     });
   };
 
+  SelectAdapter.prototype.addOptions = function ($options) {
+    this.$element.append($options);
+  };
+
   SelectAdapter.prototype.option = function (data) {
     var option;
 
@@ -3071,7 +3074,7 @@ define('select2/data/array',[
 
     ArrayAdapter.__super__.constructor.call(this, $element, options);
 
-    $element.append(this.convertToOptions(data));
+    this.addOptions(this.convertToOptions(data));
   }
 
   Utils.Extend(ArrayAdapter, SelectAdapter);
@@ -3082,7 +3085,7 @@ define('select2/data/array',[
     if ($option.length === 0) {
       $option = this.option(data);
 
-      this.$element.append($option);
+      this.addOptions([$option]);
     }
 
     ArrayAdapter.__super__.select.call(this, data);
@@ -3310,7 +3313,7 @@ define('select2/data/tags',[
         var $option = self.option(tag);
         $option.attr('data-select2-tag', true);
 
-        self.$element.append($option);
+        self.addOptions([$option]);
 
         self.insertTag(data, tag);
       }
@@ -4496,6 +4499,15 @@ define('select2/options',[
     }
 
     this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
   }
 
   Options.prototype.fromElement = function ($e) {
@@ -5413,6 +5425,134 @@ define('select2/compat/initSelection',[
   return InitSelection;
 });
 
+define('select2/compat/inputData',[
+  'jquery'
+], function ($) {
+  function InputData (decorated, $element, options) {
+    this._currentData = [];
+    this._valueSeparator = options.get('valueSeparator') || ',';
+
+    if ($element.prop('type') === 'hidden') {
+      if (console && console.warn) {
+        console.warn(
+          'Select2: Using a hidden input with Select2 is no longer ' +
+          'supported and may stop working in the future. It is recommended ' +
+          'to use a `<select>` element instead.'
+        );
+      }
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  InputData.prototype.current = function (_, callback) {
+    function getSelected (data, selectedIds) {
+      var selected = [];
+
+      if (data.selected || $.inArray(data.id, selectedIds) !== -1) {
+        data.selected = true;
+        selected.push(data);
+      } else {
+        data.selected = false;
+      }
+
+      if (data.children) {
+        selected.push.apply(selected, getSelected(data.children, selectedIds));
+      }
+
+      return selected;
+    }
+
+    var selected = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      selected.push.apply(
+        selected,
+        getSelected(
+          data,
+          this.$element.val().split(
+            this._valueSeparator
+          )
+        )
+      );
+    }
+
+    callback(selected);
+  };
+
+  InputData.prototype.select = function (_, data) {
+    if (!this.options.get('multiple')) {
+      this.current(function (allData) {
+        $.map(allData, function (data) {
+          data.selected = false;
+        });
+      });
+
+      this.$element.val(data.id);
+      this.$element.trigger('change');
+    } else {
+      var value = this.$element.val();
+      value += this._valueSeparator + data.id;
+
+      this.$element.val(value);
+      this.$element.trigger('change');
+    }
+  };
+
+  InputData.prototype.unselect = function (_, data) {
+    var self = this;
+
+    data.selected = false;
+
+    this.current(function (allData) {
+      var values = [];
+
+      for (var d = 0; d < allData; d++) {
+        var item = allData[d];
+
+        if (data.id == item.id) {
+          continue;
+        }
+
+        values.push(data.id);
+      }
+
+      self.$element.val(values.join(self._valueSeparator));
+      self.$element.trigger('change');
+    });
+  };
+
+  InputData.prototype.query = function (_, params, callback) {
+    var results = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      var matches = this.matches(params, data);
+
+      if (matches !== null) {
+        results.push(matches);
+      }
+    }
+
+    callback({
+      results: results
+    });
+  };
+
+  InputData.prototype.addOptions = function (_, $options) {
+    var options = $.map($options, function ($option) {
+      return $.data($option[0], 'data');
+    });
+
+    this._currentData.push.apply(this._currentData, options);
+  };
+
+  return InputData;
+});
+
 define('select2/compat/query',[
 
 ], function () {

File diff suppressed because it is too large
+ 0 - 0
dist/js/select2.full.min.js


+ 16 - 4
dist/js/select2.js

@@ -2845,7 +2845,6 @@ define('select2/data/select',[
       var val = data.id;
 
       this.$element.val(val);
-
       this.$element.trigger('change');
     }
   };
@@ -2931,6 +2930,10 @@ define('select2/data/select',[
     });
   };
 
+  SelectAdapter.prototype.addOptions = function ($options) {
+    this.$element.append($options);
+  };
+
   SelectAdapter.prototype.option = function (data) {
     var option;
 
@@ -3071,7 +3074,7 @@ define('select2/data/array',[
 
     ArrayAdapter.__super__.constructor.call(this, $element, options);
 
-    $element.append(this.convertToOptions(data));
+    this.addOptions(this.convertToOptions(data));
   }
 
   Utils.Extend(ArrayAdapter, SelectAdapter);
@@ -3082,7 +3085,7 @@ define('select2/data/array',[
     if ($option.length === 0) {
       $option = this.option(data);
 
-      this.$element.append($option);
+      this.addOptions([$option]);
     }
 
     ArrayAdapter.__super__.select.call(this, data);
@@ -3310,7 +3313,7 @@ define('select2/data/tags',[
         var $option = self.option(tag);
         $option.attr('data-select2-tag', true);
 
-        self.$element.append($option);
+        self.addOptions([$option]);
 
         self.insertTag(data, tag);
       }
@@ -4496,6 +4499,15 @@ define('select2/options',[
     }
 
     this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
   }
 
   Options.prototype.fromElement = function ($e) {

File diff suppressed because it is too large
+ 0 - 0
dist/js/select2.min.js


+ 32 - 0
docs/options.html

@@ -1588,6 +1588,38 @@ $.fn.select2.defaults.set("theme", "classic");
       </div>
     </div>
   </section>
+
+  <h2 id="input-fallback">
+    Compatibility with <code>&lt;input type="text" /&gt;</code>
+  </h2>
+
+  <p class="alert alert-warning">
+    <a href="announcements-4.0.html#hidden-input" class="alert-link">Deprecated in Select2 4.0.</a>
+    It is now encouraged to use the <code>&lt;select&gt;</code> tag instead.
+  </p>
+
+  <p>
+    In past versions of Select2, a <code>&lt;select&gt;</code> element could
+    only be used with a limited subset of options. An
+    <code>&lt;input type="hidden" /&gt;</code> was required instead, which did
+    not allow for a graceful fallback for users who did not have JavaScript
+    enabled. Select2 now supports the <code>&lt;select&gt;</code> element for
+    all options, so it is no longer required to use <code>&lt;input /&gt;</code>
+    elements with Select2.
+  </p>
+
+  <dl class="dl-horizontal">
+    <dt>Adapter</dt>
+    <dd>
+      <code title="select2/data/base">DataAdapter</code>
+    </dd>
+
+    <dt>Decorator</dt>
+    <dd>
+      <code title="select2/compat/inputData">InputData</code>
+    </dd>
+  </dl>
+</section>
 </div>
 
 <script type="text/javascript">

+ 127 - 0
src/js/select2/compat/inputData.js

@@ -0,0 +1,127 @@
+define([
+  'jquery'
+], function ($) {
+  function InputData (decorated, $element, options) {
+    this._currentData = [];
+    this._valueSeparator = options.get('valueSeparator') || ',';
+
+    if ($element.prop('type') === 'hidden') {
+      if (console && console.warn) {
+        console.warn(
+          'Select2: Using a hidden input with Select2 is no longer ' +
+          'supported and may stop working in the future. It is recommended ' +
+          'to use a `<select>` element instead.'
+        );
+      }
+    }
+
+    decorated.call(this, $element, options);
+  }
+
+  InputData.prototype.current = function (_, callback) {
+    function getSelected (data, selectedIds) {
+      var selected = [];
+
+      if (data.selected || $.inArray(data.id, selectedIds) !== -1) {
+        data.selected = true;
+        selected.push(data);
+      } else {
+        data.selected = false;
+      }
+
+      if (data.children) {
+        selected.push.apply(selected, getSelected(data.children, selectedIds));
+      }
+
+      return selected;
+    }
+
+    var selected = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      selected.push.apply(
+        selected,
+        getSelected(
+          data,
+          this.$element.val().split(
+            this._valueSeparator
+          )
+        )
+      );
+    }
+
+    callback(selected);
+  };
+
+  InputData.prototype.select = function (_, data) {
+    if (!this.options.get('multiple')) {
+      this.current(function (allData) {
+        $.map(allData, function (data) {
+          data.selected = false;
+        });
+      });
+
+      this.$element.val(data.id);
+      this.$element.trigger('change');
+    } else {
+      var value = this.$element.val();
+      value += this._valueSeparator + data.id;
+
+      this.$element.val(value);
+      this.$element.trigger('change');
+    }
+  };
+
+  InputData.prototype.unselect = function (_, data) {
+    var self = this;
+
+    data.selected = false;
+
+    this.current(function (allData) {
+      var values = [];
+
+      for (var d = 0; d < allData; d++) {
+        var item = allData[d];
+
+        if (data.id == item.id) {
+          continue;
+        }
+
+        values.push(data.id);
+      }
+
+      self.$element.val(values.join(self._valueSeparator));
+      self.$element.trigger('change');
+    });
+  };
+
+  InputData.prototype.query = function (_, params, callback) {
+    var results = [];
+
+    for (var d = 0; d < this._currentData.length; d++) {
+      var data = this._currentData[d];
+
+      var matches = this.matches(params, data);
+
+      if (matches !== null) {
+        results.push(matches);
+      }
+    }
+
+    callback({
+      results: results
+    });
+  };
+
+  InputData.prototype.addOptions = function (_, $options) {
+    var options = $.map($options, function ($option) {
+      return $.data($option[0], 'data');
+    });
+
+    this._currentData.push.apply(this._currentData, options);
+  };
+
+  return InputData;
+});

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

@@ -8,7 +8,7 @@ define([
 
     ArrayAdapter.__super__.constructor.call(this, $element, options);
 
-    $element.append(this.convertToOptions(data));
+    this.addOptions(this.convertToOptions(data));
   }
 
   Utils.Extend(ArrayAdapter, SelectAdapter);
@@ -19,7 +19,7 @@ define([
     if ($option.length === 0) {
       $option = this.option(data);
 
-      this.$element.append($option);
+      this.addOptions([$option]);
     }
 
     ArrayAdapter.__super__.select.call(this, data);

+ 4 - 1
src/js/select2/data/select.js

@@ -61,7 +61,6 @@ define([
       var val = data.id;
 
       this.$element.val(val);
-
       this.$element.trigger('change');
     }
   };
@@ -147,6 +146,10 @@ define([
     });
   };
 
+  SelectAdapter.prototype.addOptions = function ($options) {
+    this.$element.append($options);
+  };
+
   SelectAdapter.prototype.option = function (data) {
     var option;
 

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

@@ -71,7 +71,7 @@ define([
         var $option = self.option(tag);
         $option.attr('data-select2-tag', true);
 
-        self.$element.append($option);
+        self.addOptions([$option]);
 
         self.insertTag(data, tag);
       }

+ 9 - 0
src/js/select2/options.js

@@ -11,6 +11,15 @@ define([
     }
 
     this.options = Defaults.apply(this.options);
+
+    if ($element && $element.is('input')) {
+      var InputCompat = require(this.get('amdBase') + 'compat/inputData');
+
+      this.options.dataAdapter = Utils.Decorate(
+        this.options.dataAdapter,
+        InputCompat
+      );
+    }
   }
 
   Options.prototype.fromElement = function ($e) {

+ 1 - 0
tests/data.html

@@ -15,6 +15,7 @@
     <script src="helpers.js" type="text/javascript"></script>
 
     <script src="data/base-tests.js" type="text/javascript"></script>
+    <script src="data/inputData-tests.js" type="text/javascript"></script>
 
     <script src="data/maximumInputLength-tests.js" type="text/javascript"></script>
     <script src="data/maximumSelectionLength-tests.js" type="text/javascript"></script>

+ 125 - 0
tests/data/inputData-tests.js

@@ -0,0 +1,125 @@
+module('Data adapters - <input> compatibility');
+
+var $ = require('jquery');
+
+var Options = require('select2/options');
+var Utils = require('select2/utils');
+
+var ArrayData = require('select2/data/array');
+var InputData = require('select2/compat/inputData');
+
+var InputAdapter = Utils.Decorate(ArrayData, InputData);
+
+test('test that options can be selected', function (assert) {
+  var options = new Options({
+    data: [
+      {
+        id: 'test',
+        text: 'Test'
+      }
+    ]
+  });
+  var $element = $('<input />');
+
+  var adapter = new InputAdapter($element, options);
+
+  adapter.select({
+    id: 'test'
+  });
+
+  assert.equal(
+    $element.val(),
+    'test',
+    'The id of the item should be the value'
+  );
+});
+
+test('test that options can be unselected', function (assert) {
+  var options = new Options({
+    data: [
+      {
+        id: 'test',
+        text: 'Test',
+        selected: true
+      }
+    ]
+  });
+  var $element = $('<input />');
+
+  var adapter = new InputAdapter($element, options);
+
+  adapter.unselect({
+    id: 'test'
+  });
+
+  assert.equal(
+    $element.val(),
+    '',
+    'The id should no longer be in the value'
+  );
+});
+
+test('default values can be set', function (assert) {
+  expect(4);
+
+  var options = new Options({
+    data: [
+      {
+        id: 'test',
+        text: 'Test'
+      }
+    ]
+  });
+  var $element = $('<input value="test" />');
+
+  var adapter = new InputAdapter($element, options);
+
+  adapter.current(function (data) {
+    assert.equal(
+      data.length,
+      1,
+      'There should only be a single selected option'
+    );
+
+    var item = data[0];
+
+    assert.equal(item.id, 'test');
+    assert.equal(item.text, 'Test');
+  });
+
+  assert.equal(
+    $element.val(),
+    'test',
+    'The value should not have been altered'
+  );
+});
+
+test('no default value', function (assert) {
+  expect(2);
+
+  var options = new Options({
+    data: [
+      {
+        id: 'test',
+        text: 'Test'
+      }
+    ]
+  });
+  var $element = $('<input />');
+
+  var adapter = new InputAdapter($element, options);
+
+  adapter.current(function (data) {
+    assert.equal(
+      data.length,
+      0,
+      'There should be no selected options'
+    );
+  });
+
+  assert.equal(
+    $element.val(),
+    '',
+    'The value should not have been altered'
+  );
+});

Some files were not shown because too many files changed in this diff