PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
← All changes | assets/js/libraries/select2.js +1024 -380 1.6.9 → 1.8.13 View file →
@@ -1,28 +1,42 @@
1 1 /*!
2 - * Select2 4.0.0
2 + * Select2 4.0.12
3 3 * https://select2.github.io
4 4 *
5 5 * Released under the MIT license
6 6 * https://github.com/select2/select2/blob/master/LICENSE.md
7 7 */
8 -(function (factory) {
8 +;(function (factory) {
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) {
28 42 var S2 = jQuery.fn.select2.amd;
@@ -29,15 +43,13 @@
29 43 }
30 44 var S2;(function () { if (!S2 || !S2.requirejs) {
31 45 if (!S2) { S2 = {}; } else { require = S2; }
32 46 /**
33 - * @license almond 0.2.9 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;
43 55 (function (undef) {
@@ -63,64 +75,60 @@
63 75 * @returns {String} normalized name
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) {
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 + }
96 +
97 + // Starts with a '.' so need the baseName
98 + if (name[0].charAt(0) === '.' && baseParts) {
78 99 //Convert baseName to array, and lop off the last part,
79 - //so that . matches that "directory" and not name of the baseName's
80 - //module. For instance, baseName of "one/two/three", maps to
81 - //"one/two/three.js", but we want the directory, "one/two" for
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
82 103 //this normalization.
83 - baseParts = baseParts.slice(0, baseParts.length - 1);
84 - name = name.split('/');
85 - lastIndex = name.length - 1;
104 + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
105 + name = normalizedBaseParts.concat(name);
106 + }
86 107
87 - // Node .js allowance:
88 - if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
89 - name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
90 - }
91 -
92 - name = baseParts.concat(name);
93 -
94 - //start trimDots
95 - for (i = 0; i < name.length; i += 1) {
96 - part = name[i];
97 - if (part === ".") {
98 - name.splice(i, 1);
99 - i -= 1;
100 - } else if (part === "..") {
101 - if (i === 1 && (name[2] === '..' || name[0] === '..')) {
102 - //End of the line. Keep at least one non-dot
103 - //path segment at the front so it can be mapped
104 - //correctly to disk. Otherwise, there is likely
105 - //no path mapping for a path starting with '..'.
106 - //This can still fail, but catches the most reasonable
107 - //uses of ..
108 - break;
109 - } else if (i > 0) {
110 - name.splice(i - 1, 2);
111 - i -= 2;
112 - }
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;
113 125 }
114 126 }
115 - //end trimDots
127 + }
128 + //end trimDots
116 129
117 - name = name.join("/");
118 - } else if (name.indexOf('./') === 0) {
119 - // No baseName, so this is ID is resolved relative
120 - // to baseUrl, pull off the leading dot.
121 - name = name.substring(2);
122 - }
130 + name = name.join('/');
123 131 }
124 132
125 133 //Apply map config if available.
126 134 if ((baseParts || starMap) && map) {
@@ -180,9 +188,17 @@
180 188 return function () {
181 189 //A version of a require function that passes a moduleName
182 190 //value for items that may need to
183 191 //look up paths relative to the moduleName
184 - return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
192 + var args = aps.call(arguments, 0);
193 +
194 + //If first arg is not require('string'), and there is only
195 + //one arg, it is the array form without a callback. Insert
196 + //a null so that the following concat is correct.
197 + if (typeof args[0] !== 'string' && args.length === 1) {
198 + args.push(null);
199 + }
200 + return req.apply(undef, args.concat([relName, forceSync]));
185 201 };
186 202 }
187 203
188 204 function makeNormalize(relName) {
@@ -223,22 +239,29 @@
223 239 }
224 240 return [prefix, name];
225 241 }
226 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 +
227 249 /**
228 250 * Makes a name map, normalizing the name, and using a plugin
229 251 * for normalization if necessary. Grabs a ref to plugin
230 252 * too, as an optimization.
231 253 */
232 - makeMap = function (name, relName) {
254 + makeMap = function (name, relParts) {
233 255 var plugin,
234 256 parts = splitPrefix(name),
235 - prefix = parts[0];
257 + prefix = parts[0],
258 + relResourceName = relParts[1];
236 259
237 260 name = parts[1];
238 261
239 262 if (prefix) {
240 - prefix = normalize(prefix, relName);
263 + prefix = normalize(prefix, relResourceName);
241 264 plugin = callDep(prefix);
242 265 }
243 266
244 267 //Normalize according
@@ -243,14 +266,14 @@
243 266
244 267 //Normalize according
245 268 if (prefix) {
246 269 if (plugin && plugin.normalize) {
247 - name = plugin.normalize(name, makeNormalize(relName));
270 + name = plugin.normalize(name, makeNormalize(relResourceName));
248 271 } else {
249 - name = normalize(name, relName);
272 + name = normalize(name, relResourceName);
250 273 }
251 274 } else {
252 - name = normalize(name, relName);
275 + name = normalize(name, relResourceName);
253 276 parts = splitPrefix(name);
254 277 prefix = parts[0];
255 278 name = parts[1];
256 279 if (prefix) {
@@ -295,9 +318,9 @@
295 318 }
296 319 };
297 320
298 321 main = function (name, deps, callback, relName) {
299 - var cjsModule, depName, ret, map, i,
322 + var cjsModule, depName, ret, map, i, relParts,
300 323 args = [],
301 324 callbackType = typeof callback,
302 325 usingExports;
303 326
@@ -302,8 +325,9 @@
302 325 usingExports;
303 326
304 327 //Use name if no relName
305 328 relName = relName || name;
329 + relParts = makeRelParts(relName);
306 330
307 331 //Call the callback to define the module, if necessary.
308 332 if (callbackType === 'undefined' || callbackType === 'function') {
309 333 //Pull out the defined dependencies and pass the ordered
@@ -310,9 +334,9 @@
310 334 //values to the callback.
311 335 //Default to [require, exports, module] if no deps
312 336 deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
313 337 for (i = 0; i < deps.length; i += 1) {
314 - map = makeMap(deps[i], relName);
338 + map = makeMap(deps[i], relParts);
315 339 depName = map.f;
316 340
317 341 //Fast path CommonJS standard dependencies.
318 342 if (depName === "require") {
@@ -366,9 +390,9 @@
366 390 //Just return the module wanted. In this scenario, the
367 391 //deps arg is the module name, and second arg (if passed)
368 392 //is just the relName.
369 393 //Normalize module name, if it contains . or ..
370 - return callDep(makeMap(deps, callback).f);
394 + return callDep(makeMap(deps, makeRelParts(callback)).f);
371 395 } else if (!deps.splice) {
372 396 //deps is a config object, not an array.
373 397 config = deps;
374 398 if (config.deps) {
@@ -430,8 +454,11 @@
430 454 */
431 455 requirejs._defined = defined;
432 456
433 457 define = function (name, deps, callback) {
458 + if (typeof name !== 'string') {
459 + throw new Error('See almond README: incorrect module build, no module name');
460 + }
434 461
435 462 //This module may not have dependencies
436 463 if (!deps.splice) {
437 464 //deps is not an array, so probably means
@@ -546,12 +573,12 @@
546 573
547 574 DecoratedClass.prototype = new ctr();
548 575
549 576 for (var m = 0; m < superMethods.length; m++) {
550 - var superMethod = superMethods[m];
577 + var superMethod = superMethods[m];
551 578
552 - DecoratedClass.prototype[superMethod] =
553 - SuperClass.prototype[superMethod];
579 + DecoratedClass.prototype[superMethod] =
580 + SuperClass.prototype[superMethod];
554 581 }
555 582
556 583 var calledMethod = function (methodName) {
557 584 // Stub out the original method if it's not decorating an actual method
@@ -596,11 +623,25 @@
596 623 };
597 624
598 625 Observable.prototype.trigger = function (event) {
599 626 var slice = Array.prototype.slice;
627 + var params = slice.call(arguments, 1);
600 628
601 629 this.listeners = this.listeners || {};
602 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 +
603 644 if (event in this.listeners) {
604 645 this.invoke(this.listeners[event], slice.call(arguments, 1));
605 646 }
606 647
@@ -730,8 +771,72 @@
730 771
731 772 $element.append($nodes);
732 773 };
733 774
775 + // Cache objects in Utils.__cache instead of $.data (see #4346)
776 + Utils.__cache = {};
777 +
778 + var id = 0;
779 + Utils.GetUniqueElementId = function (element) {
780 + // Get a unique element Id. If element has no id,
781 + // creates a new unique number, stores it in the id
782 + // attribute and returns the new id.
783 + // If an id already exists, it simply returns it.
784 +
785 + var select2Id = element.getAttribute('data-select2-id');
786 + if (select2Id == null) {
787 + // If element has id, use it.
788 + if (element.id) {
789 + select2Id = element.id;
790 + element.setAttribute('data-select2-id', select2Id);
791 + } else {
792 + element.setAttribute('data-select2-id', ++id);
793 + select2Id = id.toString();
794 + }
795 + }
796 + return select2Id;
797 + };
798 +
799 + Utils.StoreData = function (element, name, value) {
800 + // Stores an item in the cache for a specified element.
801 + // name is the cache key.
802 + var id = Utils.GetUniqueElementId(element);
803 + if (!Utils.__cache[id]) {
804 + Utils.__cache[id] = {};
805 + }
806 +
807 + Utils.__cache[id][name] = value;
808 + };
809 +
810 + Utils.GetData = function (element, name) {
811 + // Retrieves a value from the cache by its key (name)
812 + // name is optional. If no name specified, return
813 + // all cache items for the specified element.
814 + // and for a specified element.
815 + var id = Utils.GetUniqueElementId(element);
816 + if (name) {
817 + if (Utils.__cache[id]) {
818 + if (Utils.__cache[id][name] != null) {
819 + return Utils.__cache[id][name];
820 + }
821 + return $(element).data(name); // Fallback to HTML5 data attribs.
822 + }
823 + return $(element).data(name); // Fallback to HTML5 data attribs.
824 + } else {
825 + return Utils.__cache[id];
826 + }
827 + };
828 +
829 + Utils.RemoveData = function (element) {
830 + // Removes all cached items for a specified element.
831 + var id = Utils.GetUniqueElementId(element);
832 + if (Utils.__cache[id] != null) {
833 + delete Utils.__cache[id];
834 + }
835 +
836 + element.removeAttribute('data-select2-id');
837 + };
838 +
734 839 return Utils;
735 840 });
736 841
737 842 S2.define('select2/results',[
@@ -749,9 +854,9 @@
749 854 Utils.Extend(Results, Utils.Observable);
750 855
751 856 Results.prototype.render = function () {
752 857 var $results = $(
753 - '<ul class="select2-results__options" role="tree"></ul>'
858 + '<ul class="select2-results__options" role="listbox"></ul>'
754 859 );
755 860
756 861 if (this.options.get('multiple')) {
757 862 $results.attr('aria-multiselectable', 'true');
@@ -772,9 +877,10 @@
772 877 this.clear();
773 878 this.hideLoading();
774 879
775 880 var $message = $(
776 - '<li role="treeitem" class="select2-results__option"></li>'
881 + '<li role="alert" aria-live="assertive"' +
882 + ' class="select2-results__option"></li>'
777 883 );
778 884
779 885 var message = this.options.get('translations').get(params.message);
780 886
@@ -783,11 +889,17 @@
783 889 message(params.args)
784 890 )
785 891 );
786 892
893 + $message[0].className += ' select2-results__message';
894 +
787 895 this.$results.append($message);
788 896 };
789 897
898 + Results.prototype.hideMessages = function () {
899 + this.$results.find('.select2-results__message').remove();
900 + };
901 +
790 902 Results.prototype.append = function (data) {
791 903 this.hideLoading();
792 904
793 905 var $options = [];
@@ -825,8 +937,27 @@
825 937
826 938 return sorter(data);
827 939 };
828 940
941 + Results.prototype.highlightFirstItem = function () {
942 + var $options = this.$results
943 + .find('.select2-results__option[aria-selected]');
944 +
945 + var $selected = $options.filter('[aria-selected=true]');
946 +
947 + // Check if there are any selected options
948 + if ($selected.length > 0) {
949 + // If there are selected options, highlight the first
950 + $selected.first().trigger('mouseenter');
951 + } else {
952 + // If there are no selected options, highlight the first option
953 + // in the dropdown
954 + $options.first().trigger('mouseenter');
955 + }
956 +
957 + this.ensureHighlightVisible();
958 + };
959 +
829 960 Results.prototype.setClasses = function () {
830 961 var self = this;
831 962
832 963 this.data.current(function (selected) {
@@ -839,9 +970,9 @@
839 970
840 971 $options.each(function () {
841 972 var $option = $(this);
842 973
843 - var item = $.data(this, 'data');
974 + var item = Utils.GetData(this, 'data');
844 975
845 976 // id needs to be converted to a string when comparing
846 977 var id = '' + item.id;
847 978
@@ -852,19 +983,8 @@
852 983 $option.attr('aria-selected', 'false');
853 984 }
854 985 });
855 986
856 - var $selected = $options.filter('[aria-selected=true]');
857 -
858 - // Check if there are any selected options
859 - if ($selected.length > 0) {
860 - // If there are selected options, highlight the first
861 - $selected.first().trigger('mouseenter');
862 - } else {
863 - // If there are no selected options, highlight the first option
864 - // in the dropdown
865 - $options.first().trigger('mouseenter');
866 - }
867 987 });
868 988 };
869 989
870 990 Results.prototype.showLoading = function (params) {
@@ -891,13 +1011,18 @@
891 1011 var option = document.createElement('li');
892 1012 option.className = 'select2-results__option';
893 1013
894 1014 var attrs = {
895 - 'role': 'treeitem',
1015 + 'role': 'option',
896 1016 'aria-selected': 'false'
897 1017 };
898 1018
899 - if (data.disabled) {
1019 + var matches = window.Element.prototype.matches ||
1020 + window.Element.prototype.msMatchesSelector ||
1021 + window.Element.prototype.webkitMatchesSelector;
1022 +
1023 + if ((data.element != null && matches.call(data.element, ':disabled')) ||
1024 + (data.element == null && data.disabled)) {
900 1025 delete attrs['aria-selected'];
901 1026 attrs['aria-disabled'] = 'true';
902 1027 }
903 1028
@@ -955,9 +1080,9 @@
955 1080 } else {
956 1081 this.template(data, option);
957 1082 }
958 1083
959 - $.data(option, 'data', data);
1084 + Utils.StoreData(option, 'data', data);
960 1085
961 1086 return option;
962 1087 };
963 1088
@@ -973,8 +1098,9 @@
973 1098 self.append(params.data);
974 1099
975 1100 if (container.isOpen()) {
976 1101 self.setClasses();
1102 + self.highlightFirstItem();
977 1103 }
978 1104 });
979 1105
980 1106 container.on('results:append', function (params) {
@@ -985,8 +1111,9 @@
985 1111 }
986 1112 });
987 1113
988 1114 container.on('query', function (params) {
1115 + self.hideMessages();
989 1116 self.showLoading(params);
990 1117 });
991 1118
992 1119 container.on('select', function () {
@@ -994,8 +1121,12 @@
994 1121 return;
995 1122 }
996 1123
997 1124 self.setClasses();
1125 +
1126 + if (self.options.get('scrollAfterSelect')) {
1127 + self.highlightFirstItem();
1128 + }
998 1129 });
999 1130
1000 1131 container.on('unselect', function () {
1001 1132 if (!container.isOpen()) {
@@ -1002,8 +1133,12 @@
1002 1133 return;
1003 1134 }
1004 1135
1005 1136 self.setClasses();
1137 +
1138 + if (self.options.get('scrollAfterSelect')) {
1139 + self.highlightFirstItem();
1140 + }
1006 1141 });
1007 1142
1008 1143 container.on('open', function () {
1009 1144 // When the dropdown is open, aria-expended="true"
@@ -1037,12 +1172,12 @@
1037 1172 if ($highlighted.length === 0) {
1038 1173 return;
1039 1174 }
1040 1175
1041 - var data = $highlighted.data('data');
1176 + var data = Utils.GetData($highlighted[0], 'data');
1042 1177
1043 1178 if ($highlighted.attr('aria-selected') == 'true') {
1044 - self.trigger('close');
1179 + self.trigger('close', {});
1045 1180 } else {
1046 1181 self.trigger('select', {
1047 1182 data: data
1048 1183 });
@@ -1055,10 +1190,11 @@
1055 1190 var $options = self.$results.find('[aria-selected]');
1056 1191
1057 1192 var currentIndex = $options.index($highlighted);
1058 1193
1059 - // If we are already at te top, don't move further
1060 - if (currentIndex === 0) {
1194 + // If we are already at the top, don't move further
1195 + // If no options, currentIndex will be -1
1196 + if (currentIndex <= 0) {
1061 1197 return;
1062 1198 }
1063 1199
1064 1200 var nextIndex = currentIndex - 1;
@@ -1124,13 +1260,9 @@
1124 1260 if ($.fn.mousewheel) {
1125 1261 this.$results.on('mousewheel', function (e) {
1126 1262 var top = self.$results.scrollTop();
1127 1263
1128 - var bottom = (
1129 - self.$results.get(0).scrollHeight -
1130 - self.$results.scrollTop() +
1131 - e.deltaY
1132 - );
1264 + var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
1133 1265
1134 1266 var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
1135 1267 var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
1136 1268
@@ -1153,9 +1285,9 @@
1153 1285 this.$results.on('mouseup', '.select2-results__option[aria-selected]',
1154 1286 function (evt) {
1155 1287 var $this = $(this);
1156 1288
1157 - var data = $this.data('data');
1289 + var data = Utils.GetData(this, 'data');
1158 1290
1159 1291 if ($this.attr('aria-selected') === 'true') {
1160 1292 if (self.options.get('multiple')) {
1161 1293 self.trigger('unselect', {
@@ -1162,9 +1294,9 @@
1162 1294 originalEvent: evt,
1163 1295 data: data
1164 1296 });
1165 1297 } else {
1166 - self.trigger('close');
1298 + self.trigger('close', {});
1167 1299 }
1168 1300
1169 1301 return;
1170 1302 }
@@ -1176,9 +1308,9 @@
1176 1308 });
1177 1309
1178 1310 this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
1179 1311 function (evt) {
1180 - var data = $(this).data('data');
1312 + var data = Utils.GetData(this, 'data');
1181 1313
1182 1314 self.getHighlightedResults()
1183 1315 .removeClass('select2-results__option--highlighted');
1184 1316
@@ -1228,9 +1360,9 @@
1228 1360 Results.prototype.template = function (result, container) {
1229 1361 var template = this.options.get('templateResult');
1230 1362 var escapeMarkup = this.options.get('escapeMarkup');
1231 1363
1232 - var content = template(result);
1364 + var content = template(result, container);
1233 1365
1234 1366 if (content == null) {
1235 1367 container.style.display = 'none';
1236 1368 } else if (typeof content === 'string') {
@@ -1285,16 +1417,16 @@
1285 1417
1286 1418 BaseSelection.prototype.render = function () {
1287 1419 var $selection = $(
1288 1420 '<span class="select2-selection" role="combobox" ' +
1289 - 'aria-autocomplete="list" aria-haspopup="true" aria-expanded="false">' +
1421 + ' aria-haspopup="true" aria-expanded="false">' +
1290 1422 '</span>'
1291 1423 );
1292 1424
1293 1425 this._tabindex = 0;
1294 1426
1295 - if (this.$element.data('old-tabindex') != null) {
1296 - this._tabindex = this.$element.data('old-tabindex');
1427 + if (Utils.GetData(this.$element[0], 'old-tabindex') != null) {
1428 + this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex');
1297 1429 } else if (this.$element.attr('tabindex') != null) {
1298 1430 this._tabindex = this.$element.attr('tabindex');
1299 1431 }
1300 1432
@@ -1299,8 +1431,9 @@
1299 1431 }
1300 1432
1301 1433 $selection.attr('title', this.$element.attr('title'));
1302 1434 $selection.attr('tabindex', this._tabindex);
1435 + $selection.attr('aria-disabled', 'false');
1303 1436
1304 1437 this.$selection = $selection;
1305 1438
1306 1439 return $selection;
@@ -1308,9 +1441,8 @@
1308 1441
1309 1442 BaseSelection.prototype.bind = function (container, $container) {
1310 1443 var self = this;
1311 1444
1312 - var id = container.id + '-container';
1313 1445 var resultsId = container.id + '-results';
1314 1446
1315 1447 this.container = container;
1316 1448
@@ -1318,9 +1450,9 @@
1318 1450 self.trigger('focus', evt);
1319 1451 });
1320 1452
1321 1453 this.$selection.on('blur', function (evt) {
1322 - self.trigger('blur', evt);
1454 + self._handleBlur(evt);
1323 1455 });
1324 1456
1325 1457 this.$selection.on('keydown', function (evt) {
1326 1458 self.trigger('keypress', evt);
@@ -1351,9 +1483,9 @@
1351 1483 self.$selection.attr('aria-expanded', 'false');
1352 1484 self.$selection.removeAttr('aria-activedescendant');
1353 1485 self.$selection.removeAttr('aria-owns');
1354 1486
1355 - self.$selection.focus();
1487 + self.$selection.trigger('focus');
1356 1488
1357 1489 self._detachCloseHandler(container);
1358 1490 });
1359 1491
@@ -1358,17 +1490,36 @@
1358 1490 });
1359 1491
1360 1492 container.on('enable', function () {
1361 1493 self.$selection.attr('tabindex', self._tabindex);
1494 + self.$selection.attr('aria-disabled', 'false');
1362 1495 });
1363 1496
1364 1497 container.on('disable', function () {
1365 1498 self.$selection.attr('tabindex', '-1');
1499 + self.$selection.attr('aria-disabled', 'true');
1366 1500 });
1367 1501 };
1368 1502
1503 + BaseSelection.prototype._handleBlur = function (evt) {
1504 + var self = this;
1505 +
1506 + // This needs to be delayed as the active element is the body when the tab
1507 + // key is pressed, possibly along with others.
1508 + window.setTimeout(function () {
1509 + // Don't trigger `blur` if the focus is still in the selection
1510 + if (
1511 + (document.activeElement == self.$selection[0]) ||
1512 + ($.contains(self.$selection[0], document.activeElement))
1513 + ) {
1514 + return;
1515 + }
1516 +
1517 + self.trigger('blur', evt);
1518 + }, 1);
1519 + };
1520 +
1369 1521 BaseSelection.prototype._attachCloseHandler = function (container) {
1370 - var self = this;
1371 1522
1372 1523 $(document.body).on('mousedown.select2.' + container.id, function (e) {
1373 1524 var $target = $(e.target);
1374 1525
@@ -1376,15 +1527,13 @@
1376 1527
1377 1528 var $all = $('.select2.select2-container--open');
1378 1529
1379 1530 $all.each(function () {
1380 - var $this = $(this);
1381 -
1382 1531 if (this == $select[0]) {
1383 1532 return;
1384 1533 }
1385 1534
1386 - var $element = $this.data('element');
1535 + var $element = Utils.GetData(this, 'element');
1387 1536
1388 1537 $element.select2('close');
1389 1538 });
1390 1539 });
@@ -1443,9 +1592,12 @@
1443 1592 SingleSelection.__super__.bind.apply(this, arguments);
1444 1593
1445 1594 var id = container.id + '-container';
1446 1595
1447 - this.$selection.find('.select2-selection__rendered').attr('id', id);
1596 + this.$selection.find('.select2-selection__rendered')
1597 + .attr('id', id)
1598 + .attr('role', 'textbox')
1599 + .attr('aria-readonly', 'true');
1448 1600 this.$selection.attr('aria-labelledby', id);
1449 1601
1450 1602 this.$selection.on('mousedown', function (evt) {
1451 1603 // Only respond to left clicks
@@ -1465,22 +1617,26 @@
1465 1617 this.$selection.on('blur', function (evt) {
1466 1618 // User exits the container
1467 1619 });
1468 1620
1469 - container.on('selection:update', function (params) {
1470 - self.update(params.data);
1621 + container.on('focus', function (evt) {
1622 + if (!container.isOpen()) {
1623 + self.$selection.trigger('focus');
1624 + }
1471 1625 });
1472 1626 };
1473 1627
1474 1628 SingleSelection.prototype.clear = function () {
1475 - this.$selection.find('.select2-selection__rendered').empty();
1629 + var $rendered = this.$selection.find('.select2-selection__rendered');
1630 + $rendered.empty();
1631 + $rendered.removeAttr('title'); // clear tooltip on empty
1476 1632 };
1477 1633
1478 - SingleSelection.prototype.display = function (data) {
1634 + SingleSelection.prototype.display = function (data, container) {
1479 1635 var template = this.options.get('templateSelection');
1480 1636 var escapeMarkup = this.options.get('escapeMarkup');
1481 1637
1482 - return escapeMarkup(template(data));
1638 + return escapeMarkup(template(data, container));
1483 1639 };
1484 1640
1485 1641 SingleSelection.prototype.selectionContainer = function () {
1486 1642 return $('<span></span>');
@@ -1493,13 +1649,20 @@
1493 1649 }
1494 1650
1495 1651 var selection = data[0];
1496 1652
1497 - var formatted = this.display(selection);
1653 + var $rendered = this.$selection.find('.select2-selection__rendered');
1654 + var formatted = this.display(selection, $rendered);
1498 1655
1499 - var $rendered = this.$selection.find('.select2-selection__rendered');
1500 1656 $rendered.empty().append(formatted);
1501 - $rendered.prop('title', selection.title || selection.text);
1657 +
1658 + var title = selection.title || selection.text;
1659 +
1660 + if (title) {
1661 + $rendered.attr('title', title);
1662 + } else {
1663 + $rendered.removeAttr('title');
1664 + }
1502 1665 };
1503 1666
1504 1667 return SingleSelection;
1505 1668 });
@@ -1537,31 +1700,41 @@
1537 1700 originalEvent: evt
1538 1701 });
1539 1702 });
1540 1703
1541 - this.$selection.on('click', '.select2-selection__choice__remove',
1704 + this.$selection.on(
1705 + 'click',
1706 + '.select2-selection__choice__remove',
1542 1707 function (evt) {
1543 - var $remove = $(this);
1544 - var $selection = $remove.parent();
1708 + // Ignore the event if it is disabled
1709 + if (self.options.get('disabled')) {
1710 + return;
1711 + }
1545 1712
1546 - var data = $selection.data('data');
1713 + var $remove = $(this);
1714 + var $selection = $remove.parent();
1547 1715
1548 - self.trigger('unselect', {
1549 - originalEvent: evt,
1550 - data: data
1551 - });
1552 - });
1716 + var data = Utils.GetData($selection[0], 'data');
1717 +
1718 + self.trigger('unselect', {
1719 + originalEvent: evt,
1720 + data: data
1721 + });
1722 + }
1723 + );
1553 1724 };
1554 1725
1555 1726 MultipleSelection.prototype.clear = function () {
1556 - this.$selection.find('.select2-selection__rendered').empty();
1727 + var $rendered = this.$selection.find('.select2-selection__rendered');
1728 + $rendered.empty();
1729 + $rendered.removeAttr('title');
1557 1730 };
1558 1731
1559 - MultipleSelection.prototype.display = function (data) {
1732 + MultipleSelection.prototype.display = function (data, container) {
1560 1733 var template = this.options.get('templateSelection');
1561 1734 var escapeMarkup = this.options.get('escapeMarkup');
1562 1735
1563 - return escapeMarkup(template(data));
1736 + return escapeMarkup(template(data, container));
1564 1737 };
1565 1738
1566 1739 MultipleSelection.prototype.selectionContainer = function () {
1567 1740 var $container = $(
@@ -1586,16 +1759,21 @@
1586 1759
1587 1760 for (var d = 0; d < data.length; d++) {
1588 1761 var selection = data[d];
1589 1762
1590 - var formatted = this.display(selection);
1591 1763 var $selection = this.selectionContainer();
1764 + var formatted = this.display(selection, $selection);
1592 1765
1593 1766 $selection.append(formatted);
1594 - $selection.prop('title', selection.title || selection.text);
1595 1767
1596 - $selection.data('data', selection);
1768 + var title = selection.title || selection.text;
1597 1769
1770 + if (title) {
1771 + $selection.attr('title', title);
1772 + }
1773 +
1774 + Utils.StoreData($selection[0], 'data', selection);
1775 +
1598 1776 $selections.push($selection);
1599 1777 }
1600 1778
1601 1779 var $rendered = this.$selection.find('.select2-selection__rendered');
@@ -1657,10 +1835,11 @@
1657 1835 });
1658 1836
1659 1837 S2.define('select2/selection/allowClear',[
1660 1838 'jquery',
1661 - '../keys'
1662 -], function ($, KEYS) {
1839 + '../keys',
1840 + '../utils'
1841 +], function ($, KEYS, Utils) {
1663 1842 function AllowClear () { }
1664 1843
1665 1844 AllowClear.prototype.bind = function (decorated, container, $container) {
1666 1845 var self = this;
@@ -1700,12 +1879,24 @@
1700 1879 }
1701 1880
1702 1881 evt.stopPropagation();
1703 1882
1704 - var data = $clear.data('data');
1883 + var data = Utils.GetData($clear[0], 'data');
1705 1884
1885 + var previousVal = this.$element.val();
1886 + this.$element.val(this.placeholder.id);
1887 +
1888 + var unselectData = {
1889 + data: data
1890 + };
1891 + this.trigger('clear', unselectData);
1892 + if (unselectData.prevented) {
1893 + this.$element.val(previousVal);
1894 + return;
1895 + }
1896 +
1706 1897 for (var d = 0; d < data.length; d++) {
1707 - var unselectData = {
1898 + unselectData = {
1708 1899 data: data[d]
1709 1900 };
1710 1901
1711 1902 // Trigger the `unselect` event, so people can prevent it from being
@@ -1713,15 +1904,16 @@
1713 1904 this.trigger('unselect', unselectData);
1714 1905
1715 1906 // If the event was prevented, don't clear it out.
1716 1907 if (unselectData.prevented) {
1908 + this.$element.val(previousVal);
1717 1909 return;
1718 1910 }
1719 1911 }
1720 1912
1721 - this.$element.val(this.placeholder.id).trigger('change');
1913 + this.$element.trigger('change');
1722 1914
1723 - this.trigger('toggle');
1915 + this.trigger('toggle', {});
1724 1916 };
1725 1917
1726 1918 AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
1727 1919 if (container.isOpen()) {
@@ -1740,14 +1932,16 @@
1740 1932 data.length === 0) {
1741 1933 return;
1742 1934 }
1743 1935
1936 + var removeAll = this.options.get('translations').get('removeAllItems');
1937 +
1744 1938 var $remove = $(
1745 - '<span class="select2-selection__clear">' +
1939 + '<span class="select2-selection__clear" title="' + removeAll() +'">' +
1746 1940 '&times;' +
1747 1941 '</span>'
1748 1942 );
1749 - $remove.data('data', data);
1943 + Utils.StoreData($remove[0], 'data', data);
1750 1944
1751 1945 this.$selection.find('.select2-selection__rendered').prepend($remove);
1752 1946 };
1753 1947
@@ -1766,10 +1960,10 @@
1766 1960 Search.prototype.render = function (decorated) {
1767 1961 var $search = $(
1768 1962 '<li class="select2-search select2-search--inline">' +
1769 1963 '<input class="select2-search__field" type="search" tabindex="-1"' +
1770 - ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
1771 - ' spellcheck="false" role="textbox" />' +
1964 + ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
1965 + ' spellcheck="false" role="searchbox" aria-autocomplete="list" />' +
1772 1966 '</li>'
1773 1967 );
1774 1968
1775 1969 this.$searchContainer = $search;
@@ -1776,8 +1970,10 @@
1776 1970 this.$search = $search.find('input');
1777 1971
1778 1972 var $rendered = decorated.call(this);
1779 1973
1974 + this._transferTabIndex();
1975 +
1780 1976 return $rendered;
1781 1977 };
1782 1978
1783 1979 Search.prototype.bind = function (decorated, container, $container) {
@@ -1782,25 +1978,28 @@
1782 1978
1783 1979 Search.prototype.bind = function (decorated, container, $container) {
1784 1980 var self = this;
1785 1981
1982 + var resultsId = container.id + '-results';
1983 +
1786 1984 decorated.call(this, container, $container);
1787 1985
1788 1986 container.on('open', function () {
1789 - self.$search.attr('tabindex', 0);
1790 -
1791 - self.$search.focus();
1987 + self.$search.attr('aria-controls', resultsId);
1988 + self.$search.trigger('focus');
1792 1989 });
1793 1990
1794 1991 container.on('close', function () {
1795 - self.$search.attr('tabindex', -1);
1796 -
1797 1992 self.$search.val('');
1798 - self.$search.focus();
1993 + self.$search.removeAttr('aria-controls');
1994 + self.$search.removeAttr('aria-activedescendant');
1995 + self.$search.trigger('focus');
1799 1996 });
1800 1997
1801 1998 container.on('enable', function () {
1802 1999 self.$search.prop('disabled', false);
2000 +
2001 + self._transferTabIndex();
1803 2002 });
1804 2003
1805 2004 container.on('disable', function () {
1806 2005 self.$search.prop('disabled', true);
@@ -1805,14 +2004,26 @@
1805 2004 container.on('disable', function () {
1806 2005 self.$search.prop('disabled', true);
1807 2006 });
1808 2007
2008 + container.on('focus', function (evt) {
2009 + self.$search.trigger('focus');
2010 + });
2011 +
2012 + container.on('results:focus', function (params) {
2013 + if (params.data._resultId) {
2014 + self.$search.attr('aria-activedescendant', params.data._resultId);
2015 + } else {
2016 + self.$search.removeAttr('aria-activedescendant');
2017 + }
2018 + });
2019 +
1809 2020 this.$selection.on('focusin', '.select2-search--inline', function (evt) {
1810 2021 self.trigger('focus', evt);
1811 2022 });
1812 2023
1813 2024 this.$selection.on('focusout', '.select2-search--inline', function (evt) {
1814 - self.trigger('blur', evt);
2025 + self._handleBlur(evt);
1815 2026 });
1816 2027
1817 2028 this.$selection.on('keydown', '.select2-search--inline', function (evt) {
1818 2029 evt.stopPropagation();
@@ -1827,9 +2038,9 @@
1827 2038 var $previousChoice = self.$searchContainer
1828 2039 .prev('.select2-selection__choice');
1829 2040
1830 2041 if ($previousChoice.length > 0) {
1831 - var item = $previousChoice.data('data');
2042 + var item = Utils.GetData($previousChoice[0], 'data');
1832 2043
1833 2044 self.searchRemoveChoice(item);
1834 2045
1835 2046 evt.preventDefault();
@@ -1836,27 +2047,90 @@
1836 2047 }
1837 2048 }
1838 2049 });
1839 2050
2051 + this.$selection.on('click', '.select2-search--inline', function (evt) {
2052 + if (self.$search.val()) {
2053 + evt.stopPropagation();
2054 + }
2055 + });
2056 +
2057 + // Try to detect the IE version should the `documentMode` property that
2058 + // is stored on the document. This is only implemented in IE and is
2059 + // slightly cleaner than doing a user agent check.
2060 + // This property is not available in Edge, but Edge also doesn't have
2061 + // this bug.
2062 + var msie = document.documentMode;
2063 + var disableInputEvents = msie && msie <= 11;
2064 +
1840 2065 // Workaround for browsers which do not support the `input` event
1841 2066 // This will prevent double-triggering of events for browsers which support
1842 2067 // both the `keyup` and `input` events.
1843 - this.$selection.on('input', '.select2-search--inline', function (evt) {
1844 - // Unbind the duplicated `keyup` event
1845 - self.$selection.off('keyup.search');
1846 - });
2068 + this.$selection.on(
2069 + 'input.searchcheck',
2070 + '.select2-search--inline',
2071 + function (evt) {
2072 + // IE will trigger the `input` event when a placeholder is used on a
2073 + // search box. To get around this issue, we are forced to ignore all
2074 + // `input` events in IE and keep using `keyup`.
2075 + if (disableInputEvents) {
2076 + self.$selection.off('input.search input.searchcheck');
2077 + return;
2078 + }
1847 2079
1848 - this.$selection.on('keyup.search input', '.select2-search--inline',
1849 - function (evt) {
1850 - self.handleSearch(evt);
1851 - });
2080 + // Unbind the duplicated `keyup` event
2081 + self.$selection.off('keyup.search');
2082 + }
2083 + );
2084 +
2085 + this.$selection.on(
2086 + 'keyup.search input.search',
2087 + '.select2-search--inline',
2088 + function (evt) {
2089 + // IE will trigger the `input` event when a placeholder is used on a
2090 + // search box. To get around this issue, we are forced to ignore all
2091 + // `input` events in IE and keep using `keyup`.
2092 + if (disableInputEvents && evt.type === 'input') {
2093 + self.$selection.off('input.search input.searchcheck');
2094 + return;
2095 + }
2096 +
2097 + var key = evt.which;
2098 +
2099 + // We can freely ignore events from modifier keys
2100 + if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
2101 + return;
2102 + }
2103 +
2104 + // Tabbing will be handled during the `keydown` phase
2105 + if (key == KEYS.TAB) {
2106 + return;
2107 + }
2108 +
2109 + self.handleSearch(evt);
2110 + }
2111 + );
1852 2112 };
1853 2113
2114 + /**
2115 + * This method will transfer the tabindex attribute from the rendered
2116 + * selection to the search box. This allows for the search box to be used as
2117 + * the primary focus instead of the selection container.
2118 + *
2119 + * @private
2120 + */
2121 + Search.prototype._transferTabIndex = function (decorated) {
2122 + this.$search.attr('tabindex', this.$selection.attr('tabindex'));
2123 + this.$selection.attr('tabindex', '-1');
2124 + };
2125 +
1854 2126 Search.prototype.createPlaceholder = function (decorated, placeholder) {
1855 2127 this.$search.attr('placeholder', placeholder.text);
1856 2128 };
1857 2129
1858 2130 Search.prototype.update = function (decorated, data) {
2131 + var searchHadFocus = this.$search[0] == document.activeElement;
2132 +
1859 2133 this.$search.attr('placeholder', '');
1860 2134
1861 2135 decorated.call(this, data);
1862 2136
@@ -1863,8 +2137,11 @@
1863 2137 this.$selection.find('.select2-selection__rendered')
1864 2138 .append(this.$searchContainer);
1865 2139
1866 2140 this.resizeSearch();
2141 + if (searchHadFocus) {
2142 + this.$search.trigger('focus');
2143 + }
1867 2144 };
1868 2145
1869 2146 Search.prototype.handleSearch = function () {
1870 2147 this.resizeSearch();
@@ -1884,11 +2161,10 @@
1884 2161 this.trigger('unselect', {
1885 2162 data: item
1886 2163 });
1887 2164
1888 - this.trigger('open');
1889 -
1890 - this.$search.val(item.text + ' ');
2165 + this.$search.val(item.text);
2166 + this.handleSearch();
1891 2167 };
1892 2168
1893 2169 Search.prototype.resizeSearch = function () {
1894 2170 this.$search.css('width', '25px');
@@ -1895,9 +2171,9 @@
1895 2171
1896 2172 var width = '';
1897 2173
1898 2174 if (this.$search.attr('placeholder') !== '') {
1899 - width = this.$selection.find('.select2-selection__rendered').innerWidth();
2175 + width = this.$selection.find('.select2-selection__rendered').width();
1900 2176 } else {
1901 2177 var minimumWidth = this.$search.val().length + 1;
1902 2178
1903 2179 width = (minimumWidth * 0.75) + 'em';
@@ -1919,12 +2195,15 @@
1919 2195 var relayEvents = [
1920 2196 'open', 'opening',
1921 2197 'close', 'closing',
1922 2198 'select', 'selecting',
1923 - 'unselect', 'unselecting'
2199 + 'unselect', 'unselecting',
2200 + 'clear', 'clearing'
1924 2201 ];
1925 2202
1926 - var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
2203 + var preventableEvents = [
2204 + 'opening', 'closing', 'selecting', 'unselecting', 'clearing'
2205 + ];
1927 2206
1928 2207 decorated.call(this, container, $container);
1929 2208
1930 2209 container.on('*', function (name, params) {
@@ -2255,8 +2534,9 @@
2255 2534 '\u0186': 'O',
2256 2535 '\u019F': 'O',
2257 2536 '\uA74A': 'O',
2258 2537 '\uA74C': 'O',
2538 + '\u0152': 'OE',
2259 2539 '\u01A2': 'OI',
2260 2540 '\uA74E': 'OO',
2261 2541 '\u0222': 'OU',
2262 2542 '\u24C5': 'P',
@@ -2664,8 +2944,9 @@
2664 2944 '\u0254': 'o',
2665 2945 '\uA74B': 'o',
2666 2946 '\uA74D': 'o',
2667 2947 '\u0275': 'o',
2948 + '\u0153': 'oe',
2668 2949 '\u01A3': 'oi',
2669 2950 '\u0223': 'ou',
2670 2951 '\uA74F': 'oo',
2671 2952 '\u24DF': 'p',
@@ -2832,10 +3113,11 @@
2832 3113 '\u03CC': '\u03BF',
2833 3114 '\u03CD': '\u03C5',
2834 3115 '\u03CB': '\u03C5',
2835 3116 '\u03B0': '\u03C5',
2836 - '\u03C9': '\u03C9',
2837 - '\u03C2': '\u03C3'
3117 + '\u03CE': '\u03C9',
3118 + '\u03C2': '\u03C3',
3119 + '\u2019': '\''
2838 3120 };
2839 3121
2840 3122 return diacritics;
2841 3123 });
@@ -3001,9 +3283,9 @@
3001 3283 SelectAdapter.prototype.destroy = function () {
3002 3284 // Remove anything added to child elements
3003 3285 this.$element.find('*').each(function () {
3004 3286 // Remove any custom data set by Select2
3005 - $.removeData(this, 'data');
3287 + Utils.RemoveData(this);
3006 3288 });
3007 3289 };
3008 3290
3009 3291 SelectAdapter.prototype.query = function (params, callback) {
@@ -3052,9 +3334,9 @@
3052 3334 option.innerText = data.text;
3053 3335 }
3054 3336 }
3055 3337
3056 - if (data.id) {
3338 + if (data.id !== undefined) {
3057 3339 option.value = data.id;
3058 3340 }
3059 3341
3060 3342 if (data.disabled) {
@@ -3074,9 +3356,9 @@
3074 3356 var normalizedData = this._normalizeItem(data);
3075 3357 normalizedData.element = option;
3076 3358
3077 3359 // Override the option's data with the combined data
3078 - $.data(option, 'data', normalizedData);
3360 + Utils.StoreData(option, 'data', normalizedData);
3079 3361
3080 3362 return $option;
3081 3363 };
3082 3364
@@ -3082,9 +3364,9 @@
3082 3364
3083 3365 SelectAdapter.prototype.item = function ($option) {
3084 3366 var data = {};
3085 3367
3086 - data = $.data($option[0], 'data');
3368 + data = Utils.GetData($option[0], 'data');
3087 3369
3088 3370 if (data != null) {
3089 3371 return data;
3090 3372 }
@@ -3120,15 +3402,15 @@
3120 3402
3121 3403 data = this._normalizeItem(data);
3122 3404 data.element = $option[0];
3123 3405
3124 - $.data($option[0], 'data', data);
3406 + Utils.StoreData($option[0], 'data', data);
3125 3407
3126 3408 return data;
3127 3409 };
3128 3410
3129 3411 SelectAdapter.prototype._normalizeItem = function (item) {
3130 - if (!$.isPlainObject(item)) {
3412 + if (item !== Object(item)) {
3131 3413 item = {
3132 3414 id: item,
3133 3415 text: item
3134 3416 };
@@ -3172,17 +3454,21 @@
3172 3454 '../utils',
3173 3455 'jquery'
3174 3456 ], function (SelectAdapter, Utils, $) {
3175 3457 function ArrayAdapter ($element, options) {
3176 - var data = options.get('data') || [];
3458 + this._dataToConvert = options.get('data') || [];
3177 3459
3178 3460 ArrayAdapter.__super__.constructor.call(this, $element, options);
3179 -
3180 - this.addOptions(this.convertToOptions(data));
3181 3461 }
3182 3462
3183 3463 Utils.Extend(ArrayAdapter, SelectAdapter);
3184 3464
3465 + ArrayAdapter.prototype.bind = function (container, $container) {
3466 + ArrayAdapter.__super__.bind.call(this, container, $container);
3467 +
3468 + this.addOptions(this.convertToOptions(this._dataToConvert));
3469 + };
3470 +
3185 3471 ArrayAdapter.prototype.select = function (data) {
3186 3472 var $option = this.$element.find('option').filter(function (i, elm) {
3187 3473 return elm.value == data.id.toString();
3188 3474 });
@@ -3220,11 +3506,11 @@
3220 3506 if ($.inArray(item.id, existingIds) >= 0) {
3221 3507 var $existingOption = $existing.filter(onlyItem(item));
3222 3508
3223 3509 var existingData = this.item($existingOption);
3224 - var newData = $.extend(true, {}, existingData, item);
3510 + var newData = $.extend(true, {}, item, existingData);
3225 3511
3226 - var $newOption = this.option(existingData);
3512 + var $newOption = this.option(newData);
3227 3513
3228 3514 $existingOption.replaceWith($newOption);
3229 3515
3230 3516 continue;
@@ -3258,9 +3544,9 @@
3258 3544 if (this.ajaxOptions.processResults != null) {
3259 3545 this.processResults = this.ajaxOptions.processResults;
3260 3546 }
3261 3547
3262 - ArrayAdapter.__super__.constructor.call(this, $element, options);
3548 + AjaxAdapter.__super__.constructor.call(this, $element, options);
3263 3549 }
3264 3550
3265 3551 Utils.Extend(AjaxAdapter, ArrayAdapter);
3266 3552
@@ -3266,11 +3552,11 @@
3266 3552
3267 3553 AjaxAdapter.prototype._applyDefaults = function (options) {
3268 3554 var defaults = {
3269 3555 data: function (params) {
3270 - return {
3556 + return $.extend({}, params, {
3271 3557 q: params.term
3272 - };
3558 + });
3273 3559 },
3274 3560 transport: function (params, success, failure) {
3275 3561 var $request = $.ajax(params);
3276 3562
@@ -3305,13 +3591,13 @@
3305 3591 type: 'GET'
3306 3592 }, this.ajaxOptions);
3307 3593
3308 3594 if (typeof options.url === 'function') {
3309 - options.url = options.url(params);
3595 + options.url = options.url.call(this.$element, params);
3310 3596 }
3311 3597
3312 3598 if (typeof options.data === 'function') {
3313 - options.data = options.data(params);
3599 + options.data = options.data.call(this.$element, params);
3314 3600 }
3315 3601
3316 3602 function request () {
3317 3603 var $request = options.transport(options, function (data) {
@@ -3328,15 +3614,24 @@
3328 3614 }
3329 3615
3330 3616 callback(results);
3331 3617 }, function () {
3332 - // TODO: Handle AJAX errors
3618 + // Attempt to detect if a request was aborted
3619 + // Only works if the transport exposes a status property
3620 + if ('status' in $request &&
3621 + ($request.status === 0 || $request.status === '0')) {
3622 + return;
3623 + }
3624 +
3625 + self.trigger('results:message', {
3626 + message: 'errorLoading'
3627 + });
3333 3628 });
3334 3629
3335 3630 self._request = $request;
3336 3631 }
3337 3632
3338 - if (this.ajaxOptions.delay && params.term !== '') {
3633 + if (this.ajaxOptions.delay && params.term != null) {
3339 3634 if (this._queryTimeout) {
3340 3635 window.clearTimeout(this._queryTimeout);
3341 3636 }
3342 3637
@@ -3360,8 +3655,14 @@
3360 3655 if (createTag !== undefined) {
3361 3656 this.createTag = createTag;
3362 3657 }
3363 3658
3659 + var insertTag = options.get('insertTag');
3660 +
3661 + if (insertTag !== undefined) {
3662 + this.insertTag = insertTag;
3663 + }
3664 +
3364 3665 decorated.call(this, $element, options);
3365 3666
3366 3667 if ($.isArray(tags)) {
3367 3668 for (var t = 0; t < tags.length; t++) {
@@ -3397,10 +3698,13 @@
3397 3698 results: option.children
3398 3699 }, true)
3399 3700 );
3400 3701
3401 - var checkText = option.text === params.term;
3702 + var optionText = (option.text || '').toUpperCase();
3703 + var paramsTerm = (params.term || '').toUpperCase();
3402 3704
3705 + var checkText = optionText === paramsTerm;
3706 +
3403 3707 if (checkText || checkChildren) {
3404 3708 if (child) {
3405 3709 return false;
3406 3710 }
@@ -3452,10 +3756,8 @@
3452 3756 data.unshift(tag);
3453 3757 };
3454 3758
3455 3759 Tags.prototype._removeOldTags = function (_) {
3456 - var tag = this._lastTag;
3457 -
3458 3760 var $options = this.$element.find('option[data-select2-tag]');
3459 3761
3460 3762 $options.each(function () {
3461 3763 if (this.selected) {
@@ -3491,21 +3793,46 @@
3491 3793
3492 3794 Tokenizer.prototype.query = function (decorated, params, callback) {
3493 3795 var self = this;
3494 3796
3797 + function createAndSelect (data) {
3798 + // Normalize the data object so we can use it for checks
3799 + var item = self._normalizeItem(data);
3800 +
3801 + // Check if the data object already exists as a tag
3802 + // Select it if it doesn't
3803 + var $existingOptions = self.$element.find('option').filter(function () {
3804 + return $(this).val() === item.id;
3805 + });
3806 +
3807 + // If an existing option wasn't found for it, create the option
3808 + if (!$existingOptions.length) {
3809 + var $option = self.option(item);
3810 + $option.attr('data-select2-tag', true);
3811 +
3812 + self._removeOldTags();
3813 + self.addOptions([$option]);
3814 + }
3815 +
3816 + // Select the item, now that we know there is an option for it
3817 + select(item);
3818 + }
3819 +
3495 3820 function select (data) {
3496 - self.select(data);
3821 + self.trigger('select', {
3822 + data: data
3823 + });
3497 3824 }
3498 3825
3499 3826 params.term = params.term || '';
3500 3827
3501 - var tokenData = this.tokenizer(params, this.options, select);
3828 + var tokenData = this.tokenizer(params, this.options, createAndSelect);
3502 3829
3503 3830 if (tokenData.term !== params.term) {
3504 3831 // Replace the search term if we have the search box
3505 3832 if (this.$search.length) {
3506 3833 this.$search.val(tokenData.term);
3507 - this.$search.focus();
3834 + this.$search.trigger('focus');
3508 3835 }
3509 3836
3510 3837 params.term = tokenData.term;
3511 3838 }
@@ -3540,8 +3867,13 @@
3540 3867 });
3541 3868
3542 3869 var data = createTag(partParams);
3543 3870
3871 + if (data == null) {
3872 + i++;
3873 + continue;
3874 + }
3875 +
3544 3876 callback(data);
3545 3877
3546 3878 // Reset the term to not include the tokenized portion
3547 3879 term = term.substr(i + 1) || '';
@@ -3627,12 +3959,32 @@
3627 3959
3628 3960 decorated.call(this, $e, options);
3629 3961 }
3630 3962
3963 + MaximumSelectionLength.prototype.bind =
3964 + function (decorated, container, $container) {
3965 + var self = this;
3966 +
3967 + decorated.call(this, container, $container);
3968 +
3969 + container.on('select', function () {
3970 + self._checkIfMaximumSelected();
3971 + });
3972 + };
3973 +
3631 3974 MaximumSelectionLength.prototype.query =
3632 3975 function (decorated, params, callback) {
3633 3976 var self = this;
3634 3977
3978 + this._checkIfMaximumSelected(function () {
3979 + decorated.call(self, params, callback);
3980 + });
3981 + };
3982 +
3983 + MaximumSelectionLength.prototype._checkIfMaximumSelected =
3984 + function (_, successCallback) {
3985 + var self = this;
3986 +
3635 3987 this.current(function (currentData) {
3636 3988 var count = currentData != null ? currentData.length : 0;
3637 3989 if (self.maximumSelectionLength > 0 &&
3638 3990 count >= self.maximumSelectionLength) {
@@ -3643,9 +3995,12 @@
3643 3995 }
3644 3996 });
3645 3997 return;
3646 3998 }
3647 - decorated.call(self, params, callback);
3999 +
4000 + if (successCallback) {
4001 + successCallback();
4002 + }
3648 4003 });
3649 4004 };
3650 4005
3651 4006 return MaximumSelectionLength;
@@ -3677,10 +4032,14 @@
3677 4032
3678 4033 return $dropdown;
3679 4034 };
3680 4035
4036 + Dropdown.prototype.bind = function () {
4037 + // Should be implemented in subclasses
4038 + };
4039 +
3681 4040 Dropdown.prototype.position = function ($dropdown, $container) {
3682 - // Should be implmented in subclasses
4041 + // Should be implemented in subclasses
3683 4042 };
3684 4043
3685 4044 Dropdown.prototype.destroy = function () {
3686 4045 // Remove the dropdown from the DOM
@@ -3701,10 +4060,10 @@
3701 4060
3702 4061 var $search = $(
3703 4062 '<span class="select2-search select2-search--dropdown">' +
3704 4063 '<input class="select2-search__field" type="search" tabindex="-1"' +
3705 - ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
3706 - ' spellcheck="false" role="textbox" />' +
4064 + ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
4065 + ' spellcheck="false" role="searchbox" aria-autocomplete="list" />' +
3707 4066 '</span>'
3708 4067 );
3709 4068
3710 4069 this.$searchContainer = $search;
@@ -3717,8 +4076,10 @@
3717 4076
3718 4077 Search.prototype.bind = function (decorated, container, $container) {
3719 4078 var self = this;
3720 4079
4080 + var resultsId = container.id + '-results';
4081 +
3721 4082 decorated.call(this, container, $container);
3722 4083
3723 4084 this.$search.on('keydown', function (evt) {
3724 4085 self.trigger('keypress', evt);
@@ -3739,22 +4100,32 @@
3739 4100 });
3740 4101
3741 4102 container.on('open', function () {
3742 4103 self.$search.attr('tabindex', 0);
4104 + self.$search.attr('aria-controls', resultsId);
3743 4105
3744 - self.$search.focus();
4106 + self.$search.trigger('focus');
3745 4107
3746 4108 window.setTimeout(function () {
3747 - self.$search.focus();
4109 + self.$search.trigger('focus');
3748 4110 }, 0);
3749 4111 });
3750 4112
3751 4113 container.on('close', function () {
3752 4114 self.$search.attr('tabindex', -1);
4115 + self.$search.removeAttr('aria-controls');
4116 + self.$search.removeAttr('aria-activedescendant');
3753 4117
3754 4118 self.$search.val('');
4119 + self.$search.trigger('blur');
3755 4120 });
3756 4121
4122 + container.on('focus', function () {
4123 + if (!container.isOpen()) {
4124 + self.$search.trigger('focus');
4125 + }
4126 + });
4127 +
3757 4128 container.on('results:all', function (params) {
3758 4129 if (params.query.term == null || params.query.term === '') {
3759 4130 var showSearch = self.showSearch(params);
3760 4131
@@ -3764,8 +4135,16 @@
3764 4135 self.$searchContainer.addClass('select2-search--hide');
3765 4136 }
3766 4137 }
3767 4138 });
4139 +
4140 + container.on('results:focus', function (params) {
4141 + if (params.data._resultId) {
4142 + self.$search.attr('aria-activedescendant', params.data._resultId);
4143 + } else {
4144 + self.$search.removeAttr('aria-activedescendant');
4145 + }
4146 + });
3768 4147 };
3769 4148
3770 4149 Search.prototype.handleSearch = function (evt) {
3771 4150 if (!this._keyUpPrevented) {
@@ -3848,8 +4227,9 @@
3848 4227 decorated.call(this, data);
3849 4228
3850 4229 if (this.showLoadingMore(data)) {
3851 4230 this.$results.append(this.$loadingMore);
4231 + this.loadMoreIfNeeded();
3852 4232 }
3853 4233 };
3854 4234
3855 4235 InfiniteScroll.prototype.bind = function (decorated, container, $container) {
@@ -3866,27 +4246,29 @@
3866 4246 self.lastParams = params;
3867 4247 self.loading = true;
3868 4248 });
3869 4249
3870 - this.$results.on('scroll', function () {
3871 - var isLoadMoreVisible = $.contains(
3872 - document.documentElement,
3873 - self.$loadingMore[0]
3874 - );
4250 + this.$results.on('scroll', this.loadMoreIfNeeded.bind(this));
4251 + };
3875 4252
3876 - if (self.loading || !isLoadMoreVisible) {
3877 - return;
3878 - }
4253 + InfiniteScroll.prototype.loadMoreIfNeeded = function () {
4254 + var isLoadMoreVisible = $.contains(
4255 + document.documentElement,
4256 + this.$loadingMore[0]
4257 + );
3879 4258
3880 - var currentOffset = self.$results.offset().top +
3881 - self.$results.outerHeight(false);
3882 - var loadingMoreOffset = self.$loadingMore.offset().top +
3883 - self.$loadingMore.outerHeight(false);
4259 + if (this.loading || !isLoadMoreVisible) {
4260 + return;
4261 + }
3884 4262
3885 - if (currentOffset + 50 >= loadingMoreOffset) {
3886 - self.loadMore();
3887 - }
3888 - });
4263 + var currentOffset = this.$results.offset().top +
4264 + this.$results.outerHeight(false);
4265 + var loadingMoreOffset = this.$loadingMore.offset().top +
4266 + this.$loadingMore.outerHeight(false);
4267 +
4268 + if (currentOffset + 50 >= loadingMoreOffset) {
4269 + this.loadMore();
4270 + }
3889 4271 };
3890 4272
3891 4273 InfiniteScroll.prototype.loadMore = function () {
3892 4274 this.loading = true;
@@ -3903,9 +4285,11 @@
3903 4285 };
3904 4286
3905 4287 InfiniteScroll.prototype.createLoadingMore = function () {
3906 4288 var $option = $(
3907 - '<li class="option load-more" role="treeitem"></li>'
4289 + '<li ' +
4290 + 'class="select2-results__option select2-results__option--load-more"' +
4291 + 'role="option" aria-disabled="true"></li>'
3908 4292 );
3909 4293
3910 4294 var message = this.options.get('translations').get('loadingMore');
3911 4295
@@ -3921,9 +4305,9 @@
3921 4305 'jquery',
3922 4306 '../utils'
3923 4307 ], function ($, Utils) {
3924 4308 function AttachBody (decorated, $element, options) {
3925 - this.$dropdownParent = options.get('dropdownParent') || document.body;
4309 + this.$dropdownParent = $(options.get('dropdownParent') || document.body);
3926 4310
3927 4311 decorated.call(this, $element, options);
3928 4312 }
3929 4313
@@ -3929,10 +4313,8 @@
3929 4313
3930 4314 AttachBody.prototype.bind = function (decorated, container, $container) {
3931 4315 var self = this;
3932 4316
3933 - var setupResultsEvents = false;
3934 -
3935 4317 decorated.call(this, container, $container);
3936 4318
3937 4319 container.on('open', function () {
3938 4320 self._showDropdown();
@@ -3937,21 +4319,10 @@
3937 4319 container.on('open', function () {
3938 4320 self._showDropdown();
3939 4321 self._attachPositioningHandler(container);
3940 4322
3941 - if (!setupResultsEvents) {
3942 - setupResultsEvents = true;
3943 -
3944 - container.on('results:all', function () {
3945 - self._positionDropdown();
3946 - self._resizeDropdown();
3947 - });
3948 -
3949 - container.on('results:append', function () {
3950 - self._positionDropdown();
3951 - self._resizeDropdown();
3952 - });
3953 - }
4323 + // Must bind after the results handlers to ensure correct sizing
4324 + self._bindContainerResultHandlers(container);
3954 4325 });
3955 4326
3956 4327 container.on('close', function () {
3957 4328 self._hideDropdown();
@@ -3962,8 +4333,14 @@
3962 4333 evt.stopPropagation();
3963 4334 });
3964 4335 };
3965 4336
4337 + AttachBody.prototype.destroy = function (decorated) {
4338 + decorated.call(this);
4339 +
4340 + this.$dropdownContainer.remove();
4341 + };
4342 +
3966 4343 AttachBody.prototype.position = function (decorated, $dropdown, $container) {
3967 4344 // Clone all of the container classes
3968 4345 $dropdown.attr('class', $container.attr('class'));
3969 4346
@@ -3992,11 +4369,50 @@
3992 4369 AttachBody.prototype._hideDropdown = function (decorated) {
3993 4370 this.$dropdownContainer.detach();
3994 4371 };
3995 4372
3996 - AttachBody.prototype._attachPositioningHandler = function (container) {
4373 + AttachBody.prototype._bindContainerResultHandlers =
4374 + function (decorated, container) {
4375 +
4376 + // These should only be bound once
4377 + if (this._containerResultsHandlersBound) {
4378 + return;
4379 + }
4380 +
3997 4381 var self = this;
3998 4382
4383 + container.on('results:all', function () {
4384 + self._positionDropdown();
4385 + self._resizeDropdown();
4386 + });
4387 +
4388 + container.on('results:append', function () {
4389 + self._positionDropdown();
4390 + self._resizeDropdown();
4391 + });
4392 +
4393 + container.on('results:message', function () {
4394 + self._positionDropdown();
4395 + self._resizeDropdown();
4396 + });
4397 +
4398 + container.on('select', function () {
4399 + self._positionDropdown();
4400 + self._resizeDropdown();
4401 + });
4402 +
4403 + container.on('unselect', function () {
4404 + self._positionDropdown();
4405 + self._resizeDropdown();
4406 + });
4407 +
4408 + this._containerResultsHandlersBound = true;
4409 + };
4410 +
4411 + AttachBody.prototype._attachPositioningHandler =
4412 + function (decorated, container) {
4413 + var self = this;
4414 +
3999 4415 var scrollEvent = 'scroll.select2.' + container.id;
4000 4416 var resizeEvent = 'resize.select2.' + container.id;
4001 4417 var orientationEvent = 'orientationchange.select2.' + container.id;
4002 4418
@@ -4001,9 +4417,9 @@
4001 4417 var orientationEvent = 'orientationchange.select2.' + container.id;
4002 4418
4003 4419 var $watchers = this.$container.parents().filter(Utils.hasScroll);
4004 4420 $watchers.each(function () {
4005 - $(this).data('select2-scroll-position', {
4421 + Utils.StoreData(this, 'select2-scroll-position', {
4006 4422 x: $(this).scrollLeft(),
4007 4423 y: $(this).scrollTop()
4008 4424 });
4009 4425 });
@@ -4008,9 +4424,9 @@
4008 4424 });
4009 4425 });
4010 4426
4011 4427 $watchers.on(scrollEvent, function (ev) {
4012 - var position = $(this).data('select2-scroll-position');
4428 + var position = Utils.GetData(this, 'select2-scroll-position');
4013 4429 $(this).scrollTop(position.y);
4014 4430 });
4015 4431
4016 4432 $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
@@ -4019,9 +4435,10 @@
4019 4435 self._resizeDropdown();
4020 4436 });
4021 4437 };
4022 4438
4023 - AttachBody.prototype._detachPositioningHandler = function (container) {
4439 + AttachBody.prototype._detachPositioningHandler =
4440 + function (decorated, container) {
4024 4441 var scrollEvent = 'scroll.select2.' + container.id;
4025 4442 var resizeEvent = 'resize.select2.' + container.id;
4026 4443 var orientationEvent = 'orientationchange.select2.' + container.id;
4027 4444
@@ -4038,9 +4455,8 @@
4038 4455 var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
4039 4456
4040 4457 var newDirection = null;
4041 4458
4042 - var position = this.$container.position();
4043 4459 var offset = this.$container.offset();
4044 4460
4045 4461 offset.bottom = offset.top + this.$container.outerHeight(false);
4046 4462
@@ -4067,8 +4483,32 @@
4067 4483 left: offset.left,
4068 4484 top: container.bottom
4069 4485 };
4070 4486
4487 + // Determine what the parent element is to use for calculating the offset
4488 + var $offsetParent = this.$dropdownParent;
4489 +
4490 + // For statically positioned elements, we need to get the element
4491 + // that is determining the offset
4492 + if ($offsetParent.css('position') === 'static') {
4493 + $offsetParent = $offsetParent.offsetParent();
4494 + }
4495 +
4496 + var parentOffset = {
4497 + top: 0,
4498 + left: 0
4499 + };
4500 +
4501 + if (
4502 + $.contains(document.body, $offsetParent[0]) ||
4503 + $offsetParent[0].isConnected
4504 + ) {
4505 + parentOffset = $offsetParent.offset();
4506 + }
4507 +
4508 + css.top -= parentOffset.top;
4509 + css.left -= parentOffset.left;
4510 +
4071 4511 if (!isCurrentlyAbove && !isCurrentlyBelow) {
4072 4512 newDirection = 'below';
4073 4513 }
4074 4514
@@ -4079,9 +4519,9 @@
4079 4519 }
4080 4520
4081 4521 if (newDirection == 'above' ||
4082 4522 (isCurrentlyAbove && newDirection !== 'below')) {
4083 - css.top = container.top - dropdown.height;
4523 + css.top = container.top - parentOffset.top - dropdown.height;
4084 4524 }
4085 4525
4086 4526 if (newDirection != null) {
4087 4527 this.$dropdown
@@ -4095,10 +4535,8 @@
4095 4535 this.$dropdownContainer.css(css);
4096 4536 };
4097 4537
4098 4538 AttachBody.prototype._resizeDropdown = function () {
4099 - this.$dropdownContainer.width();
4100 -
4101 4539 var css = {
4102 4540 width: this.$container.outerWidth(false) + 'px'
4103 4541 };
4104 4542
@@ -4103,8 +4541,9 @@
4103 4541 };
4104 4542
4105 4543 if (this.options.get('dropdownAutoWidth')) {
4106 4544 css.minWidth = css.width;
4545 + css.position = 'relative';
4107 4546 css.width = 'auto';
4108 4547 }
4109 4548
4110 4549 this.$dropdown.css(css);
@@ -4160,10 +4599,10 @@
4160 4599 return MinimumResultsForSearch;
4161 4600 });
4162 4601
4163 4602 S2.define('select2/dropdown/selectOnClose',[
4164 -
4165 -], function () {
4603 + '../utils'
4604 +], function (Utils) {
4166 4605 function SelectOnClose () { }
4167 4606
4168 4607 SelectOnClose.prototype.bind = function (decorated, container, $container) {
4169 4608 var self = this;
@@ -4169,22 +4608,43 @@
4169 4608 var self = this;
4170 4609
4171 4610 decorated.call(this, container, $container);
4172 4611
4173 - container.on('close', function () {
4174 - self._handleSelectOnClose();
4612 + container.on('close', function (params) {
4613 + self._handleSelectOnClose(params);
4175 4614 });
4176 4615 };
4177 4616
4178 - SelectOnClose.prototype._handleSelectOnClose = function () {
4617 + SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
4618 + if (params && params.originalSelect2Event != null) {
4619 + var event = params.originalSelect2Event;
4620 +
4621 + // Don't select an item if the close event was triggered from a select or
4622 + // unselect event
4623 + if (event._type === 'select' || event._type === 'unselect') {
4624 + return;
4625 + }
4626 + }
4627 +
4179 4628 var $highlightedResults = this.getHighlightedResults();
4180 4629
4630 + // Only select highlighted results
4181 4631 if ($highlightedResults.length < 1) {
4182 4632 return;
4183 4633 }
4184 4634
4635 + var data = Utils.GetData($highlightedResults[0], 'data');
4636 +
4637 + // Don't re-select already selected resulte
4638 + if (
4639 + (data.element != null && data.element.selected) ||
4640 + (data.element == null && data.selected)
4641 + ) {
4642 + return;
4643 + }
4644 +
4185 4645 this.trigger('select', {
4186 - data: $highlightedResults.data('data')
4646 + data: data
4187 4647 });
4188 4648 };
4189 4649
4190 4650 return SelectOnClose;
@@ -4212,13 +4672,16 @@
4212 4672 CloseOnSelect.prototype._selectTriggered = function (_, evt) {
4213 4673 var originalEvent = evt.originalEvent;
4214 4674
4215 4675 // Don't close if the control key is being held
4216 - if (originalEvent && originalEvent.ctrlKey) {
4676 + if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) {
4217 4677 return;
4218 4678 }
4219 4679
4220 - this.trigger('close');
4680 + this.trigger('close', {
4681 + originalEvent: originalEvent,
4682 + originalSelect2Event: evt
4683 + });
4221 4684 };
4222 4685
4223 4686 return CloseOnSelect;
4224 4687 });
@@ -4263,8 +4726,11 @@
4263 4726 return 'No results found';
4264 4727 },
4265 4728 searching: function () {
4266 4729 return 'Searching…';
4730 + },
4731 + removeAllItems: function () {
4732 + return 'Remove all items';
4267 4733 }
4268 4734 };
4269 4735 });
4270 4736
@@ -4324,9 +4790,9 @@
4324 4790 this.reset();
4325 4791 }
4326 4792
4327 4793 Defaults.prototype.apply = function (options) {
4328 - options = $.extend({}, this.defaults, options);
4794 + options = $.extend(true, {}, this.defaults, options);
4329 4795
4330 4796 if (options.dataAdapter == null) {
4331 4797 if (options.ajax != null) {
4332 4798 options.dataAdapter = AjaxData;
@@ -4501,69 +4967,32 @@
4501 4967 EventRelay
4502 4968 );
4503 4969 }
4504 4970
4505 - if (typeof options.language === 'string') {
4506 - // Check if the language is specified with a region
4507 - if (options.language.indexOf('-') > 0) {
4508 - // Extract the region information if it is included
4509 - var languageParts = options.language.split('-');
4510 - var baseLanguage = languageParts[0];
4971 + // If the defaults were not previously applied from an element, it is
4972 + // possible for the language option to have not been resolved
4973 + options.language = this._resolveLanguage(options.language);
4511 4974
4512 - options.language = [options.language, baseLanguage];
4513 - } else {
4514 - options.language = [options.language];
4515 - }
4516 - }
4975 + // Always fall back to English since it will always be complete
4976 + options.language.push('en');
4517 4977
4518 - if ($.isArray(options.language)) {
4519 - var languages = new Translation();
4520 - options.language.push('en');
4978 + var uniqueLanguages = [];
4521 4979
4522 - var languageNames = options.language;
4980 + for (var l = 0; l < options.language.length; l++) {
4981 + var language = options.language[l];
4523 4982
4524 - for (var l = 0; l < languageNames.length; l++) {
4525 - var name = languageNames[l];
4526 - var language = {};
4527 -
4528 - try {
4529 - // Try to load it with the original name
4530 - language = Translation.loadPath(name);
4531 - } catch (e) {
4532 - try {
4533 - // If we couldn't load it, check if it wasn't the full path
4534 - name = this.defaults.amdLanguageBase + name;
4535 - language = Translation.loadPath(name);
4536 - } catch (ex) {
4537 - // The translation could not be loaded at all. Sometimes this is
4538 - // because of a configuration problem, other times this can be
4539 - // because of how Select2 helps load all possible translation files.
4540 - if (options.debug && window.console && console.warn) {
4541 - console.warn(
4542 - 'Select2: The language file for "' + name + '" could not be ' +
4543 - 'automatically loaded. A fallback will be used instead.'
4544 - );
4545 - }
4546 -
4547 - continue;
4548 - }
4549 - }
4550 -
4551 - languages.extend(language);
4983 + if (uniqueLanguages.indexOf(language) === -1) {
4984 + uniqueLanguages.push(language);
4552 4985 }
4986 + }
4553 4987
4554 - options.translations = languages;
4555 - } else {
4556 - var baseTranslation = Translation.loadPath(
4557 - this.defaults.amdLanguageBase + 'en'
4558 - );
4559 - var customTranslation = new Translation(options.language);
4988 + options.language = uniqueLanguages;
4560 4989
4561 - customTranslation.extend(baseTranslation);
4990 + options.translations = this._processTranslations(
4991 + options.language,
4992 + options.debug
4993 + );
4562 4994
4563 - options.translations = customTranslation;
4564 - }
4565 -
4566 4995 return options;
4567 4996 };
4568 4997
4569 4998 Defaults.prototype.reset = function () {
@@ -4627,9 +5056,9 @@
4627 5056 closeOnSelect: true,
4628 5057 debug: false,
4629 5058 dropdownAutoWidth: false,
4630 5059 escapeMarkup: Utils.escapeMarkup,
4631 - language: EnglishTranslation,
5060 + language: {},
4632 5061 matcher: matcher,
4633 5062 minimumInputLength: 0,
4634 5063 maximumInputLength: 0,
4635 5064 maximumSelectionLength: 0,
@@ -4634,8 +5063,9 @@
4634 5063 maximumInputLength: 0,
4635 5064 maximumSelectionLength: 0,
4636 5065 minimumResultsForSearch: 0,
4637 5066 selectOnClose: false,
5067 + scrollAfterSelect: false,
4638 5068 sorter: function (data) {
4639 5069 return data;
4640 5070 },
4641 5071 templateResult: function (result) {
@@ -4648,8 +5078,105 @@
4648 5078 width: 'resolve'
4649 5079 };
4650 5080 };
4651 5081
5082 + Defaults.prototype.applyFromElement = function (options, $element) {
5083 + var optionLanguage = options.language;
5084 + var defaultLanguage = this.defaults.language;
5085 + var elementLanguage = $element.prop('lang');
5086 + var parentLanguage = $element.closest('[lang]').prop('lang');
5087 +
5088 + var languages = Array.prototype.concat.call(
5089 + this._resolveLanguage(elementLanguage),
5090 + this._resolveLanguage(optionLanguage),
5091 + this._resolveLanguage(defaultLanguage),
5092 + this._resolveLanguage(parentLanguage)
5093 + );
5094 +
5095 + options.language = languages;
5096 +
5097 + return options;
5098 + };
5099 +
5100 + Defaults.prototype._resolveLanguage = function (language) {
5101 + if (!language) {
5102 + return [];
5103 + }
5104 +
5105 + if ($.isEmptyObject(language)) {
5106 + return [];
5107 + }
5108 +
5109 + if ($.isPlainObject(language)) {
5110 + return [language];
5111 + }
5112 +
5113 + var languages;
5114 +
5115 + if (!$.isArray(language)) {
5116 + languages = [language];
5117 + } else {
5118 + languages = language;
5119 + }
5120 +
5121 + var resolvedLanguages = [];
5122 +
5123 + for (var l = 0; l < languages.length; l++) {
5124 + resolvedLanguages.push(languages[l]);
5125 +
5126 + if (typeof languages[l] === 'string' && languages[l].indexOf('-') > 0) {
5127 + // Extract the region information if it is included
5128 + var languageParts = languages[l].split('-');
5129 + var baseLanguage = languageParts[0];
5130 +
5131 + resolvedLanguages.push(baseLanguage);
5132 + }
5133 + }
5134 +
5135 + return resolvedLanguages;
5136 + };
5137 +
5138 + Defaults.prototype._processTranslations = function (languages, debug) {
5139 + var translations = new Translation();
5140 +
5141 + for (var l = 0; l < languages.length; l++) {
5142 + var languageData = new Translation();
5143 +
5144 + var language = languages[l];
5145 +
5146 + if (typeof language === 'string') {
5147 + try {
5148 + // Try to load it with the original name
5149 + languageData = Translation.loadPath(language);
5150 + } catch (e) {
5151 + try {
5152 + // If we couldn't load it, check if it wasn't the full path
5153 + language = this.defaults.amdLanguageBase + language;
5154 + languageData = Translation.loadPath(language);
5155 + } catch (ex) {
5156 + // The translation could not be loaded at all. Sometimes this is
5157 + // because of a configuration problem, other times this can be
5158 + // because of how Select2 helps load all possible translation files
5159 + if (debug && window.console && console.warn) {
5160 + console.warn(
5161 + 'Select2: The language file for "' + language + '" could ' +
5162 + 'not be automatically loaded. A fallback will be used instead.'
5163 + );
5164 + }
5165 + }
5166 + }
5167 + } else if ($.isPlainObject(language)) {
5168 + languageData = new Translation(language);
5169 + } else {
5170 + languageData = language;
5171 + }
5172 +
5173 + translations.extend(languageData);
5174 + }
5175 +
5176 + return translations;
5177 + };
5178 +
4652 5179 Defaults.prototype.set = function (key, value) {
4653 5180 var camelKey = $.camelCase(key);
4654 5181
4655 5182 var data = {};
@@ -4656,9 +5183,9 @@
4656 5183 data[camelKey] = value;
4657 5184
4658 5185 var convertedData = Utils._convertData(data);
4659 5186
4660 - $.extend(this.defaults, convertedData);
5187 + $.extend(true, this.defaults, convertedData);
4661 5188 };
4662 5189
4663 5190 var defaults = new Defaults();
4664 5191
@@ -4677,8 +5204,12 @@
4677 5204 if ($element != null) {
4678 5205 this.fromElement($element);
4679 5206 }
4680 5207
5208 + if ($element != null) {
5209 + this.options = Defaults.applyFromElement(this.options, $element);
5210 + }
5211 +
4681 5212 this.options = Defaults.apply(this.options);
4682 5213
4683 5214 if ($element && $element.is('input')) {
4684 5215 var InputCompat = require(this.get('amdBase') + 'compat/inputData');
@@ -4700,16 +5231,8 @@
4700 5231 if (this.options.disabled == null) {
4701 5232 this.options.disabled = $e.prop('disabled');
4702 5233 }
4703 5234
4704 - if (this.options.language == null) {
4705 - if ($e.prop('lang')) {
4706 - this.options.language = $e.prop('lang').toLowerCase();
4707 - } else if ($e.closest('[lang]').prop('lang')) {
4708 - this.options.language = $e.closest('[lang]').prop('lang');
4709 - }
4710 - }
4711 -
4712 5235 if (this.options.dir == null) {
4713 5236 if ($e.prop('dir')) {
4714 5237 this.options.dir = $e.prop('dir');
4715 5238 } else if ($e.closest('[dir]').prop('dir')) {
@@ -4721,9 +5244,9 @@
4721 5244
4722 5245 $e.prop('disabled', this.options.disabled);
4723 5246 $e.prop('multiple', this.options.multiple);
4724 5247
4725 - if ($e.data('select2Tags')) {
5248 + if (Utils.GetData($e[0], 'select2Tags')) {
4726 5249 if (this.options.debug && window.console && console.warn) {
4727 5250 console.warn(
4728 5251 'Select2: The `data-select2-tags` attribute has been changed to ' +
4729 5252 'use the `data-data` and `data-tags="true"` attributes and will be ' +
@@ -4730,13 +5253,13 @@
4730 5253 'removed in future versions of Select2.'
4731 5254 );
4732 5255 }
4733 5256
4734 - $e.data('data', $e.data('select2Tags'));
4735 - $e.data('tags', true);
5257 + Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags'));
5258 + Utils.StoreData($e[0], 'tags', true);
4736 5259 }
4737 5260
4738 - if ($e.data('ajaxUrl')) {
5261 + if (Utils.GetData($e[0], 'ajaxUrl')) {
4739 5262 if (this.options.debug && window.console && console.warn) {
4740 5263 console.warn(
4741 5264 'Select2: The `data-ajax-url` attribute has been changed to ' +
4742 5265 '`data-ajax--url` and support for the old attribute will be removed' +
@@ -4743,23 +5266,47 @@
4743 5266 ' in future versions of Select2.'
4744 5267 );
4745 5268 }
4746 5269
4747 - $e.attr('ajax--url', $e.data('ajaxUrl'));
4748 - $e.data('ajax--url', $e.data('ajaxUrl'));
5270 + $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl'));
5271 + Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl'));
4749 5272 }
4750 5273
4751 5274 var dataset = {};
4752 5275
5276 + function upperCaseLetter(_, letter) {
5277 + return letter.toUpperCase();
5278 + }
5279 +
5280 + // Pre-load all of the attributes which are prefixed with `data-`
5281 + for (var attr = 0; attr < $e[0].attributes.length; attr++) {
5282 + var attributeName = $e[0].attributes[attr].name;
5283 + var prefix = 'data-';
5284 +
5285 + if (attributeName.substr(0, prefix.length) == prefix) {
5286 + // Get the contents of the attribute after `data-`
5287 + var dataName = attributeName.substring(prefix.length);
5288 +
5289 + // Get the data contents from the consistent source
5290 + // This is more than likely the jQuery data helper
5291 + var dataValue = Utils.GetData($e[0], dataName);
5292 +
5293 + // camelCase the attribute name to match the spec
5294 + var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter);
5295 +
5296 + // Store the data attribute contents into the dataset since
5297 + dataset[camelDataName] = dataValue;
5298 + }
5299 + }
5300 +
4753 5301 // Prefer the element's `dataset` attribute if it exists
4754 5302 // jQuery 1.x does not correctly handle data attributes with multiple dashes
4755 5303 if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
4756 - dataset = $.extend(true, {}, $e[0].dataset, $e.data());
4757 - } else {
4758 - dataset = $e.data();
5304 + dataset = $.extend(true, {}, $e[0].dataset, dataset);
4759 5305 }
4760 5306
4761 - var data = $.extend(true, {}, dataset);
5307 + // Prefer our internal data cache if it exists
5308 + var data = $.extend(true, {}, Utils.GetData($e[0]), dataset);
4762 5309
4763 5310 data = Utils._convertData(data);
4764 5311
4765 5312 for (var key in data) {
@@ -4794,10 +5341,10 @@
4794 5341 './utils',
4795 5342 './keys'
4796 5343 ], function ($, Options, Utils, KEYS) {
4797 5344 var Select2 = function ($element, options) {
4798 - if ($element.data('select2') != null) {
4799 - $element.data('select2').destroy();
5345 + if (Utils.GetData($element[0], 'select2') != null) {
5346 + Utils.GetData($element[0], 'select2').destroy();
4800 5347 }
4801 5348
4802 5349 this.$element = $element;
4803 5350
@@ -4811,9 +5358,9 @@
4811 5358
4812 5359 // Set up the tabindex
4813 5360
4814 5361 var tabindex = $element.attr('tabindex') || 0;
4815 - $element.data('old-tabindex', tabindex);
5362 + Utils.StoreData($element[0], 'old-tabindex', tabindex);
4816 5363 $element.attr('tabindex', '-1');
4817 5364
4818 5365 // Set up containers and adapters
4819 5366
@@ -4867,13 +5414,16 @@
4867 5414 });
4868 5415
4869 5416 // Hide the original select
4870 5417 $element.addClass('select2-hidden-accessible');
4871 - $element.attr('aria-hidden', 'true');
4872 -
5418 + $element.attr('aria-hidden', 'true');
5419 +
4873 5420 // Synchronize any monitored attributes
4874 5421 this._syncAttributes();
4875 5422
5423 + Utils.StoreData($element[0], 'select2', this);
5424 +
5425 + // Ensure backwards compatibility with $element.data('select2').
4876 5426 $element.data('select2', this);
4877 5427 };
4878 5428
4879 5429 Utils.Extend(Select2, Utils.Observable);
@@ -4888,8 +5438,9 @@
4888 5438 } else {
4889 5439 id = Utils.generateChars(4);
4890 5440 }
4891 5441
5442 + id = id.replace(/(:|\.|\[|\]|,)/g, '');
4892 5443 id = 'select2-' + id;
4893 5444
4894 5445 return id;
4895 5446 };
@@ -4947,8 +5498,14 @@
4947 5498
4948 5499 return null;
4949 5500 }
4950 5501
5502 + if (method == 'computedstyle') {
5503 + var computedStyle = window.getComputedStyle($element[0]);
5504 +
5505 + return computedStyle.width;
5506 + }
5507 +
4951 5508 return method;
4952 5509 };
4953 5510
4954 5511 Select2.prototype._bindAdapters = function () {
@@ -4969,12 +5526,17 @@
4969 5526 });
4970 5527 });
4971 5528 });
4972 5529
4973 - this._sync = Utils.bind(this._syncAttributes, this);
5530 + this.$element.on('focus.select2', function (evt) {
5531 + self.trigger('focus', evt);
5532 + });
4974 5533
5534 + this._syncA = Utils.bind(this._syncAttributes, this);
5535 + this._syncS = Utils.bind(this._syncSubtree, this);
5536 +
4975 5537 if (this.$element[0].attachEvent) {
4976 - this.$element[0].attachEvent('onpropertychange', this._sync);
5538 + this.$element[0].attachEvent('onpropertychange', this._syncA);
4977 5539 }
4978 5540
4979 5541 var observer = window.MutationObserver ||
4980 5542 window.WebKitMutationObserver ||
@@ -4982,16 +5544,32 @@
4982 5544 ;
4983 5545
4984 5546 if (observer != null) {
4985 5547 this._observer = new observer(function (mutations) {
4986 - $.each(mutations, self._sync);
5548 + $.each(mutations, self._syncA);
5549 + $.each(mutations, self._syncS);
4987 5550 });
4988 5551 this._observer.observe(this.$element[0], {
4989 5552 attributes: true,
5553 + childList: true,
4990 5554 subtree: false
4991 5555 });
4992 5556 } else if (this.$element[0].addEventListener) {
4993 - this.$element[0].addEventListener('DOMAttrModified', self._sync, false);
5557 + this.$element[0].addEventListener(
5558 + 'DOMAttrModified',
5559 + self._syncA,
5560 + false
5561 + );
5562 + this.$element[0].addEventListener(
5563 + 'DOMNodeInserted',
5564 + self._syncS,
5565 + false
5566 + );
5567 + this.$element[0].addEventListener(
5568 + 'DOMNodeRemoved',
5569 + self._syncS,
5570 + false
5571 + );
4994 5572 }
4995 5573 };
4996 5574
4997 5575 Select2.prototype._registerDataEvents = function () {
@@ -5003,14 +5581,18 @@
5003 5581 };
5004 5582
5005 5583 Select2.prototype._registerSelectionEvents = function () {
5006 5584 var self = this;
5007 - var nonRelayEvents = ['toggle'];
5585 + var nonRelayEvents = ['toggle', 'focus'];
5008 5586
5009 5587 this.selection.on('toggle', function () {
5010 5588 self.toggleDropdown();
5011 5589 });
5012 5590
5591 + this.selection.on('focus', function (params) {
5592 + self.focus(params);
5593 + });
5594 +
5013 5595 this.selection.on('*', function (name, params) {
5014 5596 if ($.inArray(name, nonRelayEvents) !== -1) {
5015 5597 return;
5016 5598 }
@@ -5053,12 +5635,8 @@
5053 5635 this.on('disable', function () {
5054 5636 self.$container.addClass('select2-container--disabled');
5055 5637 });
5056 5638
5057 - this.on('focus', function () {
5058 - self.$container.addClass('select2-container--focus');
5059 - });
5060 -
5061 5639 this.on('blur', function () {
5062 5640 self.$container.removeClass('select2-container--focus');
5063 5641 });
5064 5642
@@ -5063,9 +5641,9 @@
5063 5641 });
5064 5642
5065 5643 this.on('query', function (params) {
5066 5644 if (!self.isOpen()) {
5067 - self.trigger('open');
5645 + self.trigger('open', {});
5068 5646 }
5069 5647
5070 5648 this.dataAdapter.query(params, function (data) {
5071 5649 self.trigger('results:all', {
@@ -5087,32 +5665,33 @@
5087 5665 this.on('keypress', function (evt) {
5088 5666 var key = evt.which;
5089 5667
5090 5668 if (self.isOpen()) {
5091 - if (key === KEYS.ENTER) {
5092 - self.trigger('results:select');
5669 + if (key === KEYS.ESC || key === KEYS.TAB ||
5670 + (key === KEYS.UP && evt.altKey)) {
5671 + self.close();
5093 5672
5094 5673 evt.preventDefault();
5674 + } else if (key === KEYS.ENTER) {
5675 + self.trigger('results:select', {});
5676 +
5677 + evt.preventDefault();
5095 5678 } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
5096 - self.trigger('results:toggle');
5679 + self.trigger('results:toggle', {});
5097 5680
5098 5681 evt.preventDefault();
5099 5682 } else if (key === KEYS.UP) {
5100 - self.trigger('results:previous');
5683 + self.trigger('results:previous', {});
5101 5684
5102 5685 evt.preventDefault();
5103 5686 } else if (key === KEYS.DOWN) {
5104 - self.trigger('results:next');
5687 + self.trigger('results:next', {});
5105 5688
5106 5689 evt.preventDefault();
5107 - } else if (key === KEYS.ESC || key === KEYS.TAB) {
5108 - self.close();
5109 -
5110 - evt.preventDefault();
5111 5690 }
5112 5691 } else {
5113 5692 if (key === KEYS.ENTER || key === KEYS.SPACE ||
5114 - ((key === KEYS.DOWN || key === KEYS.UP) && evt.altKey)) {
5693 + (key === KEYS.DOWN && evt.altKey)) {
5115 5694 self.open();
5116 5695
5117 5696 evt.preventDefault();
5118 5697 }
@@ -5127,14 +5706,54 @@
5127 5706 if (this.isOpen()) {
5128 5707 this.close();
5129 5708 }
5130 5709
5131 - this.trigger('disable');
5710 + this.trigger('disable', {});
5132 5711 } else {
5133 - this.trigger('enable');
5712 + this.trigger('enable', {});
5134 5713 }
5135 5714 };
5136 5715
5716 + Select2.prototype._syncSubtree = function (evt, mutations) {
5717 + var changed = false;
5718 + var self = this;
5719 +
5720 + // Ignore any mutation events raised for elements that aren't options or
5721 + // optgroups. This handles the case when the select element is destroyed
5722 + if (
5723 + evt && evt.target && (
5724 + evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
5725 + )
5726 + ) {
5727 + return;
5728 + }
5729 +
5730 + if (!mutations) {
5731 + // If mutation events aren't supported, then we can only assume that the
5732 + // change affected the selections
5733 + changed = true;
5734 + } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
5735 + for (var n = 0; n < mutations.addedNodes.length; n++) {
5736 + var node = mutations.addedNodes[n];
5737 +
5738 + if (node.selected) {
5739 + changed = true;
5740 + }
5741 + }
5742 + } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
5743 + changed = true;
5744 + }
5745 +
5746 + // Only re-pull the data if we think there is a change
5747 + if (changed) {
5748 + this.dataAdapter.current(function (currentData) {
5749 + self.trigger('selection:update', {
5750 + data: currentData
5751 + });
5752 + });
5753 + }
5754 + };
5755 +
5137 5756 /**
5138 5757 * Override the trigger method to automatically trigger pre-events when
5139 5758 * there are events that can be prevented.
5140 5759 */
@@ -5143,11 +5762,16 @@
5143 5762 var preTriggerMap = {
5144 5763 'open': 'opening',
5145 5764 'close': 'closing',
5146 5765 'select': 'selecting',
5147 - 'unselect': 'unselecting'
5766 + 'unselect': 'unselecting',
5767 + 'clear': 'clearing'
5148 5768 };
5149 5769
5770 + if (args === undefined) {
5771 + args = {};
5772 + }
5773 +
5150 5774 if (name in preTriggerMap) {
5151 5775 var preTriggerName = preTriggerMap[name];
5152 5776 var preTriggerArgs = {
5153 5777 prevented: false,
@@ -5184,10 +5808,8 @@
5184 5808 return;
5185 5809 }
5186 5810
5187 5811 this.trigger('query', {});
5188 -
5189 - this.trigger('open');
5190 5812 };
5191 5813
5192 5814 Select2.prototype.close = function () {
5193 5815 if (!this.isOpen()) {
@@ -5193,9 +5815,9 @@
5193 5815 if (!this.isOpen()) {
5194 5816 return;
5195 5817 }
5196 5818
5197 - this.trigger('close');
5819 + this.trigger('close', {});
5198 5820 };
5199 5821
5200 5822 Select2.prototype.isOpen = function () {
5201 5823 return this.$container.hasClass('select2-container--open');
@@ -5200,8 +5822,22 @@
5200 5822 Select2.prototype.isOpen = function () {
5201 5823 return this.$container.hasClass('select2-container--open');
5202 5824 };
5203 5825
5826 + Select2.prototype.hasFocus = function () {
5827 + return this.$container.hasClass('select2-container--focus');
5828 + };
5829 +
5830 + Select2.prototype.focus = function (data) {
5831 + // No need to re-trigger focus events if we are already focused
5832 + if (this.hasFocus()) {
5833 + return;
5834 + }
5835 +
5836 + this.$container.addClass('select2-container--focus');
5837 + this.trigger('focus', {});
5838 + };
5839 +
5204 5840 Select2.prototype.enable = function (args) {
5205 5841 if (this.options.get('debug') && window.console && console.warn) {
5206 5842 console.warn(
5207 5843 'Select2: The `select2("enable")` method has been deprecated and will' +
@@ -5263,9 +5899,9 @@
5263 5899 Select2.prototype.destroy = function () {
5264 5900 this.$container.remove();
5265 5901
5266 5902 if (this.$element[0].detachEvent) {
5267 - this.$element[0].detachEvent('onpropertychange', this._sync);
5903 + this.$element[0].detachEvent('onpropertychange', this._syncA);
5268 5904 }
5269 5905
5270 5906 if (this._observer != null) {
5271 5907 this._observer.disconnect();
@@ -5271,18 +5907,25 @@
5271 5907 this._observer.disconnect();
5272 5908 this._observer = null;
5273 5909 } else if (this.$element[0].removeEventListener) {
5274 5910 this.$element[0]
5275 - .removeEventListener('DOMAttrModified', this._sync, false);
5911 + .removeEventListener('DOMAttrModified', this._syncA, false);
5912 + this.$element[0]
5913 + .removeEventListener('DOMNodeInserted', this._syncS, false);
5914 + this.$element[0]
5915 + .removeEventListener('DOMNodeRemoved', this._syncS, false);
5276 5916 }
5277 5917
5278 - this._sync = null;
5918 + this._syncA = null;
5919 + this._syncS = null;
5279 5920
5280 5921 this.$element.off('.select2');
5281 - this.$element.attr('tabindex', this.$element.data('old-tabindex'));
5922 + this.$element.attr('tabindex',
5923 + Utils.GetData(this.$element[0], 'old-tabindex'));
5282 5924
5283 5925 this.$element.removeClass('select2-hidden-accessible');
5284 - this.$element.attr('aria-hidden', 'false');
5926 + this.$element.attr('aria-hidden', 'false');
5927 + Utils.RemoveData(this.$element[0]);
5285 5928 this.$element.removeData('select2');
5286 5929
5287 5930 this.dataAdapter.destroy();
5288 5931 this.selection.destroy();
@@ -5308,9 +5951,9 @@
5308 5951 this.$container = $container;
5309 5952
5310 5953 this.$container.addClass('select2-container--' + this.options.get('theme'));
5311 5954
5312 - $container.data('element', this.$element);
5955 + Utils.StoreData($container[0], 'element', this.$element);
5313 5956
5314 5957 return $container;
5315 5958 };
5316 5959
@@ -5316,18 +5959,23 @@
5316 5959
5317 5960 return Select2;
5318 5961 });
5319 5962
5963 +S2.define('jquery-mousewheel',[
5964 + 'jquery'
5965 +], function ($) {
5966 + // Used to shim jQuery.mousewheel for non-full builds.
5967 + return $;
5968 +});
5969 +
5320 5970 S2.define('jquery.select2',[
5321 5971 'jquery',
5322 - 'require',
5972 + 'jquery-mousewheel',
5323 5973
5324 5974 './select2/core',
5325 - './select2/defaults'
5326 -], function ($, require, Select2, Defaults) {
5327 - // Force jQuery.mousewheel to be loaded if it hasn't already
5328 - require('jquery.mousewheel');
5329 -
5975 + './select2/defaults',
5976 + './select2/utils'
5977 +], function ($, _, Select2, Defaults, Utils) {
5330 5978 if ($.fn.select2 == null) {
5331 5979 // All methods that should return the element
5332 5980 var thisMethods = ['open', 'close', 'destroy'];
5333 5981
@@ -5335,9 +5983,9 @@
5335 5983 options = options || {};
5336 5984
5337 5985 if (typeof options === 'object') {
5338 5986 this.each(function () {
5339 - var instanceOptions = $.extend({}, options, true);
5987 + var instanceOptions = $.extend(true, {}, options);
5340 5988
5341 5989 var instance = new Select2($(this), instanceOptions);
5342 5990 });
5343 5991
@@ -5342,20 +5990,23 @@
5342 5990 });
5343 5991
5344 5992 return this;
5345 5993 } else if (typeof options === 'string') {
5346 - var instance = this.data('select2');
5994 + var ret;
5995 + var args = Array.prototype.slice.call(arguments, 1);
5347 5996
5348 - if (instance == null && window.console && console.error) {
5349 - console.error(
5350 - 'The select2(\'' + options + '\') method was called on an ' +
5351 - 'element that is not using Select2.'
5352 - );
5353 - }
5997 + this.each(function () {
5998 + var instance = Utils.GetData(this, 'select2');
5354 5999
5355 - var args = Array.prototype.slice.call(arguments, 1);
6000 + if (instance == null && window.console && console.error) {
6001 + console.error(
6002 + 'The select2(\'' + options + '\') method was called on an ' +
6003 + 'element that is not using Select2.'
6004 + );
6005 + }
5356 6006
5357 - var ret = instance[options](args);
6007 + ret = instance[options].apply(instance, args);
6008 + });
5358 6009
5359 6010 // Check if we should be returning `this`
5360 6011 if ($.inArray(options, thisMethods) > -1) {
5361 6012 return this;
@@ -5372,15 +6023,8 @@
5372 6023 $.fn.select2.defaults = Defaults;
5373 6024 }
5374 6025
5375 6026 return Select2;
5376 -});
5377 -
5378 -S2.define('jquery.mousewheel',[
5379 - 'jquery'
5380 -], function ($) {
5381 - // Used to shim jQuery.mousewheel for non-full builds.
5382 - return $;
5383 6027 });
5384 6028
5385 6029 // Return the AMD loader configuration so it can be used outside of this file
5386 6030 return {