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;
610 this.listeners = this.listeners || {};
612 if (event in this.listeners) {
613 this.invoke(this.listeners[event], slice.call(arguments, 1));
616 if ('*' in this.listeners) {
617 this.invoke(this.listeners['*'], arguments);
621 Observable.prototype.invoke = function (listeners, params) {
622 for (var i = 0, len = listeners.length; i < len; i++) {
623 listeners[i].apply(this, params);
627 Utils.Observable = Observable;
629 Utils.generateChars = function (length) {
632 for (var i = 0; i < length; i++) {
633 var randomChar = Math.floor(Math.random() * 36);
634 chars += randomChar.toString(36);
640 Utils.bind = function (func, context) {
642 func.apply(context, arguments);
646 Utils._convertData = function (data) {
647 for (var originalKey in data) {
648 var keys = originalKey.split('-');
650 var dataLevel = data;
652 if (keys.length === 1) {
656 for (var k = 0; k < keys.length; k++) {
659 // Lowercase the first letter
660 // By default, dash-separated becomes camelCase
661 key = key.substring(0, 1).toLowerCase() + key.substring(1);
663 if (!(key in dataLevel)) {
667 if (k == keys.length - 1) {
668 dataLevel[key] = data[originalKey];
671 dataLevel = dataLevel[key];
674 delete data[originalKey];
680 Utils.hasScroll = function (index, el) {
681 // Adapted from the function created by @ShadowScripter
682 // and adapted by @BillBarry on the Stack Exchange Code Review website.
683 // The original code can be found at
684 // http://codereview.stackexchange.com/q/13338
685 // and was designed to be used with the Sizzle selector engine.
688 var overflowX = el.style.overflowX;
689 var overflowY = el.style.overflowY;
691 //Check both x and y declarations
692 if (overflowX === overflowY &&
693 (overflowY === 'hidden' || overflowY === 'visible')) {
697 if (overflowX === 'scroll' || overflowY === 'scroll') {
701 return ($el.innerHeight() < el.scrollHeight ||
702 $el.innerWidth() < el.scrollWidth);
705 Utils.escapeMarkup = function (markup) {
716 // Do not try to escape the markup if it's not a string
717 if (typeof markup !== 'string') {
721 return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
722 return replaceMap[match];
726 // Append an array of jQuery nodes to a given element.
727 Utils.appendMany = function ($element, $nodes) {
728 // jQuery 1.7.x does not support $.fn.append() with an array
729 // Fall back to a jQuery object collection using $.fn.add()
730 if ($.fn.jquery.substr(0, 3) === '1.7') {
733 $.map($nodes, function (node) {
734 $jqNodes = $jqNodes.add(node);
740 $element.append($nodes);
746 S2.define('select2/results',[
749 ], function ($, Utils) {
750 function Results ($element, options, dataAdapter) {
751 this.$element = $element;
752 this.data = dataAdapter;
753 this.options = options;
755 Results.__super__.constructor.call(this);
758 Utils.Extend(Results, Utils.Observable);
760 Results.prototype.render = function () {
762 '<ul class="select2-results__options" role="tree"></ul>'
765 if (this.options.get('multiple')) {
766 $results.attr('aria-multiselectable', 'true');
769 this.$results = $results;
774 Results.prototype.clear = function () {
775 this.$results.empty();
778 Results.prototype.displayMessage = function (params) {
779 var escapeMarkup = this.options.get('escapeMarkup');
785 '<li role="treeitem" aria-live="assertive"' +
786 ' class="select2-results__option"></li>'
789 var message = this.options.get('translations').get(params.message);
797 $message[0].className += ' select2-results__message';
799 this.$results.append($message);
802 Results.prototype.hideMessages = function () {
803 this.$results.find('.select2-results__message').remove();
806 Results.prototype.append = function (data) {
811 if (data.results == null || data.results.length === 0) {
812 if (this.$results.children().length === 0) {
813 this.trigger('results:message', {
821 data.results = this.sort(data.results);
823 for (var d = 0; d < data.results.length; d++) {
824 var item = data.results[d];
826 var $option = this.option(item);
828 $options.push($option);
831 this.$results.append($options);
834 Results.prototype.position = function ($results, $dropdown) {
835 var $resultsContainer = $dropdown.find('.select2-results');
836 $resultsContainer.append($results);
839 Results.prototype.sort = function (data) {
840 var sorter = this.options.get('sorter');
845 Results.prototype.setClasses = function () {
848 this.data.current(function (selected) {
849 var selectedIds = $.map(selected, function (s) {
850 return s.id.toString();
853 var $options = self.$results
854 .find('.select2-results__option[aria-selected]');
856 $options.each(function () {
857 var $option = $(this);
859 var item = $.data(this, 'data');
861 // id needs to be converted to a string when comparing
862 var id = '' + item.id;
864 if ((item.element != null && item.element.selected) ||
865 (item.element == null && $.inArray(id, selectedIds) > -1)) {
866 $option.attr('aria-selected', 'true');
868 $option.attr('aria-selected', 'false');
872 var $selected = $options.filter('[aria-selected=true]');
874 // Check if there are any selected options
875 if ($selected.length > 0) {
876 // If there are selected options, highlight the first
877 $selected.first().trigger('mouseenter');
879 // If there are no selected options, highlight the first option
881 $options.first().trigger('mouseenter');
886 Results.prototype.showLoading = function (params) {
889 var loadingMore = this.options.get('translations').get('searching');
894 text: loadingMore(params)
896 var $loading = this.option(loading);
897 $loading.className += ' loading-results';
899 this.$results.prepend($loading);
902 Results.prototype.hideLoading = function () {
903 this.$results.find('.loading-results').remove();
906 Results.prototype.option = function (data) {
907 var option = document.createElement('li');
908 option.className = 'select2-results__option';
912 'aria-selected': 'false'
916 delete attrs['aria-selected'];
917 attrs['aria-disabled'] = 'true';
920 if (data.id == null) {
921 delete attrs['aria-selected'];
924 if (data._resultId != null) {
925 option.id = data._resultId;
929 option.title = data.title;
933 attrs.role = 'group';
934 attrs['aria-label'] = data.text;
935 delete attrs['aria-selected'];
938 for (var attr in attrs) {
939 var val = attrs[attr];
941 option.setAttribute(attr, val);
945 var $option = $(option);
947 var label = document.createElement('strong');
948 label.className = 'select2-results__group';
950 var $label = $(label);
951 this.template(data, label);
955 for (var c = 0; c < data.children.length; c++) {
956 var child = data.children[c];
958 var $child = this.option(child);
960 $children.push($child);
963 var $childrenContainer = $('<ul></ul>', {
964 'class': 'select2-results__options select2-results__options--nested'
967 $childrenContainer.append($children);
969 $option.append(label);
970 $option.append($childrenContainer);
972 this.template(data, option);
975 $.data(option, 'data', data);
980 Results.prototype.bind = function (container, $container) {
983 var id = container.id + '-results';
985 this.$results.attr('id', id);
987 container.on('results:all', function (params) {
989 self.append(params.data);
991 if (container.isOpen()) {
996 container.on('results:append', function (params) {
997 self.append(params.data);
999 if (container.isOpen()) {
1004 container.on('query', function (params) {
1005 self.hideMessages();
1006 self.showLoading(params);
1009 container.on('select', function () {
1010 if (!container.isOpen()) {
1017 container.on('unselect', function () {
1018 if (!container.isOpen()) {
1025 container.on('open', function () {
1026 // When the dropdown is open, aria-expended="true"
1027 self.$results.attr('aria-expanded', 'true');
1028 self.$results.attr('aria-hidden', 'false');
1031 self.ensureHighlightVisible();
1034 container.on('close', function () {
1035 // When the dropdown is closed, aria-expended="false"
1036 self.$results.attr('aria-expanded', 'false');
1037 self.$results.attr('aria-hidden', 'true');
1038 self.$results.removeAttr('aria-activedescendant');
1041 container.on('results:toggle', function () {
1042 var $highlighted = self.getHighlightedResults();
1044 if ($highlighted.length === 0) {
1048 $highlighted.trigger('mouseup');
1051 container.on('results:select', function () {
1052 var $highlighted = self.getHighlightedResults();
1054 if ($highlighted.length === 0) {
1058 var data = $highlighted.data('data');
1060 if ($highlighted.attr('aria-selected') == 'true') {
1061 self.trigger('close', {});
1063 self.trigger('select', {
1069 container.on('results:previous', function () {
1070 var $highlighted = self.getHighlightedResults();
1072 var $options = self.$results.find('[aria-selected]');
1074 var currentIndex = $options.index($highlighted);
1076 // If we are already at te top, don't move further
1077 if (currentIndex === 0) {
1081 var nextIndex = currentIndex - 1;
1083 // If none are highlighted, highlight the first
1084 if ($highlighted.length === 0) {
1088 var $next = $options.eq(nextIndex);
1090 $next.trigger('mouseenter');
1092 var currentOffset = self.$results.offset().top;
1093 var nextTop = $next.offset().top;
1094 var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
1096 if (nextIndex === 0) {
1097 self.$results.scrollTop(0);
1098 } else if (nextTop - currentOffset < 0) {
1099 self.$results.scrollTop(nextOffset);
1103 container.on('results:next', function () {
1104 var $highlighted = self.getHighlightedResults();
1106 var $options = self.$results.find('[aria-selected]');
1108 var currentIndex = $options.index($highlighted);
1110 var nextIndex = currentIndex + 1;
1112 // If we are at the last option, stay there
1113 if (nextIndex >= $options.length) {
1117 var $next = $options.eq(nextIndex);
1119 $next.trigger('mouseenter');
1121 var currentOffset = self.$results.offset().top +
1122 self.$results.outerHeight(false);
1123 var nextBottom = $next.offset().top + $next.outerHeight(false);
1124 var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
1126 if (nextIndex === 0) {
1127 self.$results.scrollTop(0);
1128 } else if (nextBottom > currentOffset) {
1129 self.$results.scrollTop(nextOffset);
1133 container.on('results:focus', function (params) {
1134 params.element.addClass('select2-results__option--highlighted');
1137 container.on('results:message', function (params) {
1138 self.displayMessage(params);
1141 if ($.fn.mousewheel) {
1142 this.$results.on('mousewheel', function (e) {
1143 var top = self.$results.scrollTop();
1146 self.$results.get(0).scrollHeight -
1147 self.$results.scrollTop() +
1151 var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
1152 var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
1155 self.$results.scrollTop(0);
1158 e.stopPropagation();
1159 } else if (isAtBottom) {
1160 self.$results.scrollTop(
1161 self.$results.get(0).scrollHeight - self.$results.height()
1165 e.stopPropagation();
1170 this.$results.on('mouseup', '.select2-results__option[aria-selected]',
1172 var $this = $(this);
1174 var data = $this.data('data');
1176 if ($this.attr('aria-selected') === 'true') {
1177 if (self.options.get('multiple')) {
1178 self.trigger('unselect', {
1183 self.trigger('close', {});
1189 self.trigger('select', {
1195 this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
1197 var data = $(this).data('data');
1199 self.getHighlightedResults()
1200 .removeClass('select2-results__option--highlighted');
1202 self.trigger('results:focus', {
1209 Results.prototype.getHighlightedResults = function () {
1210 var $highlighted = this.$results
1211 .find('.select2-results__option--highlighted');
1213 return $highlighted;
1216 Results.prototype.destroy = function () {
1217 this.$results.remove();
1220 Results.prototype.ensureHighlightVisible = function () {
1221 var $highlighted = this.getHighlightedResults();
1223 if ($highlighted.length === 0) {
1227 var $options = this.$results.find('[aria-selected]');
1229 var currentIndex = $options.index($highlighted);
1231 var currentOffset = this.$results.offset().top;
1232 var nextTop = $highlighted.offset().top;
1233 var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
1235 var offsetDelta = nextTop - currentOffset;
1236 nextOffset -= $highlighted.outerHeight(false) * 2;
1238 if (currentIndex <= 2) {
1239 this.$results.scrollTop(0);
1240 } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
1241 this.$results.scrollTop(nextOffset);
1245 Results.prototype.template = function (result, container) {
1246 var template = this.options.get('templateResult');
1247 var escapeMarkup = this.options.get('escapeMarkup');
1249 var content = template(result, container);
1251 if (content == null) {
1252 container.style.display = 'none';
1253 } else if (typeof content === 'string') {
1254 container.innerHTML = escapeMarkup(content);
1256 $(container).append(content);
1263 S2.define('select2/keys',[
1289 S2.define('select2/selection/base',[
1293 ], function ($, Utils, KEYS) {
1294 function BaseSelection ($element, options) {
1295 this.$element = $element;
1296 this.options = options;
1298 BaseSelection.__super__.constructor.call(this);
1301 Utils.Extend(BaseSelection, Utils.Observable);
1303 BaseSelection.prototype.render = function () {
1305 '<span class="select2-selection" role="combobox" ' +
1306 ' aria-haspopup="true" aria-expanded="false">' +
1312 if (this.$element.data('old-tabindex') != null) {
1313 this._tabindex = this.$element.data('old-tabindex');
1314 } else if (this.$element.attr('tabindex') != null) {
1315 this._tabindex = this.$element.attr('tabindex');
1318 $selection.attr('title', this.$element.attr('title'));
1319 $selection.attr('tabindex', this._tabindex);
1321 this.$selection = $selection;
1326 BaseSelection.prototype.bind = function (container, $container) {
1329 var id = container.id + '-container';
1330 var resultsId = container.id + '-results';
1332 this.container = container;
1334 this.$selection.on('focus', function (evt) {
1335 self.trigger('focus', evt);
1338 this.$selection.on('blur', function (evt) {
1339 self._handleBlur(evt);
1342 this.$selection.on('keydown', function (evt) {
1343 self.trigger('keypress', evt);
1345 if (evt.which === KEYS.SPACE) {
1346 evt.preventDefault();
1350 container.on('results:focus', function (params) {
1351 self.$selection.attr('aria-activedescendant', params.data._resultId);
1354 container.on('selection:update', function (params) {
1355 self.update(params.data);
1358 container.on('open', function () {
1359 // When the dropdown is open, aria-expanded="true"
1360 self.$selection.attr('aria-expanded', 'true');
1361 self.$selection.attr('aria-owns', resultsId);
1363 self._attachCloseHandler(container);
1366 container.on('close', function () {
1367 // When the dropdown is closed, aria-expanded="false"
1368 self.$selection.attr('aria-expanded', 'false');
1369 self.$selection.removeAttr('aria-activedescendant');
1370 self.$selection.removeAttr('aria-owns');
1372 self.$selection.focus();
1374 self._detachCloseHandler(container);
1377 container.on('enable', function () {
1378 self.$selection.attr('tabindex', self._tabindex);
1381 container.on('disable', function () {
1382 self.$selection.attr('tabindex', '-1');
1386 BaseSelection.prototype._handleBlur = function (evt) {
1389 // This needs to be delayed as the active element is the body when the tab
1390 // key is pressed, possibly along with others.
1391 window.setTimeout(function () {
1392 // Don't trigger `blur` if the focus is still in the selection
1394 (document.activeElement == self.$selection[0]) ||
1395 ($.contains(self.$selection[0], document.activeElement))
1400 self.trigger('blur', evt);
1404 BaseSelection.prototype._attachCloseHandler = function (container) {
1407 $(document.body).on('mousedown.select2.' + container.id, function (e) {
1408 var $target = $(e.target);
1410 var $select = $target.closest('.select2');
1412 var $all = $('.select2.select2-container--open');
1414 $all.each(function () {
1415 var $this = $(this);
1417 if (this == $select[0]) {
1421 var $element = $this.data('element');
1423 $element.select2('close');
1428 BaseSelection.prototype._detachCloseHandler = function (container) {
1429 $(document.body).off('mousedown.select2.' + container.id);
1432 BaseSelection.prototype.position = function ($selection, $container) {
1433 var $selectionContainer = $container.find('.selection');
1434 $selectionContainer.append($selection);
1437 BaseSelection.prototype.destroy = function () {
1438 this._detachCloseHandler(this.container);
1441 BaseSelection.prototype.update = function (data) {
1442 throw new Error('The `update` method must be defined in child classes.');
1445 return BaseSelection;
1448 S2.define('select2/selection/single',[
1453 ], function ($, BaseSelection, Utils, KEYS) {
1454 function SingleSelection () {
1455 SingleSelection.__super__.constructor.apply(this, arguments);
1458 Utils.Extend(SingleSelection, BaseSelection);
1460 SingleSelection.prototype.render = function () {
1461 var $selection = SingleSelection.__super__.render.call(this);
1463 $selection.addClass('select2-selection--single');
1466 '<span class="select2-selection__rendered"></span>' +
1467 '<span class="select2-selection__arrow" role="presentation">' +
1468 '<b role="presentation"></b>' +
1475 SingleSelection.prototype.bind = function (container, $container) {
1478 SingleSelection.__super__.bind.apply(this, arguments);
1480 var id = container.id + '-container';
1482 this.$selection.find('.select2-selection__rendered').attr('id', id);
1483 this.$selection.attr('aria-labelledby', id);
1485 this.$selection.on('mousedown', function (evt) {
1486 // Only respond to left clicks
1487 if (evt.which !== 1) {
1491 self.trigger('toggle', {
1496 this.$selection.on('focus', function (evt) {
1497 // User focuses on the container
1500 this.$selection.on('blur', function (evt) {
1501 // User exits the container
1504 container.on('selection:update', function (params) {
1505 self.update(params.data);
1509 SingleSelection.prototype.clear = function () {
1510 this.$selection.find('.select2-selection__rendered').empty();
1513 SingleSelection.prototype.display = function (data, container) {
1514 var template = this.options.get('templateSelection');
1515 var escapeMarkup = this.options.get('escapeMarkup');
1517 return escapeMarkup(template(data, container));
1520 SingleSelection.prototype.selectionContainer = function () {
1521 return $('<span></span>');
1524 SingleSelection.prototype.update = function (data) {
1525 if (data.length === 0) {
1530 var selection = data[0];
1532 var $rendered = this.$selection.find('.select2-selection__rendered');
1533 var formatted = this.display(selection, $rendered);
1535 $rendered.empty().append(formatted);
1536 $rendered.prop('title', selection.title || selection.text);
1539 return SingleSelection;
1542 S2.define('select2/selection/multiple',[
1546 ], function ($, BaseSelection, Utils) {
1547 function MultipleSelection ($element, options) {
1548 MultipleSelection.__super__.constructor.apply(this, arguments);
1551 Utils.Extend(MultipleSelection, BaseSelection);
1553 MultipleSelection.prototype.render = function () {
1554 var $selection = MultipleSelection.__super__.render.call(this);
1556 $selection.addClass('select2-selection--multiple');
1559 '<ul class="select2-selection__rendered"></ul>'
1565 MultipleSelection.prototype.bind = function (container, $container) {
1568 MultipleSelection.__super__.bind.apply(this, arguments);
1570 this.$selection.on('click', function (evt) {
1571 self.trigger('toggle', {
1578 '.select2-selection__choice__remove',
1580 // Ignore the event if it is disabled
1581 if (self.options.get('disabled')) {
1585 var $remove = $(this);
1586 var $selection = $remove.parent();
1588 var data = $selection.data('data');
1590 self.trigger('unselect', {
1598 MultipleSelection.prototype.clear = function () {
1599 this.$selection.find('.select2-selection__rendered').empty();
1602 MultipleSelection.prototype.display = function (data, container) {
1603 var template = this.options.get('templateSelection');
1604 var escapeMarkup = this.options.get('escapeMarkup');
1606 return escapeMarkup(template(data, container));
1609 MultipleSelection.prototype.selectionContainer = function () {
1611 '<li class="select2-selection__choice">' +
1612 '<span class="select2-selection__choice__remove" role="presentation">' +
1621 MultipleSelection.prototype.update = function (data) {
1624 if (data.length === 0) {
1628 var $selections = [];
1630 for (var d = 0; d < data.length; d++) {
1631 var selection = data[d];
1633 var $selection = this.selectionContainer();
1634 var formatted = this.display(selection, $selection);
1636 $selection.append(formatted);
1637 $selection.prop('title', selection.title || selection.text);
1639 $selection.data('data', selection);
1641 $selections.push($selection);
1644 var $rendered = this.$selection.find('.select2-selection__rendered');
1646 Utils.appendMany($rendered, $selections);
1649 return MultipleSelection;
1652 S2.define('select2/selection/placeholder',[
1654 ], function (Utils) {
1655 function Placeholder (decorated, $element, options) {
1656 this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
1658 decorated.call(this, $element, options);
1661 Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
1662 if (typeof placeholder === 'string') {
1672 Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
1673 var $placeholder = this.selectionContainer();
1675 $placeholder.html(this.display(placeholder));
1676 $placeholder.addClass('select2-selection__placeholder')
1677 .removeClass('select2-selection__choice');
1679 return $placeholder;
1682 Placeholder.prototype.update = function (decorated, data) {
1683 var singlePlaceholder = (
1684 data.length == 1 && data[0].id != this.placeholder.id
1686 var multipleSelections = data.length > 1;
1688 if (multipleSelections || singlePlaceholder) {
1689 return decorated.call(this, data);
1694 var $placeholder = this.createPlaceholder(this.placeholder);
1696 this.$selection.find('.select2-selection__rendered').append($placeholder);
1702 S2.define('select2/selection/allowClear',[
1705 ], function ($, KEYS) {
1706 function AllowClear () { }
1708 AllowClear.prototype.bind = function (decorated, container, $container) {
1711 decorated.call(this, container, $container);
1713 if (this.placeholder == null) {
1714 if (this.options.get('debug') && window.console && console.error) {
1716 'Select2: The `allowClear` option should be used in combination ' +
1717 'with the `placeholder` option.'
1722 this.$selection.on('mousedown', '.select2-selection__clear',
1724 self._handleClear(evt);
1727 container.on('keypress', function (evt) {
1728 self._handleKeyboardClear(evt, container);
1732 AllowClear.prototype._handleClear = function (_, evt) {
1733 // Ignore the event if it is disabled
1734 if (this.options.get('disabled')) {
1738 var $clear = this.$selection.find('.select2-selection__clear');
1740 // Ignore the event if nothing has been selected
1741 if ($clear.length === 0) {
1745 evt.stopPropagation();
1747 var data = $clear.data('data');
1749 for (var d = 0; d < data.length; d++) {
1750 var unselectData = {
1754 // Trigger the `unselect` event, so people can prevent it from being
1756 this.trigger('unselect', unselectData);
1758 // If the event was prevented, don't clear it out.
1759 if (unselectData.prevented) {
1764 this.$element.val(this.placeholder.id).trigger('change');
1766 this.trigger('toggle', {});
1769 AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
1770 if (container.isOpen()) {
1774 if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
1775 this._handleClear(evt);
1779 AllowClear.prototype.update = function (decorated, data) {
1780 decorated.call(this, data);
1782 if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
1783 data.length === 0) {
1788 '<span class="select2-selection__clear">' +
1792 $remove.data('data', data);
1794 this.$selection.find('.select2-selection__rendered').prepend($remove);
1800 S2.define('select2/selection/search',[
1804 ], function ($, Utils, KEYS) {
1805 function Search (decorated, $element, options) {
1806 decorated.call(this, $element, options);
1809 Search.prototype.render = function (decorated) {
1811 '<li class="select2-search select2-search--inline">' +
1812 '<input class="select2-search__field" type="search" tabindex="-1"' +
1813 ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
1814 ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
1818 this.$searchContainer = $search;
1819 this.$search = $search.find('input');
1821 var $rendered = decorated.call(this);
1823 this._transferTabIndex();
1828 Search.prototype.bind = function (decorated, container, $container) {
1831 decorated.call(this, container, $container);
1833 container.on('open', function () {
1834 self.$search.trigger('focus');
1837 container.on('close', function () {
1838 self.$search.val('');
1839 self.$search.removeAttr('aria-activedescendant');
1840 self.$search.trigger('focus');
1843 container.on('enable', function () {
1844 self.$search.prop('disabled', false);
1846 self._transferTabIndex();
1849 container.on('disable', function () {
1850 self.$search.prop('disabled', true);
1853 container.on('focus', function (evt) {
1854 self.$search.trigger('focus');
1857 container.on('results:focus', function (params) {
1858 self.$search.attr('aria-activedescendant', params.id);
1861 this.$selection.on('focusin', '.select2-search--inline', function (evt) {
1862 self.trigger('focus', evt);
1865 this.$selection.on('focusout', '.select2-search--inline', function (evt) {
1866 self._handleBlur(evt);
1869 this.$selection.on('keydown', '.select2-search--inline', function (evt) {
1870 evt.stopPropagation();
1872 self.trigger('keypress', evt);
1874 self._keyUpPrevented = evt.isDefaultPrevented();
1876 var key = evt.which;
1878 if (key === KEYS.BACKSPACE && self.$search.val() === '') {
1879 var $previousChoice = self.$searchContainer
1880 .prev('.select2-selection__choice');
1882 if ($previousChoice.length > 0) {
1883 var item = $previousChoice.data('data');
1885 self.searchRemoveChoice(item);
1887 evt.preventDefault();
1892 // Try to detect the IE version should the `documentMode` property that
1893 // is stored on the document. This is only implemented in IE and is
1894 // slightly cleaner than doing a user agent check.
1895 // This property is not available in Edge, but Edge also doesn't have
1897 var msie = document.documentMode;
1898 var disableInputEvents = msie && msie <= 11;
1900 // Workaround for browsers which do not support the `input` event
1901 // This will prevent double-triggering of events for browsers which support
1902 // both the `keyup` and `input` events.
1904 'input.searchcheck',
1905 '.select2-search--inline',
1907 // IE will trigger the `input` event when a placeholder is used on a
1908 // search box. To get around this issue, we are forced to ignore all
1909 // `input` events in IE and keep using `keyup`.
1910 if (disableInputEvents) {
1911 self.$selection.off('input.search input.searchcheck');
1915 // Unbind the duplicated `keyup` event
1916 self.$selection.off('keyup.search');
1921 'keyup.search input.search',
1922 '.select2-search--inline',
1924 // IE will trigger the `input` event when a placeholder is used on a
1925 // search box. To get around this issue, we are forced to ignore all
1926 // `input` events in IE and keep using `keyup`.
1927 if (disableInputEvents && evt.type === 'input') {
1928 self.$selection.off('input.search input.searchcheck');
1932 var key = evt.which;
1934 // We can freely ignore events from modifier keys
1935 if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
1939 // Tabbing will be handled during the `keydown` phase
1940 if (key == KEYS.TAB) {
1944 self.handleSearch(evt);
1950 * This method will transfer the tabindex attribute from the rendered
1951 * selection to the search box. This allows for the search box to be used as
1952 * the primary focus instead of the selection container.
1956 Search.prototype._transferTabIndex = function (decorated) {
1957 this.$search.attr('tabindex', this.$selection.attr('tabindex'));
1958 this.$selection.attr('tabindex', '-1');
1961 Search.prototype.createPlaceholder = function (decorated, placeholder) {
1962 this.$search.attr('placeholder', placeholder.text);
1965 Search.prototype.update = function (decorated, data) {
1966 var searchHadFocus = this.$search[0] == document.activeElement;
1968 this.$search.attr('placeholder', '');
1970 decorated.call(this, data);
1972 this.$selection.find('.select2-selection__rendered')
1973 .append(this.$searchContainer);
1975 this.resizeSearch();
1976 if (searchHadFocus) {
1977 this.$search.focus();
1981 Search.prototype.handleSearch = function () {
1982 this.resizeSearch();
1984 if (!this._keyUpPrevented) {
1985 var input = this.$search.val();
1987 this.trigger('query', {
1992 this._keyUpPrevented = false;
1995 Search.prototype.searchRemoveChoice = function (decorated, item) {
1996 this.trigger('unselect', {
2000 this.$search.val(item.text);
2001 this.handleSearch();
2004 Search.prototype.resizeSearch = function () {
2005 this.$search.css('width', '25px');
2009 if (this.$search.attr('placeholder') !== '') {
2010 width = this.$selection.find('.select2-selection__rendered').innerWidth();
2012 var minimumWidth = this.$search.val().length + 1;
2014 width = (minimumWidth * 0.75) + 'em';
2017 this.$search.css('width', width);
2023 S2.define('select2/selection/eventRelay',[
2026 function EventRelay () { }
2028 EventRelay.prototype.bind = function (decorated, container, $container) {
2033 'select', 'selecting',
2034 'unselect', 'unselecting'
2037 var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
2039 decorated.call(this, container, $container);
2041 container.on('*', function (name, params) {
2042 // Ignore events that should not be relayed
2043 if ($.inArray(name, relayEvents) === -1) {
2047 // The parameters should always be an object
2048 params = params || {};
2050 // Generate the jQuery event for the Select2 event
2051 var evt = $.Event('select2:' + name, {
2055 self.$element.trigger(evt);
2057 // Only handle preventable events if it was one
2058 if ($.inArray(name, preventableEvents) === -1) {
2062 params.prevented = evt.isDefaultPrevented();
2069 S2.define('select2/translation',[
2072 ], function ($, require) {
2073 function Translation (dict) {
2074 this.dict = dict || {};
2077 Translation.prototype.all = function () {
2081 Translation.prototype.get = function (key) {
2082 return this.dict[key];
2085 Translation.prototype.extend = function (translation) {
2086 this.dict = $.extend({}, translation.all(), this.dict);
2091 Translation._cache = {};
2093 Translation.loadPath = function (path) {
2094 if (!(path in Translation._cache)) {
2095 var translations = require(path);
2097 Translation._cache[path] = translations;
2100 return new Translation(Translation._cache[path]);
2106 S2.define('select2/diacritics',[
2954 S2.define('select2/data/base',[
2956 ], function (Utils) {
2957 function BaseAdapter ($element, options) {
2958 BaseAdapter.__super__.constructor.call(this);
2961 Utils.Extend(BaseAdapter, Utils.Observable);
2963 BaseAdapter.prototype.current = function (callback) {
2964 throw new Error('The `current` method must be defined in child classes.');
2967 BaseAdapter.prototype.query = function (params, callback) {
2968 throw new Error('The `query` method must be defined in child classes.');
2971 BaseAdapter.prototype.bind = function (container, $container) {
2972 // Can be implemented in subclasses
2975 BaseAdapter.prototype.destroy = function () {
2976 // Can be implemented in subclasses
2979 BaseAdapter.prototype.generateResultId = function (container, data) {
2980 var id = container.id + '-result-';
2982 id += Utils.generateChars(4);
2984 if (data.id != null) {
2985 id += '-' + data.id.toString();
2987 id += '-' + Utils.generateChars(4);
2995 S2.define('select2/data/select',[
2999 ], function (BaseAdapter, Utils, $) {
3000 function SelectAdapter ($element, options) {
3001 this.$element = $element;
3002 this.options = options;
3004 SelectAdapter.__super__.constructor.call(this);
3007 Utils.Extend(SelectAdapter, BaseAdapter);
3009 SelectAdapter.prototype.current = function (callback) {
3013 this.$element.find(':selected').each(function () {
3014 var $option = $(this);
3016 var option = self.item($option);
3024 SelectAdapter.prototype.select = function (data) {
3027 data.selected = true;
3029 // If data.element is a DOM node, use it instead
3030 if ($(data.element).is('option')) {
3031 data.element.selected = true;
3033 this.$element.trigger('change');
3038 if (this.$element.prop('multiple')) {
3039 this.current(function (currentData) {
3043 data.push.apply(data, currentData);
3045 for (var d = 0; d < data.length; d++) {
3046 var id = data[d].id;
3048 if ($.inArray(id, val) === -1) {
3053 self.$element.val(val);
3054 self.$element.trigger('change');
3059 this.$element.val(val);
3060 this.$element.trigger('change');
3064 SelectAdapter.prototype.unselect = function (data) {
3067 if (!this.$element.prop('multiple')) {
3071 data.selected = false;
3073 if ($(data.element).is('option')) {
3074 data.element.selected = false;
3076 this.$element.trigger('change');
3081 this.current(function (currentData) {
3084 for (var d = 0; d < currentData.length; d++) {
3085 var id = currentData[d].id;
3087 if (id !== data.id && $.inArray(id, val) === -1) {
3092 self.$element.val(val);
3094 self.$element.trigger('change');
3098 SelectAdapter.prototype.bind = function (container, $container) {
3101 this.container = container;
3103 container.on('select', function (params) {
3104 self.select(params.data);
3107 container.on('unselect', function (params) {
3108 self.unselect(params.data);
3112 SelectAdapter.prototype.destroy = function () {
3113 // Remove anything added to child elements
3114 this.$element.find('*').each(function () {
3115 // Remove any custom data set by Select2
3116 $.removeData(this, 'data');
3120 SelectAdapter.prototype.query = function (params, callback) {
3124 var $options = this.$element.children();
3126 $options.each(function () {
3127 var $option = $(this);
3129 if (!$option.is('option') && !$option.is('optgroup')) {
3133 var option = self.item($option);
3135 var matches = self.matches(params, option);
3137 if (matches !== null) {
3147 SelectAdapter.prototype.addOptions = function ($options) {
3148 Utils.appendMany(this.$element, $options);
3151 SelectAdapter.prototype.option = function (data) {
3154 if (data.children) {
3155 option = document.createElement('optgroup');
3156 option.label = data.text;
3158 option = document.createElement('option');
3160 if (option.textContent !== undefined) {
3161 option.textContent = data.text;
3163 option.innerText = data.text;
3168 option.value = data.id;
3171 if (data.disabled) {
3172 option.disabled = true;
3175 if (data.selected) {
3176 option.selected = true;
3180 option.title = data.title;
3183 var $option = $(option);
3185 var normalizedData = this._normalizeItem(data);
3186 normalizedData.element = option;
3188 // Override the option's data with the combined data
3189 $.data(option, 'data', normalizedData);
3194 SelectAdapter.prototype.item = function ($option) {
3197 data = $.data($option[0], 'data');
3203 if ($option.is('option')) {
3206 text: $option.text(),
3207 disabled: $option.prop('disabled'),
3208 selected: $option.prop('selected'),
3209 title: $option.prop('title')
3211 } else if ($option.is('optgroup')) {
3213 text: $option.prop('label'),
3215 title: $option.prop('title')
3218 var $children = $option.children('option');
3221 for (var c = 0; c < $children.length; c++) {
3222 var $child = $($children[c]);
3224 var child = this.item($child);
3226 children.push(child);
3229 data.children = children;
3232 data = this._normalizeItem(data);
3233 data.element = $option[0];
3235 $.data($option[0], 'data', data);
3240 SelectAdapter.prototype._normalizeItem = function (item) {
3241 if (!$.isPlainObject(item)) {
3248 item = $.extend({}, {
3257 if (item.id != null) {
3258 item.id = item.id.toString();
3261 if (item.text != null) {
3262 item.text = item.text.toString();
3265 if (item._resultId == null && item.id && this.container != null) {
3266 item._resultId = this.generateResultId(this.container, item);
3269 return $.extend({}, defaults, item);
3272 SelectAdapter.prototype.matches = function (params, data) {
3273 var matcher = this.options.get('matcher');
3275 return matcher(params, data);
3278 return SelectAdapter;
3281 S2.define('select2/data/array',[
3285 ], function (SelectAdapter, Utils, $) {
3286 function ArrayAdapter ($element, options) {
3287 var data = options.get('data') || [];
3289 ArrayAdapter.__super__.constructor.call(this, $element, options);
3291 this.addOptions(this.convertToOptions(data));
3294 Utils.Extend(ArrayAdapter, SelectAdapter);
3296 ArrayAdapter.prototype.select = function (data) {
3297 var $option = this.$element.find('option').filter(function (i, elm) {
3298 return elm.value == data.id.toString();
3301 if ($option.length === 0) {
3302 $option = this.option(data);
3304 this.addOptions($option);
3307 ArrayAdapter.__super__.select.call(this, data);
3310 ArrayAdapter.prototype.convertToOptions = function (data) {
3313 var $existing = this.$element.find('option');
3314 var existingIds = $existing.map(function () {
3315 return self.item($(this)).id;
3320 // Filter out all items except for the one passed in the argument
3321 function onlyItem (item) {
3322 return function () {
3323 return $(this).val() == item.id;
3327 for (var d = 0; d < data.length; d++) {
3328 var item = this._normalizeItem(data[d]);
3330 // Skip items which were pre-loaded, only merge the data
3331 if ($.inArray(item.id, existingIds) >= 0) {
3332 var $existingOption = $existing.filter(onlyItem(item));
3334 var existingData = this.item($existingOption);
3335 var newData = $.extend(true, {}, existingData, item);
3337 var $newOption = this.option(newData);
3339 $existingOption.replaceWith($newOption);
3344 var $option = this.option(item);
3346 if (item.children) {
3347 var $children = this.convertToOptions(item.children);
3349 Utils.appendMany($option, $children);
3352 $options.push($option);
3358 return ArrayAdapter;
3361 S2.define('select2/data/ajax',[
3365 ], function (ArrayAdapter, Utils, $) {
3366 function AjaxAdapter ($element, options) {
3367 this.ajaxOptions = this._applyDefaults(options.get('ajax'));
3369 if (this.ajaxOptions.processResults != null) {
3370 this.processResults = this.ajaxOptions.processResults;
3373 AjaxAdapter.__super__.constructor.call(this, $element, options);
3376 Utils.Extend(AjaxAdapter, ArrayAdapter);
3378 AjaxAdapter.prototype._applyDefaults = function (options) {
3380 data: function (params) {
3381 return $.extend({}, params, {
3385 transport: function (params, success, failure) {
3386 var $request = $.ajax(params);
3388 $request.then(success);
3389 $request.fail(failure);
3395 return $.extend({}, defaults, options, true);
3398 AjaxAdapter.prototype.processResults = function (results) {
3402 AjaxAdapter.prototype.query = function (params, callback) {
3406 if (this._request != null) {
3407 // JSONP requests cannot always be aborted
3408 if ($.isFunction(this._request.abort)) {
3409 this._request.abort();
3412 this._request = null;
3415 var options = $.extend({
3417 }, this.ajaxOptions);
3419 if (typeof options.url === 'function') {
3420 options.url = options.url.call(this.$element, params);
3423 if (typeof options.data === 'function') {
3424 options.data = options.data.call(this.$element, params);
3427 function request () {
3428 var $request = options.transport(options, function (data) {
3429 var results = self.processResults(data, params);
3431 if (self.options.get('debug') && window.console && console.error) {
3432 // Check to make sure that the response included a `results` key.
3433 if (!results || !results.results || !$.isArray(results.results)) {
3435 'Select2: The AJAX results did not return an array in the ' +
3436 '`results` key of the response.'
3443 // TODO: Handle AJAX errors
3446 self._request = $request;
3449 if (this.ajaxOptions.delay && params.term !== '') {
3450 if (this._queryTimeout) {
3451 window.clearTimeout(this._queryTimeout);
3454 this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
3463 S2.define('select2/data/tags',[
3466 function Tags (decorated, $element, options) {
3467 var tags = options.get('tags');
3469 var createTag = options.get('createTag');
3471 if (createTag !== undefined) {
3472 this.createTag = createTag;
3475 decorated.call(this, $element, options);
3477 if ($.isArray(tags)) {
3478 for (var t = 0; t < tags.length; t++) {
3480 var item = this._normalizeItem(tag);
3482 var $option = this.option(item);
3484 this.$element.append($option);
3489 Tags.prototype.query = function (decorated, params, callback) {
3492 this._removeOldTags();
3494 if (params.term == null || params.page != null) {
3495 decorated.call(this, params, callback);
3499 function wrapper (obj, child) {
3500 var data = obj.results;
3502 for (var i = 0; i < data.length; i++) {
3503 var option = data[i];
3505 var checkChildren = (
3506 option.children != null &&
3508 results: option.children
3512 var checkText = option.text === params.term;
3514 if (checkText || checkChildren) {
3530 var tag = self.createTag(params);
3533 var $option = self.option(tag);
3534 $option.attr('data-select2-tag', true);
3536 self.addOptions([$option]);
3538 self.insertTag(data, tag);
3546 decorated.call(this, params, wrapper);
3549 Tags.prototype.createTag = function (decorated, params) {
3550 var term = $.trim(params.term);
3562 Tags.prototype.insertTag = function (_, data, tag) {
3566 Tags.prototype._removeOldTags = function (_) {
3567 var tag = this._lastTag;
3569 var $options = this.$element.find('option[data-select2-tag]');
3571 $options.each(function () {
3572 if (this.selected) {
3583 S2.define('select2/data/tokenizer',[
3586 function Tokenizer (decorated, $element, options) {
3587 var tokenizer = options.get('tokenizer');
3589 if (tokenizer !== undefined) {
3590 this.tokenizer = tokenizer;
3593 decorated.call(this, $element, options);
3596 Tokenizer.prototype.bind = function (decorated, container, $container) {
3597 decorated.call(this, container, $container);
3599 this.$search = container.dropdown.$search || container.selection.$search ||
3600 $container.find('.select2-search__field');
3603 Tokenizer.prototype.query = function (decorated, params, callback) {
3606 function select (data) {
3607 self.trigger('select', {
3612 params.term = params.term || '';
3614 var tokenData = this.tokenizer(params, this.options, select);
3616 if (tokenData.term !== params.term) {
3617 // Replace the search term if we have the search box
3618 if (this.$search.length) {
3619 this.$search.val(tokenData.term);
3620 this.$search.focus();
3623 params.term = tokenData.term;
3626 decorated.call(this, params, callback);
3629 Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
3630 var separators = options.get('tokenSeparators') || [];
3631 var term = params.term;
3634 var createTag = this.createTag || function (params) {
3641 while (i < term.length) {
3642 var termChar = term[i];
3644 if ($.inArray(termChar, separators) === -1) {
3650 var part = term.substr(0, i);
3651 var partParams = $.extend({}, params, {
3655 var data = createTag(partParams);
3664 // Reset the term to not include the tokenized portion
3665 term = term.substr(i + 1) || '';
3677 S2.define('select2/data/minimumInputLength',[
3680 function MinimumInputLength (decorated, $e, options) {
3681 this.minimumInputLength = options.get('minimumInputLength');
3683 decorated.call(this, $e, options);
3686 MinimumInputLength.prototype.query = function (decorated, params, callback) {
3687 params.term = params.term || '';
3689 if (params.term.length < this.minimumInputLength) {
3690 this.trigger('results:message', {
3691 message: 'inputTooShort',
3693 minimum: this.minimumInputLength,
3702 decorated.call(this, params, callback);
3705 return MinimumInputLength;
3708 S2.define('select2/data/maximumInputLength',[
3711 function MaximumInputLength (decorated, $e, options) {
3712 this.maximumInputLength = options.get('maximumInputLength');
3714 decorated.call(this, $e, options);
3717 MaximumInputLength.prototype.query = function (decorated, params, callback) {
3718 params.term = params.term || '';
3720 if (this.maximumInputLength > 0 &&
3721 params.term.length > this.maximumInputLength) {
3722 this.trigger('results:message', {
3723 message: 'inputTooLong',
3725 maximum: this.maximumInputLength,
3734 decorated.call(this, params, callback);
3737 return MaximumInputLength;
3740 S2.define('select2/data/maximumSelectionLength',[
3743 function MaximumSelectionLength (decorated, $e, options) {
3744 this.maximumSelectionLength = options.get('maximumSelectionLength');
3746 decorated.call(this, $e, options);
3749 MaximumSelectionLength.prototype.query =
3750 function (decorated, params, callback) {
3753 this.current(function (currentData) {
3754 var count = currentData != null ? currentData.length : 0;
3755 if (self.maximumSelectionLength > 0 &&
3756 count >= self.maximumSelectionLength) {
3757 self.trigger('results:message', {
3758 message: 'maximumSelected',
3760 maximum: self.maximumSelectionLength
3765 decorated.call(self, params, callback);
3769 return MaximumSelectionLength;
3772 S2.define('select2/dropdown',[
3775 ], function ($, Utils) {
3776 function Dropdown ($element, options) {
3777 this.$element = $element;
3778 this.options = options;
3780 Dropdown.__super__.constructor.call(this);
3783 Utils.Extend(Dropdown, Utils.Observable);
3785 Dropdown.prototype.render = function () {
3787 '<span class="select2-dropdown">' +
3788 '<span class="select2-results"></span>' +
3792 $dropdown.attr('dir', this.options.get('dir'));
3794 this.$dropdown = $dropdown;
3799 Dropdown.prototype.bind = function () {
3800 // Should be implemented in subclasses
3803 Dropdown.prototype.position = function ($dropdown, $container) {
3804 // Should be implmented in subclasses
3807 Dropdown.prototype.destroy = function () {
3808 // Remove the dropdown from the DOM
3809 this.$dropdown.remove();
3815 S2.define('select2/dropdown/search',[
3818 ], function ($, Utils) {
3819 function Search () { }
3821 Search.prototype.render = function (decorated) {
3822 var $rendered = decorated.call(this);
3825 '<span class="select2-search select2-search--dropdown">' +
3826 '<input class="select2-search__field" type="search" tabindex="-1"' +
3827 ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
3828 ' spellcheck="false" role="textbox" />' +
3832 this.$searchContainer = $search;
3833 this.$search = $search.find('input');
3835 $rendered.prepend($search);
3840 Search.prototype.bind = function (decorated, container, $container) {
3843 decorated.call(this, container, $container);
3845 this.$search.on('keydown', function (evt) {
3846 self.trigger('keypress', evt);
3848 self._keyUpPrevented = evt.isDefaultPrevented();
3851 // Workaround for browsers which do not support the `input` event
3852 // This will prevent double-triggering of events for browsers which support
3853 // both the `keyup` and `input` events.
3854 this.$search.on('input', function (evt) {
3855 // Unbind the duplicated `keyup` event
3856 $(this).off('keyup');
3859 this.$search.on('keyup input', function (evt) {
3860 self.handleSearch(evt);
3863 container.on('open', function () {
3864 self.$search.attr('tabindex', 0);
3866 self.$search.focus();
3868 window.setTimeout(function () {
3869 self.$search.focus();
3873 container.on('close', function () {
3874 self.$search.attr('tabindex', -1);
3876 self.$search.val('');
3879 container.on('results:all', function (params) {
3880 if (params.query.term == null || params.query.term === '') {
3881 var showSearch = self.showSearch(params);
3884 self.$searchContainer.removeClass('select2-search--hide');
3886 self.$searchContainer.addClass('select2-search--hide');
3892 Search.prototype.handleSearch = function (evt) {
3893 if (!this._keyUpPrevented) {
3894 var input = this.$search.val();
3896 this.trigger('query', {
3901 this._keyUpPrevented = false;
3904 Search.prototype.showSearch = function (_, params) {
3911 S2.define('select2/dropdown/hidePlaceholder',[
3914 function HidePlaceholder (decorated, $element, options, dataAdapter) {
3915 this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
3917 decorated.call(this, $element, options, dataAdapter);
3920 HidePlaceholder.prototype.append = function (decorated, data) {
3921 data.results = this.removePlaceholder(data.results);
3923 decorated.call(this, data);
3926 HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
3927 if (typeof placeholder === 'string') {
3937 HidePlaceholder.prototype.removePlaceholder = function (_, data) {
3938 var modifiedData = data.slice(0);
3940 for (var d = data.length - 1; d >= 0; d--) {
3943 if (this.placeholder.id === item.id) {
3944 modifiedData.splice(d, 1);
3948 return modifiedData;
3951 return HidePlaceholder;
3954 S2.define('select2/dropdown/infiniteScroll',[
3957 function InfiniteScroll (decorated, $element, options, dataAdapter) {
3958 this.lastParams = {};
3960 decorated.call(this, $element, options, dataAdapter);
3962 this.$loadingMore = this.createLoadingMore();
3963 this.loading = false;
3966 InfiniteScroll.prototype.append = function (decorated, data) {
3967 this.$loadingMore.remove();
3968 this.loading = false;
3970 decorated.call(this, data);
3972 if (this.showLoadingMore(data)) {
3973 this.$results.append(this.$loadingMore);
3977 InfiniteScroll.prototype.bind = function (decorated, container, $container) {
3980 decorated.call(this, container, $container);
3982 container.on('query', function (params) {
3983 self.lastParams = params;
3984 self.loading = true;
3987 container.on('query:append', function (params) {
3988 self.lastParams = params;
3989 self.loading = true;
3992 this.$results.on('scroll', function () {
3993 var isLoadMoreVisible = $.contains(
3994 document.documentElement,
3995 self.$loadingMore[0]
3998 if (self.loading || !isLoadMoreVisible) {
4002 var currentOffset = self.$results.offset().top +
4003 self.$results.outerHeight(false);
4004 var loadingMoreOffset = self.$loadingMore.offset().top +
4005 self.$loadingMore.outerHeight(false);
4007 if (currentOffset + 50 >= loadingMoreOffset) {
4013 InfiniteScroll.prototype.loadMore = function () {
4014 this.loading = true;
4016 var params = $.extend({}, {page: 1}, this.lastParams);
4020 this.trigger('query:append', params);
4023 InfiniteScroll.prototype.showLoadingMore = function (_, data) {
4024 return data.pagination && data.pagination.more;
4027 InfiniteScroll.prototype.createLoadingMore = function () {
4030 'class="select2-results__option select2-results__option--load-more"' +
4031 'role="treeitem" aria-disabled="true"></li>'
4034 var message = this.options.get('translations').get('loadingMore');
4036 $option.html(message(this.lastParams));
4041 return InfiniteScroll;
4044 S2.define('select2/dropdown/attachBody',[
4047 ], function ($, Utils) {
4048 function AttachBody (decorated, $element, options) {
4049 this.$dropdownParent = options.get('dropdownParent') || $(document.body);
4051 decorated.call(this, $element, options);
4054 AttachBody.prototype.bind = function (decorated, container, $container) {
4057 var setupResultsEvents = false;
4059 decorated.call(this, container, $container);
4061 container.on('open', function () {
4062 self._showDropdown();
4063 self._attachPositioningHandler(container);
4065 if (!setupResultsEvents) {
4066 setupResultsEvents = true;
4068 container.on('results:all', function () {
4069 self._positionDropdown();
4070 self._resizeDropdown();
4073 container.on('results:append', function () {
4074 self._positionDropdown();
4075 self._resizeDropdown();
4080 container.on('close', function () {
4081 self._hideDropdown();
4082 self._detachPositioningHandler(container);
4085 this.$dropdownContainer.on('mousedown', function (evt) {
4086 evt.stopPropagation();
4090 AttachBody.prototype.destroy = function (decorated) {
4091 decorated.call(this);
4093 this.$dropdownContainer.remove();
4096 AttachBody.prototype.position = function (decorated, $dropdown, $container) {
4097 // Clone all of the container classes
4098 $dropdown.attr('class', $container.attr('class'));
4100 $dropdown.removeClass('select2');
4101 $dropdown.addClass('select2-container--open');
4104 position: 'absolute',
4108 this.$container = $container;
4111 AttachBody.prototype.render = function (decorated) {
4112 var $container = $('<span></span>');
4114 var $dropdown = decorated.call(this);
4115 $container.append($dropdown);
4117 this.$dropdownContainer = $container;
4122 AttachBody.prototype._hideDropdown = function (decorated) {
4123 this.$dropdownContainer.detach();
4126 AttachBody.prototype._attachPositioningHandler =
4127 function (decorated, container) {
4130 var scrollEvent = 'scroll.select2.' + container.id;
4131 var resizeEvent = 'resize.select2.' + container.id;
4132 var orientationEvent = 'orientationchange.select2.' + container.id;
4134 var $watchers = this.$container.parents().filter(Utils.hasScroll);
4135 $watchers.each(function () {
4136 $(this).data('select2-scroll-position', {
4137 x: $(this).scrollLeft(),
4138 y: $(this).scrollTop()
4142 $watchers.on(scrollEvent, function (ev) {
4143 var position = $(this).data('select2-scroll-position');
4144 $(this).scrollTop(position.y);
4147 $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
4149 self._positionDropdown();
4150 self._resizeDropdown();
4154 AttachBody.prototype._detachPositioningHandler =
4155 function (decorated, container) {
4156 var scrollEvent = 'scroll.select2.' + container.id;
4157 var resizeEvent = 'resize.select2.' + container.id;
4158 var orientationEvent = 'orientationchange.select2.' + container.id;
4160 var $watchers = this.$container.parents().filter(Utils.hasScroll);
4161 $watchers.off(scrollEvent);
4163 $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
4166 AttachBody.prototype._positionDropdown = function () {
4167 var $window = $(window);
4169 var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
4170 var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
4172 var newDirection = null;
4174 var position = this.$container.position();
4175 var offset = this.$container.offset();
4177 offset.bottom = offset.top + this.$container.outerHeight(false);
4180 height: this.$container.outerHeight(false)
4183 container.top = offset.top;
4184 container.bottom = offset.top + container.height;
4187 height: this.$dropdown.outerHeight(false)
4191 top: $window.scrollTop(),
4192 bottom: $window.scrollTop() + $window.height()
4195 var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
4196 var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
4200 top: container.bottom
4203 // Fix positioning with static parents
4204 if (this.$dropdownParent[0].style.position !== 'static') {
4205 var parentOffset = this.$dropdownParent.offset();
4207 css.top -= parentOffset.top;
4208 css.left -= parentOffset.left;
4211 if (!isCurrentlyAbove && !isCurrentlyBelow) {
4212 newDirection = 'below';
4215 if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
4216 newDirection = 'above';
4217 } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
4218 newDirection = 'below';
4221 if (newDirection == 'above' ||
4222 (isCurrentlyAbove && newDirection !== 'below')) {
4223 css.top = container.top - dropdown.height;
4226 if (newDirection != null) {
4228 .removeClass('select2-dropdown--below select2-dropdown--above')
4229 .addClass('select2-dropdown--' + newDirection);
4231 .removeClass('select2-container--below select2-container--above')
4232 .addClass('select2-container--' + newDirection);
4235 this.$dropdownContainer.css(css);
4238 AttachBody.prototype._resizeDropdown = function () {
4240 width: this.$container.outerWidth(false) + 'px'
4243 if (this.options.get('dropdownAutoWidth')) {
4244 css.minWidth = css.width;
4248 this.$dropdown.css(css);
4251 AttachBody.prototype._showDropdown = function (decorated) {
4252 this.$dropdownContainer.appendTo(this.$dropdownParent);
4254 this._positionDropdown();
4255 this._resizeDropdown();
4261 S2.define('select2/dropdown/minimumResultsForSearch',[
4264 function countResults (data) {
4267 for (var d = 0; d < data.length; d++) {
4270 if (item.children) {
4271 count += countResults(item.children);
4280 function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
4281 this.minimumResultsForSearch = options.get('minimumResultsForSearch');
4283 if (this.minimumResultsForSearch < 0) {
4284 this.minimumResultsForSearch = Infinity;
4287 decorated.call(this, $element, options, dataAdapter);
4290 MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
4291 if (countResults(params.data.results) < this.minimumResultsForSearch) {
4295 return decorated.call(this, params);
4298 return MinimumResultsForSearch;
4301 S2.define('select2/dropdown/selectOnClose',[
4304 function SelectOnClose () { }
4306 SelectOnClose.prototype.bind = function (decorated, container, $container) {
4309 decorated.call(this, container, $container);
4311 container.on('close', function () {
4312 self._handleSelectOnClose();
4316 SelectOnClose.prototype._handleSelectOnClose = function () {
4317 var $highlightedResults = this.getHighlightedResults();
4319 // Only select highlighted results
4320 if ($highlightedResults.length < 1) {
4324 var data = $highlightedResults.data('data');
4326 // Don't re-select already selected resulte
4328 (data.element != null && data.element.selected) ||
4329 (data.element == null && data.selected)
4334 this.trigger('select', {
4339 return SelectOnClose;
4342 S2.define('select2/dropdown/closeOnSelect',[
4345 function CloseOnSelect () { }
4347 CloseOnSelect.prototype.bind = function (decorated, container, $container) {
4350 decorated.call(this, container, $container);
4352 container.on('select', function (evt) {
4353 self._selectTriggered(evt);
4356 container.on('unselect', function (evt) {
4357 self._selectTriggered(evt);
4361 CloseOnSelect.prototype._selectTriggered = function (_, evt) {
4362 var originalEvent = evt.originalEvent;
4364 // Don't close if the control key is being held
4365 if (originalEvent && originalEvent.ctrlKey) {
4369 this.trigger('close', {});
4372 return CloseOnSelect;
4375 S2.define('select2/i18n/en',[],function () {
4378 errorLoading: function () {
4379 return 'The results could not be loaded.';
4381 inputTooLong: function (args) {
4382 var overChars = args.input.length - args.maximum;
4384 var message = 'Please delete ' + overChars + ' character';
4386 if (overChars != 1) {
4392 inputTooShort: function (args) {
4393 var remainingChars = args.minimum - args.input.length;
4395 var message = 'Please enter ' + remainingChars + ' or more characters';
4399 loadingMore: function () {
4400 return 'Loading more results…';
4402 maximumSelected: function (args) {
4403 var message = 'You can only select ' + args.maximum + ' item';
4405 if (args.maximum != 1) {
4411 noResults: function () {
4412 return 'No results found';
4414 searching: function () {
4415 return 'Searching…';
4420 S2.define('select2/defaults',[
4426 './selection/single',
4427 './selection/multiple',
4428 './selection/placeholder',
4429 './selection/allowClear',
4430 './selection/search',
4431 './selection/eventRelay',
4442 './data/minimumInputLength',
4443 './data/maximumInputLength',
4444 './data/maximumSelectionLength',
4447 './dropdown/search',
4448 './dropdown/hidePlaceholder',
4449 './dropdown/infiniteScroll',
4450 './dropdown/attachBody',
4451 './dropdown/minimumResultsForSearch',
4452 './dropdown/selectOnClose',
4453 './dropdown/closeOnSelect',
4456 ], function ($, require,
4460 SingleSelection, MultipleSelection, Placeholder, AllowClear,
4461 SelectionSearch, EventRelay,
4463 Utils, Translation, DIACRITICS,
4465 SelectData, ArrayData, AjaxData, Tags, Tokenizer,
4466 MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
4468 Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
4469 AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
4471 EnglishTranslation) {
4472 function Defaults () {
4476 Defaults.prototype.apply = function (options) {
4477 options = $.extend({}, this.defaults, options);
4479 if (options.dataAdapter == null) {
4480 if (options.ajax != null) {
4481 options.dataAdapter = AjaxData;
4482 } else if (options.data != null) {
4483 options.dataAdapter = ArrayData;
4485 options.dataAdapter = SelectData;
4488 if (options.minimumInputLength > 0) {
4489 options.dataAdapter = Utils.Decorate(
4490 options.dataAdapter,
4495 if (options.maximumInputLength > 0) {
4496 options.dataAdapter = Utils.Decorate(
4497 options.dataAdapter,
4502 if (options.maximumSelectionLength > 0) {
4503 options.dataAdapter = Utils.Decorate(
4504 options.dataAdapter,
4505 MaximumSelectionLength
4510 options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
4513 if (options.tokenSeparators != null || options.tokenizer != null) {
4514 options.dataAdapter = Utils.Decorate(
4515 options.dataAdapter,
4520 if (options.query != null) {
4521 var Query = require(options.amdBase + 'compat/query');
4523 options.dataAdapter = Utils.Decorate(
4524 options.dataAdapter,
4529 if (options.initSelection != null) {
4530 var InitSelection = require(options.amdBase + 'compat/initSelection');
4532 options.dataAdapter = Utils.Decorate(
4533 options.dataAdapter,
4539 if (options.resultsAdapter == null) {
4540 options.resultsAdapter = ResultsList;
4542 if (options.ajax != null) {
4543 options.resultsAdapter = Utils.Decorate(
4544 options.resultsAdapter,
4549 if (options.placeholder != null) {
4550 options.resultsAdapter = Utils.Decorate(
4551 options.resultsAdapter,
4556 if (options.selectOnClose) {
4557 options.resultsAdapter = Utils.Decorate(
4558 options.resultsAdapter,
4564 if (options.dropdownAdapter == null) {
4565 if (options.multiple) {
4566 options.dropdownAdapter = Dropdown;
4568 var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
4570 options.dropdownAdapter = SearchableDropdown;
4573 if (options.minimumResultsForSearch !== 0) {
4574 options.dropdownAdapter = Utils.Decorate(
4575 options.dropdownAdapter,
4576 MinimumResultsForSearch
4580 if (options.closeOnSelect) {
4581 options.dropdownAdapter = Utils.Decorate(
4582 options.dropdownAdapter,
4588 options.dropdownCssClass != null ||
4589 options.dropdownCss != null ||
4590 options.adaptDropdownCssClass != null
4592 var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
4594 options.dropdownAdapter = Utils.Decorate(
4595 options.dropdownAdapter,
4600 options.dropdownAdapter = Utils.Decorate(
4601 options.dropdownAdapter,
4606 if (options.selectionAdapter == null) {
4607 if (options.multiple) {
4608 options.selectionAdapter = MultipleSelection;
4610 options.selectionAdapter = SingleSelection;
4613 // Add the placeholder mixin if a placeholder was specified
4614 if (options.placeholder != null) {
4615 options.selectionAdapter = Utils.Decorate(
4616 options.selectionAdapter,
4621 if (options.allowClear) {
4622 options.selectionAdapter = Utils.Decorate(
4623 options.selectionAdapter,
4628 if (options.multiple) {
4629 options.selectionAdapter = Utils.Decorate(
4630 options.selectionAdapter,
4636 options.containerCssClass != null ||
4637 options.containerCss != null ||
4638 options.adaptContainerCssClass != null
4640 var ContainerCSS = require(options.amdBase + 'compat/containerCss');
4642 options.selectionAdapter = Utils.Decorate(
4643 options.selectionAdapter,
4648 options.selectionAdapter = Utils.Decorate(
4649 options.selectionAdapter,
4654 if (typeof options.language === 'string') {
4655 // Check if the language is specified with a region
4656 if (options.language.indexOf('-') > 0) {
4657 // Extract the region information if it is included
4658 var languageParts = options.language.split('-');
4659 var baseLanguage = languageParts[0];
4661 options.language = [options.language, baseLanguage];
4663 options.language = [options.language];
4667 if ($.isArray(options.language)) {
4668 var languages = new Translation();
4669 options.language.push('en');
4671 var languageNames = options.language;
4673 for (var l = 0; l < languageNames.length; l++) {
4674 var name = languageNames[l];
4678 // Try to load it with the original name
4679 language = Translation.loadPath(name);
4682 // If we couldn't load it, check if it wasn't the full path
4683 name = this.defaults.amdLanguageBase + name;
4684 language = Translation.loadPath(name);
4686 // The translation could not be loaded at all. Sometimes this is
4687 // because of a configuration problem, other times this can be
4688 // because of how Select2 helps load all possible translation files.
4689 if (options.debug && window.console && console.warn) {
4691 'Select2: The language file for "' + name + '" could not be ' +
4692 'automatically loaded. A fallback will be used instead.'
4700 languages.extend(language);
4703 options.translations = languages;
4705 var baseTranslation = Translation.loadPath(
4706 this.defaults.amdLanguageBase + 'en'
4708 var customTranslation = new Translation(options.language);
4710 customTranslation.extend(baseTranslation);
4712 options.translations = customTranslation;
4718 Defaults.prototype.reset = function () {
4719 function stripDiacritics (text) {
4720 // Used 'uni range + named function' from http://jsperf.com/diacritics/18
4722 return DIACRITICS[a] || a;
4725 return text.replace(/[^\u0000-\u007E]/g, match);
4728 function matcher (params, data) {
4729 // Always return the object if there is nothing to compare
4730 if ($.trim(params.term) === '') {
4734 // Do a recursive check for options with children
4735 if (data.children && data.children.length > 0) {
4736 // Clone the data object if there are children
4737 // This is required as we modify the object to remove any non-matches
4738 var match = $.extend(true, {}, data);
4740 // Check each child of the option
4741 for (var c = data.children.length - 1; c >= 0; c--) {
4742 var child = data.children[c];
4744 var matches = matcher(params, child);
4746 // If there wasn't a match, remove the object in the array
4747 if (matches == null) {
4748 match.children.splice(c, 1);
4752 // If any children matched, return the new object
4753 if (match.children.length > 0) {
4757 // If there were no matching children, check just the plain object
4758 return matcher(params, match);
4761 var original = stripDiacritics(data.text).toUpperCase();
4762 var term = stripDiacritics(params.term).toUpperCase();
4764 // Check if the text contains the term
4765 if (original.indexOf(term) > -1) {
4769 // If it doesn't contain the term, don't return anything
4775 amdLanguageBase: './i18n/',
4776 closeOnSelect: true,
4778 dropdownAutoWidth: false,
4779 escapeMarkup: Utils.escapeMarkup,
4780 language: EnglishTranslation,
4782 minimumInputLength: 0,
4783 maximumInputLength: 0,
4784 maximumSelectionLength: 0,
4785 minimumResultsForSearch: 0,
4786 selectOnClose: false,
4787 sorter: function (data) {
4790 templateResult: function (result) {
4793 templateSelection: function (selection) {
4794 return selection.text;
4801 Defaults.prototype.set = function (key, value) {
4802 var camelKey = $.camelCase(key);
4805 data[camelKey] = value;
4807 var convertedData = Utils._convertData(data);
4809 $.extend(this.defaults, convertedData);
4812 var defaults = new Defaults();
4817 S2.define('select2/options',[
4822 ], function (require, $, Defaults, Utils) {
4823 function Options (options, $element) {
4824 this.options = options;
4826 if ($element != null) {
4827 this.fromElement($element);
4830 this.options = Defaults.apply(this.options);
4832 if ($element && $element.is('input')) {
4833 var InputCompat = require(this.get('amdBase') + 'compat/inputData');
4835 this.options.dataAdapter = Utils.Decorate(
4836 this.options.dataAdapter,
4842 Options.prototype.fromElement = function ($e) {
4843 var excludedData = ['select2'];
4845 if (this.options.multiple == null) {
4846 this.options.multiple = $e.prop('multiple');
4849 if (this.options.disabled == null) {
4850 this.options.disabled = $e.prop('disabled');
4853 if (this.options.language == null) {
4854 if ($e.prop('lang')) {
4855 this.options.language = $e.prop('lang').toLowerCase();
4856 } else if ($e.closest('[lang]').prop('lang')) {
4857 this.options.language = $e.closest('[lang]').prop('lang');
4861 if (this.options.dir == null) {
4862 if ($e.prop('dir')) {
4863 this.options.dir = $e.prop('dir');
4864 } else if ($e.closest('[dir]').prop('dir')) {
4865 this.options.dir = $e.closest('[dir]').prop('dir');
4867 this.options.dir = 'ltr';
4871 $e.prop('disabled', this.options.disabled);
4872 $e.prop('multiple', this.options.multiple);
4874 if ($e.data('select2Tags')) {
4875 if (this.options.debug && window.console && console.warn) {
4877 'Select2: The `data-select2-tags` attribute has been changed to ' +
4878 'use the `data-data` and `data-tags="true"` attributes and will be ' +
4879 'removed in future versions of Select2.'
4883 $e.data('data', $e.data('select2Tags'));
4884 $e.data('tags', true);
4887 if ($e.data('ajaxUrl')) {
4888 if (this.options.debug && window.console && console.warn) {
4890 'Select2: The `data-ajax-url` attribute has been changed to ' +
4891 '`data-ajax--url` and support for the old attribute will be removed' +
4892 ' in future versions of Select2.'
4896 $e.attr('ajax--url', $e.data('ajaxUrl'));
4897 $e.data('ajax--url', $e.data('ajaxUrl'));
4902 // Prefer the element's `dataset` attribute if it exists
4903 // jQuery 1.x does not correctly handle data attributes with multiple dashes
4904 if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
4905 dataset = $.extend(true, {}, $e[0].dataset, $e.data());
4907 dataset = $e.data();
4910 var data = $.extend(true, {}, dataset);
4912 data = Utils._convertData(data);
4914 for (var key in data) {
4915 if ($.inArray(key, excludedData) > -1) {
4919 if ($.isPlainObject(this.options[key])) {
4920 $.extend(this.options[key], data[key]);
4922 this.options[key] = data[key];
4929 Options.prototype.get = function (key) {
4930 return this.options[key];
4933 Options.prototype.set = function (key, val) {
4934 this.options[key] = val;
4940 S2.define('select2/core',[
4945 ], function ($, Options, Utils, KEYS) {
4946 var Select2 = function ($element, options) {
4947 if ($element.data('select2') != null) {
4948 $element.data('select2').destroy();
4951 this.$element = $element;
4953 this.id = this._generateId($element);
4955 options = options || {};
4957 this.options = new Options(options, $element);
4959 Select2.__super__.constructor.call(this);
4961 // Set up the tabindex
4963 var tabindex = $element.attr('tabindex') || 0;
4964 $element.data('old-tabindex', tabindex);
4965 $element.attr('tabindex', '-1');
4967 // Set up containers and adapters
4969 var DataAdapter = this.options.get('dataAdapter');
4970 this.dataAdapter = new DataAdapter($element, this.options);
4972 var $container = this.render();
4974 this._placeContainer($container);
4976 var SelectionAdapter = this.options.get('selectionAdapter');
4977 this.selection = new SelectionAdapter($element, this.options);
4978 this.$selection = this.selection.render();
4980 this.selection.position(this.$selection, $container);
4982 var DropdownAdapter = this.options.get('dropdownAdapter');
4983 this.dropdown = new DropdownAdapter($element, this.options);
4984 this.$dropdown = this.dropdown.render();
4986 this.dropdown.position(this.$dropdown, $container);
4988 var ResultsAdapter = this.options.get('resultsAdapter');
4989 this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
4990 this.$results = this.results.render();
4992 this.results.position(this.$results, this.$dropdown);
4998 // Bind the container to all of the adapters
4999 this._bindAdapters();
5001 // Register any DOM event handlers
5002 this._registerDomEvents();
5004 // Register any internal event handlers
5005 this._registerDataEvents();
5006 this._registerSelectionEvents();
5007 this._registerDropdownEvents();
5008 this._registerResultsEvents();
5009 this._registerEvents();
5011 // Set the initial state
5012 this.dataAdapter.current(function (initialData) {
5013 self.trigger('selection:update', {
5018 // Hide the original select
5019 $element.addClass('select2-hidden-accessible');
5020 $element.attr('aria-hidden', 'true');
5022 // Synchronize any monitored attributes
5023 this._syncAttributes();
5025 $element.data('select2', this);
5028 Utils.Extend(Select2, Utils.Observable);
5030 Select2.prototype._generateId = function ($element) {
5033 if ($element.attr('id') != null) {
5034 id = $element.attr('id');
5035 } else if ($element.attr('name') != null) {
5036 id = $element.attr('name') + '-' + Utils.generateChars(2);
5038 id = Utils.generateChars(4);
5041 id = 'select2-' + id;
5046 Select2.prototype._placeContainer = function ($container) {
5047 $container.insertAfter(this.$element);
5049 var width = this._resolveWidth(this.$element, this.options.get('width'));
5051 if (width != null) {
5052 $container.css('width', width);
5056 Select2.prototype._resolveWidth = function ($element, method) {
5057 var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
5059 if (method == 'resolve') {
5060 var styleWidth = this._resolveWidth($element, 'style');
5062 if (styleWidth != null) {
5066 return this._resolveWidth($element, 'element');
5069 if (method == 'element') {
5070 var elementWidth = $element.outerWidth(false);
5072 if (elementWidth <= 0) {
5076 return elementWidth + 'px';
5079 if (method == 'style') {
5080 var style = $element.attr('style');
5082 if (typeof(style) !== 'string') {
5086 var attrs = style.split(';');
5088 for (var i = 0, l = attrs.length; i < l; i = i + 1) {
5089 var attr = attrs[i].replace(/\s/g, '');
5090 var matches = attr.match(WIDTH);
5092 if (matches !== null && matches.length >= 1) {
5103 Select2.prototype._bindAdapters = function () {
5104 this.dataAdapter.bind(this, this.$container);
5105 this.selection.bind(this, this.$container);
5107 this.dropdown.bind(this, this.$container);
5108 this.results.bind(this, this.$container);
5111 Select2.prototype._registerDomEvents = function () {
5114 this.$element.on('change.select2', function () {
5115 self.dataAdapter.current(function (data) {
5116 self.trigger('selection:update', {
5122 this._sync = Utils.bind(this._syncAttributes, this);
5124 if (this.$element[0].attachEvent) {
5125 this.$element[0].attachEvent('onpropertychange', this._sync);
5128 var observer = window.MutationObserver ||
5129 window.WebKitMutationObserver ||
5130 window.MozMutationObserver
5133 if (observer != null) {
5134 this._observer = new observer(function (mutations) {
5135 $.each(mutations, self._sync);
5137 this._observer.observe(this.$element[0], {
5141 } else if (this.$element[0].addEventListener) {
5142 this.$element[0].addEventListener('DOMAttrModified', self._sync, false);
5146 Select2.prototype._registerDataEvents = function () {
5149 this.dataAdapter.on('*', function (name, params) {
5150 self.trigger(name, params);
5154 Select2.prototype._registerSelectionEvents = function () {
5156 var nonRelayEvents = ['toggle', 'focus'];
5158 this.selection.on('toggle', function () {
5159 self.toggleDropdown();
5162 this.selection.on('focus', function (params) {
5166 this.selection.on('*', function (name, params) {
5167 if ($.inArray(name, nonRelayEvents) !== -1) {
5171 self.trigger(name, params);
5175 Select2.prototype._registerDropdownEvents = function () {
5178 this.dropdown.on('*', function (name, params) {
5179 self.trigger(name, params);
5183 Select2.prototype._registerResultsEvents = function () {
5186 this.results.on('*', function (name, params) {
5187 self.trigger(name, params);
5191 Select2.prototype._registerEvents = function () {
5194 this.on('open', function () {
5195 self.$container.addClass('select2-container--open');
5198 this.on('close', function () {
5199 self.$container.removeClass('select2-container--open');
5202 this.on('enable', function () {
5203 self.$container.removeClass('select2-container--disabled');
5206 this.on('disable', function () {
5207 self.$container.addClass('select2-container--disabled');
5210 this.on('blur', function () {
5211 self.$container.removeClass('select2-container--focus');
5214 this.on('query', function (params) {
5215 if (!self.isOpen()) {
5216 self.trigger('open', {});
5219 this.dataAdapter.query(params, function (data) {
5220 self.trigger('results:all', {
5227 this.on('query:append', function (params) {
5228 this.dataAdapter.query(params, function (data) {
5229 self.trigger('results:append', {
5236 this.on('keypress', function (evt) {
5237 var key = evt.which;
5239 if (self.isOpen()) {
5240 if (key === KEYS.ESC || key === KEYS.TAB ||
5241 (key === KEYS.UP && evt.altKey)) {
5244 evt.preventDefault();
5245 } else if (key === KEYS.ENTER) {
5246 self.trigger('results:select', {});
5248 evt.preventDefault();
5249 } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
5250 self.trigger('results:toggle', {});
5252 evt.preventDefault();
5253 } else if (key === KEYS.UP) {
5254 self.trigger('results:previous', {});
5256 evt.preventDefault();
5257 } else if (key === KEYS.DOWN) {
5258 self.trigger('results:next', {});
5260 evt.preventDefault();
5263 if (key === KEYS.ENTER || key === KEYS.SPACE ||
5264 (key === KEYS.DOWN && evt.altKey)) {
5267 evt.preventDefault();
5273 Select2.prototype._syncAttributes = function () {
5274 this.options.set('disabled', this.$element.prop('disabled'));
5276 if (this.options.get('disabled')) {
5277 if (this.isOpen()) {
5281 this.trigger('disable', {});
5283 this.trigger('enable', {});
5288 * Override the trigger method to automatically trigger pre-events when
5289 * there are events that can be prevented.
5291 Select2.prototype.trigger = function (name, args) {
5292 var actualTrigger = Select2.__super__.trigger;
5293 var preTriggerMap = {
5296 'select': 'selecting',
5297 'unselect': 'unselecting'
5300 if (args === undefined) {
5304 if (name in preTriggerMap) {
5305 var preTriggerName = preTriggerMap[name];
5306 var preTriggerArgs = {
5312 actualTrigger.call(this, preTriggerName, preTriggerArgs);
5314 if (preTriggerArgs.prevented) {
5315 args.prevented = true;
5321 actualTrigger.call(this, name, args);
5324 Select2.prototype.toggleDropdown = function () {
5325 if (this.options.get('disabled')) {
5329 if (this.isOpen()) {
5336 Select2.prototype.open = function () {
5337 if (this.isOpen()) {
5341 this.trigger('query', {});
5344 Select2.prototype.close = function () {
5345 if (!this.isOpen()) {
5349 this.trigger('close', {});
5352 Select2.prototype.isOpen = function () {
5353 return this.$container.hasClass('select2-container--open');
5356 Select2.prototype.hasFocus = function () {
5357 return this.$container.hasClass('select2-container--focus');
5360 Select2.prototype.focus = function (data) {
5361 // No need to re-trigger focus events if we are already focused
5362 if (this.hasFocus()) {
5366 this.$container.addClass('select2-container--focus');
5367 this.trigger('focus', {});
5370 Select2.prototype.enable = function (args) {
5371 if (this.options.get('debug') && window.console && console.warn) {
5373 'Select2: The `select2("enable")` method has been deprecated and will' +
5374 ' be removed in later Select2 versions. Use $element.prop("disabled")' +
5379 if (args == null || args.length === 0) {
5383 var disabled = !args[0];
5385 this.$element.prop('disabled', disabled);
5388 Select2.prototype.data = function () {
5389 if (this.options.get('debug') &&
5390 arguments.length > 0 && window.console && console.warn) {
5392 'Select2: Data can no longer be set using `select2("data")`. You ' +
5393 'should consider setting the value instead using `$element.val()`.'
5399 this.dataAdapter.current(function (currentData) {
5406 Select2.prototype.val = function (args) {
5407 if (this.options.get('debug') && window.console && console.warn) {
5409 'Select2: The `select2("val")` method has been deprecated and will be' +
5410 ' removed in later Select2 versions. Use $element.val() instead.'
5414 if (args == null || args.length === 0) {
5415 return this.$element.val();
5418 var newVal = args[0];
5420 if ($.isArray(newVal)) {
5421 newVal = $.map(newVal, function (obj) {
5422 return obj.toString();
5426 this.$element.val(newVal).trigger('change');
5429 Select2.prototype.destroy = function () {
5430 this.$container.remove();
5432 if (this.$element[0].detachEvent) {
5433 this.$element[0].detachEvent('onpropertychange', this._sync);
5436 if (this._observer != null) {
5437 this._observer.disconnect();
5438 this._observer = null;
5439 } else if (this.$element[0].removeEventListener) {
5441 .removeEventListener('DOMAttrModified', this._sync, false);
5446 this.$element.off('.select2');
5447 this.$element.attr('tabindex', this.$element.data('old-tabindex'));
5449 this.$element.removeClass('select2-hidden-accessible');
5450 this.$element.attr('aria-hidden', 'false');
5451 this.$element.removeData('select2');
5453 this.dataAdapter.destroy();
5454 this.selection.destroy();
5455 this.dropdown.destroy();
5456 this.results.destroy();
5458 this.dataAdapter = null;
5459 this.selection = null;
5460 this.dropdown = null;
5461 this.results = null;
5464 Select2.prototype.render = function () {
5466 '<span class="select2 select2-container">' +
5467 '<span class="selection"></span>' +
5468 '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
5472 $container.attr('dir', this.options.get('dir'));
5474 this.$container = $container;
5476 this.$container.addClass('select2-container--' + this.options.get('theme'));
5478 $container.data('element', this.$element);
5486 S2.define('jquery-mousewheel',[
5489 // Used to shim jQuery.mousewheel for non-full builds.
5493 S2.define('jquery.select2',[
5495 'jquery-mousewheel',
5498 './select2/defaults'
5499 ], function ($, _, Select2, Defaults) {
5500 if ($.fn.select2 == null) {
5501 // All methods that should return the element
5502 var thisMethods = ['open', 'close', 'destroy'];
5504 $.fn.select2 = function (options) {
5505 options = options || {};
5507 if (typeof options === 'object') {
5508 this.each(function () {
5509 var instanceOptions = $.extend(true, {}, options);
5511 var instance = new Select2($(this), instanceOptions);
5515 } else if (typeof options === 'string') {
5518 this.each(function () {
5519 var instance = $(this).data('select2');
5521 if (instance == null && window.console && console.error) {
5523 'The select2(\'' + options + '\') method was called on an ' +
5524 'element that is not using Select2.'
5528 var args = Array.prototype.slice.call(arguments, 1);
5530 ret = instance[options].apply(instance, args);
5533 // Check if we should be returning `this`
5534 if ($.inArray(options, thisMethods) > -1) {
5540 throw new Error('Invalid arguments for Select2: ' + options);
5545 if ($.fn.select2.defaults == null) {
5546 $.fn.select2.defaults = Defaults;
5552 // Return the AMD loader configuration so it can be used outside of this file
5559 // Autoload the jQuery bindings
5560 // We know that all of the modules exist above this, so we're safe
5561 var select2 = S2.require('jquery.select2');
5563 // Hold the AMD module references on the jQuery function that was just loaded
5564 // This allows Select2 to use the internal loader outside of this file, such
5565 // as in the language files.
5566 jQuery.fn.select2.amd = S2;
5568 // Return the Select2 instance for anyone who is importing it.