select2-rails 4.0.1 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +12 -2
  3. data/lib/select2-rails/source_file.rb +10 -7
  4. data/lib/select2-rails/version.rb +1 -1
  5. data/select2-rails.gemspec +1 -1
  6. data/vendor/assets/javascripts/select2-full.js +6457 -0
  7. data/vendor/assets/javascripts/select2.js +283 -107
  8. data/vendor/assets/javascripts/select2_locale_ar.js +2 -2
  9. data/vendor/assets/javascripts/select2_locale_az.js +1 -1
  10. data/vendor/assets/javascripts/select2_locale_bg.js +1 -1
  11. data/vendor/assets/javascripts/select2_locale_ca.js +1 -1
  12. data/vendor/assets/javascripts/select2_locale_cs.js +1 -1
  13. data/vendor/assets/javascripts/select2_locale_da.js +2 -2
  14. data/vendor/assets/javascripts/select2_locale_de.js +2 -2
  15. data/vendor/assets/javascripts/select2_locale_el.js +3 -0
  16. data/vendor/assets/javascripts/select2_locale_en.js +1 -1
  17. data/vendor/assets/javascripts/select2_locale_es.js +1 -1
  18. data/vendor/assets/javascripts/select2_locale_et.js +1 -1
  19. data/vendor/assets/javascripts/select2_locale_eu.js +1 -1
  20. data/vendor/assets/javascripts/select2_locale_fa.js +1 -1
  21. data/vendor/assets/javascripts/select2_locale_fi.js +2 -2
  22. data/vendor/assets/javascripts/select2_locale_fr.js +2 -2
  23. data/vendor/assets/javascripts/select2_locale_gl.js +2 -2
  24. data/vendor/assets/javascripts/select2_locale_he.js +1 -1
  25. data/vendor/assets/javascripts/select2_locale_hi.js +1 -1
  26. data/vendor/assets/javascripts/select2_locale_hr.js +1 -1
  27. data/vendor/assets/javascripts/select2_locale_hu.js +2 -2
  28. data/vendor/assets/javascripts/select2_locale_hy.js +3 -0
  29. data/vendor/assets/javascripts/select2_locale_id.js +1 -1
  30. data/vendor/assets/javascripts/select2_locale_is.js +1 -1
  31. data/vendor/assets/javascripts/select2_locale_it.js +1 -1
  32. data/vendor/assets/javascripts/select2_locale_ja.js +1 -1
  33. data/vendor/assets/javascripts/select2_locale_km.js +3 -0
  34. data/vendor/assets/javascripts/select2_locale_ko.js +1 -1
  35. data/vendor/assets/javascripts/select2_locale_lt.js +2 -2
  36. data/vendor/assets/javascripts/select2_locale_lv.js +1 -1
  37. data/vendor/assets/javascripts/select2_locale_mk.js +1 -1
  38. data/vendor/assets/javascripts/select2_locale_ms.js +1 -1
  39. data/vendor/assets/javascripts/select2_locale_nb.js +2 -2
  40. data/vendor/assets/javascripts/select2_locale_nl.js +1 -1
  41. data/vendor/assets/javascripts/select2_locale_pl.js +1 -1
  42. data/vendor/assets/javascripts/select2_locale_pt-BR.js +1 -1
  43. data/vendor/assets/javascripts/select2_locale_pt.js +2 -2
  44. data/vendor/assets/javascripts/select2_locale_ro.js +2 -2
  45. data/vendor/assets/javascripts/select2_locale_ru.js +1 -1
  46. data/vendor/assets/javascripts/select2_locale_sk.js +1 -1
  47. data/vendor/assets/javascripts/select2_locale_sl.js +3 -0
  48. data/vendor/assets/javascripts/select2_locale_sr-Cyrl.js +1 -1
  49. data/vendor/assets/javascripts/select2_locale_sr.js +1 -1
  50. data/vendor/assets/javascripts/select2_locale_sv.js +1 -1
  51. data/vendor/assets/javascripts/select2_locale_th.js +2 -2
  52. data/vendor/assets/javascripts/select2_locale_tr.js +2 -2
  53. data/vendor/assets/javascripts/select2_locale_uk.js +1 -1
  54. data/vendor/assets/javascripts/select2_locale_vi.js +1 -1
  55. data/vendor/assets/javascripts/select2_locale_zh-CN.js +1 -1
  56. data/vendor/assets/javascripts/select2_locale_zh-TW.js +1 -1
  57. data/vendor/assets/stylesheets/select2-bootstrap.css +38 -26
  58. data/vendor/assets/stylesheets/select2.css +2 -0
  59. metadata +11 -7
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Select2 4.0.1
2
+ * Select2 4.0.4
3
3
  * https://select2.github.io
