3 * https://select2.github.io
5 * Released under the MIT license
6 * https://github.com/select2/select2/blob/master/LICENSE.md
9 if (typeof define === 'function' && define.amd) {
10 // AMD. Register as an anonymous module.
11 define(['jquery'], factory);
12 } else if (typeof exports === 'object') {
14 factory(require('jquery'));
20 // This is needed so we can catch the AMD loader configuration and use it
21 // The inner file should be wrapped (by `banner.start.js`) in a function that
22 // returns the AMD loader references.
25 // Restore the Select2 AMD loader so it can be used
26 // Needed mostly in the language files, where the loader is not inserted
27 if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
28 var S2 = jQuery.fn.select2.amd;
30 var S2;(function () { if (!S2 || !S2.requirejs) {
31 if (!S2) { S2 = {}; } else { require = S2; }
33 * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
34 * Available via the MIT or new BSD license.
35 * see: http://github.com/jrburke/almond for details
37 //Going sloppy to avoid 'use strict' string cost, but strict practices should
39 /*jslint sloppy: true */
40 /*global setTimeout: false */
42 var requirejs, require, define;
44 var main, req, makeMap, handlers,
49 hasOwn = Object.prototype.hasOwnProperty,
51 jsSuffixRegExp = /\.js$/;
53 function hasProp(obj, prop) {
54 return hasOwn.call(obj, prop);
58 * Given a relative module name, like ./something, normalize it to
59 * a real name that can be mapped to a path.
60 * @param {String} name the relative name
61 * @param {String} baseName a real name that the name arg is relative
63 * @returns {String} normalized name
65 function normalize(name, baseName) {
66 var nameParts, nameSegment, mapValue, foundMap, lastIndex,
67 foundI, foundStarMap, starI, i, j, part,
68 baseParts = baseName && baseName.split("/"),
70 starMap = (map && map['*']) || {};
72 //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.
78 name = name.split('/');
79 lastIndex = name.length - 1;
81 // Node .js allowance:
82 if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
83 name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
86 //Lop off the last part of baseParts, so that . matches the
87 //"directory" and not name of the baseName's module. For instance,
88 //baseName of "one/two/three", maps to "one/two/three.js", but we
89 //want the directory, "one/two" for this normalization.
90 name = baseParts.slice(0, baseParts.length - 1).concat(name);
93 for (i = 0; i < name.length; i += 1) {
98 } else if (part === "..") {
99 if (i === 1 && (name[2] === '..' || name[0] === '..')) {
100 //End of the line. Keep at least one non-dot
101 //path segment at the front so it can be mapped
102 //correctly to disk. Otherwise, there is likely
103 //no path mapping for a path starting with '..'.
104 //This can still fail, but catches the most reasonable
108 name.splice(i - 1, 2);
115 name = name.join("/");
116 } else if (name.indexOf('./') === 0) {
117 // No baseName, so this is ID is resolved relative
118 // to baseUrl, pull off the leading dot.
119 name = name.substring(2);
123 //Apply map config if available.
124 if ((baseParts || starMap) && map) {
125 nameParts = name.split('/');
127 for (i = nameParts.length; i > 0; i -= 1) {
128 nameSegment = nameParts.slice(0, i).join("/");
131 //Find the longest baseName segment match in the config.
132 //So, do joins on the biggest to smallest lengths of baseParts.
133 for (j = baseParts.length; j > 0; j -= 1) {
134 mapValue = map[baseParts.slice(0, j).join('/')];
136 //baseName segment has config, find if it has one for
139 mapValue = mapValue[nameSegment];
141 //Match, update name to the new value.
154 //Check for a star map match, but just hold on to it,
155 //if there is a shorter segment match later in a matching
156 //config, then favor over this star map.
157 if (!foundStarMap && starMap && starMap[nameSegment]) {
158 foundStarMap = starMap[nameSegment];
163 if (!foundMap && foundStarMap) {
164 foundMap = foundStarMap;
169 nameParts.splice(0, foundI, foundMap);
170 name = nameParts.join('/');
177 function makeRequire(relName, forceSync) {
179 //A version of a require function that passes a moduleName
180 //value for items that may need to
181 //look up paths relative to the moduleName
182 var args = aps.call(arguments, 0);
184 //If first arg is not require('string'), and there is only
185 //one arg, it is the array form without a callback. Insert
186 //a null so that the following concat is correct.
187 if (typeof args[0] !== 'string' && args.length === 1) {
190 return req.apply(undef, args.concat([relName, forceSync]));
194 function makeNormalize(relName) {
195 return function (name) {
196 return normalize(name, relName);
200 function makeLoad(depName) {
201 return function (value) {
202 defined[depName] = value;
206 function callDep(name) {
207 if (hasProp(waiting, name)) {
208 var args = waiting[name];
209 delete waiting[name];
210 defining[name] = true;
211 main.apply(undef, args);
214 if (!hasProp(defined, name) && !hasProp(defining, name)) {
215 throw new Error('No ' + name);
217 return defined[name];
220 //Turns a plugin!resource to [plugin, resource]
221 //with the plugin being undefined if the name
222 //did not have a plugin prefix.
223 function splitPrefix(name) {
225 index = name ? name.indexOf('!') : -1;
227 prefix = name.substring(0, index);
228 name = name.substring(index + 1, name.length);
230 return [prefix, name];
234 * Makes a name map, normalizing the name, and using a plugin
235 * for normalization if necessary. Grabs a ref to plugin
236 * too, as an optimization.
238 makeMap = function (name, relName) {
240 parts = splitPrefix(name),
246 prefix = normalize(prefix, relName);
247 plugin = callDep(prefix);
250 //Normalize according
252 if (plugin && plugin.normalize) {
253 name = plugin.normalize(name, makeNormalize(relName));
255 name = normalize(name, relName);
258 name = normalize(name, relName);
259 parts = splitPrefix(name);
263 plugin = callDep(prefix);
267 //Using ridiculous property names for space reasons
269 f: prefix ? prefix + '!' + name : name, //fullName
276 function makeConfig(name) {
278 return (config && config.config && config.config[name]) || {};
283 require: function (name) {
284 return makeRequire(name);
286 exports: function (name) {
287 var e = defined[name];
288 if (typeof e !== 'undefined') {
291 return (defined[name] = {});
294 module: function (name) {
298 exports: defined[name],
299 config: makeConfig(name)
304 main = function (name, deps, callback, relName) {
305 var cjsModule, depName, ret, map, i,
307 callbackType = typeof callback,
310 //Use name if no relName
311 relName = relName || name;
313 //Call the callback to define the module, if necessary.
314 if (callbackType === 'undefined' || callbackType === 'function') {
315 //Pull out the defined dependencies and pass the ordered
316 //values to the callback.
317 //Default to [require, exports, module] if no deps
318 deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
319 for (i = 0; i < deps.length; i += 1) {
320 map = makeMap(deps[i], relName);
323 //Fast path CommonJS standard dependencies.
324 if (depName === "require") {
325 args[i] = handlers.require(name);
326 } else if (depName === "exports") {
327 //CommonJS module spec 1.1
328 args[i] = handlers.exports(name);
330 } else if (depName === "module") {
331 //CommonJS module spec 1.1
332 cjsModule = args[i] = handlers.module(name);
333 } else if (hasProp(defined, depName) ||
334 hasProp(waiting, depName) ||
335 hasProp(defining, depName)) {
336 args[i] = callDep(depName);
338 map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
339 args[i] = defined[depName];
341 throw new Error(name + ' missing ' + depName);
345 ret = callback ? callback.apply(defined[name], args) : undefined;
348 //If setting exports via "module" is in play,
349 //favor that over return value and exports. After that,
350 //favor a non-undefined return value over exports use.
351 if (cjsModule && cjsModule.exports !== undef &&
352 cjsModule.exports !== defined[name]) {
353 defined[name] = cjsModule.exports;
354 } else if (ret !== undef || !usingExports) {
355 //Use the return value from the function.
360 //May just be an object definition for the module. Only
361 //worry about defining if have a module name.
362 defined[name] = callback;
366 requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
367 if (typeof deps === "string") {
368 if (handlers[deps]) {
369 //callback in this case is really relName
370 return handlers[deps](callback);
372 //Just return the module wanted. In this scenario, the
373 //deps arg is the module name, and second arg (if passed)
374 //is just the relName.
375 //Normalize module name, if it contains . or ..
376 return callDep(makeMap(deps, callback).f);
377 } else if (!deps.splice) {
378 //deps is a config object, not an array.
381 req(config.deps, config.callback);
387 if (callback.splice) {
388 //callback is an array, which means it is a dependency list.
389 //Adjust args if there are dependencies
398 //Support require(['a'])
399 callback = callback || function () {};
401 //If relName is a function, it is an errback handler,
403 if (typeof relName === 'function') {
408 //Simulate async callback;
410 main(undef, deps, callback, relName);
412 //Using a non-zero value because of concern for what old browsers
413 //do, and latest browsers "upgrade" to 4 if lower value is used:
414 //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
415 //If want a value immediately, use require('id') instead -- something
416 //that works in almond on the global level, but not guaranteed and
417 //unlikely to work in other AMD implementations.
418 setTimeout(function () {
419 main(undef, deps, callback, relName);
427 * Just drops the config on the floor, but returns req in case
428 * the config return value is used.
430 req.config = function (cfg) {
435 * Expose module registry for debugging and tooling
437 requirejs._defined = defined;
439 define = function (name, deps, callback) {
440 if (typeof name !== 'string') {
441 throw new Error('See almond README: incorrect module build, no module name');
444 //This module may not have dependencies
446 //deps is not an array, so probably means
447 //an object literal or factory function for
448 //the value. Adjust args.
453 if (!hasProp(defined, name) && !hasProp(waiting, name)) {
454 waiting[name] = [name, deps, callback];
463 S2.requirejs = requirejs;S2.require = require;S2.define = define;
466 S2.define("almond", function(){});
468 /* global jQuery:false, $:false */
469 S2.define('jquery',[],function () {
470 var _$ = jQuery || $;
472 if (_$ == null && console && console.error) {
474 'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
475 'found. Make sure that you are including jQuery before Select2 on your ' +
483 S2.define('select2/utils',[
488 Utils.Extend = function (ChildClass, SuperClass) {
489 var __hasProp = {}.hasOwnProperty;
491 function BaseConstructor () {
492 this.constructor = ChildClass;
495 for (var key in SuperClass) {
496 if (__hasProp.call(SuperClass, key)) {
497 ChildClass[key] = SuperClass[key];
501 BaseConstructor.prototype = SuperClass.prototype;
502 ChildClass.prototype = new BaseConstructor();
503 ChildClass.__super__ = SuperClass.prototype;
508 function getMethods (theClass) {
509 var proto = theClass.prototype;
513 for (var methodName in proto) {
514 var m = proto[methodName];
516 if (typeof m !== 'function') {
520 if (methodName === 'constructor') {
524 methods.push(methodName);
530 Utils.Decorate = function (SuperClass, DecoratorClass) {
531 var decoratedMethods = getMethods(DecoratorClass);
532 var superMethods = getMethods(SuperClass);
534 function DecoratedClass () {
535 var unshift = Array.prototype.unshift;
537 var argCount = DecoratorClass.prototype.constructor.length;
539 var calledConstructor = SuperClass.prototype.constructor;
542 unshift.call(arguments, SuperClass.prototype.constructor);
544 calledConstructor = DecoratorClass.prototype.constructor;
547 calledConstructor.apply(this, arguments);
550 DecoratorClass.displayName = SuperClass.displayName;
553 this.constructor = DecoratedClass;
556 DecoratedClass.prototype = new ctr();
558 for (var m = 0; m < superMethods.length; m++) {
559 var superMethod = superMethods[m];
561 DecoratedClass.prototype[superMethod] =
562 SuperClass.prototype[superMethod];
565 var calledMethod = function (methodName) {
566 // Stub out the original method if it's not decorating an actual method
567 var originalMethod = function () {};
569 if (methodName in DecoratedClass.prototype) {
570 originalMethod = DecoratedClass.prototype[methodName];
573 var decoratedMethod = DecoratorClass.prototype[methodName];
576 var unshift = Array.prototype.unshift;
578 unshift.call(arguments, originalMethod);
580 return decoratedMethod.apply(this, arguments);
584 for (var d = 0; d < decoratedMethods.length; d++) {
585 var decoratedMethod = decoratedMethods[d];
587 DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
590 return DecoratedClass;
593 var Observable = function () {
597 Observable.prototype.on = function (event, callback) {
598 this.listeners = this.listeners || {};
600 if (event in this.listeners) {
601 this.listeners[event].push(callback);
603 this.listeners[event] = [callback];
607 Observable.prototype.trigger = function (event) {
608 var slice = Array.prototype.slice;
609 var params = slice.call(arguments, 1);
611 this.listeners = this.listeners || {};
613 // Params should always come in as an array
614 if (params == null) {
618 // If there are no arguments to the event, use a temporary object
619 if (params.length === 0) {
623 // Set the `_type` of the first object to the event
624 params[0]._type = event;
626 if (event in this.listeners) {
627 this.invoke(this.listeners[event], slice.call(arguments, 1));
630 if ('*' in this.listeners) {
631 this.invoke(this.listeners['*'], arguments);
635 Observable.prototype.invoke = function (listeners, params) {
636 for (var i = 0, len = listeners.length; i < len; i++) {
637 listeners[i].apply(this, params);
641 Utils.Observable = Observable;
643 Utils.generateChars = function (length) {
646 for (var i = 0; i < length; i++) {
647 var randomChar = Math.floor(Math.random() * 36);
648 chars += randomChar.toString(36);
654 Utils.bind = function (func, context) {
656 func.apply(context, arguments);
660 Utils._convertData = function (data) {
661 for (var originalKey in data) {
662 var keys = originalKey.split('-');
664 var dataLevel = data;
666 if (keys.length === 1) {
670 for (var k = 0; k < keys.length; k++) {
673 // Lowercase the first letter
674 // By default, dash-separated becomes camelCase
675 key = key.substring(0, 1).toLowerCase() + key.substring(1);
677 if (!(key in dataLevel)) {
681 if (k == keys.length - 1) {
682 dataLevel[key] = data[originalKey];
685 dataLevel = dataLevel[key];
688 delete data[originalKey];
694 Utils.hasScroll = function (index, el) {
695 // Adapted from the function created by @ShadowScripter
696 // and adapted by @BillBarry on the Stack Exchange Code Review website.
697 // The original code can be found at
698 // http://codereview.stackexchange.com/q/13338
699 // and was designed to be used with the Sizzle selector engine.
702 var overflowX = el.style.overflowX;
703 var overflowY = el.style.overflowY;
705 //Check both x and y declarations
706 if (overflowX === overflowY &&
707 (overflowY === 'hidden' || overflowY === 'visible')) {
711 if (overflowX === 'scroll' || overflowY === 'scroll') {
715 return ($el.innerHeight() < el.scrollHeight ||
716 $el.innerWidth() < el.scrollWidth);
719 Utils.escapeMarkup = function (markup) {
730 // Do not try to escape the markup if it's not a string
731 if (typeof markup !== 'string') {
735 return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
736 return replaceMap[match];
740 // Append an array of jQuery nodes to a given element.
741 Utils.appendMany = function ($element, $nodes) {
742 // jQuery 1.7.x does not support $.fn.append() with an array
743 // Fall back to a jQuery object collection using $.fn.add()
744 if ($.fn.jquery.substr(0, 3) === '1.7') {
747 $.map($nodes, function (node) {
748 $jqNodes = $jqNodes.add(node);
754 $element.append($nodes);
760 S2.define('select2/results',[
763 ], function ($, Utils) {
764 function Results ($element, options, dataAdapter) {
765 this.$element = $element;
766 this.data = dataAdapter;
767 this.options = options;
769 Results.__super__.constructor.call(this);
772 Utils.Extend(Results, Utils.Observable);
774 Results.prototype.render = function () {
776 '<ul class="select2-results__options" role="tree"></ul>'
779 if (this.options.get('multiple')) {
780 $results.attr('aria-multiselectable', 'true');
783 this.$results = $results;
788 Results.prototype.clear = function () {
789 this.$results.empty();
792 Results.prototype.displayMessage = function (params) {
793 var escapeMarkup = this.options.get('escapeMarkup');
799 '<li role="treeitem" aria-live="assertive"' +
800 ' class="select2-results__option"></li>'
803 var message = this.options.get('translations').get(params.message);
811 $message[0].className += ' select2-results__message';
813 this.$results.append($message);
816 Results.prototype.hideMessages = function () {
817 this.$results.find('.select2-results__message').remove();
820 Results.prototype.append = function (data) {
825 if (data.results == null || data.results.length === 0) {
826 if (this.$results.children().length === 0) {
827 this.trigger('results:message', {
835 data.results = this.sort(data.results);
837 for (var d = 0; d < data.results.length; d++) {
838 var item = data.results[d];
840 var $option = this.option(item);
842 $options.push($option);
845 this.$results.append($options);
848 Results.prototype.position = function ($results, $dropdown) {
849 var $resultsContainer = $dropdown.find('.select2-results');
850 $resultsContainer.append($results);
853 Results.prototype.sort = function (data) {
854 var sorter = this.options.get('sorter');
859 Results.prototype.highlightFirstItem = function () {
860 var $options = this.$results
861 .find('.select2-results__option[aria-selected]');
863 var $selected = $options.filter('[aria-selected=true]');
865 // Check if there are any selected options
866 if ($selected.length > 0) {
867 // If there are selected options, highlight the first
868 $selected.first().trigger('mouseenter');
870 // If there are no selected options, highlight the first option
872 $options.first().trigger('mouseenter');
875 this.ensureHighlightVisible();
878 Results.prototype.setClasses = function () {
881 this.data.current(function (selected) {
882 var selectedIds = $.map(selected, function (s) {
883 return s.id.toString();
886 var $options = self.$results
887 .find('.select2-results__option[aria-selected]');
889 $options.each(function () {
890 var $option = $(this);
892 var item = $.data(this, 'data');
894 // id needs to be converted to a string when comparing
895 var id = '' + item.id;
897 if ((item.element != null && item.element.selected) ||
898 (item.element == null && $.inArray(id, selectedIds) > -1)) {
899 $option.attr('aria-selected', 'true');
901 $option.attr('aria-selected', 'false');
908 Results.prototype.showLoading = function (params) {
911 var loadingMore = this.options.get('translations').get('searching');
916 text: loadingMore(params)
918 var $loading = this.option(loading);
919 $loading.className += ' loading-results';
921 this.$results.prepend($loading);
924 Results.prototype.hideLoading = function () {
925 this.$results.find('.loading-results').remove();
928 Results.prototype.option = function (data) {
929 var option = document.createElement('li');
930 option.className = 'select2-results__option';
934 'aria-selected': 'false'
938 delete attrs['aria-selected'];
939 attrs['aria-disabled'] = 'true';
942 if (data.id == null) {
943 delete attrs['aria-selected'];
946 if (data._resultId != null) {
947 option.id = data._resultId;
951 option.title = data.title;
955 attrs.role = 'group';
956 attrs['aria-label'] = data.text;
957 delete attrs['aria-selected'];
960 for (var attr in attrs) {
961 var val = attrs[attr];
963 option.setAttribute(attr, val);
967 var $option = $(option);
969 var label = document.createElement('strong');
970 label.className = 'select2-results__group';
972 var $label = $(label);
973 this.template(data, label);
977 for (var c = 0; c < data.children.length; c++) {
978 var child = data.children[c];
980 var $child = this.option(child);
982 $children.push($child);
985 var $childrenContainer = $('<ul></ul>', {
986 'class': 'select2-results__options select2-results__options--nested'
989 $childrenContainer.append($children);
991 $option.append(label);
992 $option.append($childrenContainer);
994 this.template(data, option);
997 $.data(option, 'data', data);
1002 Results.prototype.bind = function (container, $container) {
1005 var id = container.id + '-results';
1007 this.$results.attr('id', id);
1009 container.on('results:all', function (params) {
1011 self.append(params.data);
1013 if (container.isOpen()) {
1015 self.highlightFirstItem();
1019 container.on('results:append', function (params) {
1020 self.append(params.data);
1022 if (container.isOpen()) {
1027 container.on('query', function (params) {
1028 self.hideMessages();
1029 self.showLoading(params);
1032 container.on('select', function () {
1033 if (!container.isOpen()) {
1038 self.highlightFirstItem();
1041 container.on('unselect', function () {
1042 if (!container.isOpen()) {
1047 self.highlightFirstItem();
1050 container.on('open', function () {
1051 // When the dropdown is open, aria-expended="true"
1052 self.$results.attr('aria-expanded', 'true');
1053 self.$results.attr('aria-hidden', 'false');
1056 self.ensureHighlightVisible();
1059 container.on('close', function () {
1060 // When the dropdown is closed, aria-expended="false"
1061 self.$results.attr('aria-expanded', 'false');
1062 self.$results.attr('aria-hidden', 'true');
1063 self.$results.removeAttr('aria-activedescendant');
1066 container.on('results:toggle', function () {
1067 var $highlighted = self.getHighlightedResults();
1069 if ($highlighted.length === 0) {
1073 $highlighted.trigger('mouseup');
1076 container.on('results:select', function () {
1077 var $highlighted = self.getHighlightedResults();
1079 if ($highlighted.length === 0) {
1083 var data = $highlighted.data('data');
1085 if ($highlighted.attr('aria-selected') == 'true') {
1086 self.trigger('close', {});
1088 self.trigger('select', {
1094 container.on('results:previous', function () {
1095 var $highlighted = self.getHighlightedResults();
1097 var $options = self.$results.find('[aria-selected]');
1099 var currentIndex = $options.index($highlighted);
1101 // If we are already at te top, don't move further
1102 if (currentIndex === 0) {
1106 var nextIndex = currentIndex - 1;
1108 // If none are highlighted, highlight the first
1109 if ($highlighted.length === 0) {
1113 var $next = $options.eq(nextIndex);
1115 $next.trigger('mouseenter');
1117 var currentOffset = self.$results.offset().top;
1118 var nextTop = $next.offset().top;
1119 var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
1121 if (nextIndex === 0) {
1122 self.$results.scrollTop(0);
1123 } else if (nextTop - currentOffset < 0) {
1124 self.$results.scrollTop(nextOffset);
1128 container.on('results:next', function () {
1129 var $highlighted = self.getHighlightedResults();
1131 var $options = self.$results.find('[aria-selected]');
1133 var currentIndex = $options.index($highlighted);
1135 var nextIndex = currentIndex + 1;
1137 // If we are at the last option, stay there
1138 if (nextIndex >= $options.length) {
1142 var $next = $options.eq(nextIndex);
1144 $next.trigger('mouseenter');
1146 var currentOffset = self.$results.offset().top +
1147 self.$results.outerHeight(false);
1148 var nextBottom = $next.offset().top + $next.outerHeight(false);
1149 var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
1151 if (nextIndex === 0) {
1152 self.$results.scrollTop(0);
1153 } else if (nextBottom > currentOffset) {
1154 self.$results.scrollTop(nextOffset);
1158 container.on('results:focus', function (params) {
1159 params.element.addClass('select2-results__option--highlighted');
1162 container.on('results:message', function (params) {
1163 self.displayMessage(params);
1166 if ($.fn.mousewheel) {
1167 this.$results.on('mousewheel', function (e) {
1168 var top = self.$results.scrollTop();
1170 var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
1172 var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
1173 var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
1176 self.$results.scrollTop(0);
1179 e.stopPropagation();
1180 } else if (isAtBottom) {
1181 self.$results.scrollTop(
1182 self.$results.get(0).scrollHeight - self.$results.height()
1186 e.stopPropagation();
1191 this.$results.on('mouseup', '.select2-results__option[aria-selected]',
1193 var $this = $(this);
1195 var data = $this.data('data');
1197 if ($this.attr('aria-selected') === 'true') {
1198 if (self.options.get('multiple')) {
1199 self.trigger('unselect', {
1204 self.trigger('close', {});
1210 self.trigger('select', {
1216 this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
1218 var data = $(this).data('data');
1220 self.getHighlightedResults()
1221 .removeClass('select2-results__option--highlighted');
1223 self.trigger('results:focus', {
1230 Results.prototype.getHighlightedResults = function () {
1231 var $highlighted = this.$results
1232 .find('.select2-results__option--highlighted');
1234 return $highlighted;
1237 Results.prototype.destroy = function () {
1238 this.$results.remove();
1241 Results.prototype.ensureHighlightVisible = function () {
1242 var $highlighted = this.getHighlightedResults();
1244 if ($highlighted.length === 0) {
1248 var $options = this.$results.find('[aria-selected]');
1250 var currentIndex = $options.index($highlighted);
1252 var currentOffset = this.$results.offset().top;
1253 var nextTop = $highlighted.offset().top;
1254 var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
1256 var offsetDelta = nextTop - currentOffset;
1257 nextOffset -= $highlighted.outerHeight(false) * 2;
1259 if (currentIndex <= 2) {
1260 this.$results.scrollTop(0);
1261 } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
1262 this.$results.scrollTop(nextOffset);
1266 Results.prototype.template = function (result, container) {
1267 var template = this.options.get('templateResult');
1268 var escapeMarkup = this.options.get('escapeMarkup');
1270 var content = template(result, container);
1272 if (content == null) {
1273 container.style.display = 'none';
1274 } else if (typeof content === 'string') {
1275 container.innerHTML = escapeMarkup(content);
1277 $(container).append(content);
1284 S2.define('select2/keys',[
1310 S2.define('select2/selection/base',[
1314 ], function ($, Utils, KEYS) {
1315 function BaseSelection ($element, options) {
1316 this.$element = $element;
1317 this.options = options;
1319 BaseSelection.__super__.constructor.call(this);
1322 Utils.Extend(BaseSelection, Utils.Observable);
1324 BaseSelection.prototype.render = function () {
1326 '<span class="select2-selection" role="combobox" ' +
1327 ' aria-haspopup="true" aria-expanded="false">' +
1333 if (this.$element.data('old-tabindex') != null) {
1334 this._tabindex = this.$element.data('old-tabindex');
1335 } else if (this.$element.attr('tabindex') != null) {
1336 this._tabindex = this.$element.attr('tabindex');
1339 $selection.attr('title', this.$element.attr('title'));
1340 $selection.attr('tabindex', this._tabindex);
1342 this.$selection = $selection;
1347 BaseSelection.prototype.bind = function (container, $container) {
1350 var id = container.id + '-container';
1351 var resultsId = container.id + '-results';
1353 this.container = container;
1355 this.$selection.on('focus', function (evt) {
1356 self.trigger('focus', evt);
1359 this.$selection.on('blur', function (evt) {
1360 self._handleBlur(evt);
1363 this.$selection.on('keydown', function (evt) {
1364 self.trigger('keypress', evt);
1366 if (evt.which === KEYS.SPACE) {
1367 evt.preventDefault();
1371 container.on('results:focus', function (params) {
1372 self.$selection.attr('aria-activedescendant', params.data._resultId);
1375 container.on('selection:update', function (params) {
1376 self.update(params.data);
1379 container.on('open', function () {
1380 // When the dropdown is open, aria-expanded="true"
1381 self.$selection.attr('aria-expanded', 'true');
1382 self.$selection.attr('aria-owns', resultsId);
1384 self._attachCloseHandler(container);
1387 container.on('close', function () {
1388 // When the dropdown is closed, aria-expanded="false"
1389 self.$selection.attr('aria-expanded', 'false');
1390 self.$selection.removeAttr('aria-activedescendant');
1391 self.$selection.removeAttr('aria-owns');
1393 self.$selection.focus();
1395 self._detachCloseHandler(container);
1398 container.on('enable', function () {
1399 self.$selection.attr('tabindex', self._tabindex);
1402 container.on('disable', function () {
1403 self.$selection.attr('tabindex', '-1');
1407 BaseSelection.prototype._handleBlur = function (evt) {
1410 // This needs to be delayed as the active element is the body when the tab
1411 // key is pressed, possibly along with others.
1412 window.setTimeout(function () {
1413 // Don't trigger `blur` if the focus is still in the selection
1415 (document.activeElement == self.$selection[0]) ||
1416 ($.contains(self.$selection[0], document.activeElement))
1421 self.trigger('blur', evt);
1425 BaseSelection.prototype._attachCloseHandler = function (container) {
1428 $(document.body).on('mousedown.select2.' + container.id, function (e) {
1429 var $target = $(e.target);
1431 var $select = $target.closest('.select2');
1433 var $all = $('.select2.select2-container--open');
1435 $all.each(function () {
1436 var $this = $(this);
1438 if (this == $select[0]) {
1442 var $element = $this.data('element');
1444 $element.select2('close');
1449 BaseSelection.prototype._detachCloseHandler = function (container) {
1450 $(document.body).off('mousedown.select2.' + container.id);
1453 BaseSelection.prototype.position = function ($selection, $container) {
1454 var $selectionContainer = $container.find('.selection');
1455 $selectionContainer.append($selection);
1458 BaseSelection.prototype.destroy = function () {
1459 this._detachCloseHandler(this.container);
1462 BaseSelection.prototype.update = function (data) {
1463 throw new Error('The `update` method must be defined in child classes.');
1466 return BaseSelection;
1469 S2.define('select2/selection/single',[
1474 ], function ($, BaseSelection, Utils, KEYS) {
1475 function SingleSelection () {
1476 SingleSelection.__super__.constructor.apply(this, arguments);
1479 Utils.Extend(SingleSelection, BaseSelection);
1481 SingleSelection.prototype.render = function () {
1482 var $selection = SingleSelection.__super__.render.call(this);
1484 $selection.addClass('select2-selection--single');
1487 '<span class="select2-selection__rendered"></span>' +
1488 '<span class="select2-selection__arrow" role="presentation">' +
1489 '<b role="presentation"></b>' +
1496 SingleSelection.prototype.bind = function (container, $container) {
1499 SingleSelection.__super__.bind.apply(this, arguments);
1501 var id = container.id + '-container';
1503 this.$selection.find('.select2-selection__rendered').attr('id', id);
1504 this.$selection.attr('aria-labelledby', id);
1506 this.$selection.on('mousedown', function (evt) {
1507 // Only respond to left clicks
1508 if (evt.which !== 1) {
1512 self.trigger('toggle', {
1517 this.$selection.on('focus', function (evt) {
1518 // User focuses on the container
1521 this.$selection.on('blur', function (evt) {
1522 // User exits the container
1525 container.on('focus', function (evt) {
1526 if (!container.isOpen()) {
1527 self.$selection.focus();
1531 container.on('selection:update', function (params) {
1532 self.update(params.data);
1536 SingleSelection.prototype.clear = function () {
1537 this.$selection.find('.select2-selection__rendered').empty();
1540 SingleSelection.prototype.display = function (data, container) {
1541 var template = this.options.get('templateSelection');
1542 var escapeMarkup = this.options.get('escapeMarkup');
1544 return escapeMarkup(template(data, container));
1547 SingleSelection.prototype.selectionContainer = function () {
1548 return $('<span></span>');
1551 SingleSelection.prototype.update = function (data) {
1552 if (data.length === 0) {
1557 var selection = data[0];
1559 var $rendered = this.$selection.find('.select2-selection__rendered');
1560 var formatted = this.display(selection, $rendered);
1562 $rendered.empty().append(formatted);
1563 $rendered.prop('title', selection.title || selection.text);
1566 return SingleSelection;
1569 S2.define('select2/selection/multiple',[
1573 ], function ($, BaseSelection, Utils) {
1574 function MultipleSelection ($element, options) {
1575 MultipleSelection.__super__.constructor.apply(this, arguments);
1578 Utils.Extend(MultipleSelection, BaseSelection);
1580 MultipleSelection.prototype.render = function () {
1581 var $selection = MultipleSelection.__super__.render.call(this);
1583 $selection.addClass('select2-selection--multiple');
1586 '<ul class="select2-selection__rendered"></ul>'
1592 MultipleSelection.prototype.bind = function (container, $container) {
1595 MultipleSelection.__super__.bind.apply(this, arguments);
1597 this.$selection.on('click', function (evt) {
1598 self.trigger('toggle', {
1605 '.select2-selection__choice__remove',
1607 // Ignore the event if it is disabled
1608 if (self.options.get('disabled')) {
1612 var $remove = $(this);
1613 var $selection = $remove.parent();
1615 var data = $selection.data('data');
1617 self.trigger('unselect', {
1625 MultipleSelection.prototype.clear = function () {
1626 this.$selection.find('.select2-selection__rendered').empty();
1629 MultipleSelection.prototype.display = function (data, container) {
1630 var template = this.options.get('templateSelection');
1631 var escapeMarkup = this.options.get('escapeMarkup');
1633 return escapeMarkup(template(data, container));
1636 MultipleSelection.prototype.selectionContainer = function () {
1638 '<li class="select2-selection__choice">' +
1639 '<span class="select2-selection__choice__remove" role="presentation">' +
1648 MultipleSelection.prototype.update = function (data) {
1651 if (data.length === 0) {
1655 var $selections = [];
1657 for (var d = 0; d < data.length; d++) {
1658 var selection = data[d];
1660 var $selection = this.selectionContainer();
1661 var formatted = this.display(selection, $selection);
1663 $selection.append(formatted);
1664 $selection.prop('title', selection.title || selection.text);
1666 $selection.data('data', selection);
1668 $selections.push($selection);
1671 var $rendered = this.$selection.find('.select2-selection__rendered');
1673 Utils.appendMany($rendered, $selections);
1676 return MultipleSelection;
1679 S2.define('select2/selection/placeholder',[
1681 ], function (Utils) {
1682 function Placeholder (decorated, $element, options) {
1683 this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
1685 decorated.call(this, $element, options);
1688 Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
1689 if (typeof placeholder === 'string') {
1699 Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
1700 var $placeholder = this.selectionContainer();
1702 $placeholder.html(this.display(placeholder));
1703 $placeholder.addClass('select2-selection__placeholder')
1704 .removeClass('select2-selection__choice');
1706 return $placeholder;
1709 Placeholder.prototype.update = function (decorated, data) {
1710 var singlePlaceholder = (
1711 data.length == 1 && data[0].id != this.placeholder.id
1713 var multipleSelections = data.length > 1;
1715 if (multipleSelections || singlePlaceholder) {
1716 return decorated.call(this, data);
1721 var $placeholder = this.createPlaceholder(this.placeholder);
1723 this.$selection.find('.select2-selection__rendered').append($placeholder);
1729 S2.define('select2/selection/allowClear',[
1732 ], function ($, KEYS) {
1733 function AllowClear () { }
1735 AllowClear.prototype.bind = function (decorated, container, $container) {
1738 decorated.call(this, container, $container);
1740 if (this.placeholder == null) {
1741 if (this.options.get('debug') && window.console && console.error) {
1743 'Select2: The `allowClear` option should be used in combination ' +
1744 'with the `placeholder` option.'
1749 this.$selection.on('mousedown', '.select2-selection__clear',
1751 self._handleClear(evt);
1754 container.on('keypress', function (evt) {
1755 self._handleKeyboardClear(evt, container);
1759 AllowClear.prototype._handleClear = function (_, evt) {
1760 // Ignore the event if it is disabled
1761 if (this.options.get('disabled')) {
1765 var $clear = this.$selection.find('.select2-selection__clear');
1767 // Ignore the event if nothing has been selected
1768 if ($clear.length === 0) {
1772 evt.stopPropagation();
1774 var data = $clear.data('data');
1776 for (var d = 0; d < data.length; d++) {
1777 var unselectData = {
1781 // Trigger the `unselect` event, so people can prevent it from being
1783 this.trigger('unselect', unselectData);
1785 // If the event was prevented, don't clear it out.
1786 if (unselectData.prevented) {
1791 this.$element.val(this.placeholder.id).trigger('change');
1793 this.trigger('toggle', {});
1796 AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
1797 if (container.isOpen()) {
1801 if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
1802 this._handleClear(evt);
1806 AllowClear.prototype.update = function (decorated, data) {
1807 decorated.call(this, data);
1809 if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
1810 data.length === 0) {
1815 '<span class="select2-selection__clear">' +
1819 $remove.data('data', data);
1821 this.$selection.find('.select2-selection__rendered').prepend($remove);
1827 S2.define('select2/selection/search',[
1831 ], function ($, Utils, KEYS) {
1832 function Search (decorated, $element, options) {
1833 decorated.call(this, $element, options);
1836 Search.prototype.render = function (decorated) {
1838 '<li class="select2-search select2-search--inline">' +
1839 '<input class="select2-search__field" type="search" tabindex="-1"' +
1840 ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
1841 ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
1845 this.$searchContainer = $search;
1846 this.$search = $search.find('input');
1848 var $rendered = decorated.call(this);
1850 this._transferTabIndex();
1855 Search.prototype.bind = function (decorated, container, $container) {
1858 decorated.call(this, container, $container);
1860 container.on('open', function () {
1861 self.$search.trigger('focus');
1864 container.on('close', function () {
1865 self.$search.val('');
1866 self.$search.removeAttr('aria-activedescendant');
1867 self.$search.trigger('focus');
1870 container.on('enable', function () {
1871 self.$search.prop('disabled', false);
1873 self._transferTabIndex();
1876 container.on('disable', function () {
1877 self.$search.prop('disabled', true);
1880 container.on('focus', function (evt) {
1881 self.$search.trigger('focus');
1884 container.on('results:focus', function (params) {
1885 self.$search.attr('aria-activedescendant', params.id);
1888 this.$selection.on('focusin', '.select2-search--inline', function (evt) {
1889 self.trigger('focus', evt);
1892 this.$selection.on('focusout', '.select2-search--inline', function (evt) {
1893 self._handleBlur(evt);
1896 this.$selection.on('keydown', '.select2-search--inline', function (evt) {
1897 evt.stopPropagation();
1899 self.trigger('keypress', evt);
1901 self._keyUpPrevented = evt.isDefaultPrevented();
1903 var key = evt.which;
1905 if (key === KEYS.BACKSPACE && self.$search.val() === '') {
1906 var $previousChoice = self.$searchContainer
1907 .prev('.select2-selection__choice');
1909 if ($previousChoice.length > 0) {
1910 var item = $previousChoice.data('data');
1912 self.searchRemoveChoice(item);
1914 evt.preventDefault();
1919 // Try to detect the IE version should the `documentMode` property that
1920 // is stored on the document. This is only implemented in IE and is
1921 // slightly cleaner than doing a user agent check.
1922 // This property is not available in Edge, but Edge also doesn't have
1924 var msie = document.documentMode;
1925 var disableInputEvents = msie && msie <= 11;
1927 // Workaround for browsers which do not support the `input` event
1928 // This will prevent double-triggering of events for browsers which support
1929 // both the `keyup` and `input` events.
1931 'input.searchcheck',
1932 '.select2-search--inline',
1934 // IE will trigger the `input` event when a placeholder is used on a
1935 // search box. To get around this issue, we are forced to ignore all
1936 // `input` events in IE and keep using `keyup`.
1937 if (disableInputEvents) {
1938 self.$selection.off('input.search input.searchcheck');
1942 // Unbind the duplicated `keyup` event
1943 self.$selection.off('keyup.search');
1948 'keyup.search input.search',
1949 '.select2-search--inline',
1951 // IE will trigger the `input` event when a placeholder is used on a
1952 // search box. To get around this issue, we are forced to ignore all
1953 // `input` events in IE and keep using `keyup`.
1954 if (disableInputEvents && evt.type === 'input') {
1955 self.$selection.off('input.search input.searchcheck');
1959 var key = evt.which;
1961 // We can freely ignore events from modifier keys
1962 if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
1966 // Tabbing will be handled during the `keydown` phase
1967 if (key == KEYS.TAB) {
1971 self.handleSearch(evt);
1977 * This method will transfer the tabindex attribute from the rendered
1978 * selection to the search box. This allows for the search box to be used as
1979 * the primary focus instead of the selection container.
1983 Search.prototype._transferTabIndex = function (decorated) {
1984 this.$search.attr('tabindex', this.$selection.attr('tabindex'));
1985 this.$selection.attr('tabindex', '-1');
1988 Search.prototype.createPlaceholder = function (decorated, placeholder) {
1989 this.$search.attr('placeholder', placeholder.text);
1992 Search.prototype.update = function (decorated, data) {
1993 var searchHadFocus = this.$search[0] == document.activeElement;
1995 this.$search.attr('placeholder', '');
1997 decorated.call(this, data);
1999 this.$selection.find('.select2-selection__rendered')
2000 .append(this.$searchContainer);
2002 this.resizeSearch();
2003 if (searchHadFocus) {
2004 this.$search.focus();
2008 Search.prototype.handleSearch = function () {
2009 this.resizeSearch();
2011 if (!this._keyUpPrevented) {
2012 var input = this.$search.val();
2014 this.trigger('query', {
2019 this._keyUpPrevented = false;
2022 Search.prototype.searchRemoveChoice = function (decorated, item) {
2023 this.trigger('unselect', {
2027 this.$search.val(item.text);
2028 this.handleSearch();
2031 Search.prototype.resizeSearch = function () {
2032 this.$search.css('width', '25px');
2036 if (this.$search.attr('placeholder') !== '') {
2037 width = this.$selection.find('.select2-selection__rendered').innerWidth();
2039 var minimumWidth = this.$search.val().length + 1;
2041 width = (minimumWidth * 0.75) + 'em';
2044 this.$search.css('width', width);
2050 S2.define('select2/selection/eventRelay',[
2053 function EventRelay () { }
2055 EventRelay.prototype.bind = function (decorated, container, $container) {
2060 'select', 'selecting',
2061 'unselect', 'unselecting'
2064 var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
2066 decorated.call(this, container, $container);
2068 container.on('*', function (name, params) {
2069 // Ignore events that should not be relayed
2070 if ($.inArray(name, relayEvents) === -1) {
2074 // The parameters should always be an object
2075 params = params || {};
2077 // Generate the jQuery event for the Select2 event
2078 var evt = $.Event('select2:' + name, {
2082 self.$element.trigger(evt);
2084 // Only handle preventable events if it was one
2085 if ($.inArray(name, preventableEvents) === -1) {
2089 params.prevented = evt.isDefaultPrevented();
2096 S2.define('select2/translation',[
2099 ], function ($, require) {
2100 function Translation (dict) {
2101 this.dict = dict || {};
2104 Translation.prototype.all = function () {
2108 Translation.prototype.get = function (key) {
2109 return this.dict[key];
2112 Translation.prototype.extend = function (translation) {
2113 this.dict = $.extend({}, translation.all(), this.dict);
2118 Translation._cache = {};
2120 Translation.loadPath = function (path) {
2121 if (!(path in Translation._cache)) {
2122 var translations = require(path);
2124 Translation._cache[path] = translations;
2127 return new Translation(Translation._cache[path]);
2133 S2.define('select2/diacritics',[
2981 S2.define('select2/data/base',[
2983 ], function (Utils) {
2984 function BaseAdapter ($element, options) {
2985 BaseAdapter.__super__.constructor.call(this);
2988 Utils.Extend(BaseAdapter, Utils.Observable);
2990 BaseAdapter.prototype.current = function (callback) {
2991 throw new Error('The `current` method must be defined in child classes.');
2994 BaseAdapter.prototype.query = function (params, callback) {
2995 throw new Error('The `query` method must be defined in child classes.');
2998 BaseAdapter.prototype.bind = function (container, $container) {
2999 // Can be implemented in subclasses
3002 BaseAdapter.prototype.destroy = function () {
3003 // Can be implemented in subclasses
3006 BaseAdapter.prototype.generateResultId = function (container, data) {
3007 var id = container.id + '-result-';
3009 id += Utils.generateChars(4);
3011 if (data.id != null) {
3012 id += '-' + data.id.toString();
3014 id += '-' + Utils.generateChars(4);
3022 S2.define('select2/data/select',[
3026 ], function (BaseAdapter, Utils, $) {
3027 function SelectAdapter ($element, options) {
3028 this.$element = $element;
3029 this.options = options;
3031 SelectAdapter.__super__.constructor.call(this);
3034 Utils.Extend(SelectAdapter, BaseAdapter);
3036 SelectAdapter.prototype.current = function (callback) {
3040 this.$element.find(':selected').each(function () {
3041 var $option = $(this);
3043 var option = self.item($option);
3051 SelectAdapter.prototype.select = function (data) {
3054 data.selected = true;
3056 // If data.element is a DOM node, use it instead
3057 if ($(data.element).is('option')) {
3058 data.element.selected = true;
3060 this.$element.trigger('change');
3065 if (this.$element.prop('multiple')) {
3066 this.current(function (currentData) {
3070 data.push.apply(data, currentData);
3072 for (var d = 0; d < data.length; d++) {
3073 var id = data[d].id;
3075 if ($.inArray(id, val) === -1) {
3080 self.$element.val(val);
3081 self.$element.trigger('change');
3086 this.$element.val(val);
3087 this.$element.trigger('change');
3091 SelectAdapter.prototype.unselect = function (data) {
3094 if (!this.$element.prop('multiple')) {
3098 data.selected = false;
3100 if ($(data.element).is('option')) {
3101 data.element.selected = false;
3103 this.$element.trigger('change');
3108 this.current(function (currentData) {
3111 for (var d = 0; d < currentData.length; d++) {
3112 var id = currentData[d].id;
3114 if (id !== data.id && $.inArray(id, val) === -1) {
3119 self.$element.val(val);
3121 self.$element.trigger('change');
3125 SelectAdapter.prototype.bind = function (container, $container) {
3128 this.container = container;
3130 container.on('select', function (params) {
3131 self.select(params.data);
3134 container.on('unselect', function (params) {
3135 self.unselect(params.data);
3139 SelectAdapter.prototype.destroy = function () {
3140 // Remove anything added to child elements
3141 this.$element.find('*').each(function () {
3142 // Remove any custom data set by Select2
3143 $.removeData(this, 'data');
3147 SelectAdapter.prototype.query = function (params, callback) {
3151 var $options = this.$element.children();
3153 $options.each(function () {
3154 var $option = $(this);
3156 if (!$option.is('option') && !$option.is('optgroup')) {
3160 var option = self.item($option);
3162 var matches = self.matches(params, option);
3164 if (matches !== null) {
3174 SelectAdapter.prototype.addOptions = function ($options) {
3175 Utils.appendMany(this.$element, $options);
3178 SelectAdapter.prototype.option = function (data) {
3181 if (data.children) {
3182 option = document.createElement('optgroup');
3183 option.label = data.text;
3185 option = document.createElement('option');
3187 if (option.textContent !== undefined) {
3188 option.textContent = data.text;
3190 option.innerText = data.text;
3195 option.value = data.id;
3198 if (data.disabled) {
3199 option.disabled = true;
3202 if (data.selected) {
3203 option.selected = true;
3207 option.title = data.title;
3210 var $option = $(option);
3212 var normalizedData = this._normalizeItem(data);
3213 normalizedData.element = option;
3215 // Override the option's data with the combined data
3216 $.data(option, 'data', normalizedData);
3221 SelectAdapter.prototype.item = function ($option) {
3224 data = $.data($option[0], 'data');
3230 if ($option.is('option')) {
3233 text: $option.text(),
3234 disabled: $option.prop('disabled'),
3235 selected: $option.prop('selected'),
3236 title: $option.prop('title')
3238 } else if ($option.is('optgroup')) {
3240 text: $option.prop('label'),
3242 title: $option.prop('title')
3245 var $children = $option.children('option');
3248 for (var c = 0; c < $children.length; c++) {
3249 var $child = $($children[c]);
3251 var child = this.item($child);
3253 children.push(child);
3256 data.children = children;
3259 data = this._normalizeItem(data);
3260 data.element = $option[0];
3262 $.data($option[0], 'data', data);
3267 SelectAdapter.prototype._normalizeItem = function (item) {
3268 if (!$.isPlainObject(item)) {
3275 item = $.extend({}, {
3284 if (item.id != null) {
3285 item.id = item.id.toString();
3288 if (item.text != null) {
3289 item.text = item.text.toString();
3292 if (item._resultId == null && item.id && this.container != null) {
3293 item._resultId = this.generateResultId(this.container, item);
3296 return $.extend({}, defaults, item);
3299 SelectAdapter.prototype.matches = function (params, data) {
3300 var matcher = this.options.get('matcher');
3302 return matcher(params, data);
3305 return SelectAdapter;
3308 S2.define('select2/data/array',[
3312 ], function (SelectAdapter, Utils, $) {
3313 function ArrayAdapter ($element, options) {
3314 var data = options.get('data') || [];
3316 ArrayAdapter.__super__.constructor.call(this, $element, options);
3318 this.addOptions(this.convertToOptions(data));
3321 Utils.Extend(ArrayAdapter, SelectAdapter);
3323 ArrayAdapter.prototype.select = function (data) {
3324 var $option = this.$element.find('option').filter(function (i, elm) {
3325 return elm.value == data.id.toString();
3328 if ($option.length === 0) {
3329 $option = this.option(data);
3331 this.addOptions($option);
3334 ArrayAdapter.__super__.select.call(this, data);
3337 ArrayAdapter.prototype.convertToOptions = function (data) {
3340 var $existing = this.$element.find('option');
3341 var existingIds = $existing.map(function () {
3342 return self.item($(this)).id;
3347 // Filter out all items except for the one passed in the argument
3348 function onlyItem (item) {
3349 return function () {
3350 return $(this).val() == item.id;
3354 for (var d = 0; d < data.length; d++) {
3355 var item = this._normalizeItem(data[d]);
3357 // Skip items which were pre-loaded, only merge the data
3358 if ($.inArray(item.id, existingIds) >= 0) {
3359 var $existingOption = $existing.filter(onlyItem(item));
3361 var existingData = this.item($existingOption);
3362 var newData = $.extend(true, {}, item, existingData);
3364 var $newOption = this.option(newData);
3366 $existingOption.replaceWith($newOption);
3371 var $option = this.option(item);
3373 if (item.children) {
3374 var $children = this.convertToOptions(item.children);
3376 Utils.appendMany($option, $children);
3379 $options.push($option);
3385 return ArrayAdapter;
3388 S2.define('select2/data/ajax',[
3392 ], function (ArrayAdapter, Utils, $) {
3393 function AjaxAdapter ($element, options) {
3394 this.ajaxOptions = this._applyDefaults(options.get('ajax'));
3396 if (this.ajaxOptions.processResults != null) {
3397 this.processResults = this.ajaxOptions.processResults;
3400 AjaxAdapter.__super__.constructor.call(this, $element, options);
3403 Utils.Extend(AjaxAdapter, ArrayAdapter);
3405 AjaxAdapter.prototype._applyDefaults = function (options) {
3407 data: function (params) {
3408 return $.extend({}, params, {
3412 transport: function (params, success, failure) {
3413 var $request = $.ajax(params);
3415 $request.then(success);
3416 $request.fail(failure);
3422 return $.extend({}, defaults, options, true);
3425 AjaxAdapter.prototype.processResults = function (results) {
3429 AjaxAdapter.prototype.query = function (params, callback) {
3433 if (this._request != null) {
3434 // JSONP requests cannot always be aborted
3435 if ($.isFunction(this._request.abort)) {
3436 this._request.abort();
3439 this._request = null;
3442 var options = $.extend({
3444 }, this.ajaxOptions);
3446 if (typeof options.url === 'function') {
3447 options.url = options.url.call(this.$element, params);
3450 if (typeof options.data === 'function') {
3451 options.data = options.data.call(this.$element, params);
3454 function request () {
3455 var $request = options.transport(options, function (data) {
3456 var results = self.processResults(data, params);
3458 if (self.options.get('debug') && window.console && console.error) {
3459 // Check to make sure that the response included a `results` key.
3460 if (!results || !results.results || !$.isArray(results.results)) {
3462 'Select2: The AJAX results did not return an array in the ' +
3463 '`results` key of the response.'
3470 // Attempt to detect if a request was aborted
3471 // Only works if the transport exposes a status property
3472 if ($request.status && $request.status === '0') {
3476 self.trigger('results:message', {
3477 message: 'errorLoading'
3481 self._request = $request;
3484 if (this.ajaxOptions.delay && params.term != null) {
3485 if (this._queryTimeout) {
3486 window.clearTimeout(this._queryTimeout);
3489 this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
3498 S2.define('select2/data/tags',[
3501 function Tags (decorated, $element, options) {
3502 var tags = options.get('tags');
3504 var createTag = options.get('createTag');
3506 if (createTag !== undefined) {
3507 this.createTag = createTag;
3510 var insertTag = options.get('insertTag');
3512 if (insertTag !== undefined) {
3513 this.insertTag = insertTag;
3516 decorated.call(this, $element, options);
3518 if ($.isArray(tags)) {
3519 for (var t = 0; t < tags.length; t++) {
3521 var item = this._normalizeItem(tag);
3523 var $option = this.option(item);
3525 this.$element.append($option);
3530 Tags.prototype.query = function (decorated, params, callback) {
3533 this._removeOldTags();
3535 if (params.term == null || params.page != null) {
3536 decorated.call(this, params, callback);
3540 function wrapper (obj, child) {
3541 var data = obj.results;
3543 for (var i = 0; i < data.length; i++) {
3544 var option = data[i];
3546 var checkChildren = (
3547 option.children != null &&
3549 results: option.children
3553 var checkText = option.text === params.term;
3555 if (checkText || checkChildren) {
3571 var tag = self.createTag(params);
3574 var $option = self.option(tag);
3575 $option.attr('data-select2-tag', true);
3577 self.addOptions([$option]);
3579 self.insertTag(data, tag);
3587 decorated.call(this, params, wrapper);
3590 Tags.prototype.createTag = function (decorated, params) {
3591 var term = $.trim(params.term);
3603 Tags.prototype.insertTag = function (_, data, tag) {
3607 Tags.prototype._removeOldTags = function (_) {
3608 var tag = this._lastTag;
3610 var $options = this.$element.find('option[data-select2-tag]');
3612 $options.each(function () {
3613 if (this.selected) {
3624 S2.define('select2/data/tokenizer',[
3627 function Tokenizer (decorated, $element, options) {
3628 var tokenizer = options.get('tokenizer');
3630 if (tokenizer !== undefined) {
3631 this.tokenizer = tokenizer;
3634 decorated.call(this, $element, options);
3637 Tokenizer.prototype.bind = function (decorated, container, $container) {
3638 decorated.call(this, container, $container);
3640 this.$search = container.dropdown.$search || container.selection.$search ||
3641 $container.find('.select2-search__field');
3644 Tokenizer.prototype.query = function (decorated, params, callback) {
3647 function createAndSelect (data) {
3648 // Normalize the data object so we can use it for checks
3649 var item = self._normalizeItem(data);
3651 // Check if the data object already exists as a tag
3652 // Select it if it doesn't
3653 var $existingOptions = self.$element.find('option').filter(function () {
3654 return $(this).val() === item.id;
3657 // If an existing option wasn't found for it, create the option
3658 if (!$existingOptions.length) {
3659 var $option = self.option(item);
3660 $option.attr('data-select2-tag', true);
3662 self._removeOldTags();
3663 self.addOptions([$option]);
3666 // Select the item, now that we know there is an option for it
3670 function select (data) {
3671 self.trigger('select', {
3676 params.term = params.term || '';
3678 var tokenData = this.tokenizer(params, this.options, createAndSelect);
3680 if (tokenData.term !== params.term) {
3681 // Replace the search term if we have the search box
3682 if (this.$search.length) {
3683 this.$search.val(tokenData.term);
3684 this.$search.focus();
3687 params.term = tokenData.term;
3690 decorated.call(this, params, callback);
3693 Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
3694 var separators = options.get('tokenSeparators') || [];
3695 var term = params.term;
3698 var createTag = this.createTag || function (params) {
3705 while (i < term.length) {
3706 var termChar = term[i];
3708 if ($.inArray(termChar, separators) === -1) {
3714 var part = term.substr(0, i);
3715 var partParams = $.extend({}, params, {
3719 var data = createTag(partParams);
3728 // Reset the term to not include the tokenized portion
3729 term = term.substr(i + 1) || '';
3741 S2.define('select2/data/minimumInputLength',[
3744 function MinimumInputLength (decorated, $e, options) {
3745 this.minimumInputLength = options.get('minimumInputLength');
3747 decorated.call(this, $e, options);
3750 MinimumInputLength.prototype.query = function (decorated, params, callback) {
3751 params.term = params.term || '';
3753 if (params.term.length < this.minimumInputLength) {
3754 this.trigger('results:message', {
3755 message: 'inputTooShort',
3757 minimum: this.minimumInputLength,
3766 decorated.call(this, params, callback);
3769 return MinimumInputLength;
3772 S2.define('select2/data/maximumInputLength',[
3775 function MaximumInputLength (decorated, $e, options) {
3776 this.maximumInputLength = options.get('maximumInputLength');
3778 decorated.call(this, $e, options);
3781 MaximumInputLength.prototype.query = function (decorated, params, callback) {
3782 params.term = params.term || '';
3784 if (this.maximumInputLength > 0 &&
3785 params.term.length > this.maximumInputLength) {
3786 this.trigger('results:message', {
3787 message: 'inputTooLong',
3789 maximum: this.maximumInputLength,
3798 decorated.call(this, params, callback);
3801 return MaximumInputLength;
3804 S2.define('select2/data/maximumSelectionLength',[
3807 function MaximumSelectionLength (decorated, $e, options) {
3808 this.maximumSelectionLength = options.get('maximumSelectionLength');
3810 decorated.call(this, $e, options);
3813 MaximumSelectionLength.prototype.query =
3814 function (decorated, params, callback) {
3817 this.current(function (currentData) {
3818 var count = currentData != null ? currentData.length : 0;
3819 if (self.maximumSelectionLength > 0 &&
3820 count >= self.maximumSelectionLength) {
3821 self.trigger('results:message', {
3822 message: 'maximumSelected',
3824 maximum: self.maximumSelectionLength
3829 decorated.call(self, params, callback);
3833 return MaximumSelectionLength;
3836 S2.define('select2/dropdown',[
3839 ], function ($, Utils) {
3840 function Dropdown ($element, options) {
3841 this.$element = $element;
3842 this.options = options;
3844 Dropdown.__super__.constructor.call(this);
3847 Utils.Extend(Dropdown, Utils.Observable);
3849 Dropdown.prototype.render = function () {
3851 '<span class="select2-dropdown">' +
3852 '<span class="select2-results"></span>' +
3856 $dropdown.attr('dir', this.options.get('dir'));
3858 this.$dropdown = $dropdown;
3863 Dropdown.prototype.bind = function () {
3864 // Should be implemented in subclasses
3867 Dropdown.prototype.position = function ($dropdown, $container) {
3868 // Should be implmented in subclasses
3871 Dropdown.prototype.destroy = function () {
3872 // Remove the dropdown from the DOM
3873 this.$dropdown.remove();
3879 S2.define('select2/dropdown/search',[
3882 ], function ($, Utils) {
3883 function Search () { }
3885 Search.prototype.render = function (decorated) {
3886 var $rendered = decorated.call(this);
3889 '<span class="select2-search select2-search--dropdown">' +
3890 '<input class="select2-search__field" type="search" tabindex="-1"' +
3891 ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
3892 ' spellcheck="false" role="textbox" />' +
3896 this.$searchContainer = $search;
3897 this.$search = $search.find('input');
3899 $rendered.prepend($search);
3904 Search.prototype.bind = function (decorated, container, $container) {
3907 decorated.call(this, container, $container);
3909 this.$search.on('keydown', function (evt) {
3910 self.trigger('keypress', evt);
3912 self._keyUpPrevented = evt.isDefaultPrevented();
3915 // Workaround for browsers which do not support the `input` event
3916 // This will prevent double-triggering of events for browsers which support
3917 // both the `keyup` and `input` events.
3918 this.$search.on('input', function (evt) {
3919 // Unbind the duplicated `keyup` event
3920 $(this).off('keyup');
3923 this.$search.on('keyup input', function (evt) {
3924 self.handleSearch(evt);
3927 container.on('open', function () {
3928 self.$search.attr('tabindex', 0);
3930 self.$search.focus();
3932 window.setTimeout(function () {
3933 self.$search.focus();
3937 container.on('close', function () {
3938 self.$search.attr('tabindex', -1);
3940 self.$search.val('');
3943 container.on('focus', function () {
3944 if (container.isOpen()) {
3945 self.$search.focus();
3949 container.on('results:all', function (params) {
3950 if (params.query.term == null || params.query.term === '') {
3951 var showSearch = self.showSearch(params);
3954 self.$searchContainer.removeClass('select2-search--hide');
3956 self.$searchContainer.addClass('select2-search--hide');
3962 Search.prototype.handleSearch = function (evt) {
3963 if (!this._keyUpPrevented) {
3964 var input = this.$search.val();
3966 this.trigger('query', {
3971 this._keyUpPrevented = false;
3974 Search.prototype.showSearch = function (_, params) {
3981 S2.define('select2/dropdown/hidePlaceholder',[
3984 function HidePlaceholder (decorated, $element, options, dataAdapter) {
3985 this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
3987 decorated.call(this, $element, options, dataAdapter);
3990 HidePlaceholder.prototype.append = function (decorated, data) {
3991 data.results = this.removePlaceholder(data.results);
3993 decorated.call(this, data);
3996 HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
3997 if (typeof placeholder === 'string') {
4007 HidePlaceholder.prototype.removePlaceholder = function (_, data) {
4008 var modifiedData = data.slice(0);
4010 for (var d = data.length - 1; d >= 0; d--) {
4013 if (this.placeholder.id === item.id) {
4014 modifiedData.splice(d, 1);
4018 return modifiedData;
4021 return HidePlaceholder;
4024 S2.define('select2/dropdown/infiniteScroll',[
4027 function InfiniteScroll (decorated, $element, options, dataAdapter) {
4028 this.lastParams = {};
4030 decorated.call(this, $element, options, dataAdapter);
4032 this.$loadingMore = this.createLoadingMore();
4033 this.loading = false;
4036 InfiniteScroll.prototype.append = function (decorated, data) {
4037 this.$loadingMore.remove();
4038 this.loading = false;
4040 decorated.call(this, data);
4042 if (this.showLoadingMore(data)) {
4043 this.$results.append(this.$loadingMore);
4047 InfiniteScroll.prototype.bind = function (decorated, container, $container) {
4050 decorated.call(this, container, $container);
4052 container.on('query', function (params) {
4053 self.lastParams = params;
4054 self.loading = true;
4057 container.on('query:append', function (params) {
4058 self.lastParams = params;
4059 self.loading = true;
4062 this.$results.on('scroll', function () {
4063 var isLoadMoreVisible = $.contains(
4064 document.documentElement,
4065 self.$loadingMore[0]
4068 if (self.loading || !isLoadMoreVisible) {
4072 var currentOffset = self.$results.offset().top +
4073 self.$results.outerHeight(false);
4074 var loadingMoreOffset = self.$loadingMore.offset().top +
4075 self.$loadingMore.outerHeight(false);
4077 if (currentOffset + 50 >= loadingMoreOffset) {
4083 InfiniteScroll.prototype.loadMore = function () {
4084 this.loading = true;
4086 var params = $.extend({}, {page: 1}, this.lastParams);
4090 this.trigger('query:append', params);
4093 InfiniteScroll.prototype.showLoadingMore = function (_, data) {
4094 return data.pagination && data.pagination.more;
4097 InfiniteScroll.prototype.createLoadingMore = function () {
4100 'class="select2-results__option select2-results__option--load-more"' +
4101 'role="treeitem" aria-disabled="true"></li>'
4104 var message = this.options.get('translations').get('loadingMore');
4106 $option.html(message(this.lastParams));
4111 return InfiniteScroll;
4114 S2.define('select2/dropdown/attachBody',[
4117 ], function ($, Utils) {
4118 function AttachBody (decorated, $element, options) {
4119 this.$dropdownParent = options.get('dropdownParent') || $(document.body);
4121 decorated.call(this, $element, options);
4124 AttachBody.prototype.bind = function (decorated, container, $container) {
4127 var setupResultsEvents = false;
4129 decorated.call(this, container, $container);
4131 container.on('open', function () {
4132 self._showDropdown();
4133 self._attachPositioningHandler(container);
4135 if (!setupResultsEvents) {
4136 setupResultsEvents = true;
4138 container.on('results:all', function () {
4139 self._positionDropdown();
4140 self._resizeDropdown();
4143 container.on('results:append', function () {
4144 self._positionDropdown();
4145 self._resizeDropdown();
4150 container.on('close', function () {
4151 self._hideDropdown();
4152 self._detachPositioningHandler(container);
4155 this.$dropdownContainer.on('mousedown', function (evt) {
4156 evt.stopPropagation();
4160 AttachBody.prototype.destroy = function (decorated) {
4161 decorated.call(this);
4163 this.$dropdownContainer.remove();
4166 AttachBody.prototype.position = function (decorated, $dropdown, $container) {
4167 // Clone all of the container classes
4168 $dropdown.attr('class', $container.attr('class'));
4170 $dropdown.removeClass('select2');
4171 $dropdown.addClass('select2-container--open');
4174 position: 'absolute',
4178 this.$container = $container;
4181 AttachBody.prototype.render = function (decorated) {
4182 var $container = $('<span></span>');
4184 var $dropdown = decorated.call(this);
4185 $container.append($dropdown);
4187 this.$dropdownContainer = $container;
4192 AttachBody.prototype._hideDropdown = function (decorated) {
4193 this.$dropdownContainer.detach();
4196 AttachBody.prototype._attachPositioningHandler =
4197 function (decorated, container) {
4200 var scrollEvent = 'scroll.select2.' + container.id;
4201 var resizeEvent = 'resize.select2.' + container.id;
4202 var orientationEvent = 'orientationchange.select2.' + container.id;
4204 var $watchers = this.$container.parents().filter(Utils.hasScroll);
4205 $watchers.each(function () {
4206 $(this).data('select2-scroll-position', {
4207 x: $(this).scrollLeft(),
4208 y: $(this).scrollTop()
4212 $watchers.on(scrollEvent, function (ev) {
4213 var position = $(this).data('select2-scroll-position');
4214 $(this).scrollTop(position.y);
4217 $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
4219 self._positionDropdown();
4220 self._resizeDropdown();
4224 AttachBody.prototype._detachPositioningHandler =
4225 function (decorated, container) {
4226 var scrollEvent = 'scroll.select2.' + container.id;
4227 var resizeEvent = 'resize.select2.' + container.id;
4228 var orientationEvent = 'orientationchange.select2.' + container.id;
4230 var $watchers = this.$container.parents().filter(Utils.hasScroll);
4231 $watchers.off(scrollEvent);
4233 $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
4236 AttachBody.prototype._positionDropdown = function () {
4237 var $window = $(window);
4239 var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
4240 var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
4242 var newDirection = null;
4244 var offset = this.$container.offset();
4246 offset.bottom = offset.top + this.$container.outerHeight(false);
4249 height: this.$container.outerHeight(false)
4252 container.top = offset.top;
4253 container.bottom = offset.top + container.height;
4256 height: this.$dropdown.outerHeight(false)
4260 top: $window.scrollTop(),
4261 bottom: $window.scrollTop() + $window.height()
4264 var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
4265 var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
4269 top: container.bottom
4272 // Determine what the parent element is to use for calciulating the offset
4273 var $offsetParent = this.$dropdownParent;
4275 // For statically positoned elements, we need to get the element
4276 // that is determining the offset
4277 if ($offsetParent.css('position') === 'static') {
4278 $offsetParent = $offsetParent.offsetParent();
4281 var parentOffset = $offsetParent.offset();
4283 css.top -= parentOffset.top;
4284 css.left -= parentOffset.left;
4286 if (!isCurrentlyAbove && !isCurrentlyBelow) {
4287 newDirection = 'below';
4290 if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
4291 newDirection = 'above';
4292 } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
4293 newDirection = 'below';
4296 if (newDirection == 'above' ||
4297 (isCurrentlyAbove && newDirection !== 'below')) {
4298 css.top = container.top - parentOffset.top - dropdown.height;
4301 if (newDirection != null) {
4303 .removeClass('select2-dropdown--below select2-dropdown--above')
4304 .addClass('select2-dropdown--' + newDirection);
4306 .removeClass('select2-container--below select2-container--above')
4307 .addClass('select2-container--' + newDirection);
4310 this.$dropdownContainer.css(css);
4313 AttachBody.prototype._resizeDropdown = function () {
4315 width: this.$container.outerWidth(false) + 'px'
4318 if (this.options.get('dropdownAutoWidth')) {
4319 css.minWidth = css.width;
4320 css.position = 'relative';
4324 this.$dropdown.css(css);
4327 AttachBody.prototype._showDropdown = function (decorated) {
4328 this.$dropdownContainer.appendTo(this.$dropdownParent);
4330 this._positionDropdown();
4331 this._resizeDropdown();
4337 S2.define('select2/dropdown/minimumResultsForSearch',[
4340 function countResults (data) {
4343 for (var d = 0; d < data.length; d++) {
4346 if (item.children) {
4347 count += countResults(item.children);
4356 function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
4357 this.minimumResultsForSearch = options.get('minimumResultsForSearch');
4359 if (this.minimumResultsForSearch < 0) {
4360 this.minimumResultsForSearch = Infinity;
4363 decorated.call(this, $element, options, dataAdapter);
4366 MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
4367 if (countResults(params.data.results) < this.minimumResultsForSearch) {
4371 return decorated.call(this, params);
4374 return MinimumResultsForSearch;
4377 S2.define('select2/dropdown/selectOnClose',[
4380 function SelectOnClose () { }
4382 SelectOnClose.prototype.bind = function (decorated, container, $container) {
4385 decorated.call(this, container, $container);
4387 container.on('close', function (params) {
4388 self._handleSelectOnClose(params);
4392 SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
4393 if (params && params.originalSelect2Event != null) {
4394 var event = params.originalSelect2Event;
4396 // Don't select an item if the close event was triggered from a select or
4398 if (event._type === 'select' || event._type === 'unselect') {
4403 var $highlightedResults = this.getHighlightedResults();
4405 // Only select highlighted results
4406 if ($highlightedResults.length < 1) {
4410 var data = $highlightedResults.data('data');
4412 // Don't re-select already selected resulte
4414 (data.element != null && data.element.selected) ||
4415 (data.element == null && data.selected)
4420 this.trigger('select', {
4425 return SelectOnClose;
4428 S2.define('select2/dropdown/closeOnSelect',[
4431 function CloseOnSelect () { }
4433 CloseOnSelect.prototype.bind = function (decorated, container, $container) {
4436 decorated.call(this, container, $container);
4438 container.on('select', function (evt) {
4439 self._selectTriggered(evt);
4442 container.on('unselect', function (evt) {
4443 self._selectTriggered(evt);
4447 CloseOnSelect.prototype._selectTriggered = function (_, evt) {
4448 var originalEvent = evt.originalEvent;
4450 // Don't close if the control key is being held
4451 if (originalEvent && originalEvent.ctrlKey) {
4455 this.trigger('close', {
4456 originalEvent: originalEvent,
4457 originalSelect2Event: evt
4461 return CloseOnSelect;
4464 S2.define('select2/i18n/en',[],function () {
4467 errorLoading: function () {
4468 return 'The results could not be loaded.';
4470 inputTooLong: function (args) {
4471 var overChars = args.input.length - args.maximum;
4473 var message = 'Please delete ' + overChars + ' character';
4475 if (overChars != 1) {
4481 inputTooShort: function (args) {
4482 var remainingChars = args.minimum - args.input.length;
4484 var message = 'Please enter ' + remainingChars + ' or more characters';
4488 loadingMore: function () {
4489 return 'Loading more results…';
4491 maximumSelected: function (args) {
4492 var message = 'You can only select ' + args.maximum + ' item';
4494 if (args.maximum != 1) {
4500 noResults: function () {
4501 return 'No results found';
4503 searching: function () {
4504 return 'Searching…';
4509 S2.define('select2/defaults',[
4515 './selection/single',
4516 './selection/multiple',
4517 './selection/placeholder',
4518 './selection/allowClear',
4519 './selection/search',
4520 './selection/eventRelay',
4531 './data/minimumInputLength',
4532 './data/maximumInputLength',
4533 './data/maximumSelectionLength',
4536 './dropdown/search',
4537 './dropdown/hidePlaceholder',
4538 './dropdown/infiniteScroll',
4539 './dropdown/attachBody',
4540 './dropdown/minimumResultsForSearch',
4541 './dropdown/selectOnClose',
4542 './dropdown/closeOnSelect',
4545 ], function ($, require,
4549 SingleSelection, MultipleSelection, Placeholder, AllowClear,
4550 SelectionSearch, EventRelay,
4552 Utils, Translation, DIACRITICS,
4554 SelectData, ArrayData, AjaxData, Tags, Tokenizer,
4555 MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
4557 Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
4558 AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
4560 EnglishTranslation) {
4561 function Defaults () {
4565 Defaults.prototype.apply = function (options) {
4566 options = $.extend(true, {}, this.defaults, options);
4568 if (options.dataAdapter == null) {
4569 if (options.ajax != null) {
4570 options.dataAdapter = AjaxData;
4571 } else if (options.data != null) {
4572 options.dataAdapter = ArrayData;
4574 options.dataAdapter = SelectData;
4577 if (options.minimumInputLength > 0) {
4578 options.dataAdapter = Utils.Decorate(
4579 options.dataAdapter,
4584 if (options.maximumInputLength > 0) {
4585 options.dataAdapter = Utils.Decorate(
4586 options.dataAdapter,
4591 if (options.maximumSelectionLength > 0) {
4592 options.dataAdapter = Utils.Decorate(
4593 options.dataAdapter,
4594 MaximumSelectionLength
4599 options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
4602 if (options.tokenSeparators != null || options.tokenizer != null) {
4603 options.dataAdapter = Utils.Decorate(
4604 options.dataAdapter,
4609 if (options.query != null) {
4610 var Query = require(options.amdBase + 'compat/query');
4612 options.dataAdapter = Utils.Decorate(
4613 options.dataAdapter,
4618 if (options.initSelection != null) {
4619 var InitSelection = require(options.amdBase + 'compat/initSelection');
4621 options.dataAdapter = Utils.Decorate(
4622 options.dataAdapter,
4628 if (options.resultsAdapter == null) {
4629 options.resultsAdapter = ResultsList;
4631 if (options.ajax != null) {
4632 options.resultsAdapter = Utils.Decorate(
4633 options.resultsAdapter,
4638 if (options.placeholder != null) {
4639 options.resultsAdapter = Utils.Decorate(
4640 options.resultsAdapter,
4645 if (options.selectOnClose) {
4646 options.resultsAdapter = Utils.Decorate(
4647 options.resultsAdapter,
4653 if (options.dropdownAdapter == null) {
4654 if (options.multiple) {
4655 options.dropdownAdapter = Dropdown;
4657 var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
4659 options.dropdownAdapter = SearchableDropdown;
4662 if (options.minimumResultsForSearch !== 0) {
4663 options.dropdownAdapter = Utils.Decorate(
4664 options.dropdownAdapter,
4665 MinimumResultsForSearch
4669 if (options.closeOnSelect) {
4670 options.dropdownAdapter = Utils.Decorate(
4671 options.dropdownAdapter,
4677 options.dropdownCssClass != null ||
4678 options.dropdownCss != null ||
4679 options.adaptDropdownCssClass != null
4681 var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
4683 options.dropdownAdapter = Utils.Decorate(
4684 options.dropdownAdapter,
4689 options.dropdownAdapter = Utils.Decorate(
4690 options.dropdownAdapter,
4695 if (options.selectionAdapter == null) {
4696 if (options.multiple) {
4697 options.selectionAdapter = MultipleSelection;
4699 options.selectionAdapter = SingleSelection;
4702 // Add the placeholder mixin if a placeholder was specified
4703 if (options.placeholder != null) {
4704 options.selectionAdapter = Utils.Decorate(
4705 options.selectionAdapter,
4710 if (options.allowClear) {
4711 options.selectionAdapter = Utils.Decorate(
4712 options.selectionAdapter,
4717 if (options.multiple) {
4718 options.selectionAdapter = Utils.Decorate(
4719 options.selectionAdapter,
4725 options.containerCssClass != null ||
4726 options.containerCss != null ||
4727 options.adaptContainerCssClass != null
4729 var ContainerCSS = require(options.amdBase + 'compat/containerCss');
4731 options.selectionAdapter = Utils.Decorate(
4732 options.selectionAdapter,
4737 options.selectionAdapter = Utils.Decorate(
4738 options.selectionAdapter,
4743 if (typeof options.language === 'string') {
4744 // Check if the language is specified with a region
4745 if (options.language.indexOf('-') > 0) {
4746 // Extract the region information if it is included
4747 var languageParts = options.language.split('-');
4748 var baseLanguage = languageParts[0];
4750 options.language = [options.language, baseLanguage];
4752 options.language = [options.language];
4756 if ($.isArray(options.language)) {
4757 var languages = new Translation();
4758 options.language.push('en');
4760 var languageNames = options.language;
4762 for (var l = 0; l < languageNames.length; l++) {
4763 var name = languageNames[l];
4767 // Try to load it with the original name
4768 language = Translation.loadPath(name);
4771 // If we couldn't load it, check if it wasn't the full path
4772 name = this.defaults.amdLanguageBase + name;
4773 language = Translation.loadPath(name);
4775 // The translation could not be loaded at all. Sometimes this is
4776 // because of a configuration problem, other times this can be
4777 // because of how Select2 helps load all possible translation files.
4778 if (options.debug && window.console && console.warn) {
4780 'Select2: The language file for "' + name + '" could not be ' +
4781 'automatically loaded. A fallback will be used instead.'
4789 languages.extend(language);
4792 options.translations = languages;
4794 var baseTranslation = Translation.loadPath(
4795 this.defaults.amdLanguageBase + 'en'
4797 var customTranslation = new Translation(options.language);
4799 customTranslation.extend(baseTranslation);
4801 options.translations = customTranslation;
4807 Defaults.prototype.reset = function () {
4808 function stripDiacritics (text) {
4809 // Used 'uni range + named function' from http://jsperf.com/diacritics/18
4811 return DIACRITICS[a] || a;
4814 return text.replace(/[^\u0000-\u007E]/g, match);
4817 function matcher (params, data) {
4818 // Always return the object if there is nothing to compare
4819 if ($.trim(params.term) === '') {
4823 // Do a recursive check for options with children
4824 if (data.children && data.children.length > 0) {
4825 // Clone the data object if there are children
4826 // This is required as we modify the object to remove any non-matches
4827 var match = $.extend(true, {}, data);
4829 // Check each child of the option
4830 for (var c = data.children.length - 1; c >= 0; c--) {
4831 var child = data.children[c];
4833 var matches = matcher(params, child);
4835 // If there wasn't a match, remove the object in the array
4836 if (matches == null) {
4837 match.children.splice(c, 1);
4841 // If any children matched, return the new object
4842 if (match.children.length > 0) {
4846 // If there were no matching children, check just the plain object
4847 return matcher(params, match);
4850 var original = stripDiacritics(data.text).toUpperCase();
4851 var term = stripDiacritics(params.term).toUpperCase();
4853 // Check if the text contains the term
4854 if (original.indexOf(term) > -1) {
4858 // If it doesn't contain the term, don't return anything
4864 amdLanguageBase: './i18n/',
4865 closeOnSelect: true,
4867 dropdownAutoWidth: false,
4868 escapeMarkup: Utils.escapeMarkup,
4869 language: EnglishTranslation,
4871 minimumInputLength: 0,
4872 maximumInputLength: 0,
4873 maximumSelectionLength: 0,
4874 minimumResultsForSearch: 0,
4875 selectOnClose: false,
4876 sorter: function (data) {
4879 templateResult: function (result) {
4882 templateSelection: function (selection) {
4883 return selection.text;
4890 Defaults.prototype.set = function (key, value) {
4891 var camelKey = $.camelCase(key);
4894 data[camelKey] = value;
4896 var convertedData = Utils._convertData(data);
4898 $.extend(this.defaults, convertedData);
4901 var defaults = new Defaults();
4906 S2.define('select2/options',[
4911 ], function (require, $, Defaults, Utils) {
4912 function Options (options, $element) {
4913 this.options = options;
4915 if ($element != null) {
4916 this.fromElement($element);
4919 this.options = Defaults.apply(this.options);
4921 if ($element && $element.is('input')) {
4922 var InputCompat = require(this.get('amdBase') + 'compat/inputData');
4924 this.options.dataAdapter = Utils.Decorate(
4925 this.options.dataAdapter,
4931 Options.prototype.fromElement = function ($e) {
4932 var excludedData = ['select2'];
4934 if (this.options.multiple == null) {
4935 this.options.multiple = $e.prop('multiple');
4938 if (this.options.disabled == null) {
4939 this.options.disabled = $e.prop('disabled');
4942 if (this.options.language == null) {
4943 if ($e.prop('lang')) {
4944 this.options.language = $e.prop('lang').toLowerCase();
4945 } else if ($e.closest('[lang]').prop('lang')) {
4946 this.options.language = $e.closest('[lang]').prop('lang');
4950 if (this.options.dir == null) {
4951 if ($e.prop('dir')) {
4952 this.options.dir = $e.prop('dir');
4953 } else if ($e.closest('[dir]').prop('dir')) {
4954 this.options.dir = $e.closest('[dir]').prop('dir');
4956 this.options.dir = 'ltr';
4960 $e.prop('disabled', this.options.disabled);
4961 $e.prop('multiple', this.options.multiple);
4963 if ($e.data('select2Tags')) {
4964 if (this.options.debug && window.console && console.warn) {
4966 'Select2: The `data-select2-tags` attribute has been changed to ' +
4967 'use the `data-data` and `data-tags="true"` attributes and will be ' +
4968 'removed in future versions of Select2.'
4972 $e.data('data', $e.data('select2Tags'));
4973 $e.data('tags', true);
4976 if ($e.data('ajaxUrl')) {
4977 if (this.options.debug && window.console && console.warn) {
4979 'Select2: The `data-ajax-url` attribute has been changed to ' +
4980 '`data-ajax--url` and support for the old attribute will be removed' +
4981 ' in future versions of Select2.'
4985 $e.attr('ajax--url', $e.data('ajaxUrl'));
4986 $e.data('ajax--url', $e.data('ajaxUrl'));
4991 // Prefer the element's `dataset` attribute if it exists
4992 // jQuery 1.x does not correctly handle data attributes with multiple dashes
4993 if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
4994 dataset = $.extend(true, {}, $e[0].dataset, $e.data());
4996 dataset = $e.data();
4999 var data = $.extend(true, {}, dataset);
5001 data = Utils._convertData(data);
5003 for (var key in data) {
5004 if ($.inArray(key, excludedData) > -1) {
5008 if ($.isPlainObject(this.options[key])) {
5009 $.extend(this.options[key], data[key]);
5011 this.options[key] = data[key];
5018 Options.prototype.get = function (key) {
5019 return this.options[key];
5022 Options.prototype.set = function (key, val) {
5023 this.options[key] = val;
5029 S2.define('select2/core',[
5034 ], function ($, Options, Utils, KEYS) {
5035 var Select2 = function ($element, options) {
5036 if ($element.data('select2') != null) {
5037 $element.data('select2').destroy();
5040 this.$element = $element;
5042 this.id = this._generateId($element);
5044 options = options || {};
5046 this.options = new Options(options, $element);
5048 Select2.__super__.constructor.call(this);
5050 // Set up the tabindex
5052 var tabindex = $element.attr('tabindex') || 0;
5053 $element.data('old-tabindex', tabindex);
5054 $element.attr('tabindex', '-1');
5056 // Set up containers and adapters
5058 var DataAdapter = this.options.get('dataAdapter');
5059 this.dataAdapter = new DataAdapter($element, this.options);
5061 var $container = this.render();
5063 this._placeContainer($container);
5065 var SelectionAdapter = this.options.get('selectionAdapter');
5066 this.selection = new SelectionAdapter($element, this.options);
5067 this.$selection = this.selection.render();
5069 this.selection.position(this.$selection, $container);
5071 var DropdownAdapter = this.options.get('dropdownAdapter');
5072 this.dropdown = new DropdownAdapter($element, this.options);
5073 this.$dropdown = this.dropdown.render();
5075 this.dropdown.position(this.$dropdown, $container);
5077 var ResultsAdapter = this.options.get('resultsAdapter');
5078 this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
5079 this.$results = this.results.render();
5081 this.results.position(this.$results, this.$dropdown);
5087 // Bind the container to all of the adapters
5088 this._bindAdapters();
5090 // Register any DOM event handlers
5091 this._registerDomEvents();
5093 // Register any internal event handlers
5094 this._registerDataEvents();
5095 this._registerSelectionEvents();
5096 this._registerDropdownEvents();
5097 this._registerResultsEvents();
5098 this._registerEvents();
5100 // Set the initial state
5101 this.dataAdapter.current(function (initialData) {
5102 self.trigger('selection:update', {
5107 // Hide the original select
5108 $element.addClass('select2-hidden-accessible');
5109 $element.attr('aria-hidden', 'true');
5111 // Synchronize any monitored attributes
5112 this._syncAttributes();
5114 $element.data('select2', this);
5117 Utils.Extend(Select2, Utils.Observable);
5119 Select2.prototype._generateId = function ($element) {
5122 if ($element.attr('id') != null) {
5123 id = $element.attr('id');
5124 } else if ($element.attr('name') != null) {
5125 id = $element.attr('name') + '-' + Utils.generateChars(2);
5127 id = Utils.generateChars(4);
5130 id = id.replace(/(:|\.|\[|\]|,)/g, '');
5131 id = 'select2-' + id;
5136 Select2.prototype._placeContainer = function ($container) {
5137 $container.insertAfter(this.$element);
5139 var width = this._resolveWidth(this.$element, this.options.get('width'));
5141 if (width != null) {
5142 $container.css('width', width);
5146 Select2.prototype._resolveWidth = function ($element, method) {
5147 var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
5149 if (method == 'resolve') {
5150 var styleWidth = this._resolveWidth($element, 'style');
5152 if (styleWidth != null) {
5156 return this._resolveWidth($element, 'element');
5159 if (method == 'element') {
5160 var elementWidth = $element.outerWidth(false);
5162 if (elementWidth <= 0) {
5166 return elementWidth + 'px';
5169 if (method == 'style') {
5170 var style = $element.attr('style');
5172 if (typeof(style) !== 'string') {
5176 var attrs = style.split(';');
5178 for (var i = 0, l = attrs.length; i < l; i = i + 1) {
5179 var attr = attrs[i].replace(/\s/g, '');
5180 var matches = attr.match(WIDTH);
5182 if (matches !== null && matches.length >= 1) {
5193 Select2.prototype._bindAdapters = function () {
5194 this.dataAdapter.bind(this, this.$container);
5195 this.selection.bind(this, this.$container);
5197 this.dropdown.bind(this, this.$container);
5198 this.results.bind(this, this.$container);
5201 Select2.prototype._registerDomEvents = function () {
5204 this.$element.on('change.select2', function () {
5205 self.dataAdapter.current(function (data) {
5206 self.trigger('selection:update', {
5212 this.$element.on('focus.select2', function (evt) {
5213 self.trigger('focus', evt);
5216 this._syncA = Utils.bind(this._syncAttributes, this);
5217 this._syncS = Utils.bind(this._syncSubtree, this);
5219 if (this.$element[0].attachEvent) {
5220 this.$element[0].attachEvent('onpropertychange', this._syncA);
5223 var observer = window.MutationObserver ||
5224 window.WebKitMutationObserver ||
5225 window.MozMutationObserver
5228 if (observer != null) {
5229 this._observer = new observer(function (mutations) {
5230 $.each(mutations, self._syncA);
5231 $.each(mutations, self._syncS);
5233 this._observer.observe(this.$element[0], {
5238 } else if (this.$element[0].addEventListener) {
5239 this.$element[0].addEventListener(
5244 this.$element[0].addEventListener(
5249 this.$element[0].addEventListener(
5257 Select2.prototype._registerDataEvents = function () {
5260 this.dataAdapter.on('*', function (name, params) {
5261 self.trigger(name, params);
5265 Select2.prototype._registerSelectionEvents = function () {
5267 var nonRelayEvents = ['toggle', 'focus'];
5269 this.selection.on('toggle', function () {
5270 self.toggleDropdown();
5273 this.selection.on('focus', function (params) {
5277 this.selection.on('*', function (name, params) {
5278 if ($.inArray(name, nonRelayEvents) !== -1) {
5282 self.trigger(name, params);
5286 Select2.prototype._registerDropdownEvents = function () {
5289 this.dropdown.on('*', function (name, params) {
5290 self.trigger(name, params);
5294 Select2.prototype._registerResultsEvents = function () {
5297 this.results.on('*', function (name, params) {
5298 self.trigger(name, params);
5302 Select2.prototype._registerEvents = function () {
5305 this.on('open', function () {
5306 self.$container.addClass('select2-container--open');
5309 this.on('close', function () {
5310 self.$container.removeClass('select2-container--open');
5313 this.on('enable', function () {
5314 self.$container.removeClass('select2-container--disabled');
5317 this.on('disable', function () {
5318 self.$container.addClass('select2-container--disabled');
5321 this.on('blur', function () {
5322 self.$container.removeClass('select2-container--focus');
5325 this.on('query', function (params) {
5326 if (!self.isOpen()) {
5327 self.trigger('open', {});
5330 this.dataAdapter.query(params, function (data) {
5331 self.trigger('results:all', {
5338 this.on('query:append', function (params) {
5339 this.dataAdapter.query(params, function (data) {
5340 self.trigger('results:append', {
5347 this.on('keypress', function (evt) {
5348 var key = evt.which;
5350 if (self.isOpen()) {
5351 if (key === KEYS.ESC || key === KEYS.TAB ||
5352 (key === KEYS.UP && evt.altKey)) {
5355 evt.preventDefault();
5356 } else if (key === KEYS.ENTER) {
5357 self.trigger('results:select', {});
5359 evt.preventDefault();
5360 } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
5361 self.trigger('results:toggle', {});
5363 evt.preventDefault();
5364 } else if (key === KEYS.UP) {
5365 self.trigger('results:previous', {});
5367 evt.preventDefault();
5368 } else if (key === KEYS.DOWN) {
5369 self.trigger('results:next', {});
5371 evt.preventDefault();
5374 if (key === KEYS.ENTER || key === KEYS.SPACE ||
5375 (key === KEYS.DOWN && evt.altKey)) {
5378 evt.preventDefault();
5384 Select2.prototype._syncAttributes = function () {
5385 this.options.set('disabled', this.$element.prop('disabled'));
5387 if (this.options.get('disabled')) {
5388 if (this.isOpen()) {
5392 this.trigger('disable', {});
5394 this.trigger('enable', {});
5398 Select2.prototype._syncSubtree = function (evt, mutations) {
5399 var changed = false;
5402 // Ignore any mutation events raised for elements that aren't options or
5403 // optgroups. This handles the case when the select element is destroyed
5405 evt && evt.target && (
5406 evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
5413 // If mutation events aren't supported, then we can only assume that the
5414 // change affected the selections
5416 } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
5417 for (var n = 0; n < mutations.addedNodes.length; n++) {
5418 var node = mutations.addedNodes[n];
5420 if (node.selected) {
5424 } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
5428 // Only re-pull the data if we think there is a change
5430 this.dataAdapter.current(function (currentData) {
5431 self.trigger('selection:update', {
5439 * Override the trigger method to automatically trigger pre-events when
5440 * there are events that can be prevented.
5442 Select2.prototype.trigger = function (name, args) {
5443 var actualTrigger = Select2.__super__.trigger;
5444 var preTriggerMap = {
5447 'select': 'selecting',
5448 'unselect': 'unselecting'
5451 if (args === undefined) {
5455 if (name in preTriggerMap) {
5456 var preTriggerName = preTriggerMap[name];
5457 var preTriggerArgs = {
5463 actualTrigger.call(this, preTriggerName, preTriggerArgs);
5465 if (preTriggerArgs.prevented) {
5466 args.prevented = true;
5472 actualTrigger.call(this, name, args);
5475 Select2.prototype.toggleDropdown = function () {
5476 if (this.options.get('disabled')) {
5480 if (this.isOpen()) {
5487 Select2.prototype.open = function () {
5488 if (this.isOpen()) {
5492 this.trigger('query', {});
5495 Select2.prototype.close = function () {
5496 if (!this.isOpen()) {
5500 this.trigger('close', {});
5503 Select2.prototype.isOpen = function () {
5504 return this.$container.hasClass('select2-container--open');
5507 Select2.prototype.hasFocus = function () {
5508 return this.$container.hasClass('select2-container--focus');
5511 Select2.prototype.focus = function (data) {
5512 // No need to re-trigger focus events if we are already focused
5513 if (this.hasFocus()) {
5517 this.$container.addClass('select2-container--focus');
5518 this.trigger('focus', {});
5521 Select2.prototype.enable = function (args) {
5522 if (this.options.get('debug') && window.console && console.warn) {
5524 'Select2: The `select2("enable")` method has been deprecated and will' +
5525 ' be removed in later Select2 versions. Use $element.prop("disabled")' +
5530 if (args == null || args.length === 0) {
5534 var disabled = !args[0];
5536 this.$element.prop('disabled', disabled);
5539 Select2.prototype.data = function () {
5540 if (this.options.get('debug') &&
5541 arguments.length > 0 && window.console && console.warn) {
5543 'Select2: Data can no longer be set using `select2("data")`. You ' +
5544 'should consider setting the value instead using `$element.val()`.'
5550 this.dataAdapter.current(function (currentData) {
5557 Select2.prototype.val = function (args) {
5558 if (this.options.get('debug') && window.console && console.warn) {
5560 'Select2: The `select2("val")` method has been deprecated and will be' +
5561 ' removed in later Select2 versions. Use $element.val() instead.'
5565 if (args == null || args.length === 0) {
5566 return this.$element.val();
5569 var newVal = args[0];
5571 if ($.isArray(newVal)) {
5572 newVal = $.map(newVal, function (obj) {
5573 return obj.toString();
5577 this.$element.val(newVal).trigger('change');
5580 Select2.prototype.destroy = function () {
5581 this.$container.remove();
5583 if (this.$element[0].detachEvent) {
5584 this.$element[0].detachEvent('onpropertychange', this._syncA);
5587 if (this._observer != null) {
5588 this._observer.disconnect();
5589 this._observer = null;
5590 } else if (this.$element[0].removeEventListener) {
5592 .removeEventListener('DOMAttrModified', this._syncA, false);
5594 .removeEventListener('DOMNodeInserted', this._syncS, false);
5596 .removeEventListener('DOMNodeRemoved', this._syncS, false);
5602 this.$element.off('.select2');
5603 this.$element.attr('tabindex', this.$element.data('old-tabindex'));
5605 this.$element.removeClass('select2-hidden-accessible');
5606 this.$element.attr('aria-hidden', 'false');
5607 this.$element.removeData('select2');
5609 this.dataAdapter.destroy();
5610 this.selection.destroy();
5611 this.dropdown.destroy();
5612 this.results.destroy();
5614 this.dataAdapter = null;
5615 this.selection = null;
5616 this.dropdown = null;
5617 this.results = null;
5620 Select2.prototype.render = function () {
5622 '<span class="select2 select2-container">' +
5623 '<span class="selection"></span>' +
5624 '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
5628 $container.attr('dir', this.options.get('dir'));
5630 this.$container = $container;
5632 this.$container.addClass('select2-container--' + this.options.get('theme'));
5634 $container.data('element', this.$element);
5642 S2.define('select2/compat/utils',[
5645 function syncCssClasses ($dest, $src, adapter) {
5646 var classes, replacements = [], adapted;
5648 classes = $.trim($dest.attr('class'));
5651 classes = '' + classes; // for IE which returns object
5653 $(classes.split(/\s+/)).each(function () {
5654 // Save all Select2 classes
5655 if (this.indexOf('select2-') === 0) {
5656 replacements.push(this);
5661 classes = $.trim($src.attr('class'));
5664 classes = '' + classes; // for IE which returns object
5666 $(classes.split(/\s+/)).each(function () {
5667 // Only adapt non-Select2 classes
5668 if (this.indexOf('select2-') !== 0) {
5669 adapted = adapter(this);
5671 if (adapted != null) {
5672 replacements.push(adapted);
5678 $dest.attr('class', replacements.join(' '));
5682 syncCssClasses: syncCssClasses
5686 S2.define('select2/compat/containerCss',[
5689 ], function ($, CompatUtils) {
5690 // No-op CSS adapter that discards all classes by default
5691 function _containerAdapter (clazz) {
5695 function ContainerCSS () { }
5697 ContainerCSS.prototype.render = function (decorated) {
5698 var $container = decorated.call(this);
5700 var containerCssClass = this.options.get('containerCssClass') || '';
5702 if ($.isFunction(containerCssClass)) {
5703 containerCssClass = containerCssClass(this.$element);
5706 var containerCssAdapter = this.options.get('adaptContainerCssClass');
5707 containerCssAdapter = containerCssAdapter || _containerAdapter;
5709 if (containerCssClass.indexOf(':all:') !== -1) {
5710 containerCssClass = containerCssClass.replace(':all:', '');
5712 var _cssAdapter = containerCssAdapter;
5714 containerCssAdapter = function (clazz) {
5715 var adapted = _cssAdapter(clazz);
5717 if (adapted != null) {
5718 // Append the old one along with the adapted one
5719 return adapted + ' ' + clazz;
5726 var containerCss = this.options.get('containerCss') || {};
5728 if ($.isFunction(containerCss)) {
5729 containerCss = containerCss(this.$element);
5732 CompatUtils.syncCssClasses($container, this.$element, containerCssAdapter);
5734 $container.css(containerCss);
5735 $container.addClass(containerCssClass);
5740 return ContainerCSS;
5743 S2.define('select2/compat/dropdownCss',[
5746 ], function ($, CompatUtils) {
5747 // No-op CSS adapter that discards all classes by default
5748 function _dropdownAdapter (clazz) {
5752 function DropdownCSS () { }
5754 DropdownCSS.prototype.render = function (decorated) {
5755 var $dropdown = decorated.call(this);
5757 var dropdownCssClass = this.options.get('dropdownCssClass') || '';
5759 if ($.isFunction(dropdownCssClass)) {
5760 dropdownCssClass = dropdownCssClass(this.$element);
5763 var dropdownCssAdapter = this.options.get('adaptDropdownCssClass');
5764 dropdownCssAdapter = dropdownCssAdapter || _dropdownAdapter;
5766 if (dropdownCssClass.indexOf(':all:') !== -1) {
5767 dropdownCssClass = dropdownCssClass.replace(':all:', '');
5769 var _cssAdapter = dropdownCssAdapter;
5771 dropdownCssAdapter = function (clazz) {
5772 var adapted = _cssAdapter(clazz);
5774 if (adapted != null) {
5775 // Append the old one along with the adapted one
5776 return adapted + ' ' + clazz;
5783 var dropdownCss = this.options.get('dropdownCss') || {};
5785 if ($.isFunction(dropdownCss)) {
5786 dropdownCss = dropdownCss(this.$element);
5789 CompatUtils.syncCssClasses($dropdown, this.$element, dropdownCssAdapter);
5791 $dropdown.css(dropdownCss);
5792 $dropdown.addClass(dropdownCssClass);
5800 S2.define('select2/compat/initSelection',[
5803 function InitSelection (decorated, $element, options) {
5804 if (options.get('debug') && window.console && console.warn) {
5806 'Select2: The `initSelection` option has been deprecated in favor' +
5807 ' of a custom data adapter that overrides the `current` method. ' +
5808 'This method is now called multiple times instead of a single ' +
5809 'time when the instance is initialized. Support will be removed ' +
5810 'for the `initSelection` option in future versions of Select2'
5814 this.initSelection = options.get('initSelection');
5815 this._isInitialized = false;
5817 decorated.call(this, $element, options);
5820 InitSelection.prototype.current = function (decorated, callback) {
5823 if (this._isInitialized) {
5824 decorated.call(this, callback);
5829 this.initSelection.call(null, this.$element, function (data) {
5830 self._isInitialized = true;
5832 if (!$.isArray(data)) {
5840 return InitSelection;
5843 S2.define('select2/compat/inputData',[
5846 function InputData (decorated, $element, options) {
5847 this._currentData = [];
5848 this._valueSeparator = options.get('valueSeparator') || ',';
5850 if ($element.prop('type') === 'hidden') {
5851 if (options.get('debug') && console && console.warn) {
5853 'Select2: Using a hidden input with Select2 is no longer ' +
5854 'supported and may stop working in the future. It is recommended ' +
5855 'to use a `<select>` element instead.'
5860 decorated.call(this, $element, options);
5863 InputData.prototype.current = function (_, callback) {
5864 function getSelected (data, selectedIds) {
5867 if (data.selected || $.inArray(data.id, selectedIds) !== -1) {
5868 data.selected = true;
5869 selected.push(data);
5871 data.selected = false;
5874 if (data.children) {
5875 selected.push.apply(selected, getSelected(data.children, selectedIds));
5883 for (var d = 0; d < this._currentData.length; d++) {
5884 var data = this._currentData[d];
5886 selected.push.apply(
5890 this.$element.val().split(
5891 this._valueSeparator
5900 InputData.prototype.select = function (_, data) {
5901 if (!this.options.get('multiple')) {
5902 this.current(function (allData) {
5903 $.map(allData, function (data) {
5904 data.selected = false;
5908 this.$element.val(data.id);
5909 this.$element.trigger('change');
5911 var value = this.$element.val();
5912 value += this._valueSeparator + data.id;
5914 this.$element.val(value);
5915 this.$element.trigger('change');
5919 InputData.prototype.unselect = function (_, data) {
5922 data.selected = false;
5924 this.current(function (allData) {
5927 for (var d = 0; d < allData.length; d++) {
5928 var item = allData[d];
5930 if (data.id == item.id) {
5934 values.push(item.id);
5937 self.$element.val(values.join(self._valueSeparator));
5938 self.$element.trigger('change');
5942 InputData.prototype.query = function (_, params, callback) {
5945 for (var d = 0; d < this._currentData.length; d++) {
5946 var data = this._currentData[d];
5948 var matches = this.matches(params, data);
5950 if (matches !== null) {
5951 results.push(matches);
5960 InputData.prototype.addOptions = function (_, $options) {
5961 var options = $.map($options, function ($option) {
5962 return $.data($option[0], 'data');
5965 this._currentData.push.apply(this._currentData, options);
5971 S2.define('select2/compat/matcher',[
5974 function oldMatcher (matcher) {
5975 function wrappedMatcher (params, data) {
5976 var match = $.extend(true, {}, data);
5978 if (params.term == null || $.trim(params.term) === '') {
5982 if (data.children) {
5983 for (var c = data.children.length - 1; c >= 0; c--) {
5984 var child = data.children[c];
5986 // Check if the child object matches
5987 // The old matcher returned a boolean true or false
5988 var doesMatch = matcher(params.term, child.text, child);
5990 // If the child didn't match, pop it off
5992 match.children.splice(c, 1);
5996 if (match.children.length > 0) {
6001 if (matcher(params.term, data.text, data)) {
6008 return wrappedMatcher;
6014 S2.define('select2/compat/query',[
6017 function Query (decorated, $element, options) {
6018 if (options.get('debug') && window.console && console.warn) {
6020 'Select2: The `query` option has been deprecated in favor of a ' +
6021 'custom data adapter that overrides the `query` method. Support ' +
6022 'will be removed for the `query` option in future versions of ' +
6027 decorated.call(this, $element, options);
6030 Query.prototype.query = function (_, params, callback) {
6031 params.callback = callback;
6033 var query = this.options.get('query');
6035 query.call(null, params);
6041 S2.define('select2/dropdown/attachContainer',[
6044 function AttachContainer (decorated, $element, options) {
6045 decorated.call(this, $element, options);
6048 AttachContainer.prototype.position =
6049 function (decorated, $dropdown, $container) {
6050 var $dropdownContainer = $container.find('.dropdown-wrapper');
6051 $dropdownContainer.append($dropdown);
6053 $dropdown.addClass('select2-dropdown--below');
6054 $container.addClass('select2-container--below');
6057 return AttachContainer;
6060 S2.define('select2/dropdown/stopPropagation',[
6063 function StopPropagation () { }
6065 StopPropagation.prototype.bind = function (decorated, container, $container) {
6066 decorated.call(this, container, $container);
6068 var stoppedEvents = [
6091 this.$dropdown.on(stoppedEvents.join(' '), function (evt) {
6092 evt.stopPropagation();
6096 return StopPropagation;
6099 S2.define('select2/selection/stopPropagation',[
6102 function StopPropagation () { }
6104 StopPropagation.prototype.bind = function (decorated, container, $container) {
6105 decorated.call(this, container, $container);
6107 var stoppedEvents = [
6130 this.$selection.on(stoppedEvents.join(' '), function (evt) {
6131 evt.stopPropagation();
6135 return StopPropagation;
6139 * jQuery Mousewheel 3.1.13
6141 * Copyright jQuery Foundation and other contributors
6142 * Released under the MIT license
6143 * http://jquery.org/license
6146 (function (factory) {
6147 if ( typeof S2.define === 'function' && S2.define.amd ) {
6148 // AMD. Register as an anonymous module.
6149 S2.define('jquery-mousewheel',['jquery'], factory);
6150 } else if (typeof exports === 'object') {
6151 // Node/CommonJS style for Browserify
6152 module.exports = factory;
6159 var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'],
6160 toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ?
6161 ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
6162 slice = Array.prototype.slice,
6163 nullLowestDeltaTimeout, lowestDelta;
6165 if ( $.event.fixHooks ) {
6166 for ( var i = toFix.length; i; ) {
6167 $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
6171 var special = $.event.special.mousewheel = {
6175 if ( this.addEventListener ) {
6176 for ( var i = toBind.length; i; ) {
6177 this.addEventListener( toBind[--i], handler, false );
6180 this.onmousewheel = handler;
6182 // Store the line height and page height for this particular element
6183 $.data(this, 'mousewheel-line-height', special.getLineHeight(this));
6184 $.data(this, 'mousewheel-page-height', special.getPageHeight(this));
6187 teardown: function() {
6188 if ( this.removeEventListener ) {
6189 for ( var i = toBind.length; i; ) {
6190 this.removeEventListener( toBind[--i], handler, false );
6193 this.onmousewheel = null;
6195 // Clean up the data we added to the element
6196 $.removeData(this, 'mousewheel-line-height');
6197 $.removeData(this, 'mousewheel-page-height');
6200 getLineHeight: function(elem) {
6201 var $elem = $(elem),
6202 $parent = $elem['offsetParent' in $.fn ? 'offsetParent' : 'parent']();
6203 if (!$parent.length) {
6204 $parent = $('body');
6206 return parseInt($parent.css('fontSize'), 10) || parseInt($elem.css('fontSize'), 10) || 16;
6209 getPageHeight: function(elem) {
6210 return $(elem).height();
6214 adjustOldDeltas: true, // see shouldAdjustOldDeltas() below
6215 normalizeOffset: true // calls getBoundingClientRect for each event
6220 mousewheel: function(fn) {
6221 return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel');
6224 unmousewheel: function(fn) {
6225 return this.unbind('mousewheel', fn);
6230 function handler(event) {
6231 var orgEvent = event || window.event,
6232 args = slice.call(arguments, 1),
6239 event = $.event.fix(orgEvent);
6240 event.type = 'mousewheel';
6242 // Old school scrollwheel delta
6243 if ( 'detail' in orgEvent ) { deltaY = orgEvent.detail * -1; }
6244 if ( 'wheelDelta' in orgEvent ) { deltaY = orgEvent.wheelDelta; }
6245 if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY; }
6246 if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; }
6248 // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
6249 if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
6250 deltaX = deltaY * -1;
6254 // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
6255 delta = deltaY === 0 ? deltaX : deltaY;
6257 // New school wheel delta (wheel event)
6258 if ( 'deltaY' in orgEvent ) {
6259 deltaY = orgEvent.deltaY * -1;
6262 if ( 'deltaX' in orgEvent ) {
6263 deltaX = orgEvent.deltaX;
6264 if ( deltaY === 0 ) { delta = deltaX * -1; }
6267 // No change actually happened, no reason to go any further
6268 if ( deltaY === 0 && deltaX === 0 ) { return; }
6270 // Need to convert lines and pages to pixels if we aren't already in pixels
6271 // There are three delta modes:
6272 // * deltaMode 0 is by pixels, nothing to do
6273 // * deltaMode 1 is by lines
6274 // * deltaMode 2 is by pages
6275 if ( orgEvent.deltaMode === 1 ) {
6276 var lineHeight = $.data(this, 'mousewheel-line-height');
6277 delta *= lineHeight;
6278 deltaY *= lineHeight;
6279 deltaX *= lineHeight;
6280 } else if ( orgEvent.deltaMode === 2 ) {
6281 var pageHeight = $.data(this, 'mousewheel-page-height');
6282 delta *= pageHeight;
6283 deltaY *= pageHeight;
6284 deltaX *= pageHeight;
6287 // Store lowest absolute delta to normalize the delta values
6288 absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
6290 if ( !lowestDelta || absDelta < lowestDelta ) {
6291 lowestDelta = absDelta;
6293 // Adjust older deltas if necessary
6294 if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
6299 // Adjust older deltas if necessary
6300 if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
6301 // Divide all the things by 40!
6307 // Get a whole, normalized value for the deltas
6308 delta = Math[ delta >= 1 ? 'floor' : 'ceil' ](delta / lowestDelta);
6309 deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
6310 deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
6312 // Normalise offsetX and offsetY properties
6313 if ( special.settings.normalizeOffset && this.getBoundingClientRect ) {
6314 var boundingRect = this.getBoundingClientRect();
6315 offsetX = event.clientX - boundingRect.left;
6316 offsetY = event.clientY - boundingRect.top;
6319 // Add information to the event object
6320 event.deltaX = deltaX;
6321 event.deltaY = deltaY;
6322 event.deltaFactor = lowestDelta;
6323 event.offsetX = offsetX;
6324 event.offsetY = offsetY;
6325 // Go ahead and set deltaMode to 0 since we converted to pixels
6326 // Although this is a little odd since we overwrite the deltaX/Y
6327 // properties with normalized deltas.
6328 event.deltaMode = 0;
6330 // Add event and delta to the front of the arguments
6331 args.unshift(event, delta, deltaX, deltaY);
6333 // Clearout lowestDelta after sometime to better
6334 // handle multiple device types that give different
6335 // a different lowestDelta
6336 // Ex: trackpad = 3 and mouse wheel = 120
6337 if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
6338 nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
6340 return ($.event.dispatch || $.event.handle).apply(this, args);
6343 function nullLowestDelta() {
6347 function shouldAdjustOldDeltas(orgEvent, absDelta) {
6348 // If this is an older event and the delta is divisable by 120,
6349 // then we are assuming that the browser is treating this as an
6350 // older mouse wheel event and that we should divide the deltas
6351 // by 40 to try and get a more usable deltaFactor.
6352 // Side note, this actually impacts the reported scroll distance
6353 // in older browsers and can cause scrolling to be slower than native.
6354 // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
6355 return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
6360 S2.define('jquery.select2',[
6362 'jquery-mousewheel',
6365 './select2/defaults'
6366 ], function ($, _, Select2, Defaults) {
6367 if ($.fn.select2 == null) {
6368 // All methods that should return the element
6369 var thisMethods = ['open', 'close', 'destroy'];
6371 $.fn.select2 = function (options) {
6372 options = options || {};
6374 if (typeof options === 'object') {
6375 this.each(function () {
6376 var instanceOptions = $.extend(true, {}, options);
6378 var instance = new Select2($(this), instanceOptions);
6382 } else if (typeof options === 'string') {
6384 var args = Array.prototype.slice.call(arguments, 1);
6386 this.each(function () {
6387 var instance = $(this).data('select2');
6389 if (instance == null && window.console && console.error) {
6391 'The select2(\'' + options + '\') method was called on an ' +
6392 'element that is not using Select2.'
6396 ret = instance[options].apply(instance, args);
6399 // Check if we should be returning `this`
6400 if ($.inArray(options, thisMethods) > -1) {
6406 throw new Error('Invalid arguments for Select2: ' + options);
6411 if ($.fn.select2.defaults == null) {
6412 $.fn.select2.defaults = Defaults;
6418 // Return the AMD loader configuration so it can be used outside of this file
6425 // Autoload the jQuery bindings
6426 // We know that all of the modules exist above this, so we're safe
6427 var select2 = S2.require('jquery.select2');
6429 // Hold the AMD module references on the jQuery function that was just loaded
6430 // This allows Select2 to use the internal loader outside of this file, such
6431 // as in the language files.
6432 jQuery.fn.select2.amd = S2;
6434 // Return the Select2 instance for anyone who is importing it.