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