4
4
  *
5
5
  * Released under the MIT license
@@ -9,19 +9,33 @@
9
9
  if (typeof define === 'function' && define.amd) {
10
10
  // AMD. Register as an anonymous module.
11
11
  define(['jquery'], factory);
12
- } else if (typeof exports === 'object') {
12
+ } else if (typeof module === 'object' && module.exports) {
13
13
  // Node/CommonJS
14
- factory(require('jquery'));
14
+ module.exports = function (root, jQuery) {
15
+ if (jQuery === undefined) {
16
+ // require('jQuery') returns a factory that requires window to
17
+ // build a jQuery instance, we normalize how we use modules
18
+ // that require this pattern but the window provided is a noop
19
+ // if it's defined (how jquery works)
20
+ if (typeof window !== 'undefined') {
21
+ jQuery = require('jquery');
22
+ }
23
+ else {
24
+ jQuery = require('jquery')(root);
25
+ }
26
+ }
27
+ factory(jQuery);
28
+ return jQuery;
29
+ };
15
30
  } else {
16
31
  // Browser globals
17
32
  factory(jQuery);
18
33
  }
19
- }(function (jQuery) {
34
+ } (function (jQuery) {
20
35
  // This is needed so we can catch the AMD loader configuration and use it
21
36
  // The inner file should be wrapped (by `banner.start.js`) in a function that
22
37
  // returns the AMD loader references.
23
- var S2 =
24
- (function () {
38
+ var S2 =(function () {
25
39
  // Restore the Select2 AMD loader so it can be used
26
40
  // Needed mostly in the language files, where the loader is not inserted
27
41
  if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
@@ -30,13 +44,11 @@
30
44
  var S2;(function () { if (!S2 || !S2.requirejs) {
31
45
  if (!S2) { S2 = {}; } else { require = S2; }
32
46
  /**
33
- * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
34
- * Available via the MIT or new BSD license.
35
- * see: http://github.com/jrburke/almond for details
47
+ * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
48
+ * Released under MIT license, http://github.com/requirejs/almond/LICENSE
36
49
  */
37
50
  //Going sloppy to avoid 'use strict' string cost, but strict practices should
38
51
  //be followed.
39
- /*jslint sloppy: true */
40
52
  /*global setTimeout: false */
41
53
 
42
54
  var requirejs, require, define;
@@ -64,60 +76,58 @@ var requirejs, require, define;
64
76
  */
65
77
  function normalize(name, baseName) {
66
78
  var nameParts, nameSegment, mapValue, foundMap, lastIndex,
67
- foundI, foundStarMap, starI, i, j, part,
79
+ foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
68
80
  baseParts = baseName && baseName.split("/"),
69
81
  map = config.map,
70
82
  starMap = (map && map['*']) || {};
71
83
 
72
84
  //Adjust any relative paths.
73
- if (name && name.charAt(0) === ".") {
74
- //If have a base name, try to normalize against it,
75
- //otherwise, assume it is a top-level require that will
76
- //be relative to baseUrl in the end.
77
- if (baseName) {
78
- name = name.split('/');
79
- lastIndex = name.length - 1;
80
-
81
- // Node .js allowance:
82
- if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
83
- name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
84
- }
85
+ if (name) {
86
+ name = name.split('/');
87
+ lastIndex = name.length - 1;
88
+
89
+ // If wanting node ID compatibility, strip .js from end
90
+ // of IDs. Have to do this here, and not in nameToUrl
91
+ // because node allows either .js or non .js to map
92
+ // to same file.
93
+ if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
94
+ name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
95
+ }
85
96
 
86
- //Lop off the last part of baseParts, so that . matches the
87
- //"directory" and not name of the baseName's module. For instance,
88
- //baseName of "one/two/three", maps to "one/two/three.js", but we
89
- //want the directory, "one/two" for this normalization.
90
- name = baseParts.slice(0, baseParts.length - 1).concat(name);
91
-
92
- //start trimDots
93
- for (i = 0; i < name.length; i += 1) {
94
- part = name[i];
95
- if (part === ".") {
96
- name.splice(i, 1);
97
- i -= 1;
98
- } else if (part === "..") {
99
- if (i === 1 && (name[2] === '..' || name[0] === '..')) {
100
- //End of the line. Keep at least one non-dot
101
- //path segment at the front so it can be mapped
102
- //correctly to disk. Otherwise, there is likely
103
- //no path mapping for a path starting with '..'.
104
- //This can still fail, but catches the most reasonable
105
- //uses of ..
106
- break;
107
- } else if (i > 0) {
108
- name.splice(i - 1, 2);
109
- i -= 2;
110
- }
97
+ // Starts with a '.' so need the baseName
98
+ if (name[0].charAt(0) === '.' && baseParts) {
99
+ //Convert baseName to array, and lop off the last part,
100
+ //so that . matches that 'directory' and not name of the baseName's
101
+ //module. For instance, baseName of 'one/two/three', maps to
102
+ //'one/two/three.js', but we want the directory, 'one/two' for
103
+ //this normalization.
104
+ normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
105
+ name = normalizedBaseParts.concat(name);
106
+ }
107
+
108
+ //start trimDots
109
+ for (i = 0; i < name.length; i++) {
110
+ part = name[i];
111
+ if (part === '.') {
112
+ name.splice(i, 1);
113
+ i -= 1;
114
+ } else if (part === '..') {
115
+ // If at the start, or previous value is still ..,
116
+ // keep them so that when converted to a path it may
117
+ // still work when converted to a path, even though
118
+ // as an ID it is less than ideal. In larger point
119
+ // releases, may be better to just kick out an error.
120
+ if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
121
+ continue;
122
+ } else if (i > 0) {
123
+ name.splice(i - 1, 2);
124
+ i -= 2;
111
125
  }
112
126
  }
113
- //end trimDots
114
-
115
- name = name.join("/");
116
- } else if (name.indexOf('./') === 0) {
117
- // No baseName, so this is ID is resolved relative
118
- // to baseUrl, pull off the leading dot.
119
- name = name.substring(2);
120
127
  }
128
+ //end trimDots
129
+
130
+ name = name.join('/');
121
131
  }
122
132
 
123
133
  //Apply map config if available.
@@ -230,32 +240,39 @@ var requirejs, require, define;
230
240
  return [prefix, name];
231
241
  }
232
242
 
243
+ //Creates a parts array for a relName where first part is plugin ID,
244
+ //second part is resource ID. Assumes relName has already been normalized.
245
+ function makeRelParts(relName) {
246
+ return relName ? splitPrefix(relName) : [];
247
+ }
248
+
233
249
  /**
234
250
  * Makes a name map, normalizing the name, and using a plugin
235
251
  * for normalization if necessary. Grabs a ref to plugin
236
252
  * too, as an optimization.
237
253
  */
238
- makeMap = function (name, relName) {
254
+ makeMap = function (name, relParts) {
239
255
  var plugin,
240
256
  parts = splitPrefix(name),
241
- prefix = parts[0];
257
+ prefix = parts[0],
258
+ relResourceName = relParts[1];
242
259
 
243
260
  name = parts[1];
244
261
 
245
262
  if (prefix) {
246
- prefix = normalize(prefix, relName);
263
+ prefix = normalize(prefix, relResourceName);
247
264
  plugin = callDep(prefix);
248
265
  }
249
266
 
250
267
  //Normalize according
251
268
  if (prefix) {
252
269
  if (plugin && plugin.normalize) {
253
- name = plugin.normalize(name, makeNormalize(relName));
270
+ name = plugin.normalize(name, makeNormalize(relResourceName));
254
271
  } else {
255
- name = normalize(name, relName);
272
+ name = normalize(name, relResourceName);
256
273
  }
257
274
  } else {
258
- name = normalize(name, relName);
275
+ name = normalize(name, relResourceName);
259
276
  parts = splitPrefix(name);
260
277
  prefix = parts[0];
261
278
  name = parts[1];
@@ -302,13 +319,14 @@ var requirejs, require, define;
302
319
  };
303
320
 
304
321
  main = function (name, deps, callback, relName) {
305
- var cjsModule, depName, ret, map, i,
322
+ var cjsModule, depName, ret, map, i, relParts,
306
323
  args = [],
307
324
  callbackType = typeof callback,
308
325
  usingExports;
309
326
 
310
327
  //Use name if no relName
311
328
  relName = relName || name;
329
+ relParts = makeRelParts(relName);
312
330
 
313
331
  //Call the callback to define the module, if necessary.
314
332
  if (callbackType === 'undefined' || callbackType === 'function') {
@@ -317,7 +335,7 @@ var requirejs, require, define;
317
335
  //Default to [require, exports, module] if no deps
318
336
  deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
319
337
  for (i = 0; i < deps.length; i += 1) {
320
- map = makeMap(deps[i], relName);
338
+ map = makeMap(deps[i], relParts);
321
339
  depName = map.f;
322
340
 
323
341
  //Fast path CommonJS standard dependencies.
@@ -373,7 +391,7 @@ var requirejs, require, define;
373
391
  //deps arg is the module name, and second arg (if passed)
374
392
  //is just the relName.
375
393
  //Normalize module name, if it contains . or ..
376
- return callDep(makeMap(deps, callback).f);
394
+ return callDep(makeMap(deps, makeRelParts(callback)).f);
377
395
  } else if (!deps.splice) {
378
396
  //deps is a config object, not an array.
379
397
  config = deps;
@@ -606,9 +624,23 @@ S2.define('select2/utils',[
606
624
 
607
625
  Observable.prototype.trigger = function (event) {
608
626
  var slice = Array.prototype.slice;
627
+ var params = slice.call(arguments, 1);
609
628
 
610
629
  this.listeners = this.listeners || {};
611
630
 
631
+ // Params should always come in as an array
632
+ if (params == null) {
633
+ params = [];
634
+ }
635
+
636
+ // If there are no arguments to the event, use a temporary object
637
+ if (params.length === 0) {
638
+ params.push({});
639
+ }
640
+
641
+ // Set the `_type` of the first object to the event
642
+ params[0]._type = event;
643
+
612
644
  if (event in this.listeners) {
613
645
  this.invoke(this.listeners[event], slice.call(arguments, 1));
614
646
  }
@@ -842,6 +874,25 @@ S2.define('select2/results',[
842
874
  return sorter(data);
843
875
  };
844
876
 
877
+ Results.prototype.highlightFirstItem = function () {
878
+ var $options = this.$results
879
+ .find('.select2-results__option[aria-selected]');
880
+
881
+ var $selected = $options.filter('[aria-selected=true]');
882
+
883
+ // Check if there are any selected options
884
+ if ($selected.length > 0) {
885
+ // If there are selected options, highlight the first
886
+ $selected.first().trigger('mouseenter');
887
+ } else {
888
+ // If there are no selected options, highlight the first option
889
+ // in the dropdown
890
+ $options.first().trigger('mouseenter');
891
+ }
892
+
893
+ this.ensureHighlightVisible();
894
+ };
895
+
845
896
  Results.prototype.setClasses = function () {
846
897
  var self = this;
847
898
 
@@ -869,17 +920,6 @@ S2.define('select2/results',[
869
920
  }
870
921
  });
871
922
 
872
- var $selected = $options.filter('[aria-selected=true]');
873
-
874
- // Check if there are any selected options
875
- if ($selected.length > 0) {
876
- // If there are selected options, highlight the first
877
- $selected.first().trigger('mouseenter');
878
- } else {
879
- // If there are no selected options, highlight the first option
880
- // in the dropdown
881
- $options.first().trigger('mouseenter');
882
- }
883
923
  });
884
924
  };
885
925
 
@@ -990,6 +1030,7 @@ S2.define('select2/results',[
990
1030
 
991
1031
  if (container.isOpen()) {
992
1032
  self.setClasses();
1033
+ self.highlightFirstItem();
993
1034
  }
994
1035
  });
995
1036
 
@@ -1012,6 +1053,7 @@ S2.define('select2/results',[
1012
1053
  }
1013
1054
 
1014
1055
  self.setClasses();
1056
+ self.highlightFirstItem();
1015
1057
  });
1016
1058
 
1017
1059
  container.on('unselect', function () {
@@ -1020,6 +1062,7 @@ S2.define('select2/results',[
1020
1062
  }
1021
1063
 
1022
1064
  self.setClasses();
1065
+ self.highlightFirstItem();
1023
1066
  });
1024
1067
 
1025
1068
  container.on('open', function () {
@@ -1142,11 +1185,7 @@ S2.define('select2/results',[
1142
1185
  this.$results.on('mousewheel', function (e) {
1143
1186
  var top = self.$results.scrollTop();
1144
1187
 
1145
- var bottom = (
1146
- self.$results.get(0).scrollHeight -
1147
- self.$results.scrollTop() +
1148
- e.deltaY
1149
- );
1188
+ var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
1150
1189
 
1151
1190
  var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
1152
1191
  var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
@@ -1501,6 +1540,12 @@ S2.define('select2/selection/single',[
1501
1540
  // User exits the container
1502
1541
  });
1503
1542
 
1543
+ container.on('focus', function (evt) {
1544
+ if (!container.isOpen()) {
1545
+ self.$selection.focus();
1546
+ }
1547
+ });
1548
+
1504
1549
  container.on('selection:update', function (params) {
1505
1550
  self.update(params.data);
1506
1551
  });
@@ -3164,7 +3209,7 @@ S2.define('select2/data/select',[
3164
3209
  }
3165
3210
  }
3166
3211
 
3167
- if (data.id) {
3212
+ if (data.id !== undefined) {
3168
3213
  option.value = data.id;
3169
3214
  }
3170
3215
 
@@ -3332,7 +3377,7 @@ S2.define('select2/data/array',[
3332
3377
  var $existingOption = $existing.filter(onlyItem(item));
3333
3378
 
3334
3379
  var existingData = this.item($existingOption);
3335
- var newData = $.extend(true, {}, existingData, item);
3380
+ var newData = $.extend(true, {}, item, existingData);
3336
3381
 
3337
3382
  var $newOption = this.option(newData);
3338
3383
 
@@ -3440,13 +3485,21 @@ S2.define('select2/data/ajax',[
3440
3485
 
3441
3486
  callback(results);
3442
3487
  }, function () {
3443
- // TODO: Handle AJAX errors
3488
+ // Attempt to detect if a request was aborted
3489
+ // Only works if the transport exposes a status property
3490
+ if ($request.status && $request.status === '0') {
3491
+ return;
3492
+ }
3493
+
3494
+ self.trigger('results:message', {
3495
+ message: 'errorLoading'
3496
+ });
3444
3497
  });
3445
3498
 
3446
3499
  self._request = $request;
3447
3500
  }
3448
3501
 
3449
- if (this.ajaxOptions.delay && params.term !== '') {
3502
+ if (this.ajaxOptions.delay && params.term != null) {
3450
3503
  if (this._queryTimeout) {
3451
3504
  window.clearTimeout(this._queryTimeout);
3452
3505
  }
@@ -3472,6 +3525,12 @@ S2.define('select2/data/tags',[
3472
3525
  this.createTag = createTag;
3473
3526
  }
3474
3527
 
3528
+ var insertTag = options.get('insertTag');
3529
+
3530
+ if (insertTag !== undefined) {
3531
+ this.insertTag = insertTag;
3532
+ }
3533
+
3475
3534
  decorated.call(this, $element, options);
3476
3535
 
3477
3536
  if ($.isArray(tags)) {
@@ -3509,7 +3568,10 @@ S2.define('select2/data/tags',[
3509
3568
  }, true)
3510
3569
  );
3511
3570
 
3512
- var checkText = option.text === params.term;
3571
+ var optionText = (option.text || '').toUpperCase();
3572
+ var paramsTerm = (params.term || '').toUpperCase();
3573
+
3574
+ var checkText = optionText === paramsTerm;
3513
3575
 
3514
3576
  if (checkText || checkChildren) {
3515
3577
  if (child) {
@@ -3603,6 +3665,29 @@ S2.define('select2/data/tokenizer',[
3603
3665
  Tokenizer.prototype.query = function (decorated, params, callback) {
3604
3666
  var self = this;
3605
3667
 
3668
+ function createAndSelect (data) {
3669
+ // Normalize the data object so we can use it for checks
3670
+ var item = self._normalizeItem(data);
3671
+
3672
+ // Check if the data object already exists as a tag
3673
+ // Select it if it doesn't
3674
+ var $existingOptions = self.$element.find('option').filter(function () {
3675
+ return $(this).val() === item.id;
3676
+ });
3677
+
3678
+ // If an existing option wasn't found for it, create the option
3679
+ if (!$existingOptions.length) {
3680
+ var $option = self.option(item);
3681
+ $option.attr('data-select2-tag', true);
3682
+
3683
+ self._removeOldTags();
3684
+ self.addOptions([$option]);
3685
+ }
3686
+
3687
+ // Select the item, now that we know there is an option for it
3688
+ select(item);
3689
+ }
3690
+
3606
3691
  function select (data) {
3607
3692
  self.trigger('select', {
3608
3693
  data: data
@@ -3611,7 +3696,7 @@ S2.define('select2/data/tokenizer',[
3611
3696
 
3612
3697
  params.term = params.term || '';
3613
3698
 
3614
- var tokenData = this.tokenizer(params, this.options, select);
3699
+ var tokenData = this.tokenizer(params, this.options, createAndSelect);
3615
3700
 
3616
3701
  if (tokenData.term !== params.term) {
3617
3702
  // Replace the search term if we have the search box
@@ -3876,6 +3961,12 @@ S2.define('select2/dropdown/search',[
3876
3961
  self.$search.val('');
3877
3962
  });
3878
3963
 
3964
+ container.on('focus', function () {
3965
+ if (!container.isOpen()) {
3966
+ self.$search.focus();
3967
+ }
3968
+ });
3969
+
3879
3970
  container.on('results:all', function (params) {
3880
3971
  if (params.query.term == null || params.query.term === '') {
3881
3972
  var showSearch = self.showSearch(params);
@@ -4171,7 +4262,6 @@ S2.define('select2/dropdown/attachBody',[
4171
4262
 
4172
4263
  var newDirection = null;
4173
4264
 
4174
- var position = this.$container.position();
4175
4265
  var offset = this.$container.offset();
4176
4266
 
4177
4267
  offset.bottom = offset.top + this.$container.outerHeight(false);
@@ -4200,14 +4290,20 @@ S2.define('select2/dropdown/attachBody',[
4200
4290
  top: container.bottom
4201
4291
  };
4202
4292
 
4203
- // Fix positioning with static parents
4204
- if (this.$dropdownParent[0].style.position !== 'static') {
4205
- var parentOffset = this.$dropdownParent.offset();
4293
+ // Determine what the parent element is to use for calciulating the offset
4294
+ var $offsetParent = this.$dropdownParent;
4206
4295
 
4207
- css.top -= parentOffset.top;
4208
- css.left -= parentOffset.left;
4296
+ // For statically positoned elements, we need to get the element
4297
+ // that is determining the offset
4298
+ if ($offsetParent.css('position') === 'static') {
4299
+ $offsetParent = $offsetParent.offsetParent();
4209
4300
  }
4210
4301
 
4302
+ var parentOffset = $offsetParent.offset();
4303
+
4304
+ css.top -= parentOffset.top;
4305
+ css.left -= parentOffset.left;
4306
+
4211
4307
  if (!isCurrentlyAbove && !isCurrentlyBelow) {
4212
4308
  newDirection = 'below';
4213
4309
  }
@@ -4220,7 +4316,7 @@ S2.define('select2/dropdown/attachBody',[
4220
4316
 
4221
4317
  if (newDirection == 'above' ||
4222
4318
  (isCurrentlyAbove && newDirection !== 'below')) {
4223
- css.top = container.top - dropdown.height;
4319
+ css.top = container.top - parentOffset.top - dropdown.height;
4224
4320
  }
4225
4321
 
4226
4322
  if (newDirection != null) {
@@ -4242,6 +4338,7 @@ S2.define('select2/dropdown/attachBody',[
4242
4338
 
4243
4339
  if (this.options.get('dropdownAutoWidth')) {
4244
4340
  css.minWidth = css.width;
4341
+ css.position = 'relative';
4245
4342
  css.width = 'auto';
4246
4343
  }
4247
4344
 
@@ -4308,12 +4405,22 @@ S2.define('select2/dropdown/selectOnClose',[
4308
4405
 
4309
4406
  decorated.call(this, container, $container);
4310
4407
 
4311
- container.on('close', function () {
4312
- self._handleSelectOnClose();
4408
+ container.on('close', function (params) {
4409
+ self._handleSelectOnClose(params);
4313
4410
  });
4314
4411
  };
4315
4412
 
4316
- SelectOnClose.prototype._handleSelectOnClose = function () {
4413
+ SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
4414
+ if (params && params.originalSelect2Event != null) {
4415
+ var event = params.originalSelect2Event;
4416
+
4417
+ // Don't select an item if the close event was triggered from a select or
4418
+ // unselect event
4419
+ if (event._type === 'select' || event._type === 'unselect') {
4420
+ return;
4421
+ }
4422
+ }
4423
+
4317
4424
  var $highlightedResults = this.getHighlightedResults();
4318
4425
 
4319
4426
  // Only select highlighted results
@@ -4366,7 +4473,10 @@ S2.define('select2/dropdown/closeOnSelect',[
4366
4473
  return;
4367
4474
  }
4368
4475
 
4369
- this.trigger('close', {});
4476
+ this.trigger('close', {
4477
+ originalEvent: originalEvent,
4478
+ originalSelect2Event: evt
4479
+ });
4370
4480
  };
4371
4481
 
4372
4482
  return CloseOnSelect;
@@ -4474,7 +4584,7 @@ S2.define('select2/defaults',[
4474
4584
  }
4475
4585
 
4476
4586
  Defaults.prototype.apply = function (options) {
4477
- options = $.extend({}, this.defaults, options);
4587
+ options = $.extend(true, {}, this.defaults, options);
4478
4588
 
4479
4589
  if (options.dataAdapter == null) {
4480
4590
  if (options.ajax != null) {
@@ -5038,6 +5148,7 @@ S2.define('select2/core',[
5038
5148
  id = Utils.generateChars(4);
5039
5149
  }
5040
5150
 
5151
+ id = id.replace(/(:|\.|\[|\]|,)/g, '');
5041
5152
  id = 'select2-' + id;
5042
5153
 
5043
5154
  return id;
@@ -5119,10 +5230,15 @@ S2.define('select2/core',[
5119
5230
  });
5120
5231
  });
5121
5232
 
5122
- this._sync = Utils.bind(this._syncAttributes, this);
5233
+ this.$element.on('focus.select2', function (evt) {
5234
+ self.trigger('focus', evt);
5235
+ });
5236
+
5237
+ this._syncA = Utils.bind(this._syncAttributes, this);
5238
+ this._syncS = Utils.bind(this._syncSubtree, this);
5123
5239
 
5124
5240
  if (this.$element[0].attachEvent) {
5125
- this.$element[0].attachEvent('onpropertychange', this._sync);
5241
+ this.$element[0].attachEvent('onpropertychange', this._syncA);
5126
5242
  }
5127
5243
 
5128
5244
  var observer = window.MutationObserver ||
@@ -5132,14 +5248,30 @@ S2.define('select2/core',[
5132
5248
 
5133
5249
  if (observer != null) {
5134
5250
  this._observer = new observer(function (mutations) {
5135
- $.each(mutations, self._sync);
5251
+ $.each(mutations, self._syncA);
5252
+ $.each(mutations, self._syncS);
5136
5253
  });
5137
5254
  this._observer.observe(this.$element[0], {
5138
5255
  attributes: true,
5256
+ childList: true,
5139
5257
  subtree: false
5140
5258
  });
5141
5259
  } else if (this.$element[0].addEventListener) {
5142
- this.$element[0].addEventListener('DOMAttrModified', self._sync, false);
5260
+ this.$element[0].addEventListener(
5261
+ 'DOMAttrModified',
5262
+ self._syncA,
5263
+ false
5264
+ );
5265
+ this.$element[0].addEventListener(
5266
+ 'DOMNodeInserted',
5267
+ self._syncS,
5268
+ false
5269
+ );
5270
+ this.$element[0].addEventListener(
5271
+ 'DOMNodeRemoved',
5272
+ self._syncS,
5273
+ false
5274
+ );
5143
5275
  }
5144
5276
  };
5145
5277
 
@@ -5284,6 +5416,46 @@ S2.define('select2/core',[
5284
5416
  }
5285
5417
  };
5286
5418
 
5419
+ Select2.prototype._syncSubtree = function (evt, mutations) {
5420
+ var changed = false;
5421
+ var self = this;
5422
+
5423
+ // Ignore any mutation events raised for elements that aren't options or
5424
+ // optgroups. This handles the case when the select element is destroyed
5425
+ if (
5426
+ evt && evt.target && (
5427
+ evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
5428
+ )
5429
+ ) {
5430
+ return;
5431
+ }
5432
+
5433
+ if (!mutations) {
5434
+ // If mutation events aren't supported, then we can only assume that the
5435
+ // change affected the selections
5436
+ changed = true;
5437
+ } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
5438
+ for (var n = 0; n < mutations.addedNodes.length; n++) {
5439
+ var node = mutations.addedNodes[n];
5440
+
5441
+ if (node.selected) {
5442
+ changed = true;
5443
+ }
5444
+ }
5445
+ } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
5446
+ changed = true;
5447
+ }
5448
+
5449
+ // Only re-pull the data if we think there is a change
5450
+ if (changed) {
5451
+ this.dataAdapter.current(function (currentData) {
5452
+ self.trigger('selection:update', {
5453
+ data: currentData
5454
+ });
5455
+ });
5456
+ }
5457
+ };
5458
+
5287
5459
  /**
5288
5460
  * Override the trigger method to automatically trigger pre-events when
5289
5461
  * there are events that can be prevented.
@@ -5430,7 +5602,7 @@ S2.define('select2/core',[
5430
5602
  this.$container.remove();
5431
5603
 
5432
5604
  if (this.$element[0].detachEvent) {
5433
- this.$element[0].detachEvent('onpropertychange', this._sync);
5605
+ this.$element[0].detachEvent('onpropertychange', this._syncA);
5434
5606
  }
5435
5607
 
5436
5608
  if (this._observer != null) {
@@ -5438,10 +5610,15 @@ S2.define('select2/core',[
5438
5610
  this._observer = null;
5439
5611
  } else if (this.$element[0].removeEventListener) {
5440
5612
  this.$element[0]
5441
- .removeEventListener('DOMAttrModified', this._sync, false);
5613
+ .removeEventListener('DOMAttrModified', this._syncA, false);
5614
+ this.$element[0]
5615
+ .removeEventListener('DOMNodeInserted', this._syncS, false);
5616
+ this.$element[0]
5617
+ .removeEventListener('DOMNodeRemoved', this._syncS, false);
5442
5618
  }
5443
5619
 
5444
- this._sync = null;
5620
+ this._syncA = null;
5621
+ this._syncS = null;
5445
5622
 
5446
5623
  this.$element.off('.select2');
5447
5624
  this.$element.attr('tabindex', this.$element.data('old-tabindex'));
@@ -5514,6 +5691,7 @@ S2.define('jquery.select2',[
5514
5691
  return this;
5515
5692
  } else if (typeof options === 'string') {
5516
5693
  var ret;
5694
+ var args = Array.prototype.slice.call(arguments, 1);
5517
5695
 
5518
5696
  this.each(function () {
5519
5697
  var instance = $(this).data('select2');
@@ -5525,8 +5703,6 @@ S2.define('jquery.select2',[
5525
5703
  );
5526
5704
  }
5527
5705
 
5528
- var args = Array.prototype.slice.call(arguments, 1);
5529
-
5530
5706
  ret = instance[options].apply(instance, args);
5531
5707
  });
5532
5708