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();
1145 var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
1147 var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
1148 var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
1151 self.$results.scrollTop(0);
1154 e.stopPropagation();
1155 } else if (isAtBottom) {
1156 self.$results.scrollTop(
1157 self.$results.get(0).scrollHeight - self.$results.height()
1161 e.stopPropagation();
1166 this.$results.on('mouseup', '.select2-results__option[aria-selected]',
1168 var $this = $(this);
1170 var data = $this.data('data');
1172 if ($this.attr('aria-selected') === 'true') {
1173 if (self.options.get('multiple')) {
1174 self.trigger('unselect', {
1179 self.trigger('close', {});
1185 self.trigger('select', {
1191 this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
1193 var data = $(this).data('data');
1195 self.getHighlightedResults()
1196 .removeClass('select2-results__option--highlighted');
1198 self.trigger('results:focus', {
1205 Results.prototype.getHighlightedResults = function () {
1206 var $highlighted = this.$results
1207 .find('.select2-results__option--highlighted');
1209 return $highlighted;
1212 Results.prototype.destroy = function () {
1213 this.$results.remove();
1216 Results.prototype.ensureHighlightVisible = function () {
1217 var $highlighted = this.getHighlightedResults();
1219 if ($highlighted.length === 0) {
1223 var $options = this.$results.find('[aria-selected]');
1225 var currentIndex = $options.index($highlighted);
1227 var currentOffset = this.$results.offset().top;
1228 var nextTop = $highlighted.offset().top;
1229 var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
1231 var offsetDelta = nextTop - currentOffset;
1232 nextOffset -= $highlighted.outerHeight(false) * 2;
1234 if (currentIndex <= 2) {
1235 this.$results.scrollTop(0);
1236 } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
1237 this.$results.scrollTop(nextOffset);
1241 Results.prototype.template = function (result, container) {
1242 var template = this.options.get('templateResult');
1243 var escapeMarkup = this.options.get('escapeMarkup');
1245 var content = template(result, container);
1247 if (content == null) {
1248 container.style.display = 'none';
1249 } else if (typeof content === 'string') {
1250 container.innerHTML = escapeMarkup(content);
1252 $(container).append(content);
1259 S2.define('select2/keys',[
1285 S2.define('select2/selection/base',[
1289 ], function ($, Utils, KEYS) {
1290 function BaseSelection ($element, options) {
1291 this.$element = $element;
1292 this.options = options;
1294 BaseSelection.__super__.constructor.call(this);
1297 Utils.Extend(BaseSelection, Utils.Observable);
1299 BaseSelection.prototype.render = function () {
1301 '<span class="select2-selection" role="combobox" ' +
1302 ' aria-haspopup="true" aria-expanded="false">' +
1308 if (this.$element.data('old-tabindex') != null) {
1309 this._tabindex = this.$element.data('old-tabindex');
1310 } else if (this.$element.attr('tabindex') != null) {
1311 this._tabindex = this.$element.attr('tabindex');
1314 $selection.attr('title', this.$element.attr('title'));
1315 $selection.attr('tabindex', this._tabindex);
1317 this.$selection = $selection;
1322 BaseSelection.prototype.bind = function (container, $container) {
1325 var id = container.id + '-container';
1326 var resultsId = container.id + '-results';
1328 this.container = container;
1330 this.$selection.on('focus', function (evt) {
1331 self.trigger('focus', evt);
1334 this.$selection.on('blur', function (evt) {
1335 self._handleBlur(evt);
1338 this.$selection.on('keydown', function (evt) {
1339 self.trigger('keypress', evt);
1341 if (evt.which === KEYS.SPACE) {
1342 evt.preventDefault();
1346 container.on('results:focus', function (params) {
1347 self.$selection.attr('aria-activedescendant', params.data._resultId);
1350 container.on('selection:update', function (params) {
1351 self.update(params.data);
1354 container.on('open', function () {
1355 // When the dropdown is open, aria-expanded="true"
1356 self.$selection.attr('aria-expanded', 'true');
1357 self.$selection.attr('aria-owns', resultsId);
1359 self._attachCloseHandler(container);
1362 container.on('close', function () {
1363 // When the dropdown is closed, aria-expanded="false"
1364 self.$selection.attr('aria-expanded', 'false');
1365 self.$selection.removeAttr('aria-activedescendant');
1366 self.$selection.removeAttr('aria-owns');
1368 self.$selection.focus();
1370 self._detachCloseHandler(container);
1373 container.on('enable', function () {
1374 self.$selection.attr('tabindex', self._tabindex);
1377 container.on('disable', function () {
1378 self.$selection.attr('tabindex', '-1');
1382 BaseSelection.prototype._handleBlur = function (evt) {
1385 // This needs to be delayed as the active element is the body when the tab
1386 // key is pressed, possibly along with others.
1387 window.setTimeout(function () {
1388 // Don't trigger `blur` if the focus is still in the selection
1390 (document.activeElement == self.$selection[0]) ||
1391 ($.contains(self.$selection[0], document.activeElement))
1396 self.trigger('blur', evt);
1400 BaseSelection.prototype._attachCloseHandler = function (container) {
1403 $(document.body).on('mousedown.select2.' + container.id, function (e) {
1404 var $target = $(e.target);
1406 var $select = $target.closest('.select2');
1408 var $all = $('.select2.select2-container--open');
1410 $all.each(function () {
1411 var $this = $(this);
1413 if (this == $select[0]) {
1417 var $element = $this.data('element');
1419 $element.select2('close');
1424 BaseSelection.prototype._detachCloseHandler = function (container) {
1425 $(document.body).off('mousedown.select2.' + container.id);
1428 BaseSelection.prototype.position = function ($selection, $container) {
1429 var $selectionContainer = $container.find('.selection');
1430 $selectionContainer.append($selection);
1433 BaseSelection.prototype.destroy = function () {
1434 this._detachCloseHandler(this.container);
1437 BaseSelection.prototype.update = function (data) {
1438 throw new Error('The `update` method must be defined in child classes.');
1441 return BaseSelection;
1444 S2.define('select2/selection/single',[
1449 ], function ($, BaseSelection, Utils, KEYS) {
1450 function SingleSelection () {
1451 SingleSelection.__super__.constructor.apply(this, arguments);
1454 Utils.Extend(SingleSelection, BaseSelection);
1456 SingleSelection.prototype.render = function () {
1457 var $selection = SingleSelection.__super__.render.call(this);
1459 $selection.addClass('select2-selection--single');
1462 '<span class="select2-selection__rendered"></span>' +
1463 '<span class="select2-selection__arrow" role="presentation">' +
1464 '<b role="presentation"></b>' +
1471 SingleSelection.prototype.bind = function (container, $container) {
1474 SingleSelection.__super__.bind.apply(this, arguments);
1476 var id = container.id + '-container';
1478 this.$selection.find('.select2-selection__rendered').attr('id', id);
1479 this.$selection.attr('aria-labelledby', id);
1481 this.$selection.on('mousedown', function (evt) {
1482 // Only respond to left clicks
1483 if (evt.which !== 1) {
1487 self.trigger('toggle', {
1492 this.$selection.on('focus', function (evt) {
1493 // User focuses on the container
1496 this.$selection.on('blur', function (evt) {
1497 // User exits the container
1500 container.on('selection:update', function (params) {
1501 self.update(params.data);
1505 SingleSelection.prototype.clear = function () {
1506 this.$selection.find('.select2-selection__rendered').empty();
1509 SingleSelection.prototype.display = function (data, container) {
1510 var template = this.options.get('templateSelection');
1511 var escapeMarkup = this.options.get('escapeMarkup');
1513 return escapeMarkup(template(data, container));
1516 SingleSelection.prototype.selectionContainer = function () {
1517 return $('<span></span>');
1520 SingleSelection.prototype.update = function (data) {
1521 if (data.length === 0) {
1526 var selection = data[0];
1528 var $rendered = this.$selection.find('.select2-selection__rendered');
1529 var formatted = this.display(selection, $rendered);
1531 $rendered.empty().append(formatted);
1532 $rendered.prop('title', selection.title || selection.text);
1535 return SingleSelection;
1538 S2.define('select2/selection/multiple',[
1542 ], function ($, BaseSelection, Utils) {
1543 function MultipleSelection ($element, options) {
1544 MultipleSelection.__super__.constructor.apply(this, arguments);
1547 Utils.Extend(MultipleSelection, BaseSelection);
1549 MultipleSelection.prototype.render = function () {
1550 var $selection = MultipleSelection.__super__.render.call(this);
1552 $selection.addClass('select2-selection--multiple');
1555 '<ul class="select2-selection__rendered"></ul>'
1561 MultipleSelection.prototype.bind = function (container, $container) {
1564 MultipleSelection.__super__.bind.apply(this, arguments);
1566 this.$selection.on('click', function (evt) {
1567 self.trigger('toggle', {
1574 '.select2-selection__choice__remove',
1576 // Ignore the event if it is disabled
1577 if (self.options.get('disabled')) {
1581 var $remove = $(this);
1582 var $selection = $remove.parent();
1584 var data = $selection.data('data');
1586 self.trigger('unselect', {
1594 MultipleSelection.prototype.clear = function () {
1595 this.$selection.find('.select2-selection__rendered').empty();
1598 MultipleSelection.prototype.display = function (data, container) {
1599 var template = this.options.get('templateSelection');
1600 var escapeMarkup = this.options.get('escapeMarkup');
1602 return escapeMarkup(template(data, container));
1605 MultipleSelection.prototype.selectionContainer = function () {
1607 '<li class="select2-selection__choice">' +
1608 '<span class="select2-selection__choice__remove" role="presentation">' +
1617 MultipleSelection.prototype.update = function (data) {
1620 if (data.length === 0) {
1624 var $selections = [];
1626 for (var d = 0; d < data.length; d++) {
1627 var selection = data[d];
1629 var $selection = this.selectionContainer();
1630 var formatted = this.display(selection, $selection);
1632 $selection.append(formatted);
1633 $selection.prop('title', selection.title || selection.text);
1635 $selection.data('data', selection);
1637 $selections.push($selection);
1640 var $rendered = this.$selection.find('.select2-selection__rendered');
1642 Utils.appendMany($rendered, $selections);
1645 return MultipleSelection;
1648 S2.define('select2/selection/placeholder',[
1650 ], function (Utils) {
1651 function Placeholder (decorated, $element, options) {
1652 this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
1654 decorated.call(this, $element, options);
1657 Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
1658 if (typeof placeholder === 'string') {
1668 Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
1669 var $placeholder = this.selectionContainer();
1671 $placeholder.html(this.display(placeholder));
1672 $placeholder.addClass('select2-selection__placeholder')
1673 .removeClass('select2-selection__choice');
1675 return $placeholder;
1678 Placeholder.prototype.update = function (decorated, data) {
1679 var singlePlaceholder = (
1680 data.length == 1 && data[0].id != this.placeholder.id
1682 var multipleSelections = data.length > 1;
1684 if (multipleSelections || singlePlaceholder) {
1685 return decorated.call(this, data);
1690 var $placeholder = this.createPlaceholder(this.placeholder);
1692 this.$selection.find('.select2-selection__rendered').append($placeholder);
1698 S2.define('select2/selection/allowClear',[
1701 ], function ($, KEYS) {
1702 function AllowClear () { }
1704 AllowClear.prototype.bind = function (decorated, container, $container) {
1707 decorated.call(this, container, $container);
1709 if (this.placeholder == null) {
1710 if (this.options.get('debug') && window.console && console.error) {
1712 'Select2: The `allowClear` option should be used in combination ' +
1713 'with the `placeholder` option.'
1718 this.$selection.on('mousedown', '.select2-selection__clear',
1720 self._handleClear(evt);
1723 container.on('keypress', function (evt) {
1724 self._handleKeyboardClear(evt, container);
1728 AllowClear.prototype._handleClear = function (_, evt) {
1729 // Ignore the event if it is disabled
1730 if (this.options.get('disabled')) {
1734 var $clear = this.$selection.find('.select2-selection__clear');
1736 // Ignore the event if nothing has been selected
1737 if ($clear.length === 0) {
1741 evt.stopPropagation();
1743 var data = $clear.data('data');
1745 for (var d = 0; d < data.length; d++) {
1746 var unselectData = {
1750 // Trigger the `unselect` event, so people can prevent it from being
1752 this.trigger('unselect', unselectData);
1754 // If the event was prevented, don't clear it out.
1755 if (unselectData.prevented) {
1760 this.$element.val(this.placeholder.id).trigger('change');
1762 this.trigger('toggle', {});
1765 AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
1766 if (container.isOpen()) {
1770 if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
1771 this._handleClear(evt);
1775 AllowClear.prototype.update = function (decorated, data) {
1776 decorated.call(this, data);
1778 if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
1779 data.length === 0) {
1784 '<span class="select2-selection__clear">' +
1788 $remove.data('data', data);
1790 this.$selection.find('.select2-selection__rendered').prepend($remove);
1796 S2.define('select2/selection/search',[
1800 ], function ($, Utils, KEYS) {
1801 function Search (decorated, $element, options) {
1802 decorated.call(this, $element, options);
1805 Search.prototype.render = function (decorated) {
1807 '<li class="select2-search select2-search--inline">' +
1808 '<input class="select2-search__field" type="search" tabindex="-1"' +
1809 ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
1810 ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
1814 this.$searchContainer = $search;
1815 this.$search = $search.find('input');
1817 var $rendered = decorated.call(this);
1819 this._transferTabIndex();
1824 Search.prototype.bind = function (decorated, container, $container) {
1827 decorated.call(this, container, $container);
1829 container.on('open', function () {
1830 self.$search.trigger('focus');
1833 container.on('close', function () {
1834 self.$search.val('');
1835 self.$search.removeAttr('aria-activedescendant');
1836 self.$search.trigger('focus');
1839 container.on('enable', function () {
1840 self.$search.prop('disabled', false);
1842 self._transferTabIndex();
1845 container.on('disable', function () {
1846 self.$search.prop('disabled', true);
1849 container.on('focus', function (evt) {
1850 self.$search.trigger('focus');
1853 container.on('results:focus', function (params) {
1854 self.$search.attr('aria-activedescendant', params.id);
1857 this.$selection.on('focusin', '.select2-search--inline', function (evt) {
1858 self.trigger('focus', evt);
1861 this.$selection.on('focusout', '.select2-search--inline', function (evt) {
1862 self._handleBlur(evt);
1865 this.$selection.on('keydown', '.select2-search--inline', function (evt) {
1866 evt.stopPropagation();
1868 self.trigger('keypress', evt);
1870 self._keyUpPrevented = evt.isDefaultPrevented();
1872 var key = evt.which;
1874 if (key === KEYS.BACKSPACE && self.$search.val() === '') {
1875 var $previousChoice = self.$searchContainer
1876 .prev('.select2-selection__choice');
1878 if ($previousChoice.length > 0) {
1879 var item = $previousChoice.data('data');
1881 self.searchRemoveChoice(item);
1883 evt.preventDefault();
1888 // Try to detect the IE version should the `documentMode` property that
1889 // is stored on the document. This is only implemented in IE and is
1890 // slightly cleaner than doing a user agent check.
1891 // This property is not available in Edge, but Edge also doesn't have
1893 var msie = document.documentMode;
1894 var disableInputEvents = msie && msie <= 11;
1896 // Workaround for browsers which do not support the `input` event
1897 // This will prevent double-triggering of events for browsers which support
1898 // both the `keyup` and `input` events.
1900 'input.searchcheck',
1901 '.select2-search--inline',
1903 // IE will trigger the `input` event when a placeholder is used on a
1904 // search box. To get around this issue, we are forced to ignore all
1905 // `input` events in IE and keep using `keyup`.
1906 if (disableInputEvents) {
1907 self.$selection.off('input.search input.searchcheck');
1911 // Unbind the duplicated `keyup` event
1912 self.$selection.off('keyup.search');
1917 'keyup.search input.search',
1918 '.select2-search--inline',
1920 // IE will trigger the `input` event when a placeholder is used on a
1921 // search box. To get around this issue, we are forced to ignore all
1922 // `input` events in IE and keep using `keyup`.
1923 if (disableInputEvents && evt.type === 'input') {
1924 self.$selection.off('input.search input.searchcheck');
1928 var key = evt.which;
1930 // We can freely ignore events from modifier keys
1931 if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
1935 // Tabbing will be handled during the `keydown` phase
1936 if (key == KEYS.TAB) {
1940 self.handleSearch(evt);
1946 * This method will transfer the tabindex attribute from the rendered
1947 * selection to the search box. This allows for the search box to be used as
1948 * the primary focus instead of the selection container.
1952 Search.prototype._transferTabIndex = function (decorated) {
1953 this.$search.attr('tabindex', this.$selection.attr('tabindex'));
1954 this.$selection.attr('tabindex', '-1');
1957 Search.prototype.createPlaceholder = function (decorated, placeholder) {
1958 this.$search.attr('placeholder', placeholder.text);
1961 Search.prototype.update = function (decorated, data) {
1962 var searchHadFocus = this.$search[0] == document.activeElement;
1964 this.$search.attr('placeholder', '');
1966 decorated.call(this, data);
1968 this.$selection.find('.select2-selection__rendered')
1969 .append(this.$searchContainer);
1971 this.resizeSearch();
1972 if (searchHadFocus) {
1973 this.$search.focus();
1977 Search.prototype.handleSearch = function () {
1978 this.resizeSearch();
1980 if (!this._keyUpPrevented) {
1981 var input = this.$search.val();
1983 this.trigger('query', {
1988 this._keyUpPrevented = false;
1991 Search.prototype.searchRemoveChoice = function (decorated, item) {
1992 this.trigger('unselect', {
1996 this.$search.val(item.text);
1997 this.handleSearch();
2000 Search.prototype.resizeSearch = function () {
2001 this.$search.css('width', '25px');
2005 if (this.$search.attr('placeholder') !== '') {
2006 width = this.$selection.find('.select2-selection__rendered').innerWidth();
2008 var minimumWidth = this.$search.val().length + 1;
2010 width = (minimumWidth * 0.75) + 'em';
2013 this.$search.css('width', width);
2019 S2.define('select2/selection/eventRelay',[
2022 function EventRelay () { }
2024 EventRelay.prototype.bind = function (decorated, container, $container) {
2029 'select', 'selecting',
2030 'unselect', 'unselecting'
2033 var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
2035 decorated.call(this, container, $container);
2037 container.on('*', function (name, params) {
2038 // Ignore events that should not be relayed
2039 if ($.inArray(name, relayEvents) === -1) {
2043 // The parameters should always be an object
2044 params = params || {};
2046 // Generate the jQuery event for the Select2 event
2047 var evt = $.Event('select2:' + name, {
2051 self.$element.trigger(evt);
2053 // Only handle preventable events if it was one
2054 if ($.inArray(name, preventableEvents) === -1) {
2058 params.prevented = evt.isDefaultPrevented();
2065 S2.define('select2/translation',[
2068 ], function ($, require) {
2069 function Translation (dict) {
2070 this.dict = dict || {};
2073 Translation.prototype.all = function () {
2077 Translation.prototype.get = function (key) {
2078 return this.dict[key];
2081 Translation.prototype.extend = function (translation) {
2082 this.dict = $.extend({}, translation.all(), this.dict);
2087 Translation._cache = {};
2089 Translation.loadPath = function (path) {
2090 if (!(path in Translation._cache)) {
2091 var translations = require(path);
2093 Translation._cache[path] = translations;
2096 return new Translation(Translation._cache[path]);
2102 S2.define('select2/diacritics',[
2950 S2.define('select2/data/base',[
2952 ], function (Utils) {
2953 function BaseAdapter ($element, options) {
2954 BaseAdapter.__super__.constructor.call(this);
2957 Utils.Extend(BaseAdapter, Utils.Observable);
2959 BaseAdapter.prototype.current = function (callback) {
2960 throw new Error('The `current` method must be defined in child classes.');
2963 BaseAdapter.prototype.query = function (params, callback) {
2964 throw new Error('The `query` method must be defined in child classes.');
2967 BaseAdapter.prototype.bind = function (container, $container) {
2968 // Can be implemented in subclasses
2971 BaseAdapter.prototype.destroy = function () {
2972 // Can be implemented in subclasses
2975 BaseAdapter.prototype.generateResultId = function (container, data) {
2976 var id = container.id + '-result-';
2978 id += Utils.generateChars(4);
2980 if (data.id != null) {
2981 id += '-' + data.id.toString();
2983 id += '-' + Utils.generateChars(4);
2991 S2.define('select2/data/select',[
2995 ], function (BaseAdapter, Utils, $) {
2996 function SelectAdapter ($element, options) {
2997 this.$element = $element;
2998 this.options = options;
3000 SelectAdapter.__super__.constructor.call(this);
3003 Utils.Extend(SelectAdapter, BaseAdapter);
3005 SelectAdapter.prototype.current = function (callback) {
3009 this.$element.find(':selected').each(function () {
3010 var $option = $(this);
3012 var option = self.item($option);
3020 SelectAdapter.prototype.select = function (data) {
3023 data.selected = true;
3025 // If data.element is a DOM node, use it instead
3026 if ($(data.element).is('option')) {
3027 data.element.selected = true;
3029 this.$element.trigger('change');
3034 if (this.$element.prop('multiple')) {
3035 this.current(function (currentData) {
3039 data.push.apply(data, currentData);
3041 for (var d = 0; d < data.length; d++) {
3042 var id = data[d].id;
3044 if ($.inArray(id, val) === -1) {
3049 self.$element.val(val);
3050 self.$element.trigger('change');
3055 this.$element.val(val);
3056 this.$element.trigger('change');
3060 SelectAdapter.prototype.unselect = function (data) {
3063 if (!this.$element.prop('multiple')) {
3067 data.selected = false;
3069 if ($(data.element).is('option')) {
3070 data.element.selected = false;
3072 this.$element.trigger('change');
3077 this.current(function (currentData) {
3080 for (var d = 0; d < currentData.length; d++) {
3081 var id = currentData[d].id;
3083 if (id !== data.id && $.inArray(id, val) === -1) {
3088 self.$element.val(val);
3090 self.$element.trigger('change');
3094 SelectAdapter.prototype.bind = function (container, $container) {
3097 this.container = container;
3099 container.on('select', function (params) {
3100 self.select(params.data);
3103 container.on('unselect', function (params) {
3104 self.unselect(params.data);
3108 SelectAdapter.prototype.destroy = function () {
3109 // Remove anything added to child elements
3110 this.$element.find('*').each(function () {
3111 // Remove any custom data set by Select2
3112 $.removeData(this, 'data');
3116 SelectAdapter.prototype.query = function (params, callback) {
3120 var $options = this.$element.children();
3122 $options.each(function () {
3123 var $option = $(this);
3125 if (!$option.is('option') && !$option.is('optgroup')) {
3129 var option = self.item($option);
3131 var matches = self.matches(params, option);
3133 if (matches !== null) {
3143 SelectAdapter.prototype.addOptions = function ($options) {
3144 Utils.appendMany(this.$element, $options);
3147 SelectAdapter.prototype.option = function (data) {
3150 if (data.children) {
3151 option = document.createElement('optgroup');
3152 option.label = data.text;
3154 option = document.createElement('option');
3156 if (option.textContent !== undefined) {
3157 option.textContent = data.text;
3159 option.innerText = data.text;
3164 option.value = data.id;
3167 if (data.disabled) {
3168 option.disabled = true;
3171 if (data.selected) {
3172 option.selected = true;
3176 option.title = data.title;
3179 var $option = $(option);
3181 var normalizedData = this._normalizeItem(data);
3182 normalizedData.element = option;
3184 // Override the option's data with the combined data
3185 $.data(option, 'data', normalizedData);
3190 SelectAdapter.prototype.item = function ($option) {
3193 data = $.data($option[0], 'data');
3199 if ($option.is('option')) {
3202 text: $option.text(),
3203 disabled: $option.prop('disabled'),
3204 selected: $option.prop('selected'),
3205 title: $option.prop('title')
3207 } else if ($option.is('optgroup')) {
3209 text: $option.prop('label'),
3211 title: $option.prop('title')
3214 var $children = $option.children('option');
3217 for (var c = 0; c < $children.length; c++) {
3218 var $child = $($children[c]);
3220 var child = this.item($child);
3222 children.push(child);
3225 data.children = children;
3228 data = this._normalizeItem(data);
3229 data.element = $option[0];
3231 $.data($option[0], 'data', data);
3236 SelectAdapter.prototype._normalizeItem = function (item) {
3237 if (!$.isPlainObject(item)) {
3244 item = $.extend({}, {
3253 if (item.id != null) {
3254 item.id = item.id.toString();
3257 if (item.text != null) {
3258 item.text = item.text.toString();
3261 if (item._resultId == null && item.id && this.container != null) {
3262 item._resultId = this.generateResultId(this.container, item);
3265 return $.extend({}, defaults, item);
3268 SelectAdapter.prototype.matches = function (params, data) {
3269 var matcher = this.options.get('matcher');
3271 return matcher(params, data);
3274 return SelectAdapter;
3277 S2.define('select2/data/array',[
3281 ], function (SelectAdapter, Utils, $) {
3282 function ArrayAdapter ($element, options) {
3283 var data = options.get('data') || [];
3285 ArrayAdapter.__super__.constructor.call(this, $element, options);
3287 this.addOptions(this.convertToOptions(data));
3290 Utils.Extend(ArrayAdapter, SelectAdapter);
3292 ArrayAdapter.prototype.select = function (data) {
3293 var $option = this.$element.find('option').filter(function (i, elm) {
3294 return elm.value == data.id.toString();
3297 if ($option.length === 0) {
3298 $option = this.option(data);
3300 this.addOptions($option);
3303 ArrayAdapter.__super__.select.call(this, data);
3306 ArrayAdapter.prototype.convertToOptions = function (data) {
3309 var $existing = this.$element.find('option');
3310 var existingIds = $existing.map(function () {
3311 return self.item($(this)).id;
3316 // Filter out all items except for the one passed in the argument
3317 function onlyItem (item) {
3318 return function () {
3319 return $(this).val() == item.id;
3323 for (var d = 0; d < data.length; d++) {
3324 var item = this._normalizeItem(data[d]);
3326 // Skip items which were pre-loaded, only merge the data
3327 if ($.inArray(item.id, existingIds) >= 0) {
3328 var $existingOption = $existing.filter(onlyItem(item));
3330 var existingData = this.item($existingOption);
3331 var newData = $.extend(true, {}, item, existingData);
3333 var $newOption = this.option(newData);
3335 $existingOption.replaceWith($newOption);
3340 var $option = this.option(item);
3342 if (item.children) {
3343 var $children = this.convertToOptions(item.children);
3345 Utils.appendMany($option, $children);
3348 $options.push($option);
3354 return ArrayAdapter;
3357 S2.define('select2/data/ajax',[
3361 ], function (ArrayAdapter, Utils, $) {
3362 function AjaxAdapter ($element, options) {
3363 this.ajaxOptions = this._applyDefaults(options.get('ajax'));
3365 if (this.ajaxOptions.processResults != null) {
3366 this.processResults = this.ajaxOptions.processResults;
3369 AjaxAdapter.__super__.constructor.call(this, $element, options);
3372 Utils.Extend(AjaxAdapter, ArrayAdapter);
3374 AjaxAdapter.prototype._applyDefaults = function (options) {
3376 data: function (params) {
3377 return $.extend({}, params, {
3381 transport: function (params, success, failure) {
3382 var $request = $.ajax(params);
3384 $request.then(success);
3385 $request.fail(failure);
3391 return $.extend({}, defaults, options, true);
3394 AjaxAdapter.prototype.processResults = function (results) {
3398 AjaxAdapter.prototype.query = function (params, callback) {
3402 if (this._request != null) {
3403 // JSONP requests cannot always be aborted
3404 if ($.isFunction(this._request.abort)) {
3405 this._request.abort();
3408 this._request = null;
3411 var options = $.extend({
3413 }, this.ajaxOptions);
3415 if (typeof options.url === 'function') {
3416 options.url = options.url.call(this.$element, params);
3419 if (typeof options.data === 'function') {
3420 options.data = options.data.call(this.$element, params);
3423 function request () {
3424 var $request = options.transport(options, function (data) {
3425 var results = self.processResults(data, params);
3427 if (self.options.get('debug') && window.console && console.error) {
3428 // Check to make sure that the response included a `results` key.
3429 if (!results || !results.results || !$.isArray(results.results)) {
3431 'Select2: The AJAX results did not return an array in the ' +
3432 '`results` key of the response.'
3439 self.trigger('results:message', {
3440 message: 'errorLoading'
3444 self._request = $request;
3447 if (this.ajaxOptions.delay && params.term !== '') {
3448 if (this._queryTimeout) {
3449 window.clearTimeout(this._queryTimeout);
3452 this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
3461 S2.define('select2/data/tags',[
3464 function Tags (decorated, $element, options) {
3465 var tags = options.get('tags');
3467 var createTag = options.get('createTag');
3469 if (createTag !== undefined) {
3470 this.createTag = createTag;
3473 var insertTag = options.get('insertTag');
3475 if (insertTag !== undefined) {
3476 this.insertTag = insertTag;
3479 decorated.call(this, $element, options);
3481 if ($.isArray(tags)) {
3482 for (var t = 0; t < tags.length; t++) {
3484 var item = this._normalizeItem(tag);
3486 var $option = this.option(item);
3488 this.$element.append($option);
3493 Tags.prototype.query = function (decorated, params, callback) {
3496 this._removeOldTags();
3498 if (params.term == null || params.page != null) {
3499 decorated.call(this, params, callback);
3503 function wrapper (obj, child) {
3504 var data = obj.results;
3506 for (var i = 0; i < data.length; i++) {
3507 var option = data[i];
3509 var checkChildren = (
3510 option.children != null &&
3512 results: option.children
3516 var checkText = option.text === params.term;
3518 if (checkText || checkChildren) {
3534 var tag = self.createTag(params);
3537 var $option = self.option(tag);
3538 $option.attr('data-select2-tag', true);
3540 self.addOptions([$option]);
3542 self.insertTag(data, tag);
3550 decorated.call(this, params, wrapper);
3553 Tags.prototype.createTag = function (decorated, params) {
3554 var term = $.trim(params.term);
3566 Tags.prototype.insertTag = function (_, data, tag) {
3570 Tags.prototype._removeOldTags = function (_) {
3571 var tag = this._lastTag;
3573 var $options = this.$element.find('option[data-select2-tag]');
3575 $options.each(function () {
3576 if (this.selected) {
3587 S2.define('select2/data/tokenizer',[
3590 function Tokenizer (decorated, $element, options) {
3591 var tokenizer = options.get('tokenizer');
3593 if (tokenizer !== undefined) {
3594 this.tokenizer = tokenizer;
3597 decorated.call(this, $element, options);
3600 Tokenizer.prototype.bind = function (decorated, container, $container) {
3601 decorated.call(this, container, $container);
3603 this.$search = container.dropdown.$search || container.selection.$search ||
3604 $container.find('.select2-search__field');
3607 Tokenizer.prototype.query = function (decorated, params, callback) {
3610 function select (data) {
3611 self.trigger('select', {
3616 params.term = params.term || '';
3618 var tokenData = this.tokenizer(params, this.options, select);
3620 if (tokenData.term !== params.term) {
3621 // Replace the search term if we have the search box
3622 if (this.$search.length) {
3623 this.$search.val(tokenData.term);
3624 this.$search.focus();
3627 params.term = tokenData.term;
3630 decorated.call(this, params, callback);
3633 Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
3634 var separators = options.get('tokenSeparators') || [];
3635 var term = params.term;
3638 var createTag = this.createTag || function (params) {
3645 while (i < term.length) {
3646 var termChar = term[i];
3648 if ($.inArray(termChar, separators) === -1) {
3654 var part = term.substr(0, i);
3655 var partParams = $.extend({}, params, {
3659 var data = createTag(partParams);
3668 // Reset the term to not include the tokenized portion
3669 term = term.substr(i + 1) || '';
3681 S2.define('select2/data/minimumInputLength',[
3684 function MinimumInputLength (decorated, $e, options) {
3685 this.minimumInputLength = options.get('minimumInputLength');
3687 decorated.call(this, $e, options);
3690 MinimumInputLength.prototype.query = function (decorated, params, callback) {
3691 params.term = params.term || '';
3693 if (params.term.length < this.minimumInputLength) {
3694 this.trigger('results:message', {
3695 message: 'inputTooShort',
3697 minimum: this.minimumInputLength,
3706 decorated.call(this, params, callback);
3709 return MinimumInputLength;
3712 S2.define('select2/data/maximumInputLength',[
3715 function MaximumInputLength (decorated, $e, options) {
3716 this.maximumInputLength = options.get('maximumInputLength');
3718 decorated.call(this, $e, options);
3721 MaximumInputLength.prototype.query = function (decorated, params, callback) {
3722 params.term = params.term || '';
3724 if (this.maximumInputLength > 0 &&
3725 params.term.length > this.maximumInputLength) {
3726 this.trigger('results:message', {
3727 message: 'inputTooLong',
3729 maximum: this.maximumInputLength,
3738 decorated.call(this, params, callback);
3741 return MaximumInputLength;
3744 S2.define('select2/data/maximumSelectionLength',[
3747 function MaximumSelectionLength (decorated, $e, options) {
3748 this.maximumSelectionLength = options.get('maximumSelectionLength');
3750 decorated.call(this, $e, options);
3753 MaximumSelectionLength.prototype.query =
3754 function (decorated, params, callback) {
3757 this.current(function (currentData) {
3758 var count = currentData != null ? currentData.length : 0;
3759 if (self.maximumSelectionLength > 0 &&
3760 count >= self.maximumSelectionLength) {
3761 self.trigger('results:message', {
3762 message: 'maximumSelected',
3764 maximum: self.maximumSelectionLength
3769 decorated.call(self, params, callback);
3773 return MaximumSelectionLength;
3776 S2.define('select2/dropdown',[
3779 ], function ($, Utils) {
3780 function Dropdown ($element, options) {
3781 this.$element = $element;
3782 this.options = options;
3784 Dropdown.__super__.constructor.call(this);
3787 Utils.Extend(Dropdown, Utils.Observable);
3789 Dropdown.prototype.render = function () {
3791 '<span class="select2-dropdown">' +
3792 '<span class="select2-results"></span>' +
3796 $dropdown.attr('dir', this.options.get('dir'));
3798 this.$dropdown = $dropdown;
3803 Dropdown.prototype.bind = function () {
3804 // Should be implemented in subclasses
3807 Dropdown.prototype.position = function ($dropdown, $container) {
3808 // Should be implmented in subclasses
3811 Dropdown.prototype.destroy = function () {
3812 // Remove the dropdown from the DOM
3813 this.$dropdown.remove();
3819 S2.define('select2/dropdown/search',[
3822 ], function ($, Utils) {
3823 function Search () { }
3825 Search.prototype.render = function (decorated) {
3826 var $rendered = decorated.call(this);
3829 '<span class="select2-search select2-search--dropdown">' +
3830 '<input class="select2-search__field" type="search" tabindex="-1"' +
3831 ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
3832 ' spellcheck="false" role="textbox" />' +
3836 this.$searchContainer = $search;
3837 this.$search = $search.find('input');
3839 $rendered.prepend($search);
3844 Search.prototype.bind = function (decorated, container, $container) {
3847 decorated.call(this, container, $container);
3849 this.$search.on('keydown', function (evt) {
3850 self.trigger('keypress', evt);
3852 self._keyUpPrevented = evt.isDefaultPrevented();
3855 // Workaround for browsers which do not support the `input` event
3856 // This will prevent double-triggering of events for browsers which support
3857 // both the `keyup` and `input` events.
3858 this.$search.on('input', function (evt) {
3859 // Unbind the duplicated `keyup` event
3860 $(this).off('keyup');
3863 this.$search.on('keyup input', function (evt) {
3864 self.handleSearch(evt);
3867 container.on('open', function () {
3868 self.$search.attr('tabindex', 0);
3870 self.$search.focus();
3872 window.setTimeout(function () {
3873 self.$search.focus();
3877 container.on('close', function () {
3878 self.$search.attr('tabindex', -1);
3880 self.$search.val('');
3883 container.on('results:all', function (params) {
3884 if (params.query.term == null || params.query.term === '') {
3885 var showSearch = self.showSearch(params);
3888 self.$searchContainer.removeClass('select2-search--hide');
3890 self.$searchContainer.addClass('select2-search--hide');
3896 Search.prototype.handleSearch = function (evt) {
3897 if (!this._keyUpPrevented) {
3898 var input = this.$search.val();
3900 this.trigger('query', {
3905 this._keyUpPrevented = false;
3908 Search.prototype.showSearch = function (_, params) {
3915 S2.define('select2/dropdown/hidePlaceholder',[
3918 function HidePlaceholder (decorated, $element, options, dataAdapter) {
3919 this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
3921 decorated.call(this, $element, options, dataAdapter);
3924 HidePlaceholder.prototype.append = function (decorated, data) {
3925 data.results = this.removePlaceholder(data.results);
3927 decorated.call(this, data);
3930 HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
3931 if (typeof placeholder === 'string') {
3941 HidePlaceholder.prototype.removePlaceholder = function (_, data) {
3942 var modifiedData = data.slice(0);
3944 for (var d = data.length - 1; d >= 0; d--) {
3947 if (this.placeholder.id === item.id) {
3948 modifiedData.splice(d, 1);
3952 return modifiedData;
3955 return HidePlaceholder;
3958 S2.define('select2/dropdown/infiniteScroll',[
3961 function InfiniteScroll (decorated, $element, options, dataAdapter) {
3962 this.lastParams = {};
3964 decorated.call(this, $element, options, dataAdapter);
3966 this.$loadingMore = this.createLoadingMore();
3967 this.loading = false;
3970 InfiniteScroll.prototype.append = function (decorated, data) {
3971 this.$loadingMore.remove();
3972 this.loading = false;
3974 decorated.call(this, data);
3976 if (this.showLoadingMore(data)) {
3977 this.$results.append(this.$loadingMore);
3981 InfiniteScroll.prototype.bind = function (decorated, container, $container) {
3984 decorated.call(this, container, $container);
3986 container.on('query', function (params) {
3987 self.lastParams = params;
3988 self.loading = true;
3991 container.on('query:append', function (params) {
3992 self.lastParams = params;
3993 self.loading = true;
3996 this.$results.on('scroll', function () {
3997 var isLoadMoreVisible = $.contains(
3998 document.documentElement,
3999 self.$loadingMore[0]
4002 if (self.loading || !isLoadMoreVisible) {
4006 var currentOffset = self.$results.offset().top +
4007 self.$results.outerHeight(false);
4008 var loadingMoreOffset = self.$loadingMore.offset().top +
4009 self.$loadingMore.outerHeight(false);
4011 if (currentOffset + 50 >= loadingMoreOffset) {
4017 InfiniteScroll.prototype.loadMore = function () {
4018 this.loading = true;
4020 var params = $.extend({}, {page: 1}, this.lastParams);
4024 this.trigger('query:append', params);
4027 InfiniteScroll.prototype.showLoadingMore = function (_, data) {
4028 return data.pagination && data.pagination.more;
4031 InfiniteScroll.prototype.createLoadingMore = function () {
4034 'class="select2-results__option select2-results__option--load-more"' +
4035 'role="treeitem" aria-disabled="true"></li>'
4038 var message = this.options.get('translations').get('loadingMore');
4040 $option.html(message(this.lastParams));
4045 return InfiniteScroll;
4048 S2.define('select2/dropdown/attachBody',[
4051 ], function ($, Utils) {
4052 function AttachBody (decorated, $element, options) {
4053 this.$dropdownParent = options.get('dropdownParent') || $(document.body);
4055 decorated.call(this, $element, options);
4058 AttachBody.prototype.bind = function (decorated, container, $container) {
4061 var setupResultsEvents = false;
4063 decorated.call(this, container, $container);
4065 container.on('open', function () {
4066 self._showDropdown();
4067 self._attachPositioningHandler(container);
4069 if (!setupResultsEvents) {
4070 setupResultsEvents = true;
4072 container.on('results:all', function () {
4073 self._positionDropdown();
4074 self._resizeDropdown();
4077 container.on('results:append', function () {
4078 self._positionDropdown();
4079 self._resizeDropdown();
4084 container.on('close', function () {
4085 self._hideDropdown();
4086 self._detachPositioningHandler(container);
4089 this.$dropdownContainer.on('mousedown', function (evt) {
4090 evt.stopPropagation();
4094 AttachBody.prototype.destroy = function (decorated) {
4095 decorated.call(this);
4097 this.$dropdownContainer.remove();
4100 AttachBody.prototype.position = function (decorated, $dropdown, $container) {
4101 // Clone all of the container classes
4102 $dropdown.attr('class', $container.attr('class'));
4104 $dropdown.removeClass('select2');
4105 $dropdown.addClass('select2-container--open');
4108 position: 'absolute',
4112 this.$container = $container;
4115 AttachBody.prototype.render = function (decorated) {
4116 var $container = $('<span></span>');
4118 var $dropdown = decorated.call(this);
4119 $container.append($dropdown);
4121 this.$dropdownContainer = $container;
4126 AttachBody.prototype._hideDropdown = function (decorated) {
4127 this.$dropdownContainer.detach();
4130 AttachBody.prototype._attachPositioningHandler =
4131 function (decorated, container) {
4134 var scrollEvent = 'scroll.select2.' + container.id;
4135 var resizeEvent = 'resize.select2.' + container.id;
4136 var orientationEvent = 'orientationchange.select2.' + container.id;
4138 var $watchers = this.$container.parents().filter(Utils.hasScroll);
4139 $watchers.each(function () {
4140 $(this).data('select2-scroll-position', {
4141 x: $(this).scrollLeft(),
4142 y: $(this).scrollTop()
4146 $watchers.on(scrollEvent, function (ev) {
4147 var position = $(this).data('select2-scroll-position');
4148 $(this).scrollTop(position.y);
4151 $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
4153 self._positionDropdown();
4154 self._resizeDropdown();
4158 AttachBody.prototype._detachPositioningHandler =
4159 function (decorated, container) {
4160 var scrollEvent = 'scroll.select2.' + container.id;
4161 var resizeEvent = 'resize.select2.' + container.id;
4162 var orientationEvent = 'orientationchange.select2.' + container.id;
4164 var $watchers = this.$container.parents().filter(Utils.hasScroll);
4165 $watchers.off(scrollEvent);
4167 $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
4170 AttachBody.prototype._positionDropdown = function () {
4171 var $window = $(window);
4173 var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
4174 var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
4176 var newDirection = null;
4178 var offset = this.$container.offset();
4180 offset.bottom = offset.top + this.$container.outerHeight(false);
4183 height: this.$container.outerHeight(false)
4186 container.top = offset.top;
4187 container.bottom = offset.top + container.height;
4190 height: this.$dropdown.outerHeight(false)
4194 top: $window.scrollTop(),
4195 bottom: $window.scrollTop() + $window.height()
4198 var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
4199 var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
4203 top: container.bottom
4206 // Determine what the parent element is to use for calciulating the offset
4207 var $offsetParent = this.$dropdownParent;
4209 // For statically positoned elements, we need to get the element
4210 // that is determining the offset
4211 if ($offsetParent.css('position') === 'static') {
4212 $offsetParent = $offsetParent.offsetParent();
4215 var parentOffset = $offsetParent.offset();
4217 css.top -= parentOffset.top;
4218 css.left -= parentOffset.left;
4220 if (!isCurrentlyAbove && !isCurrentlyBelow) {
4221 newDirection = 'below';
4224 if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
4225 newDirection = 'above';
4226 } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
4227 newDirection = 'below';
4230 if (newDirection == 'above' ||
4231 (isCurrentlyAbove && newDirection !== 'below')) {
4232 css.top = container.top - dropdown.height;
4235 if (newDirection != null) {
4237 .removeClass('select2-dropdown--below select2-dropdown--above')
4238 .addClass('select2-dropdown--' + newDirection);
4240 .removeClass('select2-container--below select2-container--above')
4241 .addClass('select2-container--' + newDirection);
4244 this.$dropdownContainer.css(css);
4247 AttachBody.prototype._resizeDropdown = function () {
4249 width: this.$container.outerWidth(false) + 'px'
4252 if (this.options.get('dropdownAutoWidth')) {
4253 css.minWidth = css.width;
4257 this.$dropdown.css(css);
4260 AttachBody.prototype._showDropdown = function (decorated) {
4261 this.$dropdownContainer.appendTo(this.$dropdownParent);
4263 this._positionDropdown();
4264 this._resizeDropdown();
4270 S2.define('select2/dropdown/minimumResultsForSearch',[
4273 function countResults (data) {
4276 for (var d = 0; d < data.length; d++) {
4279 if (item.children) {
4280 count += countResults(item.children);
4289 function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
4290 this.minimumResultsForSearch = options.get('minimumResultsForSearch');
4292 if (this.minimumResultsForSearch < 0) {
4293 this.minimumResultsForSearch = Infinity;
4296 decorated.call(this, $element, options, dataAdapter);
4299 MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
4300 if (countResults(params.data.results) < this.minimumResultsForSearch) {
4304 return decorated.call(this, params);
4307 return MinimumResultsForSearch;
4310 S2.define('select2/dropdown/selectOnClose',[
4313 function SelectOnClose () { }
4315 SelectOnClose.prototype.bind = function (decorated, container, $container) {
4318 decorated.call(this, container, $container);
4320 container.on('close', function () {
4321 self._handleSelectOnClose();
4325 SelectOnClose.prototype._handleSelectOnClose = function () {
4326 var $highlightedResults = this.getHighlightedResults();
4328 // Only select highlighted results
4329 if ($highlightedResults.length < 1) {
4333 var data = $highlightedResults.data('data');
4335 // Don't re-select already selected resulte
4337 (data.element != null && data.element.selected) ||
4338 (data.element == null && data.selected)
4343 this.trigger('select', {
4348 return SelectOnClose;
4351 S2.define('select2/dropdown/closeOnSelect',[
4354 function CloseOnSelect () { }
4356 CloseOnSelect.prototype.bind = function (decorated, container, $container) {
4359 decorated.call(this, container, $container);
4361 container.on('select', function (evt) {
4362 self._selectTriggered(evt);
4365 container.on('unselect', function (evt) {
4366 self._selectTriggered(evt);
4370 CloseOnSelect.prototype._selectTriggered = function (_, evt) {
4371 var originalEvent = evt.originalEvent;
4373 // Don't close if the control key is being held
4374 if (originalEvent && originalEvent.ctrlKey) {
4378 this.trigger('close', {});
4381 return CloseOnSelect;
4384 S2.define('select2/i18n/en',[],function () {
4387 errorLoading: function () {
4388 return 'The results could not be loaded.';
4390 inputTooLong: function (args) {
4391 var overChars = args.input.length - args.maximum;
4393 var message = 'Please delete ' + overChars + ' character';
4395 if (overChars != 1) {
4401 inputTooShort: function (args) {
4402 var remainingChars = args.minimum - args.input.length;
4404 var message = 'Please enter ' + remainingChars + ' or more characters';
4408 loadingMore: function () {
4409 return 'Loading more results…';
4411 maximumSelected: function (args) {
4412 var message = 'You can only select ' + args.maximum + ' item';
4414 if (args.maximum != 1) {
4420 noResults: function () {
4421 return 'No results found';
4423 searching: function () {
4424 return 'Searching…';
4429 S2.define('select2/defaults',[
4435 './selection/single',
4436 './selection/multiple',
4437 './selection/placeholder',
4438 './selection/allowClear',
4439 './selection/search',
4440 './selection/eventRelay',
4451 './data/minimumInputLength',
4452 './data/maximumInputLength',
4453 './data/maximumSelectionLength',
4456 './dropdown/search',
4457 './dropdown/hidePlaceholder',
4458 './dropdown/infiniteScroll',
4459 './dropdown/attachBody',
4460 './dropdown/minimumResultsForSearch',
4461 './dropdown/selectOnClose',
4462 './dropdown/closeOnSelect',
4465 ], function ($, require,
4469 SingleSelection, MultipleSelection, Placeholder, AllowClear,
4470 SelectionSearch, EventRelay,
4472 Utils, Translation, DIACRITICS,
4474 SelectData, ArrayData, AjaxData, Tags, Tokenizer,
4475 MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
4477 Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
4478 AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
4480 EnglishTranslation) {
4481 function Defaults () {
4485 Defaults.prototype.apply = function (options) {
4486 options = $.extend(true, {}, this.defaults, options);
4488 if (options.dataAdapter == null) {
4489 if (options.ajax != null) {
4490 options.dataAdapter = AjaxData;
4491 } else if (options.data != null) {
4492 options.dataAdapter = ArrayData;
4494 options.dataAdapter = SelectData;
4497 if (options.minimumInputLength > 0) {
4498 options.dataAdapter = Utils.Decorate(
4499 options.dataAdapter,
4504 if (options.maximumInputLength > 0) {
4505 options.dataAdapter = Utils.Decorate(
4506 options.dataAdapter,
4511 if (options.maximumSelectionLength > 0) {
4512 options.dataAdapter = Utils.Decorate(
4513 options.dataAdapter,
4514 MaximumSelectionLength
4519 options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
4522 if (options.tokenSeparators != null || options.tokenizer != null) {
4523 options.dataAdapter = Utils.Decorate(
4524 options.dataAdapter,
4529 if (options.query != null) {
4530 var Query = require(options.amdBase + 'compat/query');
4532 options.dataAdapter = Utils.Decorate(
4533 options.dataAdapter,
4538 if (options.initSelection != null) {
4539 var InitSelection = require(options.amdBase + 'compat/initSelection');
4541 options.dataAdapter = Utils.Decorate(
4542 options.dataAdapter,
4548 if (options.resultsAdapter == null) {
4549 options.resultsAdapter = ResultsList;
4551 if (options.ajax != null) {
4552 options.resultsAdapter = Utils.Decorate(
4553 options.resultsAdapter,
4558 if (options.placeholder != null) {
4559 options.resultsAdapter = Utils.Decorate(
4560 options.resultsAdapter,
4565 if (options.selectOnClose) {
4566 options.resultsAdapter = Utils.Decorate(
4567 options.resultsAdapter,
4573 if (options.dropdownAdapter == null) {
4574 if (options.multiple) {
4575 options.dropdownAdapter = Dropdown;
4577 var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
4579 options.dropdownAdapter = SearchableDropdown;
4582 if (options.minimumResultsForSearch !== 0) {
4583 options.dropdownAdapter = Utils.Decorate(
4584 options.dropdownAdapter,
4585 MinimumResultsForSearch
4589 if (options.closeOnSelect) {
4590 options.dropdownAdapter = Utils.Decorate(
4591 options.dropdownAdapter,
4597 options.dropdownCssClass != null ||
4598 options.dropdownCss != null ||
4599 options.adaptDropdownCssClass != null
4601 var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
4603 options.dropdownAdapter = Utils.Decorate(
4604 options.dropdownAdapter,
4609 options.dropdownAdapter = Utils.Decorate(
4610 options.dropdownAdapter,
4615 if (options.selectionAdapter == null) {
4616 if (options.multiple) {
4617 options.selectionAdapter = MultipleSelection;
4619 options.selectionAdapter = SingleSelection;
4622 // Add the placeholder mixin if a placeholder was specified
4623 if (options.placeholder != null) {
4624 options.selectionAdapter = Utils.Decorate(
4625 options.selectionAdapter,
4630 if (options.allowClear) {
4631 options.selectionAdapter = Utils.Decorate(
4632 options.selectionAdapter,
4637 if (options.multiple) {
4638 options.selectionAdapter = Utils.Decorate(
4639 options.selectionAdapter,
4645 options.containerCssClass != null ||
4646 options.containerCss != null ||
4647 options.adaptContainerCssClass != null
4649 var ContainerCSS = require(options.amdBase + 'compat/containerCss');
4651 options.selectionAdapter = Utils.Decorate(
4652 options.selectionAdapter,
4657 options.selectionAdapter = Utils.Decorate(
4658 options.selectionAdapter,
4663 if (typeof options.language === 'string') {
4664 // Check if the language is specified with a region
4665 if (options.language.indexOf('-') > 0) {
4666 // Extract the region information if it is included
4667 var languageParts = options.language.split('-');
4668 var baseLanguage = languageParts[0];
4670 options.language = [options.language, baseLanguage];
4672 options.language = [options.language];
4676 if ($.isArray(options.language)) {
4677 var languages = new Translation();
4678 options.language.push('en');
4680 var languageNames = options.language;
4682 for (var l = 0; l < languageNames.length; l++) {
4683 var name = languageNames[l];
4687 // Try to load it with the original name
4688 language = Translation.loadPath(name);
4691 // If we couldn't load it, check if it wasn't the full path
4692 name = this.defaults.amdLanguageBase + name;
4693 language = Translation.loadPath(name);
4695 // The translation could not be loaded at all. Sometimes this is
4696 // because of a configuration problem, other times this can be
4697 // because of how Select2 helps load all possible translation files.
4698 if (options.debug && window.console && console.warn) {
4700 'Select2: The language file for "' + name + '" could not be ' +
4701 'automatically loaded. A fallback will be used instead.'
4709 languages.extend(language);
4712 options.translations = languages;
4714 var baseTranslation = Translation.loadPath(
4715 this.defaults.amdLanguageBase + 'en'
4717 var customTranslation = new Translation(options.language);
4719 customTranslation.extend(baseTranslation);
4721 options.translations = customTranslation;
4727 Defaults.prototype.reset = function () {
4728 function stripDiacritics (text) {
4729 // Used 'uni range + named function' from http://jsperf.com/diacritics/18
4731 return DIACRITICS[a] || a;
4734 return text.replace(/[^\u0000-\u007E]/g, match);
4737 function matcher (params, data) {
4738 // Always return the object if there is nothing to compare
4739 if ($.trim(params.term) === '') {
4743 // Do a recursive check for options with children
4744 if (data.children && data.children.length > 0) {
4745 // Clone the data object if there are children
4746 // This is required as we modify the object to remove any non-matches
4747 var match = $.extend(true, {}, data);
4749 // Check each child of the option
4750 for (var c = data.children.length - 1; c >= 0; c--) {
4751 var child = data.children[c];
4753 var matches = matcher(params, child);
4755 // If there wasn't a match, remove the object in the array
4756 if (matches == null) {
4757 match.children.splice(c, 1);
4761 // If any children matched, return the new object
4762 if (match.children.length > 0) {
4766 // If there were no matching children, check just the plain object
4767 return matcher(params, match);
4770 var original = stripDiacritics(data.text).toUpperCase();
4771 var term = stripDiacritics(params.term).toUpperCase();
4773 // Check if the text contains the term
4774 if (original.indexOf(term) > -1) {
4778 // If it doesn't contain the term, don't return anything
4784 amdLanguageBase: './i18n/',
4785 closeOnSelect: true,
4787 dropdownAutoWidth: false,
4788 escapeMarkup: Utils.escapeMarkup,
4789 language: EnglishTranslation,
4791 minimumInputLength: 0,
4792 maximumInputLength: 0,
4793 maximumSelectionLength: 0,
4794 minimumResultsForSearch: 0,
4795 selectOnClose: false,
4796 sorter: function (data) {
4799 templateResult: function (result) {
4802 templateSelection: function (selection) {
4803 return selection.text;
4810 Defaults.prototype.set = function (key, value) {
4811 var camelKey = $.camelCase(key);
4814 data[camelKey] = value;
4816 var convertedData = Utils._convertData(data);
4818 $.extend(this.defaults, convertedData);
4821 var defaults = new Defaults();
4826 S2.define('select2/options',[
4831 ], function (require, $, Defaults, Utils) {
4832 function Options (options, $element) {
4833 this.options = options;
4835 if ($element != null) {
4836 this.fromElement($element);
4839 this.options = Defaults.apply(this.options);
4841 if ($element && $element.is('input')) {
4842 var InputCompat = require(this.get('amdBase') + 'compat/inputData');
4844 this.options.dataAdapter = Utils.Decorate(
4845 this.options.dataAdapter,
4851 Options.prototype.fromElement = function ($e) {
4852 var excludedData = ['select2'];
4854 if (this.options.multiple == null) {
4855 this.options.multiple = $e.prop('multiple');
4858 if (this.options.disabled == null) {
4859 this.options.disabled = $e.prop('disabled');
4862 if (this.options.language == null) {
4863 if ($e.prop('lang')) {
4864 this.options.language = $e.prop('lang').toLowerCase();
4865 } else if ($e.closest('[lang]').prop('lang')) {
4866 this.options.language = $e.closest('[lang]').prop('lang');
4870 if (this.options.dir == null) {
4871 if ($e.prop('dir')) {
4872 this.options.dir = $e.prop('dir');
4873 } else if ($e.closest('[dir]').prop('dir')) {
4874 this.options.dir = $e.closest('[dir]').prop('dir');
4876 this.options.dir = 'ltr';
4880 $e.prop('disabled', this.options.disabled);
4881 $e.prop('multiple', this.options.multiple);
4883 if ($e.data('select2Tags')) {
4884 if (this.options.debug && window.console && console.warn) {
4886 'Select2: The `data-select2-tags` attribute has been changed to ' +
4887 'use the `data-data` and `data-tags="true"` attributes and will be ' +
4888 'removed in future versions of Select2.'
4892 $e.data('data', $e.data('select2Tags'));
4893 $e.data('tags', true);
4896 if ($e.data('ajaxUrl')) {
4897 if (this.options.debug && window.console && console.warn) {
4899 'Select2: The `data-ajax-url` attribute has been changed to ' +
4900 '`data-ajax--url` and support for the old attribute will be removed' +
4901 ' in future versions of Select2.'
4905 $e.attr('ajax--url', $e.data('ajaxUrl'));
4906 $e.data('ajax--url', $e.data('ajaxUrl'));
4911 // Prefer the element's `dataset` attribute if it exists
4912 // jQuery 1.x does not correctly handle data attributes with multiple dashes
4913 if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
4914 dataset = $.extend(true, {}, $e[0].dataset, $e.data());
4916 dataset = $e.data();
4919 var data = $.extend(true, {}, dataset);
4921 data = Utils._convertData(data);
4923 for (var key in data) {
4924 if ($.inArray(key, excludedData) > -1) {
4928 if ($.isPlainObject(this.options[key])) {
4929 $.extend(this.options[key], data[key]);
4931 this.options[key] = data[key];
4938 Options.prototype.get = function (key) {
4939 return this.options[key];
4942 Options.prototype.set = function (key, val) {
4943 this.options[key] = val;
4949 S2.define('select2/core',[
4954 ], function ($, Options, Utils, KEYS) {
4955 var Select2 = function ($element, options) {
4956 if ($element.data('select2') != null) {
4957 $element.data('select2').destroy();
4960 this.$element = $element;
4962 this.id = this._generateId($element);
4964 options = options || {};
4966 this.options = new Options(options, $element);
4968 Select2.__super__.constructor.call(this);
4970 // Set up the tabindex
4972 var tabindex = $element.attr('tabindex') || 0;
4973 $element.data('old-tabindex', tabindex);
4974 $element.attr('tabindex', '-1');
4976 // Set up containers and adapters
4978 var DataAdapter = this.options.get('dataAdapter');
4979 this.dataAdapter = new DataAdapter($element, this.options);
4981 var $container = this.render();
4983 this._placeContainer($container);
4985 var SelectionAdapter = this.options.get('selectionAdapter');
4986 this.selection = new SelectionAdapter($element, this.options);
4987 this.$selection = this.selection.render();
4989 this.selection.position(this.$selection, $container);
4991 var DropdownAdapter = this.options.get('dropdownAdapter');
4992 this.dropdown = new DropdownAdapter($element, this.options);
4993 this.$dropdown = this.dropdown.render();
4995 this.dropdown.position(this.$dropdown, $container);
4997 var ResultsAdapter = this.options.get('resultsAdapter');
4998 this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
4999 this.$results = this.results.render();
5001 this.results.position(this.$results, this.$dropdown);
5007 // Bind the container to all of the adapters
5008 this._bindAdapters();
5010 // Register any DOM event handlers
5011 this._registerDomEvents();
5013 // Register any internal event handlers
5014 this._registerDataEvents();
5015 this._registerSelectionEvents();
5016 this._registerDropdownEvents();
5017 this._registerResultsEvents();
5018 this._registerEvents();
5020 // Set the initial state
5021 this.dataAdapter.current(function (initialData) {
5022 self.trigger('selection:update', {
5027 // Hide the original select
5028 $element.addClass('select2-hidden-accessible');
5029 $element.attr('aria-hidden', 'true');
5031 // Synchronize any monitored attributes
5032 this._syncAttributes();
5034 $element.data('select2', this);
5037 Utils.Extend(Select2, Utils.Observable);
5039 Select2.prototype._generateId = function ($element) {
5042 if ($element.attr('id') != null) {
5043 id = $element.attr('id');
5044 } else if ($element.attr('name') != null) {
5045 id = $element.attr('name') + '-' + Utils.generateChars(2);
5047 id = Utils.generateChars(4);
5050 id = id.replace(/(:|\.|\[|\]|,)/g, '');
5051 id = 'select2-' + id;
5056 Select2.prototype._placeContainer = function ($container) {
5057 $container.insertAfter(this.$element);
5059 var width = this._resolveWidth(this.$element, this.options.get('width'));
5061 if (width != null) {
5062 $container.css('width', width);
5066 Select2.prototype._resolveWidth = function ($element, method) {
5067 var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
5069 if (method == 'resolve') {
5070 var styleWidth = this._resolveWidth($element, 'style');
5072 if (styleWidth != null) {
5076 return this._resolveWidth($element, 'element');
5079 if (method == 'element') {
5080 var elementWidth = $element.outerWidth(false);
5082 if (elementWidth <= 0) {
5086 return elementWidth + 'px';
5089 if (method == 'style') {
5090 var style = $element.attr('style');
5092 if (typeof(style) !== 'string') {
5096 var attrs = style.split(';');
5098 for (var i = 0, l = attrs.length; i < l; i = i + 1) {
5099 var attr = attrs[i].replace(/\s/g, '');
5100 var matches = attr.match(WIDTH);
5102 if (matches !== null && matches.length >= 1) {
5113 Select2.prototype._bindAdapters = function () {
5114 this.dataAdapter.bind(this, this.$container);
5115 this.selection.bind(this, this.$container);
5117 this.dropdown.bind(this, this.$container);
5118 this.results.bind(this, this.$container);
5121 Select2.prototype._registerDomEvents = function () {
5124 this.$element.on('change.select2', function () {
5125 self.dataAdapter.current(function (data) {
5126 self.trigger('selection:update', {
5132 this._sync = Utils.bind(this._syncAttributes, this);
5134 if (this.$element[0].attachEvent) {
5135 this.$element[0].attachEvent('onpropertychange', this._sync);
5138 var observer = window.MutationObserver ||
5139 window.WebKitMutationObserver ||
5140 window.MozMutationObserver
5143 if (observer != null) {
5144 this._observer = new observer(function (mutations) {
5145 $.each(mutations, self._sync);
5147 this._observer.observe(this.$element[0], {
5151 } else if (this.$element[0].addEventListener) {
5152 this.$element[0].addEventListener('DOMAttrModified', self._sync, false);
5156 Select2.prototype._registerDataEvents = function () {
5159 this.dataAdapter.on('*', function (name, params) {
5160 self.trigger(name, params);
5164 Select2.prototype._registerSelectionEvents = function () {
5166 var nonRelayEvents = ['toggle', 'focus'];
5168 this.selection.on('toggle', function () {
5169 self.toggleDropdown();
5172 this.selection.on('focus', function (params) {
5176 this.selection.on('*', function (name, params) {
5177 if ($.inArray(name, nonRelayEvents) !== -1) {
5181 self.trigger(name, params);
5185 Select2.prototype._registerDropdownEvents = function () {
5188 this.dropdown.on('*', function (name, params) {
5189 self.trigger(name, params);
5193 Select2.prototype._registerResultsEvents = function () {
5196 this.results.on('*', function (name, params) {
5197 self.trigger(name, params);
5201 Select2.prototype._registerEvents = function () {
5204 this.on('open', function () {
5205 self.$container.addClass('select2-container--open');
5208 this.on('close', function () {
5209 self.$container.removeClass('select2-container--open');
5212 this.on('enable', function () {
5213 self.$container.removeClass('select2-container--disabled');
5216 this.on('disable', function () {
5217 self.$container.addClass('select2-container--disabled');
5220 this.on('blur', function () {
5221 self.$container.removeClass('select2-container--focus');
5224 this.on('query', function (params) {
5225 if (!self.isOpen()) {
5226 self.trigger('open', {});
5229 this.dataAdapter.query(params, function (data) {
5230 self.trigger('results:all', {
5237 this.on('query:append', function (params) {
5238 this.dataAdapter.query(params, function (data) {
5239 self.trigger('results:append', {
5246 this.on('keypress', function (evt) {
5247 var key = evt.which;
5249 if (self.isOpen()) {
5250 if (key === KEYS.ESC || key === KEYS.TAB ||
5251 (key === KEYS.UP && evt.altKey)) {
5254 evt.preventDefault();
5255 } else if (key === KEYS.ENTER) {
5256 self.trigger('results:select', {});
5258 evt.preventDefault();
5259 } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
5260 self.trigger('results:toggle', {});
5262 evt.preventDefault();
5263 } else if (key === KEYS.UP) {
5264 self.trigger('results:previous', {});
5266 evt.preventDefault();
5267 } else if (key === KEYS.DOWN) {
5268 self.trigger('results:next', {});
5270 evt.preventDefault();
5273 if (key === KEYS.ENTER || key === KEYS.SPACE ||
5274 (key === KEYS.DOWN && evt.altKey)) {
5277 evt.preventDefault();
5283 Select2.prototype._syncAttributes = function () {
5284 this.options.set('disabled', this.$element.prop('disabled'));
5286 if (this.options.get('disabled')) {
5287 if (this.isOpen()) {
5291 this.trigger('disable', {});
5293 this.trigger('enable', {});
5298 * Override the trigger method to automatically trigger pre-events when
5299 * there are events that can be prevented.
5301 Select2.prototype.trigger = function (name, args) {
5302 var actualTrigger = Select2.__super__.trigger;
5303 var preTriggerMap = {
5306 'select': 'selecting',
5307 'unselect': 'unselecting'
5310 if (args === undefined) {
5314 if (name in preTriggerMap) {
5315 var preTriggerName = preTriggerMap[name];
5316 var preTriggerArgs = {
5322 actualTrigger.call(this, preTriggerName, preTriggerArgs);
5324 if (preTriggerArgs.prevented) {
5325 args.prevented = true;
5331 actualTrigger.call(this, name, args);
5334 Select2.prototype.toggleDropdown = function () {
5335 if (this.options.get('disabled')) {
5339 if (this.isOpen()) {
5346 Select2.prototype.open = function () {
5347 if (this.isOpen()) {
5351 this.trigger('query', {});
5354 Select2.prototype.close = function () {
5355 if (!this.isOpen()) {
5359 this.trigger('close', {});
5362 Select2.prototype.isOpen = function () {
5363 return this.$container.hasClass('select2-container--open');
5366 Select2.prototype.hasFocus = function () {
5367 return this.$container.hasClass('select2-container--focus');
5370 Select2.prototype.focus = function (data) {
5371 // No need to re-trigger focus events if we are already focused
5372 if (this.hasFocus()) {
5376 this.$container.addClass('select2-container--focus');
5377 this.trigger('focus', {});
5380 Select2.prototype.enable = function (args) {
5381 if (this.options.get('debug') && window.console && console.warn) {
5383 'Select2: The `select2("enable")` method has been deprecated and will' +
5384 ' be removed in later Select2 versions. Use $element.prop("disabled")' +
5389 if (args == null || args.length === 0) {
5393 var disabled = !args[0];
5395 this.$element.prop('disabled', disabled);
5398 Select2.prototype.data = function () {
5399 if (this.options.get('debug') &&
5400 arguments.length > 0 && window.console && console.warn) {
5402 'Select2: Data can no longer be set using `select2("data")`. You ' +
5403 'should consider setting the value instead using `$element.val()`.'
5409 this.dataAdapter.current(function (currentData) {
5416 Select2.prototype.val = function (args) {
5417 if (this.options.get('debug') && window.console && console.warn) {
5419 'Select2: The `select2("val")` method has been deprecated and will be' +
5420 ' removed in later Select2 versions. Use $element.val() instead.'
5424 if (args == null || args.length === 0) {
5425 return this.$element.val();
5428 var newVal = args[0];
5430 if ($.isArray(newVal)) {
5431 newVal = $.map(newVal, function (obj) {
5432 return obj.toString();
5436 this.$element.val(newVal).trigger('change');
5439 Select2.prototype.destroy = function () {
5440 this.$container.remove();
5442 if (this.$element[0].detachEvent) {
5443 this.$element[0].detachEvent('onpropertychange', this._sync);
5446 if (this._observer != null) {
5447 this._observer.disconnect();
5448 this._observer = null;
5449 } else if (this.$element[0].removeEventListener) {
5451 .removeEventListener('DOMAttrModified', this._sync, false);
5456 this.$element.off('.select2');
5457 this.$element.attr('tabindex', this.$element.data('old-tabindex'));
5459 this.$element.removeClass('select2-hidden-accessible');
5460 this.$element.attr('aria-hidden', 'false');
5461 this.$element.removeData('select2');
5463 this.dataAdapter.destroy();
5464 this.selection.destroy();
5465 this.dropdown.destroy();
5466 this.results.destroy();
5468 this.dataAdapter = null;
5469 this.selection = null;
5470 this.dropdown = null;
5471 this.results = null;
5474 Select2.prototype.render = function () {
5476 '<span class="select2 select2-container">' +
5477 '<span class="selection"></span>' +
5478 '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
5482 $container.attr('dir', this.options.get('dir'));
5484 this.$container = $container;
5486 this.$container.addClass('select2-container--' + this.options.get('theme'));
5488 $container.data('element', this.$element);
5496 S2.define('jquery-mousewheel',[
5499 // Used to shim jQuery.mousewheel for non-full builds.
5503 S2.define('jquery.select2',[
5505 'jquery-mousewheel',
5508 './select2/defaults'
5509 ], function ($, _, Select2, Defaults) {
5510 if ($.fn.select2 == null) {
5511 // All methods that should return the element
5512 var thisMethods = ['open', 'close', 'destroy'];
5514 $.fn.select2 = function (options) {
5515 options = options || {};
5517 if (typeof options === 'object') {
5518 this.each(function () {
5519 var instanceOptions = $.extend(true, {}, options);
5521 var instance = new Select2($(this), instanceOptions);
5525 } else if (typeof options === 'string') {
5528 this.each(function () {
5529 var instance = $(this).data('select2');
5531 if (instance == null && window.console && console.error) {
5533 'The select2(\'' + options + '\') method was called on an ' +
5534 'element that is not using Select2.'
5538 var args = Array.prototype.slice.call(arguments, 1);
5540 ret = instance[options].apply(instance, args);
5543 // Check if we should be returning `this`
5544 if ($.inArray(options, thisMethods) > -1) {
5550 throw new Error('Invalid arguments for Select2: ' + options);
5555 if ($.fn.select2.defaults == null) {
5556 $.fn.select2.defaults = Defaults;
5562 // Return the AMD loader configuration so it can be used outside of this file
5569 // Autoload the jQuery bindings
5570 // We know that all of the modules exist above this, so we're safe
5571 var select2 = S2.require('jquery.select2');
5573 // Hold the AMD module references on the jQuery function that was just loaded
5574 // This allows Select2 to use the internal loader outside of this file, such
5575 // as in the language files.
5576 jQuery.fn.select2.amd = S2;
5578 // Return the Select2 instance for anyone who is importing it.