PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / trunk
Secure Custom Fields vtrunk
6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / assets / inc / select2 / 3 / select2.js
secure-custom-fields / assets / inc / select2 / 3 Last commit date
index.php 1 year ago select2-spinner.gif 1 year ago select2.css 1 year ago select2.js 1 year ago select2.min.js 1 year ago select2.png 1 year ago select2x2.png 1 year ago
select2.js
5419 lines
1 /*
2 Copyright 2012 Igor Vaynberg
3
4 Version: 3.5.2 Timestamp: Sat Nov 1 14:43:36 EDT 2014
5
6 This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
7 General Public License version 2 (the "GPL License"). You may choose either license to govern your
8 use of this software only upon the condition that you accept all of the terms of either the Apache
9 License or the GPL License.
10
11 You may obtain a copy of the Apache License and the GPL License at:
12
13 http://www.apache.org/licenses/LICENSE-2.0
14 http://www.gnu.org/licenses/gpl-2.0.html
15
16 Unless required by applicable law or agreed to in writing, software distributed under the
17 Apache License or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
18 CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for
19 the specific language governing permissions and limitations under the Apache License and the GPL License.
20 */
21 ( function ( $ ) {
22 if ( typeof $.fn.each2 == 'undefined' ) {
23 $.extend( $.fn, {
24 /*
25 * 4-10 times faster .each replacement
26 * use it carefully, as it overrides jQuery context of element on each iteration
27 */
28 each2: function ( c ) {
29 var j = $( [ 0 ] ),
30 i = -1,
31 l = this.length;
32 while (
33 ++i < l &&
34 ( j.context = j[ 0 ] = this[ i ] ) &&
35 c.call( j[ 0 ], i, j ) !== false //"this"=DOM, i=index, j=jQuery object
36 );
37 return this;
38 },
39 } );
40 }
41 } )( jQuery );
42
43 ( function ( $, undefined ) {
44 'use strict';
45 /*global document, window, jQuery, console */
46
47 if ( window.Select2 !== undefined ) {
48 return;
49 }
50
51 var AbstractSelect2,
52 SingleSelect2,
53 MultiSelect2,
54 nextUid,
55 sizer,
56 lastMousePosition = { x: 0, y: 0 },
57 $document,
58 scrollBarDimensions,
59 KEY = {
60 TAB: 9,
61 ENTER: 13,
62 ESC: 27,
63 SPACE: 32,
64 LEFT: 37,
65 UP: 38,
66 RIGHT: 39,
67 DOWN: 40,
68 SHIFT: 16,
69 CTRL: 17,
70 ALT: 18,
71 PAGE_UP: 33,
72 PAGE_DOWN: 34,
73 HOME: 36,
74 END: 35,
75 BACKSPACE: 8,
76 DELETE: 46,
77 isArrow: function ( k ) {
78 k = k.which ? k.which : k;
79 switch ( k ) {
80 case KEY.LEFT:
81 case KEY.RIGHT:
82 case KEY.UP:
83 case KEY.DOWN:
84 return true;
85 }
86 return false;
87 },
88 isControl: function ( e ) {
89 var k = e.which;
90 switch ( k ) {
91 case KEY.SHIFT:
92 case KEY.CTRL:
93 case KEY.ALT:
94 return true;
95 }
96
97 if ( e.metaKey ) return true;
98
99 return false;
100 },
101 isFunctionKey: function ( k ) {
102 k = k.which ? k.which : k;
103 return k >= 112 && k <= 123;
104 },
105 },
106 MEASURE_SCROLLBAR_TEMPLATE =
107 "<div class='select2-measure-scrollbar'></div>",
108 DIACRITICS = {
109 '\u24B6': 'A',
110 '\uFF21': 'A',
111 '\u00C0': 'A',
112 '\u00C1': 'A',
113 '\u00C2': 'A',
114 '\u1EA6': 'A',
115 '\u1EA4': 'A',
116 '\u1EAA': 'A',
117 '\u1EA8': 'A',
118 '\u00C3': 'A',
119 '\u0100': 'A',
120 '\u0102': 'A',
121 '\u1EB0': 'A',
122 '\u1EAE': 'A',
123 '\u1EB4': 'A',
124 '\u1EB2': 'A',
125 '\u0226': 'A',
126 '\u01E0': 'A',
127 '\u00C4': 'A',
128 '\u01DE': 'A',
129 '\u1EA2': 'A',
130 '\u00C5': 'A',
131 '\u01FA': 'A',
132 '\u01CD': 'A',
133 '\u0200': 'A',
134 '\u0202': 'A',
135 '\u1EA0': 'A',
136 '\u1EAC': 'A',
137 '\u1EB6': 'A',
138 '\u1E00': 'A',
139 '\u0104': 'A',
140 '\u023A': 'A',
141 '\u2C6F': 'A',
142 '\uA732': 'AA',
143 '\u00C6': 'AE',
144 '\u01FC': 'AE',
145 '\u01E2': 'AE',
146 '\uA734': 'AO',
147 '\uA736': 'AU',
148 '\uA738': 'AV',
149 '\uA73A': 'AV',
150 '\uA73C': 'AY',
151 '\u24B7': 'B',
152 '\uFF22': 'B',
153 '\u1E02': 'B',
154 '\u1E04': 'B',
155 '\u1E06': 'B',
156 '\u0243': 'B',
157 '\u0182': 'B',
158 '\u0181': 'B',
159 '\u24B8': 'C',
160 '\uFF23': 'C',
161 '\u0106': 'C',
162 '\u0108': 'C',
163 '\u010A': 'C',
164 '\u010C': 'C',
165 '\u00C7': 'C',
166 '\u1E08': 'C',
167 '\u0187': 'C',
168 '\u023B': 'C',
169 '\uA73E': 'C',
170 '\u24B9': 'D',
171 '\uFF24': 'D',
172 '\u1E0A': 'D',
173 '\u010E': 'D',
174 '\u1E0C': 'D',
175 '\u1E10': 'D',
176 '\u1E12': 'D',
177 '\u1E0E': 'D',
178 '\u0110': 'D',
179 '\u018B': 'D',
180 '\u018A': 'D',
181 '\u0189': 'D',
182 '\uA779': 'D',
183 '\u01F1': 'DZ',
184 '\u01C4': 'DZ',
185 '\u01F2': 'Dz',
186 '\u01C5': 'Dz',
187 '\u24BA': 'E',
188 '\uFF25': 'E',
189 '\u00C8': 'E',
190 '\u00C9': 'E',
191 '\u00CA': 'E',
192 '\u1EC0': 'E',
193 '\u1EBE': 'E',
194 '\u1EC4': 'E',
195 '\u1EC2': 'E',
196 '\u1EBC': 'E',
197 '\u0112': 'E',
198 '\u1E14': 'E',
199 '\u1E16': 'E',
200 '\u0114': 'E',
201 '\u0116': 'E',
202 '\u00CB': 'E',
203 '\u1EBA': 'E',
204 '\u011A': 'E',
205 '\u0204': 'E',
206 '\u0206': 'E',
207 '\u1EB8': 'E',
208 '\u1EC6': 'E',
209 '\u0228': 'E',
210 '\u1E1C': 'E',
211 '\u0118': 'E',
212 '\u1E18': 'E',
213 '\u1E1A': 'E',
214 '\u0190': 'E',
215 '\u018E': 'E',
216 '\u24BB': 'F',
217 '\uFF26': 'F',
218 '\u1E1E': 'F',
219 '\u0191': 'F',
220 '\uA77B': 'F',
221 '\u24BC': 'G',
222 '\uFF27': 'G',
223 '\u01F4': 'G',
224 '\u011C': 'G',
225 '\u1E20': 'G',
226 '\u011E': 'G',
227 '\u0120': 'G',
228 '\u01E6': 'G',
229 '\u0122': 'G',
230 '\u01E4': 'G',
231 '\u0193': 'G',
232 '\uA7A0': 'G',
233 '\uA77D': 'G',
234 '\uA77E': 'G',
235 '\u24BD': 'H',
236 '\uFF28': 'H',
237 '\u0124': 'H',
238 '\u1E22': 'H',
239 '\u1E26': 'H',
240 '\u021E': 'H',
241 '\u1E24': 'H',
242 '\u1E28': 'H',
243 '\u1E2A': 'H',
244 '\u0126': 'H',
245 '\u2C67': 'H',
246 '\u2C75': 'H',
247 '\uA78D': 'H',
248 '\u24BE': 'I',
249 '\uFF29': 'I',
250 '\u00CC': 'I',
251 '\u00CD': 'I',
252 '\u00CE': 'I',
253 '\u0128': 'I',
254 '\u012A': 'I',
255 '\u012C': 'I',
256 '\u0130': 'I',
257 '\u00CF': 'I',
258 '\u1E2E': 'I',
259 '\u1EC8': 'I',
260 '\u01CF': 'I',
261 '\u0208': 'I',
262 '\u020A': 'I',
263 '\u1ECA': 'I',
264 '\u012E': 'I',
265 '\u1E2C': 'I',
266 '\u0197': 'I',
267 '\u24BF': 'J',
268 '\uFF2A': 'J',
269 '\u0134': 'J',
270 '\u0248': 'J',
271 '\u24C0': 'K',
272 '\uFF2B': 'K',
273 '\u1E30': 'K',
274 '\u01E8': 'K',
275 '\u1E32': 'K',
276 '\u0136': 'K',
277 '\u1E34': 'K',
278 '\u0198': 'K',
279 '\u2C69': 'K',
280 '\uA740': 'K',
281 '\uA742': 'K',
282 '\uA744': 'K',
283 '\uA7A2': 'K',
284 '\u24C1': 'L',
285 '\uFF2C': 'L',
286 '\u013F': 'L',
287 '\u0139': 'L',
288 '\u013D': 'L',
289 '\u1E36': 'L',
290 '\u1E38': 'L',
291 '\u013B': 'L',
292 '\u1E3C': 'L',
293 '\u1E3A': 'L',
294 '\u0141': 'L',
295 '\u023D': 'L',
296 '\u2C62': 'L',
297 '\u2C60': 'L',
298 '\uA748': 'L',
299 '\uA746': 'L',
300 '\uA780': 'L',
301 '\u01C7': 'LJ',
302 '\u01C8': 'Lj',
303 '\u24C2': 'M',
304 '\uFF2D': 'M',
305 '\u1E3E': 'M',
306 '\u1E40': 'M',
307 '\u1E42': 'M',
308 '\u2C6E': 'M',
309 '\u019C': 'M',
310 '\u24C3': 'N',
311 '\uFF2E': 'N',
312 '\u01F8': 'N',
313 '\u0143': 'N',
314 '\u00D1': 'N',
315 '\u1E44': 'N',
316 '\u0147': 'N',
317 '\u1E46': 'N',
318 '\u0145': 'N',
319 '\u1E4A': 'N',
320 '\u1E48': 'N',
321 '\u0220': 'N',
322 '\u019D': 'N',
323 '\uA790': 'N',
324 '\uA7A4': 'N',
325 '\u01CA': 'NJ',
326 '\u01CB': 'Nj',
327 '\u24C4': 'O',
328 '\uFF2F': 'O',
329 '\u00D2': 'O',
330 '\u00D3': 'O',
331 '\u00D4': 'O',
332 '\u1ED2': 'O',
333 '\u1ED0': 'O',
334 '\u1ED6': 'O',
335 '\u1ED4': 'O',
336 '\u00D5': 'O',
337 '\u1E4C': 'O',
338 '\u022C': 'O',
339 '\u1E4E': 'O',
340 '\u014C': 'O',
341 '\u1E50': 'O',
342 '\u1E52': 'O',
343 '\u014E': 'O',
344 '\u022E': 'O',
345 '\u0230': 'O',
346 '\u00D6': 'O',
347 '\u022A': 'O',
348 '\u1ECE': 'O',
349 '\u0150': 'O',
350 '\u01D1': 'O',
351 '\u020C': 'O',
352 '\u020E': 'O',
353 '\u01A0': 'O',
354 '\u1EDC': 'O',
355 '\u1EDA': 'O',
356 '\u1EE0': 'O',
357 '\u1EDE': 'O',
358 '\u1EE2': 'O',
359 '\u1ECC': 'O',
360 '\u1ED8': 'O',
361 '\u01EA': 'O',
362 '\u01EC': 'O',
363 '\u00D8': 'O',
364 '\u01FE': 'O',
365 '\u0186': 'O',
366 '\u019F': 'O',
367 '\uA74A': 'O',
368 '\uA74C': 'O',
369 '\u01A2': 'OI',
370 '\uA74E': 'OO',
371 '\u0222': 'OU',
372 '\u24C5': 'P',
373 '\uFF30': 'P',
374 '\u1E54': 'P',
375 '\u1E56': 'P',
376 '\u01A4': 'P',
377 '\u2C63': 'P',
378 '\uA750': 'P',
379 '\uA752': 'P',
380 '\uA754': 'P',
381 '\u24C6': 'Q',
382 '\uFF31': 'Q',
383 '\uA756': 'Q',
384 '\uA758': 'Q',
385 '\u024A': 'Q',
386 '\u24C7': 'R',
387 '\uFF32': 'R',
388 '\u0154': 'R',
389 '\u1E58': 'R',
390 '\u0158': 'R',
391 '\u0210': 'R',
392 '\u0212': 'R',
393 '\u1E5A': 'R',
394 '\u1E5C': 'R',
395 '\u0156': 'R',
396 '\u1E5E': 'R',
397 '\u024C': 'R',
398 '\u2C64': 'R',
399 '\uA75A': 'R',
400 '\uA7A6': 'R',
401 '\uA782': 'R',
402 '\u24C8': 'S',
403 '\uFF33': 'S',
404 '\u1E9E': 'S',
405 '\u015A': 'S',
406 '\u1E64': 'S',
407 '\u015C': 'S',
408 '\u1E60': 'S',
409 '\u0160': 'S',
410 '\u1E66': 'S',
411 '\u1E62': 'S',
412 '\u1E68': 'S',
413 '\u0218': 'S',
414 '\u015E': 'S',
415 '\u2C7E': 'S',
416 '\uA7A8': 'S',
417 '\uA784': 'S',
418 '\u24C9': 'T',
419 '\uFF34': 'T',
420 '\u1E6A': 'T',
421 '\u0164': 'T',
422 '\u1E6C': 'T',
423 '\u021A': 'T',
424 '\u0162': 'T',
425 '\u1E70': 'T',
426 '\u1E6E': 'T',
427 '\u0166': 'T',
428 '\u01AC': 'T',
429 '\u01AE': 'T',
430 '\u023E': 'T',
431 '\uA786': 'T',
432 '\uA728': 'TZ',
433 '\u24CA': 'U',
434 '\uFF35': 'U',
435 '\u00D9': 'U',
436 '\u00DA': 'U',
437 '\u00DB': 'U',
438 '\u0168': 'U',
439 '\u1E78': 'U',
440 '\u016A': 'U',
441 '\u1E7A': 'U',
442 '\u016C': 'U',
443 '\u00DC': 'U',
444 '\u01DB': 'U',
445 '\u01D7': 'U',
446 '\u01D5': 'U',
447 '\u01D9': 'U',
448 '\u1EE6': 'U',
449 '\u016E': 'U',
450 '\u0170': 'U',
451 '\u01D3': 'U',
452 '\u0214': 'U',
453 '\u0216': 'U',
454 '\u01AF': 'U',
455 '\u1EEA': 'U',
456 '\u1EE8': 'U',
457 '\u1EEE': 'U',
458 '\u1EEC': 'U',
459 '\u1EF0': 'U',
460 '\u1EE4': 'U',
461 '\u1E72': 'U',
462 '\u0172': 'U',
463 '\u1E76': 'U',
464 '\u1E74': 'U',
465 '\u0244': 'U',
466 '\u24CB': 'V',
467 '\uFF36': 'V',
468 '\u1E7C': 'V',
469 '\u1E7E': 'V',
470 '\u01B2': 'V',
471 '\uA75E': 'V',
472 '\u0245': 'V',
473 '\uA760': 'VY',
474 '\u24CC': 'W',
475 '\uFF37': 'W',
476 '\u1E80': 'W',
477 '\u1E82': 'W',
478 '\u0174': 'W',
479 '\u1E86': 'W',
480 '\u1E84': 'W',
481 '\u1E88': 'W',
482 '\u2C72': 'W',
483 '\u24CD': 'X',
484 '\uFF38': 'X',
485 '\u1E8A': 'X',
486 '\u1E8C': 'X',
487 '\u24CE': 'Y',
488 '\uFF39': 'Y',
489 '\u1EF2': 'Y',
490 '\u00DD': 'Y',
491 '\u0176': 'Y',
492 '\u1EF8': 'Y',
493 '\u0232': 'Y',
494 '\u1E8E': 'Y',
495 '\u0178': 'Y',
496 '\u1EF6': 'Y',
497 '\u1EF4': 'Y',
498 '\u01B3': 'Y',
499 '\u024E': 'Y',
500 '\u1EFE': 'Y',
501 '\u24CF': 'Z',
502 '\uFF3A': 'Z',
503 '\u0179': 'Z',
504 '\u1E90': 'Z',
505 '\u017B': 'Z',
506 '\u017D': 'Z',
507 '\u1E92': 'Z',
508 '\u1E94': 'Z',
509 '\u01B5': 'Z',
510 '\u0224': 'Z',
511 '\u2C7F': 'Z',
512 '\u2C6B': 'Z',
513 '\uA762': 'Z',
514 '\u24D0': 'a',
515 '\uFF41': 'a',
516 '\u1E9A': 'a',
517 '\u00E0': 'a',
518 '\u00E1': 'a',
519 '\u00E2': 'a',
520 '\u1EA7': 'a',
521 '\u1EA5': 'a',
522 '\u1EAB': 'a',
523 '\u1EA9': 'a',
524 '\u00E3': 'a',
525 '\u0101': 'a',
526 '\u0103': 'a',
527 '\u1EB1': 'a',
528 '\u1EAF': 'a',
529 '\u1EB5': 'a',
530 '\u1EB3': 'a',
531 '\u0227': 'a',
532 '\u01E1': 'a',
533 '\u00E4': 'a',
534 '\u01DF': 'a',
535 '\u1EA3': 'a',
536 '\u00E5': 'a',
537 '\u01FB': 'a',
538 '\u01CE': 'a',
539 '\u0201': 'a',
540 '\u0203': 'a',
541 '\u1EA1': 'a',
542 '\u1EAD': 'a',
543 '\u1EB7': 'a',
544 '\u1E01': 'a',
545 '\u0105': 'a',
546 '\u2C65': 'a',
547 '\u0250': 'a',
548 '\uA733': 'aa',
549 '\u00E6': 'ae',
550 '\u01FD': 'ae',
551 '\u01E3': 'ae',
552 '\uA735': 'ao',
553 '\uA737': 'au',
554 '\uA739': 'av',
555 '\uA73B': 'av',
556 '\uA73D': 'ay',
557 '\u24D1': 'b',
558 '\uFF42': 'b',
559 '\u1E03': 'b',
560 '\u1E05': 'b',
561 '\u1E07': 'b',
562 '\u0180': 'b',
563 '\u0183': 'b',
564 '\u0253': 'b',
565 '\u24D2': 'c',
566 '\uFF43': 'c',
567 '\u0107': 'c',
568 '\u0109': 'c',
569 '\u010B': 'c',
570 '\u010D': 'c',
571 '\u00E7': 'c',
572 '\u1E09': 'c',
573 '\u0188': 'c',
574 '\u023C': 'c',
575 '\uA73F': 'c',
576 '\u2184': 'c',
577 '\u24D3': 'd',
578 '\uFF44': 'd',
579 '\u1E0B': 'd',
580 '\u010F': 'd',
581 '\u1E0D': 'd',
582 '\u1E11': 'd',
583 '\u1E13': 'd',
584 '\u1E0F': 'd',
585 '\u0111': 'd',
586 '\u018C': 'd',
587 '\u0256': 'd',
588 '\u0257': 'd',
589 '\uA77A': 'd',
590 '\u01F3': 'dz',
591 '\u01C6': 'dz',
592 '\u24D4': 'e',
593 '\uFF45': 'e',
594 '\u00E8': 'e',
595 '\u00E9': 'e',
596 '\u00EA': 'e',
597 '\u1EC1': 'e',
598 '\u1EBF': 'e',
599 '\u1EC5': 'e',
600 '\u1EC3': 'e',
601 '\u1EBD': 'e',
602 '\u0113': 'e',
603 '\u1E15': 'e',
604 '\u1E17': 'e',
605 '\u0115': 'e',
606 '\u0117': 'e',
607 '\u00EB': 'e',
608 '\u1EBB': 'e',
609 '\u011B': 'e',
610 '\u0205': 'e',
611 '\u0207': 'e',
612 '\u1EB9': 'e',
613 '\u1EC7': 'e',
614 '\u0229': 'e',
615 '\u1E1D': 'e',
616 '\u0119': 'e',
617 '\u1E19': 'e',
618 '\u1E1B': 'e',
619 '\u0247': 'e',
620 '\u025B': 'e',
621 '\u01DD': 'e',
622 '\u24D5': 'f',
623 '\uFF46': 'f',
624 '\u1E1F': 'f',
625 '\u0192': 'f',
626 '\uA77C': 'f',
627 '\u24D6': 'g',
628 '\uFF47': 'g',
629 '\u01F5': 'g',
630 '\u011D': 'g',
631 '\u1E21': 'g',
632 '\u011F': 'g',
633 '\u0121': 'g',
634 '\u01E7': 'g',
635 '\u0123': 'g',
636 '\u01E5': 'g',
637 '\u0260': 'g',
638 '\uA7A1': 'g',
639 '\u1D79': 'g',
640 '\uA77F': 'g',
641 '\u24D7': 'h',
642 '\uFF48': 'h',
643 '\u0125': 'h',
644 '\u1E23': 'h',
645 '\u1E27': 'h',
646 '\u021F': 'h',
647 '\u1E25': 'h',
648 '\u1E29': 'h',
649 '\u1E2B': 'h',
650 '\u1E96': 'h',
651 '\u0127': 'h',
652 '\u2C68': 'h',
653 '\u2C76': 'h',
654 '\u0265': 'h',
655 '\u0195': 'hv',
656 '\u24D8': 'i',
657 '\uFF49': 'i',
658 '\u00EC': 'i',
659 '\u00ED': 'i',
660 '\u00EE': 'i',
661 '\u0129': 'i',
662 '\u012B': 'i',
663 '\u012D': 'i',
664 '\u00EF': 'i',
665 '\u1E2F': 'i',
666 '\u1EC9': 'i',
667 '\u01D0': 'i',
668 '\u0209': 'i',
669 '\u020B': 'i',
670 '\u1ECB': 'i',
671 '\u012F': 'i',
672 '\u1E2D': 'i',
673 '\u0268': 'i',
674 '\u0131': 'i',
675 '\u24D9': 'j',
676 '\uFF4A': 'j',
677 '\u0135': 'j',
678 '\u01F0': 'j',
679 '\u0249': 'j',
680 '\u24DA': 'k',
681 '\uFF4B': 'k',
682 '\u1E31': 'k',
683 '\u01E9': 'k',
684 '\u1E33': 'k',
685 '\u0137': 'k',
686 '\u1E35': 'k',
687 '\u0199': 'k',
688 '\u2C6A': 'k',
689 '\uA741': 'k',
690 '\uA743': 'k',
691 '\uA745': 'k',
692 '\uA7A3': 'k',
693 '\u24DB': 'l',
694 '\uFF4C': 'l',
695 '\u0140': 'l',
696 '\u013A': 'l',
697 '\u013E': 'l',
698 '\u1E37': 'l',
699 '\u1E39': 'l',
700 '\u013C': 'l',
701 '\u1E3D': 'l',
702 '\u1E3B': 'l',
703 '\u017F': 'l',
704 '\u0142': 'l',
705 '\u019A': 'l',
706 '\u026B': 'l',
707 '\u2C61': 'l',
708 '\uA749': 'l',
709 '\uA781': 'l',
710 '\uA747': 'l',
711 '\u01C9': 'lj',
712 '\u24DC': 'm',
713 '\uFF4D': 'm',
714 '\u1E3F': 'm',
715 '\u1E41': 'm',
716 '\u1E43': 'm',
717 '\u0271': 'm',
718 '\u026F': 'm',
719 '\u24DD': 'n',
720 '\uFF4E': 'n',
721 '\u01F9': 'n',
722 '\u0144': 'n',
723 '\u00F1': 'n',
724 '\u1E45': 'n',
725 '\u0148': 'n',
726 '\u1E47': 'n',
727 '\u0146': 'n',
728 '\u1E4B': 'n',
729 '\u1E49': 'n',
730 '\u019E': 'n',
731 '\u0272': 'n',
732 '\u0149': 'n',
733 '\uA791': 'n',
734 '\uA7A5': 'n',
735 '\u01CC': 'nj',
736 '\u24DE': 'o',
737 '\uFF4F': 'o',
738 '\u00F2': 'o',
739 '\u00F3': 'o',
740 '\u00F4': 'o',
741 '\u1ED3': 'o',
742 '\u1ED1': 'o',
743 '\u1ED7': 'o',
744 '\u1ED5': 'o',
745 '\u00F5': 'o',
746 '\u1E4D': 'o',
747 '\u022D': 'o',
748 '\u1E4F': 'o',
749 '\u014D': 'o',
750 '\u1E51': 'o',
751 '\u1E53': 'o',
752 '\u014F': 'o',
753 '\u022F': 'o',
754 '\u0231': 'o',
755 '\u00F6': 'o',
756 '\u022B': 'o',
757 '\u1ECF': 'o',
758 '\u0151': 'o',
759 '\u01D2': 'o',
760 '\u020D': 'o',
761 '\u020F': 'o',
762 '\u01A1': 'o',
763 '\u1EDD': 'o',
764 '\u1EDB': 'o',
765 '\u1EE1': 'o',
766 '\u1EDF': 'o',
767 '\u1EE3': 'o',
768 '\u1ECD': 'o',
769 '\u1ED9': 'o',
770 '\u01EB': 'o',
771 '\u01ED': 'o',
772 '\u00F8': 'o',
773 '\u01FF': 'o',
774 '\u0254': 'o',
775 '\uA74B': 'o',
776 '\uA74D': 'o',
777 '\u0275': 'o',
778 '\u01A3': 'oi',
779 '\u0223': 'ou',
780 '\uA74F': 'oo',
781 '\u24DF': 'p',
782 '\uFF50': 'p',
783 '\u1E55': 'p',
784 '\u1E57': 'p',
785 '\u01A5': 'p',
786 '\u1D7D': 'p',
787 '\uA751': 'p',
788 '\uA753': 'p',
789 '\uA755': 'p',
790 '\u24E0': 'q',
791 '\uFF51': 'q',
792 '\u024B': 'q',
793 '\uA757': 'q',
794 '\uA759': 'q',
795 '\u24E1': 'r',
796 '\uFF52': 'r',
797 '\u0155': 'r',
798 '\u1E59': 'r',
799 '\u0159': 'r',
800 '\u0211': 'r',
801 '\u0213': 'r',
802 '\u1E5B': 'r',
803 '\u1E5D': 'r',
804 '\u0157': 'r',
805 '\u1E5F': 'r',
806 '\u024D': 'r',
807 '\u027D': 'r',
808 '\uA75B': 'r',
809 '\uA7A7': 'r',
810 '\uA783': 'r',
811 '\u24E2': 's',
812 '\uFF53': 's',
813 '\u00DF': 's',
814 '\u015B': 's',
815 '\u1E65': 's',
816 '\u015D': 's',
817 '\u1E61': 's',
818 '\u0161': 's',
819 '\u1E67': 's',
820 '\u1E63': 's',
821 '\u1E69': 's',
822 '\u0219': 's',
823 '\u015F': 's',
824 '\u023F': 's',
825 '\uA7A9': 's',
826 '\uA785': 's',
827 '\u1E9B': 's',
828 '\u24E3': 't',
829 '\uFF54': 't',
830 '\u1E6B': 't',
831 '\u1E97': 't',
832 '\u0165': 't',
833 '\u1E6D': 't',
834 '\u021B': 't',
835 '\u0163': 't',
836 '\u1E71': 't',
837 '\u1E6F': 't',
838 '\u0167': 't',
839 '\u01AD': 't',
840 '\u0288': 't',
841 '\u2C66': 't',
842 '\uA787': 't',
843 '\uA729': 'tz',
844 '\u24E4': 'u',
845 '\uFF55': 'u',
846 '\u00F9': 'u',
847 '\u00FA': 'u',
848 '\u00FB': 'u',
849 '\u0169': 'u',
850 '\u1E79': 'u',
851 '\u016B': 'u',
852 '\u1E7B': 'u',
853 '\u016D': 'u',
854 '\u00FC': 'u',
855 '\u01DC': 'u',
856 '\u01D8': 'u',
857 '\u01D6': 'u',
858 '\u01DA': 'u',
859 '\u1EE7': 'u',
860 '\u016F': 'u',
861 '\u0171': 'u',
862 '\u01D4': 'u',
863 '\u0215': 'u',
864 '\u0217': 'u',
865 '\u01B0': 'u',
866 '\u1EEB': 'u',
867 '\u1EE9': 'u',
868 '\u1EEF': 'u',
869 '\u1EED': 'u',
870 '\u1EF1': 'u',
871 '\u1EE5': 'u',
872 '\u1E73': 'u',
873 '\u0173': 'u',
874 '\u1E77': 'u',
875 '\u1E75': 'u',
876 '\u0289': 'u',
877 '\u24E5': 'v',
878 '\uFF56': 'v',
879 '\u1E7D': 'v',
880 '\u1E7F': 'v',
881 '\u028B': 'v',
882 '\uA75F': 'v',
883 '\u028C': 'v',
884 '\uA761': 'vy',
885 '\u24E6': 'w',
886 '\uFF57': 'w',
887 '\u1E81': 'w',
888 '\u1E83': 'w',
889 '\u0175': 'w',
890 '\u1E87': 'w',
891 '\u1E85': 'w',
892 '\u1E98': 'w',
893 '\u1E89': 'w',
894 '\u2C73': 'w',
895 '\u24E7': 'x',
896 '\uFF58': 'x',
897 '\u1E8B': 'x',
898 '\u1E8D': 'x',
899 '\u24E8': 'y',
900 '\uFF59': 'y',
901 '\u1EF3': 'y',
902 '\u00FD': 'y',
903 '\u0177': 'y',
904 '\u1EF9': 'y',
905 '\u0233': 'y',
906 '\u1E8F': 'y',
907 '\u00FF': 'y',
908 '\u1EF7': 'y',
909 '\u1E99': 'y',
910 '\u1EF5': 'y',
911 '\u01B4': 'y',
912 '\u024F': 'y',
913 '\u1EFF': 'y',
914 '\u24E9': 'z',
915 '\uFF5A': 'z',
916 '\u017A': 'z',
917 '\u1E91': 'z',
918 '\u017C': 'z',
919 '\u017E': 'z',
920 '\u1E93': 'z',
921 '\u1E95': 'z',
922 '\u01B6': 'z',
923 '\u0225': 'z',
924 '\u0240': 'z',
925 '\u2C6C': 'z',
926 '\uA763': 'z',
927 '\u0386': '\u0391',
928 '\u0388': '\u0395',
929 '\u0389': '\u0397',
930 '\u038A': '\u0399',
931 '\u03AA': '\u0399',
932 '\u038C': '\u039F',
933 '\u038E': '\u03A5',
934 '\u03AB': '\u03A5',
935 '\u038F': '\u03A9',
936 '\u03AC': '\u03B1',
937 '\u03AD': '\u03B5',
938 '\u03AE': '\u03B7',
939 '\u03AF': '\u03B9',
940 '\u03CA': '\u03B9',
941 '\u0390': '\u03B9',
942 '\u03CC': '\u03BF',
943 '\u03CD': '\u03C5',
944 '\u03CB': '\u03C5',
945 '\u03B0': '\u03C5',
946 '\u03C9': '\u03C9',
947 '\u03C2': '\u03C3',
948 };
949
950 $document = $( document );
951
952 nextUid = ( function () {
953 var counter = 1;
954 return function () {
955 return counter++;
956 };
957 } )();
958
959 function reinsertElement( element ) {
960 var placeholder = $( document.createTextNode( '' ) );
961
962 element.before( placeholder );
963 placeholder.before( element );
964 placeholder.remove();
965 }
966
967 function stripDiacritics( str ) {
968 // Used 'uni range + named function' from http://jsperf.com/diacritics/18
969 function match( a ) {
970 return DIACRITICS[ a ] || a;
971 }
972
973 return str.replace( /[^\u0000-\u007E]/g, match );
974 }
975
976 function indexOf( value, array ) {
977 var i = 0,
978 l = array.length;
979 for ( ; i < l; i = i + 1 ) {
980 if ( equal( value, array[ i ] ) ) return i;
981 }
982 return -1;
983 }
984
985 function measureScrollbar() {
986 var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
987 $template.appendTo( document.body );
988
989 var dim = {
990 width: $template.width() - $template[ 0 ].clientWidth,
991 height: $template.height() - $template[ 0 ].clientHeight,
992 };
993 $template.remove();
994
995 return dim;
996 }
997
998 /**
999 * Compares equality of a and b
1000 * @param a
1001 * @param b
1002 */
1003 function equal( a, b ) {
1004 if ( a === b ) return true;
1005 if ( a === undefined || b === undefined ) return false;
1006 if ( a === null || b === null ) return false;
1007 // Check whether 'a' or 'b' is a string (primitive or object).
1008 // The concatenation of an empty string (+'') converts its argument to a string's primitive.
1009 if ( a.constructor === String ) return a + '' === b + ''; // a+'' - in case 'a' is a String object
1010 if ( b.constructor === String ) return b + '' === a + ''; // b+'' - in case 'b' is a String object
1011 return false;
1012 }
1013
1014 /**
1015 * Splits the string into an array of values, transforming each value. An empty array is returned for nulls or empty
1016 * strings
1017 * @param string
1018 * @param separator
1019 */
1020 function splitVal( string, separator, transform ) {
1021 var val, i, l;
1022 if ( string === null || string.length < 1 ) return [];
1023 val = string.split( separator );
1024 for ( i = 0, l = val.length; i < l; i = i + 1 )
1025 val[ i ] = transform( val[ i ] );
1026 return val;
1027 }
1028
1029 function getSideBorderPadding( element ) {
1030 return element.outerWidth( false ) - element.width();
1031 }
1032
1033 function installKeyUpChangeEvent( element ) {
1034 var key = 'keyup-change-value';
1035 element.on( 'keydown', function () {
1036 if ( $.data( element, key ) === undefined ) {
1037 $.data( element, key, element.val() );
1038 }
1039 } );
1040 element.on( 'keyup', function () {
1041 var val = $.data( element, key );
1042 if ( val !== undefined && element.val() !== val ) {
1043 $.removeData( element, key );
1044 element.trigger( 'keyup-change' );
1045 }
1046 } );
1047 }
1048
1049 /**
1050 * filters mouse events so an event is fired only if the mouse moved.
1051 *
1052 * filters out mouse events that occur when mouse is stationary but
1053 * the elements under the pointer are scrolled.
1054 */
1055 function installFilteredMouseMove( element ) {
1056 element.on( 'mousemove', function ( e ) {
1057 var lastpos = lastMousePosition;
1058 if (
1059 lastpos === undefined ||
1060 lastpos.x !== e.pageX ||
1061 lastpos.y !== e.pageY
1062 ) {
1063 $( e.target ).trigger( 'mousemove-filtered', e );
1064 }
1065 } );
1066 }
1067
1068 /**
1069 * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
1070 * within the last quietMillis milliseconds.
1071 *
1072 * @param quietMillis number of milliseconds to wait before invoking fn
1073 * @param fn function to be debounced
1074 * @param ctx object to be used as this reference within fn
1075 * @return debounced version of fn
1076 */
1077 function debounce( quietMillis, fn, ctx ) {
1078 ctx = ctx || undefined;
1079 var timeout;
1080 return function () {
1081 var args = arguments;
1082 window.clearTimeout( timeout );
1083 timeout = window.setTimeout( function () {
1084 fn.apply( ctx, args );
1085 }, quietMillis );
1086 };
1087 }
1088
1089 function installDebouncedScroll( threshold, element ) {
1090 var notify = debounce( threshold, function ( e ) {
1091 element.trigger( 'scroll-debounced', e );
1092 } );
1093 element.on( 'scroll', function ( e ) {
1094 if ( indexOf( e.target, element.get() ) >= 0 ) notify( e );
1095 } );
1096 }
1097
1098 function focus( $el ) {
1099 if ( $el[ 0 ] === document.activeElement ) return;
1100
1101 /* set the focus in a 0 timeout - that way the focus is set after the processing
1102 of the current event has finished - which seems like the only reliable way
1103 to set focus */
1104 window.setTimeout( function () {
1105 var el = $el[ 0 ],
1106 pos = $el.val().length,
1107 range;
1108
1109 $el.focus();
1110
1111 /* make sure el received focus so we do not error out when trying to manipulate the caret.
1112 sometimes modals or others listeners may steal it after its set */
1113 var isVisible = el.offsetWidth > 0 || el.offsetHeight > 0;
1114 if ( isVisible && el === document.activeElement ) {
1115 /* after the focus is set move the caret to the end, necessary when we val()
1116 just before setting focus */
1117 if ( el.setSelectionRange ) {
1118 el.setSelectionRange( pos, pos );
1119 } else if ( el.createTextRange ) {
1120 range = el.createTextRange();
1121 range.collapse( false );
1122 range.select();
1123 }
1124 }
1125 }, 0 );
1126 }
1127
1128 function getCursorInfo( el ) {
1129 el = $( el )[ 0 ];
1130 var offset = 0;
1131 var length = 0;
1132 if ( 'selectionStart' in el ) {
1133 offset = el.selectionStart;
1134 length = el.selectionEnd - offset;
1135 } else if ( 'selection' in document ) {
1136 el.focus();
1137 var sel = document.selection.createRange();
1138 length = document.selection.createRange().text.length;
1139 sel.moveStart( 'character', -el.value.length );
1140 offset = sel.text.length - length;
1141 }
1142 return { offset: offset, length: length };
1143 }
1144
1145 function killEvent( event ) {
1146 event.preventDefault();
1147 event.stopPropagation();
1148 }
1149 function killEventImmediately( event ) {
1150 event.preventDefault();
1151 event.stopImmediatePropagation();
1152 }
1153
1154 function measureTextWidth( e ) {
1155 if ( ! sizer ) {
1156 var style =
1157 e[ 0 ].currentStyle || window.getComputedStyle( e[ 0 ], null );
1158 sizer = $( document.createElement( 'div' ) ).css( {
1159 position: 'absolute',
1160 left: '-10000px',
1161 top: '-10000px',
1162 display: 'none',
1163 fontSize: style.fontSize,
1164 fontFamily: style.fontFamily,
1165 fontStyle: style.fontStyle,
1166 fontWeight: style.fontWeight,
1167 letterSpacing: style.letterSpacing,
1168 textTransform: style.textTransform,
1169 whiteSpace: 'nowrap',
1170 } );
1171 sizer.attr( 'class', 'select2-sizer' );
1172 $( document.body ).append( sizer );
1173 }
1174 sizer.text( e.val() );
1175 return sizer.width();
1176 }
1177
1178 function syncCssClasses( dest, src, adapter ) {
1179 var classes,
1180 replacements = [],
1181 adapted;
1182
1183 classes = $.trim( dest.attr( 'class' ) );
1184
1185 if ( classes ) {
1186 classes = '' + classes; // for IE which returns object
1187
1188 $( classes.split( /\s+/ ) ).each2( function () {
1189 if ( this.indexOf( 'select2-' ) === 0 ) {
1190 replacements.push( this );
1191 }
1192 } );
1193 }
1194
1195 classes = $.trim( src.attr( 'class' ) );
1196
1197 if ( classes ) {
1198 classes = '' + classes; // for IE which returns object
1199
1200 $( classes.split( /\s+/ ) ).each2( function () {
1201 if ( this.indexOf( 'select2-' ) !== 0 ) {
1202 adapted = adapter( this );
1203
1204 if ( adapted ) {
1205 replacements.push( adapted );
1206 }
1207 }
1208 } );
1209 }
1210
1211 dest.attr( 'class', replacements.join( ' ' ) );
1212 }
1213
1214 function markMatch( text, term, markup, escapeMarkup ) {
1215 var match = stripDiacritics( text.toUpperCase() ).indexOf(
1216 stripDiacritics( term.toUpperCase() )
1217 ),
1218 tl = term.length;
1219
1220 if ( match < 0 ) {
1221 markup.push( escapeMarkup( text ) );
1222 return;
1223 }
1224
1225 markup.push( escapeMarkup( text.substring( 0, match ) ) );
1226 markup.push( "<span class='select2-match'>" );
1227 markup.push( escapeMarkup( text.substring( match, match + tl ) ) );
1228 markup.push( '</span>' );
1229 markup.push(
1230 escapeMarkup( text.substring( match + tl, text.length ) )
1231 );
1232 }
1233
1234 function defaultEscapeMarkup( markup ) {
1235 var replace_map = {
1236 '\\': '&#92;',
1237 '&': '&amp;',
1238 '<': '&lt;',
1239 '>': '&gt;',
1240 '"': '&quot;',
1241 "'": '&#39;',
1242 '/': '&#47;',
1243 };
1244
1245 return String( markup ).replace( /[&<>"'\/\\]/g, function ( match ) {
1246 return replace_map[ match ];
1247 } );
1248 }
1249
1250 /**
1251 * Produces an ajax-based query function
1252 *
1253 * @param options object containing configuration parameters
1254 * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
1255 * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
1256 * @param options.url url for the data
1257 * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
1258 * @param options.dataType request data type: ajax, jsonp, other datatypes supported by jQuery's $.ajax function or the transport function if specified
1259 * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
1260 * @param options.results a function(remoteData, pageNumber, query) that converts data returned form the remote request to the format expected by Select2.
1261 * The expected format is an object containing the following keys:
1262 * results array of objects that will be used as choices
1263 * more (optional) boolean indicating whether there are more results available
1264 * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
1265 */
1266 function ajax( options ) {
1267 var timeout, // current scheduled but not yet executed request
1268 handler = null,
1269 quietMillis = options.quietMillis || 100,
1270 ajaxUrl = options.url,
1271 self = this;
1272
1273 return function ( query ) {
1274 window.clearTimeout( timeout );
1275 timeout = window.setTimeout( function () {
1276 var data = options.data, // ajax data function
1277 url = ajaxUrl, // ajax url string or function
1278 transport =
1279 options.transport ||
1280 $.fn.select2.ajaxDefaults.transport,
1281 // deprecated - to be removed in 4.0 - use params instead
1282 deprecated = {
1283 type: options.type || 'GET', // set type of request (GET or POST)
1284 cache: options.cache || false,
1285 jsonpCallback: options.jsonpCallback || undefined,
1286 dataType: options.dataType || 'json',
1287 },
1288 params = $.extend(
1289 {},
1290 $.fn.select2.ajaxDefaults.params,
1291 deprecated
1292 );
1293
1294 data = data
1295 ? data.call( self, query.term, query.page, query.context )
1296 : null;
1297 url =
1298 typeof url === 'function'
1299 ? url.call(
1300 self,
1301 query.term,
1302 query.page,
1303 query.context
1304 )
1305 : url;
1306
1307 if ( handler && typeof handler.abort === 'function' ) {
1308 handler.abort();
1309 }
1310
1311 if ( options.params ) {
1312 if ( $.isFunction( options.params ) ) {
1313 $.extend( params, options.params.call( self ) );
1314 } else {
1315 $.extend( params, options.params );
1316 }
1317 }
1318
1319 $.extend( params, {
1320 url: url,
1321 dataType: options.dataType,
1322 data: data,
1323 success: function ( data ) {
1324 // TODO - replace query.page with query so users have access to term, page, etc.
1325 // added query as third parameter to keep backwards compatibility
1326 var results = options.results(
1327 data,
1328 query.page,
1329 query
1330 );
1331 query.callback( results );
1332 },
1333 error: function ( jqXHR, textStatus, errorThrown ) {
1334 var results = {
1335 hasError: true,
1336 jqXHR: jqXHR,
1337 textStatus: textStatus,
1338 errorThrown: errorThrown,
1339 };
1340
1341 query.callback( results );
1342 },
1343 } );
1344 handler = transport.call( self, params );
1345 }, quietMillis );
1346 };
1347 }
1348
1349 /**
1350 * Produces a query function that works with a local array
1351 *
1352 * @param options object containing configuration parameters. The options parameter can either be an array or an
1353 * object.
1354 *
1355 * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
1356 *
1357 * If the object form is used it is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
1358 * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
1359 * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
1360 * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
1361 * the text.
1362 */
1363 function local( options ) {
1364 var data = options, // data elements
1365 dataText,
1366 tmp,
1367 text = function ( item ) {
1368 return '' + item.text;
1369 }; // function used to retrieve the text portion of a data item that is matched against the search
1370
1371 if ( $.isArray( data ) ) {
1372 tmp = data;
1373 data = { results: tmp };
1374 }
1375
1376 if ( $.isFunction( data ) === false ) {
1377 tmp = data;
1378 data = function () {
1379 return tmp;
1380 };
1381 }
1382
1383 var dataItem = data();
1384 if ( dataItem.text ) {
1385 text = dataItem.text;
1386 // if text is not a function we assume it to be a key name
1387 if ( ! $.isFunction( text ) ) {
1388 dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
1389 text = function ( item ) {
1390 return item[ dataText ];
1391 };
1392 }
1393 }
1394
1395 return function ( query ) {
1396 var t = query.term,
1397 filtered = { results: [] },
1398 process;
1399 if ( t === '' ) {
1400 query.callback( data() );
1401 return;
1402 }
1403
1404 process = function ( datum, collection ) {
1405 var group, attr;
1406 datum = datum[ 0 ];
1407 if ( datum.children ) {
1408 group = {};
1409 for ( attr in datum ) {
1410 if ( datum.hasOwnProperty( attr ) )
1411 group[ attr ] = datum[ attr ];
1412 }
1413 group.children = [];
1414 $( datum.children ).each2( function ( i, childDatum ) {
1415 process( childDatum, group.children );
1416 } );
1417 if (
1418 group.children.length ||
1419 query.matcher( t, text( group ), datum )
1420 ) {
1421 collection.push( group );
1422 }
1423 } else {
1424 if ( query.matcher( t, text( datum ), datum ) ) {
1425 collection.push( datum );
1426 }
1427 }
1428 };
1429
1430 $( data().results ).each2( function ( i, datum ) {
1431 process( datum, filtered.results );
1432 } );
1433 query.callback( filtered );
1434 };
1435 }
1436
1437 // TODO javadoc
1438 function tags( data ) {
1439 var isFunc = $.isFunction( data );
1440 return function ( query ) {
1441 var t = query.term,
1442 filtered = { results: [] };
1443 var result = isFunc ? data( query ) : data;
1444 if ( $.isArray( result ) ) {
1445 $( result ).each( function () {
1446 var isObject = this.text !== undefined,
1447 text = isObject ? this.text : this;
1448 if ( t === '' || query.matcher( t, text ) ) {
1449 filtered.results.push(
1450 isObject ? this : { id: this, text: this }
1451 );
1452 }
1453 } );
1454 query.callback( filtered );
1455 }
1456 };
1457 }
1458
1459 /**
1460 * Checks if the formatter function should be used.
1461 *
1462 * Throws an error if it is not a function. Returns true if it should be used,
1463 * false if no formatting should be performed.
1464 *
1465 * @param formatter
1466 */
1467 function checkFormatter( formatter, formatterName ) {
1468 if ( $.isFunction( formatter ) ) return true;
1469 if ( ! formatter ) return false;
1470 if ( typeof formatter === 'string' ) return true;
1471 throw new Error(
1472 formatterName + ' must be a string, function, or falsy value'
1473 );
1474 }
1475
1476 /**
1477 * Returns a given value
1478 * If given a function, returns its output
1479 *
1480 * @param val string|function
1481 * @param context value of "this" to be passed to function
1482 * @returns {*}
1483 */
1484 function evaluate( val, context ) {
1485 if ( $.isFunction( val ) ) {
1486 var args = Array.prototype.slice.call( arguments, 2 );
1487 return val.apply( context, args );
1488 }
1489 return val;
1490 }
1491
1492 function countResults( results ) {
1493 var count = 0;
1494 $.each( results, function ( i, item ) {
1495 if ( item.children ) {
1496 count += countResults( item.children );
1497 } else {
1498 count++;
1499 }
1500 } );
1501 return count;
1502 }
1503
1504 /**
1505 * Default tokenizer. This function uses breaks the input on substring match of any string from the
1506 * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
1507 * two options have to be defined in order for the tokenizer to work.
1508 *
1509 * @param input text user has typed so far or pasted into the search field
1510 * @param selection currently selected choices
1511 * @param selectCallback function(choice) callback tho add the choice to selection
1512 * @param opts select2's opts
1513 * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
1514 */
1515 function defaultTokenizer( input, selection, selectCallback, opts ) {
1516 var original = input, // store the original so we can compare and know if we need to tell the search to update its text
1517 dupe = false, // check for whether a token we extracted represents a duplicate selected choice
1518 token, // token
1519 index, // position at which the separator was found
1520 i,
1521 l, // looping variables
1522 separator; // the matched separator
1523
1524 if (
1525 ! opts.createSearchChoice ||
1526 ! opts.tokenSeparators ||
1527 opts.tokenSeparators.length < 1
1528 )
1529 return undefined;
1530
1531 while ( true ) {
1532 index = -1;
1533
1534 for ( i = 0, l = opts.tokenSeparators.length; i < l; i++ ) {
1535 separator = opts.tokenSeparators[ i ];
1536 index = input.indexOf( separator );
1537 if ( index >= 0 ) break;
1538 }
1539
1540 if ( index < 0 ) break; // did not find any token separator in the input string, bail
1541
1542 token = input.substring( 0, index );
1543 input = input.substring( index + separator.length );
1544
1545 if ( token.length > 0 ) {
1546 token = opts.createSearchChoice.call( this, token, selection );
1547 if (
1548 token !== undefined &&
1549 token !== null &&
1550 opts.id( token ) !== undefined &&
1551 opts.id( token ) !== null
1552 ) {
1553 dupe = false;
1554 for ( i = 0, l = selection.length; i < l; i++ ) {
1555 if (
1556 equal( opts.id( token ), opts.id( selection[ i ] ) )
1557 ) {
1558 dupe = true;
1559 break;
1560 }
1561 }
1562
1563 if ( ! dupe ) selectCallback( token );
1564 }
1565 }
1566 }
1567
1568 if ( original !== input ) return input;
1569 }
1570
1571 function cleanupJQueryElements() {
1572 var self = this;
1573
1574 $.each( arguments, function ( i, element ) {
1575 self[ element ].remove();
1576 self[ element ] = null;
1577 } );
1578 }
1579
1580 /**
1581 * Creates a new class
1582 *
1583 * @param superClass
1584 * @param methods
1585 */
1586 function clazz( SuperClass, methods ) {
1587 var constructor = function () {};
1588 constructor.prototype = new SuperClass();
1589 constructor.prototype.constructor = constructor;
1590 constructor.prototype.parent = SuperClass.prototype;
1591 constructor.prototype = $.extend( constructor.prototype, methods );
1592 return constructor;
1593 }
1594
1595 AbstractSelect2 = clazz( Object, {
1596 // abstract
1597 bind: function ( func ) {
1598 var self = this;
1599 return function () {
1600 func.apply( self, arguments );
1601 };
1602 },
1603
1604 // abstract
1605 init: function ( opts ) {
1606 var results,
1607 search,
1608 resultsSelector = '.select2-results';
1609
1610 // prepare options
1611 this.opts = opts = this.prepareOpts( opts );
1612
1613 this.id = opts.id;
1614
1615 // destroy if called on an existing component
1616 if (
1617 opts.element.data( 'select2' ) !== undefined &&
1618 opts.element.data( 'select2' ) !== null
1619 ) {
1620 opts.element.data( 'select2' ).destroy();
1621 }
1622
1623 this.container = this.createContainer();
1624
1625 this.liveRegion = $( '.select2-hidden-accessible' );
1626 if ( this.liveRegion.length == 0 ) {
1627 this.liveRegion = $( '<span>', {
1628 role: 'status',
1629 'aria-live': 'polite',
1630 } )
1631 .addClass( 'select2-hidden-accessible' )
1632 .appendTo( document.body );
1633 }
1634
1635 this.containerId =
1636 's2id_' +
1637 ( opts.element.attr( 'id' ) || 'autogen' + nextUid() );
1638 this.containerEventName = this.containerId
1639 .replace( /([.])/g, '_' )
1640 .replace( /([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1' );
1641 this.container.attr( 'id', this.containerId );
1642
1643 this.container.attr( 'title', opts.element.attr( 'title' ) );
1644
1645 this.body = $( document.body );
1646
1647 syncCssClasses(
1648 this.container,
1649 this.opts.element,
1650 this.opts.adaptContainerCssClass
1651 );
1652
1653 this.container.attr( 'style', opts.element.attr( 'style' ) );
1654 this.container.css(
1655 evaluate( opts.containerCss, this.opts.element )
1656 );
1657 this.container.addClass(
1658 evaluate( opts.containerCssClass, this.opts.element )
1659 );
1660
1661 this.elementTabIndex = this.opts.element.attr( 'tabindex' );
1662
1663 // swap container for the element
1664 this.opts.element
1665 .data( 'select2', this )
1666 .attr( 'tabindex', '-1' )
1667 .before( this.container )
1668 .on( 'click.select2', killEvent ); // do not leak click events
1669
1670 this.container.data( 'select2', this );
1671
1672 this.dropdown = this.container.find( '.select2-drop' );
1673
1674 syncCssClasses(
1675 this.dropdown,
1676 this.opts.element,
1677 this.opts.adaptDropdownCssClass
1678 );
1679
1680 this.dropdown.addClass(
1681 evaluate( opts.dropdownCssClass, this.opts.element )
1682 );
1683 this.dropdown.data( 'select2', this );
1684 this.dropdown.on( 'click', killEvent );
1685
1686 this.results = results = this.container.find( resultsSelector );
1687 this.search = search = this.container.find( 'input.select2-input' );
1688
1689 this.queryCount = 0;
1690 this.resultsPage = 0;
1691 this.context = null;
1692
1693 // initialize the container
1694 this.initContainer();
1695
1696 this.container.on( 'click', killEvent );
1697
1698 installFilteredMouseMove( this.results );
1699
1700 this.dropdown.on(
1701 'mousemove-filtered',
1702 resultsSelector,
1703 this.bind( this.highlightUnderEvent )
1704 );
1705 this.dropdown.on(
1706 'touchstart touchmove touchend',
1707 resultsSelector,
1708 this.bind( function ( event ) {
1709 this._touchEvent = true;
1710 this.highlightUnderEvent( event );
1711 } )
1712 );
1713 this.dropdown.on(
1714 'touchmove',
1715 resultsSelector,
1716 this.bind( this.touchMoved )
1717 );
1718 this.dropdown.on(
1719 'touchstart touchend',
1720 resultsSelector,
1721 this.bind( this.clearTouchMoved )
1722 );
1723
1724 // Waiting for a click event on touch devices to select option and hide dropdown
1725 // otherwise click will be triggered on an underlying element
1726 this.dropdown.on(
1727 'click',
1728 this.bind( function ( event ) {
1729 if ( this._touchEvent ) {
1730 this._touchEvent = false;
1731 this.selectHighlighted();
1732 }
1733 } )
1734 );
1735
1736 installDebouncedScroll( 80, this.results );
1737 this.dropdown.on(
1738 'scroll-debounced',
1739 resultsSelector,
1740 this.bind( this.loadMoreIfNeeded )
1741 );
1742
1743 // do not propagate change event from the search field out of the component
1744 $( this.container ).on( 'change', '.select2-input', function ( e ) {
1745 e.stopPropagation();
1746 } );
1747 $( this.dropdown ).on( 'change', '.select2-input', function ( e ) {
1748 e.stopPropagation();
1749 } );
1750
1751 // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
1752 if ( $.fn.mousewheel ) {
1753 results.mousewheel( function ( e, delta, deltaX, deltaY ) {
1754 var top = results.scrollTop();
1755 if ( deltaY > 0 && top - deltaY <= 0 ) {
1756 results.scrollTop( 0 );
1757 killEvent( e );
1758 } else if (
1759 deltaY < 0 &&
1760 results.get( 0 ).scrollHeight -
1761 results.scrollTop() +
1762 deltaY <=
1763 results.height()
1764 ) {
1765 results.scrollTop(
1766 results.get( 0 ).scrollHeight - results.height()
1767 );
1768 killEvent( e );
1769 }
1770 } );
1771 }
1772
1773 installKeyUpChangeEvent( search );
1774 search.on(
1775 'keyup-change input paste',
1776 this.bind( this.updateResults )
1777 );
1778 search.on( 'focus', function () {
1779 search.addClass( 'select2-focused' );
1780 } );
1781 search.on( 'blur', function () {
1782 search.removeClass( 'select2-focused' );
1783 } );
1784
1785 this.dropdown.on(
1786 'mouseup',
1787 resultsSelector,
1788 this.bind( function ( e ) {
1789 if (
1790 $( e.target ).closest( '.select2-result-selectable' )
1791 .length > 0
1792 ) {
1793 this.highlightUnderEvent( e );
1794 this.selectHighlighted( e );
1795 }
1796 } )
1797 );
1798
1799 // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
1800 // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
1801 // dom it will trigger the popup close, which is not what we want
1802 // focusin can cause focus wars between modals and select2 since the dropdown is outside the modal.
1803 this.dropdown.on(
1804 'click mouseup mousedown touchstart touchend focusin',
1805 function ( e ) {
1806 e.stopPropagation();
1807 }
1808 );
1809
1810 this.nextSearchTerm = undefined;
1811
1812 if ( $.isFunction( this.opts.initSelection ) ) {
1813 // initialize selection based on the current value of the source element
1814 this.initSelection();
1815
1816 // if the user has provided a function that can set selection based on the value of the source element
1817 // we monitor the change event on the element and trigger it, allowing for two way synchronization
1818 this.monitorSource();
1819 }
1820
1821 if ( opts.maximumInputLength !== null ) {
1822 this.search.attr( 'maxlength', opts.maximumInputLength );
1823 }
1824
1825 var disabled = opts.element.prop( 'disabled' );
1826 if ( disabled === undefined ) disabled = false;
1827 this.enable( ! disabled );
1828
1829 var readonly = opts.element.prop( 'readonly' );
1830 if ( readonly === undefined ) readonly = false;
1831 this.readonly( readonly );
1832
1833 // Calculate size of scrollbar
1834 scrollBarDimensions = scrollBarDimensions || measureScrollbar();
1835
1836 this.autofocus = opts.element.prop( 'autofocus' );
1837 opts.element.prop( 'autofocus', false );
1838 if ( this.autofocus ) this.focus();
1839
1840 this.search.attr( 'placeholder', opts.searchInputPlaceholder );
1841 },
1842
1843 // abstract
1844 destroy: function () {
1845 var element = this.opts.element,
1846 select2 = element.data( 'select2' ),
1847 self = this;
1848
1849 this.close();
1850
1851 if ( element.length && element[ 0 ].detachEvent && self._sync ) {
1852 element.each( function () {
1853 if ( self._sync ) {
1854 this.detachEvent( 'onpropertychange', self._sync );
1855 }
1856 } );
1857 }
1858 if ( this.propertyObserver ) {
1859 this.propertyObserver.disconnect();
1860 this.propertyObserver = null;
1861 }
1862 this._sync = null;
1863
1864 if ( select2 !== undefined ) {
1865 select2.container.remove();
1866 select2.liveRegion.remove();
1867 select2.dropdown.remove();
1868 element
1869 .show()
1870 .removeData( 'select2' )
1871 .off( '.select2' )
1872 .prop( 'autofocus', this.autofocus || false );
1873 if ( this.elementTabIndex ) {
1874 element.attr( { tabindex: this.elementTabIndex } );
1875 } else {
1876 element.removeAttr( 'tabindex' );
1877 }
1878 element.show();
1879 }
1880
1881 cleanupJQueryElements.call(
1882 this,
1883 'container',
1884 'liveRegion',
1885 'dropdown',
1886 'results',
1887 'search'
1888 );
1889 },
1890
1891 // abstract
1892 optionToData: function ( element ) {
1893 if ( element.is( 'option' ) ) {
1894 return {
1895 id: element.prop( 'value' ),
1896 text: element.text(),
1897 element: element.get(),
1898 css: element.attr( 'class' ),
1899 disabled: element.prop( 'disabled' ),
1900 locked:
1901 equal( element.attr( 'locked' ), 'locked' ) ||
1902 equal( element.data( 'locked' ), true ),
1903 };
1904 } else if ( element.is( 'optgroup' ) ) {
1905 return {
1906 text: element.attr( 'label' ),
1907 children: [],
1908 element: element.get(),
1909 css: element.attr( 'class' ),
1910 };
1911 }
1912 },
1913
1914 // abstract
1915 prepareOpts: function ( opts ) {
1916 var element,
1917 select,
1918 idKey,
1919 ajaxUrl,
1920 self = this;
1921
1922 element = opts.element;
1923
1924 if ( element.get( 0 ).tagName.toLowerCase() === 'select' ) {
1925 this.select = select = opts.element;
1926 }
1927
1928 if ( select ) {
1929 // these options are not allowed when attached to a select because they are picked up off the element itself
1930 $.each(
1931 [
1932 'id',
1933 'multiple',
1934 'ajax',
1935 'query',
1936 'createSearchChoice',
1937 'initSelection',
1938 'data',
1939 'tags',
1940 ],
1941 function () {
1942 if ( this in opts ) {
1943 throw new Error(
1944 "Option '" +
1945 this +
1946 "' is not allowed for Select2 when attached to a <select> element."
1947 );
1948 }
1949 }
1950 );
1951 }
1952
1953 opts = $.extend(
1954 {},
1955 {
1956 populateResults: function ( container, results, query ) {
1957 var populate,
1958 id = this.opts.id,
1959 liveRegion = this.liveRegion;
1960
1961 populate = function ( results, container, depth ) {
1962 var i,
1963 l,
1964 result,
1965 selectable,
1966 disabled,
1967 compound,
1968 node,
1969 label,
1970 innerContainer,
1971 formatted;
1972
1973 results = opts.sortResults(
1974 results,
1975 container,
1976 query
1977 );
1978
1979 // collect the created nodes for bulk append
1980 var nodes = [];
1981 for (
1982 i = 0, l = results.length;
1983 i < l;
1984 i = i + 1
1985 ) {
1986 result = results[ i ];
1987
1988 disabled = result.disabled === true;
1989 selectable =
1990 ! disabled && id( result ) !== undefined;
1991
1992 compound =
1993 result.children &&
1994 result.children.length > 0;
1995
1996 node = $( '<li></li>' );
1997 node.addClass(
1998 'select2-results-dept-' + depth
1999 );
2000 node.addClass( 'select2-result' );
2001 node.addClass(
2002 selectable
2003 ? 'select2-result-selectable'
2004 : 'select2-result-unselectable'
2005 );
2006 if ( disabled ) {
2007 node.addClass( 'select2-disabled' );
2008 }
2009 if ( compound ) {
2010 node.addClass(
2011 'select2-result-with-children'
2012 );
2013 }
2014 node.addClass(
2015 self.opts.formatResultCssClass( result )
2016 );
2017 node.attr( 'role', 'presentation' );
2018
2019 label = $( document.createElement( 'div' ) );
2020 label.addClass( 'select2-result-label' );
2021 label.attr(
2022 'id',
2023 'select2-result-label-' + nextUid()
2024 );
2025 label.attr( 'role', 'option' );
2026
2027 formatted = opts.formatResult(
2028 result,
2029 label,
2030 query,
2031 self.opts.escapeMarkup
2032 );
2033 if ( formatted !== undefined ) {
2034 label.html( formatted );
2035 node.append( label );
2036 }
2037
2038 if ( compound ) {
2039 innerContainer = $( '<ul></ul>' );
2040 innerContainer.addClass(
2041 'select2-result-sub'
2042 );
2043 populate(
2044 result.children,
2045 innerContainer,
2046 depth + 1
2047 );
2048 node.append( innerContainer );
2049 }
2050
2051 node.data( 'select2-data', result );
2052 nodes.push( node[ 0 ] );
2053 }
2054
2055 // bulk append the created nodes
2056 container.append( nodes );
2057 liveRegion.text(
2058 opts.formatMatches( results.length )
2059 );
2060 };
2061
2062 populate( results, container, 0 );
2063 },
2064 },
2065 $.fn.select2.defaults,
2066 opts
2067 );
2068
2069 if ( typeof opts.id !== 'function' ) {
2070 idKey = opts.id;
2071 opts.id = function ( e ) {
2072 return e[ idKey ];
2073 };
2074 }
2075
2076 if ( $.isArray( opts.element.data( 'select2Tags' ) ) ) {
2077 if ( 'tags' in opts ) {
2078 throw (
2079 "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " +
2080 opts.element.attr( 'id' )
2081 );
2082 }
2083 opts.tags = opts.element.data( 'select2Tags' );
2084 }
2085
2086 if ( select ) {
2087 opts.query = this.bind( function ( query ) {
2088 var data = { results: [], more: false },
2089 term = query.term,
2090 children,
2091 placeholderOption,
2092 process;
2093
2094 process = function ( element, collection ) {
2095 var group;
2096 if ( element.is( 'option' ) ) {
2097 if (
2098 query.matcher( term, element.text(), element )
2099 ) {
2100 collection.push( self.optionToData( element ) );
2101 }
2102 } else if ( element.is( 'optgroup' ) ) {
2103 group = self.optionToData( element );
2104 element.children().each2( function ( i, elm ) {
2105 process( elm, group.children );
2106 } );
2107 if ( group.children.length > 0 ) {
2108 collection.push( group );
2109 }
2110 }
2111 };
2112
2113 children = element.children();
2114
2115 // ignore the placeholder option if there is one
2116 if (
2117 this.getPlaceholder() !== undefined &&
2118 children.length > 0
2119 ) {
2120 placeholderOption = this.getPlaceholderOption();
2121 if ( placeholderOption ) {
2122 children = children.not( placeholderOption );
2123 }
2124 }
2125
2126 children.each2( function ( i, elm ) {
2127 process( elm, data.results );
2128 } );
2129
2130 query.callback( data );
2131 } );
2132 // this is needed because inside val() we construct choices from options and their id is hardcoded
2133 opts.id = function ( e ) {
2134 return e.id;
2135 };
2136 } else {
2137 if ( ! ( 'query' in opts ) ) {
2138 if ( 'ajax' in opts ) {
2139 ajaxUrl = opts.element.data( 'ajax-url' );
2140 if ( ajaxUrl && ajaxUrl.length > 0 ) {
2141 opts.ajax.url = ajaxUrl;
2142 }
2143 opts.query = ajax.call( opts.element, opts.ajax );
2144 } else if ( 'data' in opts ) {
2145 opts.query = local( opts.data );
2146 } else if ( 'tags' in opts ) {
2147 opts.query = tags( opts.tags );
2148 if ( opts.createSearchChoice === undefined ) {
2149 opts.createSearchChoice = function ( term ) {
2150 return {
2151 id: $.trim( term ),
2152 text: $.trim( term ),
2153 };
2154 };
2155 }
2156 if ( opts.initSelection === undefined ) {
2157 opts.initSelection = function (
2158 element,
2159 callback
2160 ) {
2161 var data = [];
2162 $(
2163 splitVal(
2164 element.val(),
2165 opts.separator,
2166 opts.transformVal
2167 )
2168 ).each( function () {
2169 var obj = { id: this, text: this },
2170 tags = opts.tags;
2171 if ( $.isFunction( tags ) ) tags = tags();
2172 $( tags ).each( function () {
2173 if ( equal( this.id, obj.id ) ) {
2174 obj = this;
2175 return false;
2176 }
2177 } );
2178 data.push( obj );
2179 } );
2180
2181 callback( data );
2182 };
2183 }
2184 }
2185 }
2186 }
2187 if ( typeof opts.query !== 'function' ) {
2188 throw (
2189 'query function not defined for Select2 ' +
2190 opts.element.attr( 'id' )
2191 );
2192 }
2193
2194 if ( opts.createSearchChoicePosition === 'top' ) {
2195 opts.createSearchChoicePosition = function ( list, item ) {
2196 list.unshift( item );
2197 };
2198 } else if ( opts.createSearchChoicePosition === 'bottom' ) {
2199 opts.createSearchChoicePosition = function ( list, item ) {
2200 list.push( item );
2201 };
2202 } else if (
2203 typeof opts.createSearchChoicePosition !== 'function'
2204 ) {
2205 throw "invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";
2206 }
2207
2208 return opts;
2209 },
2210
2211 /**
2212 * Monitor the original element for changes and update select2 accordingly
2213 */
2214 // abstract
2215 monitorSource: function () {
2216 var el = this.opts.element,
2217 observer,
2218 self = this;
2219
2220 el.on(
2221 'change.select2',
2222 this.bind( function ( e ) {
2223 if (
2224 this.opts.element.data( 'select2-change-triggered' ) !==
2225 true
2226 ) {
2227 this.initSelection();
2228 }
2229 } )
2230 );
2231
2232 this._sync = this.bind( function () {
2233 // sync enabled state
2234 var disabled = el.prop( 'disabled' );
2235 if ( disabled === undefined ) disabled = false;
2236 this.enable( ! disabled );
2237
2238 var readonly = el.prop( 'readonly' );
2239 if ( readonly === undefined ) readonly = false;
2240 this.readonly( readonly );
2241
2242 if ( this.container ) {
2243 syncCssClasses(
2244 this.container,
2245 this.opts.element,
2246 this.opts.adaptContainerCssClass
2247 );
2248 this.container.addClass(
2249 evaluate(
2250 this.opts.containerCssClass,
2251 this.opts.element
2252 )
2253 );
2254 }
2255
2256 if ( this.dropdown ) {
2257 syncCssClasses(
2258 this.dropdown,
2259 this.opts.element,
2260 this.opts.adaptDropdownCssClass
2261 );
2262 this.dropdown.addClass(
2263 evaluate(
2264 this.opts.dropdownCssClass,
2265 this.opts.element
2266 )
2267 );
2268 }
2269 } );
2270
2271 // IE8-10 (IE9/10 won't fire propertyChange via attachEventListener)
2272 if ( el.length && el[ 0 ].attachEvent ) {
2273 el.each( function () {
2274 this.attachEvent( 'onpropertychange', self._sync );
2275 } );
2276 }
2277
2278 // safari, chrome, firefox, IE11
2279 observer =
2280 window.MutationObserver ||
2281 window.WebKitMutationObserver ||
2282 window.MozMutationObserver;
2283 if ( observer !== undefined ) {
2284 if ( this.propertyObserver ) {
2285 delete this.propertyObserver;
2286 this.propertyObserver = null;
2287 }
2288 this.propertyObserver = new observer( function ( mutations ) {
2289 $.each( mutations, self._sync );
2290 } );
2291 this.propertyObserver.observe( el.get( 0 ), {
2292 attributes: true,
2293 subtree: false,
2294 } );
2295 }
2296 },
2297
2298 // abstract
2299 triggerSelect: function ( data ) {
2300 var evt = $.Event( 'select2-selecting', {
2301 val: this.id( data ),
2302 object: data,
2303 choice: data,
2304 } );
2305 this.opts.element.trigger( evt );
2306 return ! evt.isDefaultPrevented();
2307 },
2308
2309 /**
2310 * Triggers the change event on the source element
2311 */
2312 // abstract
2313 triggerChange: function ( details ) {
2314 details = details || {};
2315 details = $.extend( {}, details, {
2316 type: 'change',
2317 val: this.val(),
2318 } );
2319 // prevents recursive triggering
2320 this.opts.element.data( 'select2-change-triggered', true );
2321 this.opts.element.trigger( details );
2322 this.opts.element.data( 'select2-change-triggered', false );
2323
2324 // some validation frameworks ignore the change event and listen instead to keyup, click for selects
2325 // so here we trigger the click event manually
2326 this.opts.element.click();
2327
2328 // ValidationEngine ignores the change event and listens instead to blur
2329 // so here we trigger the blur event manually if so desired
2330 if ( this.opts.blurOnChange ) this.opts.element.blur();
2331 },
2332
2333 //abstract
2334 isInterfaceEnabled: function () {
2335 return this.enabledInterface === true;
2336 },
2337
2338 // abstract
2339 enableInterface: function () {
2340 var enabled = this._enabled && ! this._readonly,
2341 disabled = ! enabled;
2342
2343 if ( enabled === this.enabledInterface ) return false;
2344
2345 this.container.toggleClass(
2346 'select2-container-disabled',
2347 disabled
2348 );
2349 this.close();
2350 this.enabledInterface = enabled;
2351
2352 return true;
2353 },
2354
2355 // abstract
2356 enable: function ( enabled ) {
2357 if ( enabled === undefined ) enabled = true;
2358 if ( this._enabled === enabled ) return;
2359 this._enabled = enabled;
2360
2361 this.opts.element.prop( 'disabled', ! enabled );
2362 this.enableInterface();
2363 },
2364
2365 // abstract
2366 disable: function () {
2367 this.enable( false );
2368 },
2369
2370 // abstract
2371 readonly: function ( enabled ) {
2372 if ( enabled === undefined ) enabled = false;
2373 if ( this._readonly === enabled ) return;
2374 this._readonly = enabled;
2375
2376 this.opts.element.prop( 'readonly', enabled );
2377 this.enableInterface();
2378 },
2379
2380 // abstract
2381 opened: function () {
2382 return this.container
2383 ? this.container.hasClass( 'select2-dropdown-open' )
2384 : false;
2385 },
2386
2387 // abstract
2388 positionDropdown: function () {
2389 var $dropdown = this.dropdown,
2390 container = this.container,
2391 offset = container.offset(),
2392 height = container.outerHeight( false ),
2393 width = container.outerWidth( false ),
2394 dropHeight = $dropdown.outerHeight( false ),
2395 $window = $( window ),
2396 windowWidth = $window.width(),
2397 windowHeight = $window.height(),
2398 viewPortRight = $window.scrollLeft() + windowWidth,
2399 viewportBottom = $window.scrollTop() + windowHeight,
2400 dropTop = offset.top + height,
2401 dropLeft = offset.left,
2402 enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
2403 enoughRoomAbove =
2404 offset.top - dropHeight >= $window.scrollTop(),
2405 dropWidth = $dropdown.outerWidth( false ),
2406 enoughRoomOnRight = function () {
2407 return dropLeft + dropWidth <= viewPortRight;
2408 },
2409 enoughRoomOnLeft = function () {
2410 return (
2411 offset.left +
2412 viewPortRight +
2413 container.outerWidth( false ) >
2414 dropWidth
2415 );
2416 },
2417 aboveNow = $dropdown.hasClass( 'select2-drop-above' ),
2418 bodyOffset,
2419 above,
2420 changeDirection,
2421 css,
2422 resultsListNode;
2423
2424 // always prefer the current above/below alignment, unless there is not enough room
2425 if ( aboveNow ) {
2426 above = true;
2427 if ( ! enoughRoomAbove && enoughRoomBelow ) {
2428 changeDirection = true;
2429 above = false;
2430 }
2431 } else {
2432 above = false;
2433 if ( ! enoughRoomBelow && enoughRoomAbove ) {
2434 changeDirection = true;
2435 above = true;
2436 }
2437 }
2438
2439 //if we are changing direction we need to get positions when dropdown is hidden;
2440 if ( changeDirection ) {
2441 $dropdown.hide();
2442 offset = this.container.offset();
2443 height = this.container.outerHeight( false );
2444 width = this.container.outerWidth( false );
2445 dropHeight = $dropdown.outerHeight( false );
2446 viewPortRight = $window.scrollLeft() + windowWidth;
2447 viewportBottom = $window.scrollTop() + windowHeight;
2448 dropTop = offset.top + height;
2449 dropLeft = offset.left;
2450 dropWidth = $dropdown.outerWidth( false );
2451 $dropdown.show();
2452
2453 // fix so the cursor does not move to the left within the search-textbox in IE
2454 this.focusSearch();
2455 }
2456
2457 if ( this.opts.dropdownAutoWidth ) {
2458 resultsListNode = $( '.select2-results', $dropdown )[ 0 ];
2459 $dropdown.addClass( 'select2-drop-auto-width' );
2460 $dropdown.css( 'width', '' );
2461 // Add scrollbar width to dropdown if vertical scrollbar is present
2462 dropWidth =
2463 $dropdown.outerWidth( false ) +
2464 ( resultsListNode.scrollHeight ===
2465 resultsListNode.clientHeight
2466 ? 0
2467 : scrollBarDimensions.width );
2468 dropWidth > width
2469 ? ( width = dropWidth )
2470 : ( dropWidth = width );
2471 dropHeight = $dropdown.outerHeight( false );
2472 } else {
2473 this.container.removeClass( 'select2-drop-auto-width' );
2474 }
2475
2476 //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
2477 //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body.scrollTop(), "enough?", enoughRoomAbove);
2478
2479 // fix positioning when body has an offset and is not position: static
2480 if ( this.body.css( 'position' ) !== 'static' ) {
2481 bodyOffset = this.body.offset();
2482 dropTop -= bodyOffset.top;
2483 dropLeft -= bodyOffset.left;
2484 }
2485
2486 if ( ! enoughRoomOnRight() && enoughRoomOnLeft() ) {
2487 dropLeft =
2488 offset.left +
2489 this.container.outerWidth( false ) -
2490 dropWidth;
2491 }
2492
2493 css = {
2494 left: dropLeft,
2495 width: width,
2496 };
2497
2498 if ( above ) {
2499 css.top = offset.top - dropHeight;
2500 css.bottom = 'auto';
2501 this.container.addClass( 'select2-drop-above' );
2502 $dropdown.addClass( 'select2-drop-above' );
2503 } else {
2504 css.top = dropTop;
2505 css.bottom = 'auto';
2506 this.container.removeClass( 'select2-drop-above' );
2507 $dropdown.removeClass( 'select2-drop-above' );
2508 }
2509 css = $.extend(
2510 css,
2511 evaluate( this.opts.dropdownCss, this.opts.element )
2512 );
2513
2514 $dropdown.css( css );
2515 },
2516
2517 // abstract
2518 shouldOpen: function () {
2519 var event;
2520
2521 if ( this.opened() ) return false;
2522
2523 if ( this._enabled === false || this._readonly === true )
2524 return false;
2525
2526 event = $.Event( 'select2-opening' );
2527 this.opts.element.trigger( event );
2528 return ! event.isDefaultPrevented();
2529 },
2530
2531 // abstract
2532 clearDropdownAlignmentPreference: function () {
2533 // clear the classes used to figure out the preference of where the dropdown should be opened
2534 this.container.removeClass( 'select2-drop-above' );
2535 this.dropdown.removeClass( 'select2-drop-above' );
2536 },
2537
2538 /**
2539 * Opens the dropdown
2540 *
2541 * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
2542 * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
2543 */
2544 // abstract
2545 open: function () {
2546 if ( ! this.shouldOpen() ) return false;
2547
2548 this.opening();
2549
2550 // Only bind the document mousemove when the dropdown is visible
2551 $document.on( 'mousemove.select2Event', function ( e ) {
2552 lastMousePosition.x = e.pageX;
2553 lastMousePosition.y = e.pageY;
2554 } );
2555
2556 return true;
2557 },
2558
2559 /**
2560 * Performs the opening of the dropdown
2561 */
2562 // abstract
2563 opening: function () {
2564 var cid = this.containerEventName,
2565 scroll = 'scroll.' + cid,
2566 resize = 'resize.' + cid,
2567 orient = 'orientationchange.' + cid,
2568 mask;
2569
2570 this.container
2571 .addClass( 'select2-dropdown-open' )
2572 .addClass( 'select2-container-active' );
2573
2574 this.clearDropdownAlignmentPreference();
2575
2576 if ( this.dropdown[ 0 ] !== this.body.children().last()[ 0 ] ) {
2577 this.dropdown.detach().appendTo( this.body );
2578 }
2579
2580 // create the dropdown mask if doesn't already exist
2581 mask = $( '#select2-drop-mask' );
2582 if ( mask.length === 0 ) {
2583 mask = $( document.createElement( 'div' ) );
2584 mask.attr( 'id', 'select2-drop-mask' ).attr(
2585 'class',
2586 'select2-drop-mask'
2587 );
2588 mask.hide();
2589 mask.appendTo( this.body );
2590 mask.on( 'mousedown touchstart click', function ( e ) {
2591 // Prevent IE from generating a click event on the body
2592 reinsertElement( mask );
2593
2594 var dropdown = $( '#select2-drop' ),
2595 self;
2596 if ( dropdown.length > 0 ) {
2597 self = dropdown.data( 'select2' );
2598 if ( self.opts.selectOnBlur ) {
2599 self.selectHighlighted( { noFocus: true } );
2600 }
2601 self.close();
2602 e.preventDefault();
2603 e.stopPropagation();
2604 }
2605 } );
2606 }
2607
2608 // ensure the mask is always right before the dropdown
2609 if ( this.dropdown.prev()[ 0 ] !== mask[ 0 ] ) {
2610 this.dropdown.before( mask );
2611 }
2612
2613 // move the global id to the correct dropdown
2614 $( '#select2-drop' ).removeAttr( 'id' );
2615 this.dropdown.attr( 'id', 'select2-drop' );
2616
2617 // show the elements
2618 mask.show();
2619
2620 this.positionDropdown();
2621 this.dropdown.show();
2622 this.positionDropdown();
2623
2624 this.dropdown.addClass( 'select2-drop-active' );
2625
2626 // attach listeners to events that can change the position of the container and thus require
2627 // the position of the dropdown to be updated as well so it does not come unglued from the container
2628 var that = this;
2629 this.container
2630 .parents()
2631 .add( window )
2632 .each( function () {
2633 $( this ).on(
2634 resize + ' ' + scroll + ' ' + orient,
2635 function ( e ) {
2636 if ( that.opened() ) that.positionDropdown();
2637 }
2638 );
2639 } );
2640 },
2641
2642 // abstract
2643 close: function () {
2644 if ( ! this.opened() ) return;
2645
2646 var cid = this.containerEventName,
2647 scroll = 'scroll.' + cid,
2648 resize = 'resize.' + cid,
2649 orient = 'orientationchange.' + cid;
2650
2651 // unbind event listeners
2652 this.container
2653 .parents()
2654 .add( window )
2655 .each( function () {
2656 $( this ).off( scroll ).off( resize ).off( orient );
2657 } );
2658
2659 this.clearDropdownAlignmentPreference();
2660
2661 $( '#select2-drop-mask' ).hide();
2662 this.dropdown.removeAttr( 'id' ); // only the active dropdown has the select2-drop id
2663 this.dropdown.hide();
2664 this.container
2665 .removeClass( 'select2-dropdown-open' )
2666 .removeClass( 'select2-container-active' );
2667 this.results.empty();
2668
2669 // Now that the dropdown is closed, unbind the global document mousemove event
2670 $document.off( 'mousemove.select2Event' );
2671
2672 this.clearSearch();
2673 this.search.removeClass( 'select2-active' );
2674 this.opts.element.trigger( $.Event( 'select2-close' ) );
2675 },
2676
2677 /**
2678 * Opens control, sets input value, and updates results.
2679 */
2680 // abstract
2681 externalSearch: function ( term ) {
2682 this.open();
2683 this.search.val( term );
2684 this.updateResults( false );
2685 },
2686
2687 // abstract
2688 clearSearch: function () {},
2689
2690 //abstract
2691 getMaximumSelectionSize: function () {
2692 return evaluate(
2693 this.opts.maximumSelectionSize,
2694 this.opts.element
2695 );
2696 },
2697
2698 // abstract
2699 ensureHighlightVisible: function () {
2700 var results = this.results,
2701 children,
2702 index,
2703 child,
2704 hb,
2705 rb,
2706 y,
2707 more,
2708 topOffset;
2709
2710 index = this.highlight();
2711
2712 if ( index < 0 ) return;
2713
2714 if ( index == 0 ) {
2715 // if the first element is highlighted scroll all the way to the top,
2716 // that way any unselectable headers above it will also be scrolled
2717 // into view
2718
2719 results.scrollTop( 0 );
2720 return;
2721 }
2722
2723 children = this.findHighlightableChoices().find(
2724 '.select2-result-label'
2725 );
2726
2727 child = $( children[ index ] );
2728
2729 topOffset = ( child.offset() || {} ).top || 0;
2730
2731 hb = topOffset + child.outerHeight( true );
2732
2733 // if this is the last child lets also make sure select2-more-results is visible
2734 if ( index === children.length - 1 ) {
2735 more = results.find( 'li.select2-more-results' );
2736 if ( more.length > 0 ) {
2737 hb = more.offset().top + more.outerHeight( true );
2738 }
2739 }
2740
2741 rb = results.offset().top + results.outerHeight( false );
2742 if ( hb > rb ) {
2743 results.scrollTop( results.scrollTop() + ( hb - rb ) );
2744 }
2745 y = topOffset - results.offset().top;
2746
2747 // make sure the top of the element is visible
2748 if ( y < 0 && child.css( 'display' ) != 'none' ) {
2749 results.scrollTop( results.scrollTop() + y ); // y is negative
2750 }
2751 },
2752
2753 // abstract
2754 findHighlightableChoices: function () {
2755 return this.results.find(
2756 '.select2-result-selectable:not(.select2-disabled):not(.select2-selected)'
2757 );
2758 },
2759
2760 // abstract
2761 moveHighlight: function ( delta ) {
2762 var choices = this.findHighlightableChoices(),
2763 index = this.highlight();
2764
2765 while ( index > -1 && index < choices.length ) {
2766 index += delta;
2767 var choice = $( choices[ index ] );
2768 if (
2769 choice.hasClass( 'select2-result-selectable' ) &&
2770 ! choice.hasClass( 'select2-disabled' ) &&
2771 ! choice.hasClass( 'select2-selected' )
2772 ) {
2773 this.highlight( index );
2774 break;
2775 }
2776 }
2777 },
2778
2779 // abstract
2780 highlight: function ( index ) {
2781 var choices = this.findHighlightableChoices(),
2782 choice,
2783 data;
2784
2785 if ( arguments.length === 0 ) {
2786 return indexOf(
2787 choices.filter( '.select2-highlighted' )[ 0 ],
2788 choices.get()
2789 );
2790 }
2791
2792 if ( index >= choices.length ) index = choices.length - 1;
2793 if ( index < 0 ) index = 0;
2794
2795 this.removeHighlight();
2796
2797 choice = $( choices[ index ] );
2798 choice.addClass( 'select2-highlighted' );
2799
2800 // ensure assistive technology can determine the active choice
2801 this.search.attr(
2802 'aria-activedescendant',
2803 choice.find( '.select2-result-label' ).attr( 'id' )
2804 );
2805
2806 this.ensureHighlightVisible();
2807
2808 this.liveRegion.text( choice.text() );
2809
2810 data = choice.data( 'select2-data' );
2811 if ( data ) {
2812 this.opts.element.trigger( {
2813 type: 'select2-highlight',
2814 val: this.id( data ),
2815 choice: data,
2816 } );
2817 }
2818 },
2819
2820 removeHighlight: function () {
2821 this.results
2822 .find( '.select2-highlighted' )
2823 .removeClass( 'select2-highlighted' );
2824 },
2825
2826 touchMoved: function () {
2827 this._touchMoved = true;
2828 },
2829
2830 clearTouchMoved: function () {
2831 this._touchMoved = false;
2832 },
2833
2834 // abstract
2835 countSelectableResults: function () {
2836 return this.findHighlightableChoices().length;
2837 },
2838
2839 // abstract
2840 highlightUnderEvent: function ( event ) {
2841 var el = $( event.target ).closest( '.select2-result-selectable' );
2842 if ( el.length > 0 && ! el.is( '.select2-highlighted' ) ) {
2843 var choices = this.findHighlightableChoices();
2844 this.highlight( choices.index( el ) );
2845 } else if ( el.length == 0 ) {
2846 // if we are over an unselectable item remove all highlights
2847 this.removeHighlight();
2848 }
2849 },
2850
2851 // abstract
2852 loadMoreIfNeeded: function () {
2853 var results = this.results,
2854 more = results.find( 'li.select2-more-results' ),
2855 below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
2856 page = this.resultsPage + 1,
2857 self = this,
2858 term = this.search.val(),
2859 context = this.context;
2860
2861 if ( more.length === 0 ) return;
2862 below = more.offset().top - results.offset().top - results.height();
2863
2864 if ( below <= this.opts.loadMorePadding ) {
2865 more.addClass( 'select2-active' );
2866 this.opts.query( {
2867 element: this.opts.element,
2868 term: term,
2869 page: page,
2870 context: context,
2871 matcher: this.opts.matcher,
2872 callback: this.bind( function ( data ) {
2873 // ignore a response if the select2 has been closed before it was received
2874 if ( ! self.opened() ) return;
2875
2876 self.opts.populateResults.call(
2877 this,
2878 results,
2879 data.results,
2880 { term: term, page: page, context: context }
2881 );
2882 self.postprocessResults( data, false, false );
2883
2884 if ( data.more === true ) {
2885 more.detach()
2886 .appendTo( results )
2887 .html(
2888 self.opts.escapeMarkup(
2889 evaluate(
2890 self.opts.formatLoadMore,
2891 self.opts.element,
2892 page + 1
2893 )
2894 )
2895 );
2896 window.setTimeout( function () {
2897 self.loadMoreIfNeeded();
2898 }, 10 );
2899 } else {
2900 more.remove();
2901 }
2902 self.positionDropdown();
2903 self.resultsPage = page;
2904 self.context = data.context;
2905 this.opts.element.trigger( {
2906 type: 'select2-loaded',
2907 items: data,
2908 } );
2909 } ),
2910 } );
2911 }
2912 },
2913
2914 /**
2915 * Default tokenizer function which does nothing
2916 */
2917 tokenize: function () {},
2918
2919 /**
2920 * @param initial whether or not this is the call to this method right after the dropdown has been opened
2921 */
2922 // abstract
2923 updateResults: function ( initial ) {
2924 var search = this.search,
2925 results = this.results,
2926 opts = this.opts,
2927 data,
2928 self = this,
2929 input,
2930 term = search.val(),
2931 lastTerm = $.data( this.container, 'select2-last-term' ),
2932 // sequence number used to drop out-of-order responses
2933 queryNumber;
2934
2935 // prevent duplicate queries against the same term
2936 if ( initial !== true && lastTerm && equal( term, lastTerm ) )
2937 return;
2938
2939 $.data( this.container, 'select2-last-term', term );
2940
2941 // if the search is currently hidden we do not alter the results
2942 if (
2943 initial !== true &&
2944 ( this.showSearchInput === false || ! this.opened() )
2945 ) {
2946 return;
2947 }
2948
2949 function postRender() {
2950 search.removeClass( 'select2-active' );
2951 self.positionDropdown();
2952 if (
2953 results.find(
2954 '.select2-no-results,.select2-selection-limit,.select2-searching'
2955 ).length
2956 ) {
2957 self.liveRegion.text( results.text() );
2958 } else {
2959 self.liveRegion.text(
2960 self.opts.formatMatches(
2961 results.find(
2962 '.select2-result-selectable:not(".select2-selected")'
2963 ).length
2964 )
2965 );
2966 }
2967 }
2968
2969 function render( html ) {
2970 results.html( html );
2971 postRender();
2972 }
2973
2974 queryNumber = ++this.queryCount;
2975
2976 var maxSelSize = this.getMaximumSelectionSize();
2977 if ( maxSelSize >= 1 ) {
2978 data = this.data();
2979 if (
2980 $.isArray( data ) &&
2981 data.length >= maxSelSize &&
2982 checkFormatter(
2983 opts.formatSelectionTooBig,
2984 'formatSelectionTooBig'
2985 )
2986 ) {
2987 render(
2988 "<li class='select2-selection-limit'>" +
2989 evaluate(
2990 opts.formatSelectionTooBig,
2991 opts.element,
2992 maxSelSize
2993 ) +
2994 '</li>'
2995 );
2996 return;
2997 }
2998 }
2999
3000 if ( search.val().length < opts.minimumInputLength ) {
3001 if (
3002 checkFormatter(
3003 opts.formatInputTooShort,
3004 'formatInputTooShort'
3005 )
3006 ) {
3007 render(
3008 "<li class='select2-no-results'>" +
3009 evaluate(
3010 opts.formatInputTooShort,
3011 opts.element,
3012 search.val(),
3013 opts.minimumInputLength
3014 ) +
3015 '</li>'
3016 );
3017 } else {
3018 render( '' );
3019 }
3020 if ( initial && this.showSearch ) this.showSearch( true );
3021 return;
3022 }
3023
3024 if (
3025 opts.maximumInputLength &&
3026 search.val().length > opts.maximumInputLength
3027 ) {
3028 if (
3029 checkFormatter(
3030 opts.formatInputTooLong,
3031 'formatInputTooLong'
3032 )
3033 ) {
3034 render(
3035 "<li class='select2-no-results'>" +
3036 evaluate(
3037 opts.formatInputTooLong,
3038 opts.element,
3039 search.val(),
3040 opts.maximumInputLength
3041 ) +
3042 '</li>'
3043 );
3044 } else {
3045 render( '' );
3046 }
3047 return;
3048 }
3049
3050 if (
3051 opts.formatSearching &&
3052 this.findHighlightableChoices().length === 0
3053 ) {
3054 render(
3055 "<li class='select2-searching'>" +
3056 evaluate( opts.formatSearching, opts.element ) +
3057 '</li>'
3058 );
3059 }
3060
3061 search.addClass( 'select2-active' );
3062
3063 this.removeHighlight();
3064
3065 // give the tokenizer a chance to pre-process the input
3066 input = this.tokenize();
3067 if ( input != undefined && input != null ) {
3068 search.val( input );
3069 }
3070
3071 this.resultsPage = 1;
3072
3073 opts.query( {
3074 element: opts.element,
3075 term: search.val(),
3076 page: this.resultsPage,
3077 context: null,
3078 matcher: opts.matcher,
3079 callback: this.bind( function ( data ) {
3080 var def; // default choice
3081
3082 // ignore old responses
3083 if ( queryNumber != this.queryCount ) {
3084 return;
3085 }
3086
3087 // ignore a response if the select2 has been closed before it was received
3088 if ( ! this.opened() ) {
3089 this.search.removeClass( 'select2-active' );
3090 return;
3091 }
3092
3093 // handle ajax error
3094 if (
3095 data.hasError !== undefined &&
3096 checkFormatter(
3097 opts.formatAjaxError,
3098 'formatAjaxError'
3099 )
3100 ) {
3101 render(
3102 "<li class='select2-ajax-error'>" +
3103 evaluate(
3104 opts.formatAjaxError,
3105 opts.element,
3106 data.jqXHR,
3107 data.textStatus,
3108 data.errorThrown
3109 ) +
3110 '</li>'
3111 );
3112 return;
3113 }
3114
3115 // save context, if any
3116 this.context =
3117 data.context === undefined ? null : data.context;
3118 // create a default choice and prepend it to the list
3119 if ( this.opts.createSearchChoice && search.val() !== '' ) {
3120 def = this.opts.createSearchChoice.call(
3121 self,
3122 search.val(),
3123 data.results
3124 );
3125 if (
3126 def !== undefined &&
3127 def !== null &&
3128 self.id( def ) !== undefined &&
3129 self.id( def ) !== null
3130 ) {
3131 if (
3132 $( data.results ).filter( function () {
3133 return equal(
3134 self.id( this ),
3135 self.id( def )
3136 );
3137 } ).length === 0
3138 ) {
3139 this.opts.createSearchChoicePosition(
3140 data.results,
3141 def
3142 );
3143 }
3144 }
3145 }
3146
3147 if (
3148 data.results.length === 0 &&
3149 checkFormatter(
3150 opts.formatNoMatches,
3151 'formatNoMatches'
3152 )
3153 ) {
3154 render(
3155 "<li class='select2-no-results'>" +
3156 evaluate(
3157 opts.formatNoMatches,
3158 opts.element,
3159 search.val()
3160 ) +
3161 '</li>'
3162 );
3163 return;
3164 }
3165
3166 results.empty();
3167 self.opts.populateResults.call(
3168 this,
3169 results,
3170 data.results,
3171 {
3172 term: search.val(),
3173 page: this.resultsPage,
3174 context: null,
3175 }
3176 );
3177
3178 if (
3179 data.more === true &&
3180 checkFormatter( opts.formatLoadMore, 'formatLoadMore' )
3181 ) {
3182 results.append(
3183 "<li class='select2-more-results'>" +
3184 opts.escapeMarkup(
3185 evaluate(
3186 opts.formatLoadMore,
3187 opts.element,
3188 this.resultsPage
3189 )
3190 ) +
3191 '</li>'
3192 );
3193 window.setTimeout( function () {
3194 self.loadMoreIfNeeded();
3195 }, 10 );
3196 }
3197
3198 this.postprocessResults( data, initial );
3199
3200 postRender();
3201
3202 this.opts.element.trigger( {
3203 type: 'select2-loaded',
3204 items: data,
3205 } );
3206 } ),
3207 } );
3208 },
3209
3210 // abstract
3211 cancel: function () {
3212 this.close();
3213 },
3214
3215 // abstract
3216 blur: function () {
3217 // if selectOnBlur == true, select the currently highlighted option
3218 if ( this.opts.selectOnBlur )
3219 this.selectHighlighted( { noFocus: true } );
3220
3221 this.close();
3222 this.container.removeClass( 'select2-container-active' );
3223 // synonymous to .is(':focus'), which is available in jquery >= 1.6
3224 if ( this.search[ 0 ] === document.activeElement ) {
3225 this.search.blur();
3226 }
3227 this.clearSearch();
3228 this.selection
3229 .find( '.select2-search-choice-focus' )
3230 .removeClass( 'select2-search-choice-focus' );
3231 },
3232
3233 // abstract
3234 focusSearch: function () {
3235 focus( this.search );
3236 },
3237
3238 // abstract
3239 selectHighlighted: function ( options ) {
3240 if ( this._touchMoved ) {
3241 this.clearTouchMoved();
3242 return;
3243 }
3244 var index = this.highlight(),
3245 highlighted = this.results.find( '.select2-highlighted' ),
3246 data = highlighted
3247 .closest( '.select2-result' )
3248 .data( 'select2-data' );
3249
3250 if ( data ) {
3251 this.highlight( index );
3252 this.onSelect( data, options );
3253 } else if ( options && options.noFocus ) {
3254 this.close();
3255 }
3256 },
3257
3258 // abstract
3259 getPlaceholder: function () {
3260 var placeholderOption;
3261 return (
3262 this.opts.element.attr( 'placeholder' ) ||
3263 this.opts.element.attr( 'data-placeholder' ) || // jquery 1.4 compat
3264 this.opts.element.data( 'placeholder' ) ||
3265 this.opts.placeholder ||
3266 ( ( placeholderOption = this.getPlaceholderOption() ) !==
3267 undefined
3268 ? placeholderOption.text()
3269 : undefined )
3270 );
3271 },
3272
3273 // abstract
3274 getPlaceholderOption: function () {
3275 if ( this.select ) {
3276 var firstOption = this.select.children( 'option' ).first();
3277 if ( this.opts.placeholderOption !== undefined ) {
3278 //Determine the placeholder option based on the specified placeholderOption setting
3279 return (
3280 ( this.opts.placeholderOption === 'first' &&
3281 firstOption ) ||
3282 ( typeof this.opts.placeholderOption === 'function' &&
3283 this.opts.placeholderOption( this.select ) )
3284 );
3285 } else if (
3286 $.trim( firstOption.text() ) === '' &&
3287 firstOption.val() === ''
3288 ) {
3289 //No explicit placeholder option specified, use the first if it's blank
3290 return firstOption;
3291 }
3292 }
3293 },
3294
3295 /**
3296 * Get the desired width for the container element. This is
3297 * derived first from option `width` passed to select2, then
3298 * the inline 'style' on the original element, and finally
3299 * falls back to the jQuery calculated element width.
3300 */
3301 // abstract
3302 initContainerWidth: function () {
3303 function resolveContainerWidth() {
3304 var style, attrs, matches, i, l, attr;
3305
3306 if ( this.opts.width === 'off' ) {
3307 return null;
3308 } else if ( this.opts.width === 'element' ) {
3309 return this.opts.element.outerWidth( false ) === 0
3310 ? 'auto'
3311 : this.opts.element.outerWidth( false ) + 'px';
3312 } else if (
3313 this.opts.width === 'copy' ||
3314 this.opts.width === 'resolve'
3315 ) {
3316 // check if there is inline style on the element that contains width
3317 style = this.opts.element.attr( 'style' );
3318 if ( style !== undefined ) {
3319 attrs = style.split( ';' );
3320 for ( i = 0, l = attrs.length; i < l; i = i + 1 ) {
3321 attr = attrs[ i ].replace( /\s/g, '' );
3322 matches = attr.match(
3323 /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i
3324 );
3325 if ( matches !== null && matches.length >= 1 )
3326 return matches[ 1 ];
3327 }
3328 }
3329
3330 if ( this.opts.width === 'resolve' ) {
3331 // next check if css('width') can resolve a width that is percent based, this is sometimes possible
3332 // when attached to input type=hidden or elements hidden via css
3333 style = this.opts.element.css( 'width' );
3334 if ( style.indexOf( '%' ) > 0 ) return style;
3335
3336 // finally, fallback on the calculated width of the element
3337 return this.opts.element.outerWidth( false ) === 0
3338 ? 'auto'
3339 : this.opts.element.outerWidth( false ) + 'px';
3340 }
3341
3342 return null;
3343 } else if ( $.isFunction( this.opts.width ) ) {
3344 return this.opts.width();
3345 } else {
3346 return this.opts.width;
3347 }
3348 }
3349
3350 var width = resolveContainerWidth.call( this );
3351 if ( width !== null ) {
3352 this.container.css( 'width', width );
3353 }
3354 },
3355 } );
3356
3357 SingleSelect2 = clazz( AbstractSelect2, {
3358 // single
3359
3360 createContainer: function () {
3361 var container = $( document.createElement( 'div' ) )
3362 .attr( {
3363 class: 'select2-container',
3364 } )
3365 .html(
3366 [
3367 "<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>",
3368 " <span class='select2-chosen'>&#160;</span><abbr class='select2-search-choice-close'></abbr>",
3369 " <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>",
3370 '</a>',
3371 "<label for='' class='select2-offscreen'></label>",
3372 "<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />",
3373 "<div class='select2-drop select2-display-none'>",
3374 " <div class='select2-search'>",
3375 " <label for='' class='select2-offscreen'></label>",
3376 " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'",
3377 " aria-autocomplete='list' />",
3378 ' </div>',
3379 " <ul class='select2-results' role='listbox'>",
3380 ' </ul>',
3381 '</div>',
3382 ].join( '' )
3383 );
3384 return container;
3385 },
3386
3387 // single
3388 enableInterface: function () {
3389 if ( this.parent.enableInterface.apply( this, arguments ) ) {
3390 this.focusser.prop( 'disabled', ! this.isInterfaceEnabled() );
3391 }
3392 },
3393
3394 // single
3395 opening: function () {
3396 var el, range, len;
3397
3398 if ( this.opts.minimumResultsForSearch >= 0 ) {
3399 this.showSearch( true );
3400 }
3401
3402 this.parent.opening.apply( this, arguments );
3403
3404 if ( this.showSearchInput !== false ) {
3405 // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
3406 // all other browsers handle this just fine
3407
3408 this.search.val( this.focusser.val() );
3409 }
3410 if ( this.opts.shouldFocusInput( this ) ) {
3411 this.search.focus();
3412 // move the cursor to the end after focussing, otherwise it will be at the beginning and
3413 // new text will appear *before* focusser.val()
3414 el = this.search.get( 0 );
3415 if ( el.createTextRange ) {
3416 range = el.createTextRange();
3417 range.collapse( false );
3418 range.select();
3419 } else if ( el.setSelectionRange ) {
3420 len = this.search.val().length;
3421 el.setSelectionRange( len, len );
3422 }
3423 }
3424
3425 // initializes search's value with nextSearchTerm (if defined by user)
3426 // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
3427 if ( this.search.val() === '' ) {
3428 if ( this.nextSearchTerm != undefined ) {
3429 this.search.val( this.nextSearchTerm );
3430 this.search.select();
3431 }
3432 }
3433
3434 this.focusser.prop( 'disabled', true ).val( '' );
3435 this.updateResults( true );
3436 this.opts.element.trigger( $.Event( 'select2-open' ) );
3437 },
3438
3439 // single
3440 close: function () {
3441 if ( ! this.opened() ) return;
3442 this.parent.close.apply( this, arguments );
3443
3444 this.focusser.prop( 'disabled', false );
3445
3446 if ( this.opts.shouldFocusInput( this ) ) {
3447 this.focusser.focus();
3448 }
3449 },
3450
3451 // single
3452 focus: function () {
3453 if ( this.opened() ) {
3454 this.close();
3455 } else {
3456 this.focusser.prop( 'disabled', false );
3457 if ( this.opts.shouldFocusInput( this ) ) {
3458 this.focusser.focus();
3459 }
3460 }
3461 },
3462
3463 // single
3464 isFocused: function () {
3465 return this.container.hasClass( 'select2-container-active' );
3466 },
3467
3468 // single
3469 cancel: function () {
3470 this.parent.cancel.apply( this, arguments );
3471 this.focusser.prop( 'disabled', false );
3472
3473 if ( this.opts.shouldFocusInput( this ) ) {
3474 this.focusser.focus();
3475 }
3476 },
3477
3478 // single
3479 destroy: function () {
3480 $( "label[for='" + this.focusser.attr( 'id' ) + "']" ).attr(
3481 'for',
3482 this.opts.element.attr( 'id' )
3483 );
3484 this.parent.destroy.apply( this, arguments );
3485
3486 cleanupJQueryElements.call( this, 'selection', 'focusser' );
3487 },
3488
3489 // single
3490 initContainer: function () {
3491 var selection,
3492 container = this.container,
3493 dropdown = this.dropdown,
3494 idSuffix = nextUid(),
3495 elementLabel;
3496
3497 if ( this.opts.minimumResultsForSearch < 0 ) {
3498 this.showSearch( false );
3499 } else {
3500 this.showSearch( true );
3501 }
3502
3503 this.selection = selection = container.find( '.select2-choice' );
3504
3505 this.focusser = container.find( '.select2-focusser' );
3506
3507 // add aria associations
3508 selection
3509 .find( '.select2-chosen' )
3510 .attr( 'id', 'select2-chosen-' + idSuffix );
3511 this.focusser.attr(
3512 'aria-labelledby',
3513 'select2-chosen-' + idSuffix
3514 );
3515 this.results.attr( 'id', 'select2-results-' + idSuffix );
3516 this.search.attr( 'aria-owns', 'select2-results-' + idSuffix );
3517
3518 // rewrite labels from original element to focusser
3519 this.focusser.attr( 'id', 's2id_autogen' + idSuffix );
3520
3521 elementLabel = $(
3522 "label[for='" + this.opts.element.attr( 'id' ) + "']"
3523 );
3524 this.opts.element.focus(
3525 this.bind( function () {
3526 this.focus();
3527 } )
3528 );
3529
3530 this.focusser
3531 .prev()
3532 .text( elementLabel.text() )
3533 .attr( 'for', this.focusser.attr( 'id' ) );
3534
3535 // Ensure the original element retains an accessible name
3536 var originalTitle = this.opts.element.attr( 'title' );
3537 this.opts.element.attr(
3538 'title',
3539 originalTitle || elementLabel.text()
3540 );
3541
3542 this.focusser.attr( 'tabindex', this.elementTabIndex );
3543
3544 // write label for search field using the label from the focusser element
3545 this.search.attr( 'id', this.focusser.attr( 'id' ) + '_search' );
3546
3547 this.search
3548 .prev()
3549 .text(
3550 $(
3551 "label[for='" + this.focusser.attr( 'id' ) + "']"
3552 ).text()
3553 )
3554 .attr( 'for', this.search.attr( 'id' ) );
3555
3556 this.search.on(
3557 'keydown',
3558 this.bind( function ( e ) {
3559 if ( ! this.isInterfaceEnabled() ) return;
3560
3561 // filter 229 keyCodes (input method editor is processing key input)
3562 if ( 229 == e.keyCode ) return;
3563
3564 if (
3565 e.which === KEY.PAGE_UP ||
3566 e.which === KEY.PAGE_DOWN
3567 ) {
3568 // prevent the page from scrolling
3569 killEvent( e );
3570 return;
3571 }
3572
3573 switch ( e.which ) {
3574 case KEY.UP:
3575 case KEY.DOWN:
3576 this.moveHighlight( e.which === KEY.UP ? -1 : 1 );
3577 killEvent( e );
3578 return;
3579 case KEY.ENTER:
3580 this.selectHighlighted();
3581 killEvent( e );
3582 return;
3583 case KEY.TAB:
3584 this.selectHighlighted( { noFocus: true } );
3585 return;
3586 case KEY.ESC:
3587 this.cancel( e );
3588 killEvent( e );
3589 return;
3590 }
3591 } )
3592 );
3593
3594 this.search.on(
3595 'blur',
3596 this.bind( function ( e ) {
3597 // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
3598 // without this the search field loses focus which is annoying
3599 if ( document.activeElement === this.body.get( 0 ) ) {
3600 window.setTimeout(
3601 this.bind( function () {
3602 if ( this.opened() ) {
3603 this.search.focus();
3604 }
3605 } ),
3606 0
3607 );
3608 }
3609 } )
3610 );
3611
3612 this.focusser.on(
3613 'keydown',
3614 this.bind( function ( e ) {
3615 if ( ! this.isInterfaceEnabled() ) return;
3616
3617 if (
3618 e.which === KEY.TAB ||
3619 KEY.isControl( e ) ||
3620 KEY.isFunctionKey( e ) ||
3621 e.which === KEY.ESC
3622 ) {
3623 return;
3624 }
3625
3626 if (
3627 this.opts.openOnEnter === false &&
3628 e.which === KEY.ENTER
3629 ) {
3630 killEvent( e );
3631 return;
3632 }
3633
3634 if (
3635 e.which == KEY.DOWN ||
3636 e.which == KEY.UP ||
3637 ( e.which == KEY.ENTER && this.opts.openOnEnter )
3638 ) {
3639 if ( e.altKey || e.ctrlKey || e.shiftKey || e.metaKey )
3640 return;
3641
3642 this.open();
3643 killEvent( e );
3644 return;
3645 }
3646
3647 if ( e.which == KEY.DELETE || e.which == KEY.BACKSPACE ) {
3648 if ( this.opts.allowClear ) {
3649 this.clear();
3650 }
3651 killEvent( e );
3652 return;
3653 }
3654 } )
3655 );
3656
3657 installKeyUpChangeEvent( this.focusser );
3658 this.focusser.on(
3659 'keyup-change input',
3660 this.bind( function ( e ) {
3661 if ( this.opts.minimumResultsForSearch >= 0 ) {
3662 e.stopPropagation();
3663 if ( this.opened() ) return;
3664 this.open();
3665 }
3666 } )
3667 );
3668
3669 selection.on(
3670 'mousedown touchstart',
3671 'abbr',
3672 this.bind( function ( e ) {
3673 if ( ! this.isInterfaceEnabled() ) {
3674 return;
3675 }
3676
3677 this.clear();
3678 killEventImmediately( e );
3679 this.close();
3680
3681 if ( this.selection ) {
3682 this.selection.focus();
3683 }
3684 } )
3685 );
3686
3687 selection.on(
3688 'mousedown touchstart',
3689 this.bind( function ( e ) {
3690 // Prevent IE from generating a click event on the body
3691 reinsertElement( selection );
3692
3693 if (
3694 ! this.container.hasClass( 'select2-container-active' )
3695 ) {
3696 this.opts.element.trigger( $.Event( 'select2-focus' ) );
3697 }
3698
3699 if ( this.opened() ) {
3700 this.close();
3701 } else if ( this.isInterfaceEnabled() ) {
3702 this.open();
3703 }
3704
3705 killEvent( e );
3706 } )
3707 );
3708
3709 dropdown.on(
3710 'mousedown touchstart',
3711 this.bind( function () {
3712 if ( this.opts.shouldFocusInput( this ) ) {
3713 this.search.focus();
3714 }
3715 } )
3716 );
3717
3718 selection.on(
3719 'focus',
3720 this.bind( function ( e ) {
3721 killEvent( e );
3722 } )
3723 );
3724
3725 this.focusser
3726 .on(
3727 'focus',
3728 this.bind( function () {
3729 if (
3730 ! this.container.hasClass(
3731 'select2-container-active'
3732 )
3733 ) {
3734 this.opts.element.trigger(
3735 $.Event( 'select2-focus' )
3736 );
3737 }
3738 this.container.addClass( 'select2-container-active' );
3739 } )
3740 )
3741 .on(
3742 'blur',
3743 this.bind( function () {
3744 if ( ! this.opened() ) {
3745 this.container.removeClass(
3746 'select2-container-active'
3747 );
3748 this.opts.element.trigger(
3749 $.Event( 'select2-blur' )
3750 );
3751 }
3752 } )
3753 );
3754 this.search.on(
3755 'focus',
3756 this.bind( function () {
3757 if (
3758 ! this.container.hasClass( 'select2-container-active' )
3759 ) {
3760 this.opts.element.trigger( $.Event( 'select2-focus' ) );
3761 }
3762 this.container.addClass( 'select2-container-active' );
3763 } )
3764 );
3765
3766 this.initContainerWidth();
3767 this.opts.element.hide();
3768 this.setPlaceholder();
3769 },
3770
3771 // single
3772 clear: function ( triggerChange ) {
3773 var data = this.selection.data( 'select2-data' );
3774 if ( data ) {
3775 // guard against queued quick consecutive clicks
3776 var evt = $.Event( 'select2-clearing' );
3777 this.opts.element.trigger( evt );
3778 if ( evt.isDefaultPrevented() ) {
3779 return;
3780 }
3781 var placeholderOption = this.getPlaceholderOption();
3782 this.opts.element.val(
3783 placeholderOption ? placeholderOption.val() : ''
3784 );
3785 this.selection.find( '.select2-chosen' ).empty();
3786 this.selection.removeData( 'select2-data' );
3787 this.setPlaceholder();
3788
3789 if ( triggerChange !== false ) {
3790 this.opts.element.trigger( {
3791 type: 'select2-removed',
3792 val: this.id( data ),
3793 choice: data,
3794 } );
3795 this.triggerChange( { removed: data } );
3796 }
3797 }
3798 },
3799
3800 /**
3801 * Sets selection based on source element's value
3802 */
3803 // single
3804 initSelection: function () {
3805 var selected;
3806 if ( this.isPlaceholderOptionSelected() ) {
3807 this.updateSelection( null );
3808 this.close();
3809 this.setPlaceholder();
3810 } else {
3811 var self = this;
3812 this.opts.initSelection.call(
3813 null,
3814 this.opts.element,
3815 function ( selected ) {
3816 if ( selected !== undefined && selected !== null ) {
3817 self.updateSelection( selected );
3818 self.close();
3819 self.setPlaceholder();
3820 self.nextSearchTerm = self.opts.nextSearchTerm(
3821 selected,
3822 self.search.val()
3823 );
3824 }
3825 }
3826 );
3827 }
3828 },
3829
3830 isPlaceholderOptionSelected: function () {
3831 var placeholderOption;
3832 if ( this.getPlaceholder() === undefined ) return false; // no placeholder specified so no option should be considered
3833 return (
3834 ( ( placeholderOption = this.getPlaceholderOption() ) !==
3835 undefined &&
3836 placeholderOption.prop( 'selected' ) ) ||
3837 this.opts.element.val() === '' ||
3838 this.opts.element.val() === undefined ||
3839 this.opts.element.val() === null
3840 );
3841 },
3842
3843 // single
3844 prepareOpts: function () {
3845 var opts = this.parent.prepareOpts.apply( this, arguments ),
3846 self = this;
3847
3848 if ( opts.element.get( 0 ).tagName.toLowerCase() === 'select' ) {
3849 // install the selection initializer
3850 opts.initSelection = function ( element, callback ) {
3851 var selected = element
3852 .find( 'option' )
3853 .filter( function () {
3854 return this.selected && ! this.disabled;
3855 } );
3856 // a single select box always has a value, no need to null check 'selected'
3857 callback( self.optionToData( selected ) );
3858 };
3859 } else if ( 'data' in opts ) {
3860 // install default initSelection when applied to hidden input and data is local
3861 opts.initSelection =
3862 opts.initSelection ||
3863 function ( element, callback ) {
3864 var id = element.val();
3865 //search in data by id, storing the actual matching item
3866 var match = null;
3867 opts.query( {
3868 matcher: function ( term, text, el ) {
3869 var is_match = equal( id, opts.id( el ) );
3870 if ( is_match ) {
3871 match = el;
3872 }
3873 return is_match;
3874 },
3875 callback: ! $.isFunction( callback )
3876 ? $.noop
3877 : function () {
3878 callback( match );
3879 },
3880 } );
3881 };
3882 }
3883
3884 return opts;
3885 },
3886
3887 // single
3888 getPlaceholder: function () {
3889 // if a placeholder is specified on a single select without a valid placeholder option ignore it
3890 if ( this.select ) {
3891 if ( this.getPlaceholderOption() === undefined ) {
3892 return undefined;
3893 }
3894 }
3895
3896 return this.parent.getPlaceholder.apply( this, arguments );
3897 },
3898
3899 // single
3900 setPlaceholder: function () {
3901 var placeholder = this.getPlaceholder();
3902
3903 if (
3904 this.isPlaceholderOptionSelected() &&
3905 placeholder !== undefined
3906 ) {
3907 // check for a placeholder option if attached to a select
3908 if ( this.select && this.getPlaceholderOption() === undefined )
3909 return;
3910
3911 this.selection
3912 .find( '.select2-chosen' )
3913 .html( this.opts.escapeMarkup( placeholder ) );
3914
3915 this.selection.addClass( 'select2-default' );
3916
3917 this.container.removeClass( 'select2-allowclear' );
3918 }
3919 },
3920
3921 // single
3922 postprocessResults: function ( data, initial, noHighlightUpdate ) {
3923 var selected = 0,
3924 self = this,
3925 showSearchInput = true;
3926
3927 // find the selected element in the result list
3928
3929 this.findHighlightableChoices().each2( function ( i, elm ) {
3930 if (
3931 equal(
3932 self.id( elm.data( 'select2-data' ) ),
3933 self.opts.element.val()
3934 )
3935 ) {
3936 selected = i;
3937 return false;
3938 }
3939 } );
3940
3941 // and highlight it
3942 if ( noHighlightUpdate !== false ) {
3943 if ( initial === true && selected >= 0 ) {
3944 this.highlight( selected );
3945 } else {
3946 this.highlight( 0 );
3947 }
3948 }
3949
3950 // hide the search box if this is the first we got the results and there are enough of them for search
3951
3952 if ( initial === true ) {
3953 var min = this.opts.minimumResultsForSearch;
3954 if ( min >= 0 ) {
3955 this.showSearch( countResults( data.results ) >= min );
3956 }
3957 }
3958 },
3959
3960 // single
3961 showSearch: function ( showSearchInput ) {
3962 if ( this.showSearchInput === showSearchInput ) return;
3963
3964 this.showSearchInput = showSearchInput;
3965
3966 this.dropdown
3967 .find( '.select2-search' )
3968 .toggleClass( 'select2-search-hidden', ! showSearchInput );
3969 this.dropdown
3970 .find( '.select2-search' )
3971 .toggleClass( 'select2-offscreen', ! showSearchInput );
3972 //add "select2-with-searchbox" to the container if search box is shown
3973 $( this.dropdown, this.container ).toggleClass(
3974 'select2-with-searchbox',
3975 showSearchInput
3976 );
3977 },
3978
3979 // single
3980 onSelect: function ( data, options ) {
3981 if ( ! this.triggerSelect( data ) ) {
3982 return;
3983 }
3984
3985 var old = this.opts.element.val(),
3986 oldData = this.data();
3987
3988 this.opts.element.val( this.id( data ) );
3989 this.updateSelection( data );
3990
3991 this.opts.element.trigger( {
3992 type: 'select2-selected',
3993 val: this.id( data ),
3994 choice: data,
3995 } );
3996
3997 this.nextSearchTerm = this.opts.nextSearchTerm(
3998 data,
3999 this.search.val()
4000 );
4001 this.close();
4002
4003 if (
4004 ( ! options || ! options.noFocus ) &&
4005 this.opts.shouldFocusInput( this )
4006 ) {
4007 this.focusser.focus();
4008 }
4009
4010 if ( ! equal( old, this.id( data ) ) ) {
4011 this.triggerChange( { added: data, removed: oldData } );
4012 }
4013 },
4014
4015 // single
4016 updateSelection: function ( data ) {
4017 var container = this.selection.find( '.select2-chosen' ),
4018 formatted,
4019 cssClass;
4020
4021 this.selection.data( 'select2-data', data );
4022
4023 container.empty();
4024 if ( data !== null ) {
4025 formatted = this.opts.formatSelection(
4026 data,
4027 container,
4028 this.opts.escapeMarkup
4029 );
4030 }
4031 if ( formatted !== undefined ) {
4032 container.append( formatted );
4033 }
4034 cssClass = this.opts.formatSelectionCssClass( data, container );
4035 if ( cssClass !== undefined ) {
4036 container.addClass( cssClass );
4037 }
4038
4039 this.selection.removeClass( 'select2-default' );
4040
4041 if ( this.opts.allowClear && this.getPlaceholder() !== undefined ) {
4042 this.container.addClass( 'select2-allowclear' );
4043 }
4044 },
4045
4046 // single
4047 val: function () {
4048 var val,
4049 triggerChange = false,
4050 data = null,
4051 self = this,
4052 oldData = this.data();
4053
4054 if ( arguments.length === 0 ) {
4055 return this.opts.element.val();
4056 }
4057
4058 val = arguments[ 0 ];
4059
4060 if ( arguments.length > 1 ) {
4061 triggerChange = arguments[ 1 ];
4062 }
4063
4064 if ( this.select ) {
4065 this.select
4066 .val( val )
4067 .find( 'option' )
4068 .filter( function () {
4069 return this.selected;
4070 } )
4071 .each2( function ( i, elm ) {
4072 data = self.optionToData( elm );
4073 return false;
4074 } );
4075 this.updateSelection( data );
4076 this.setPlaceholder();
4077 if ( triggerChange ) {
4078 this.triggerChange( { added: data, removed: oldData } );
4079 }
4080 } else {
4081 // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
4082 if ( ! val && val !== 0 ) {
4083 this.clear( triggerChange );
4084 return;
4085 }
4086 if ( this.opts.initSelection === undefined ) {
4087 throw new Error(
4088 'cannot call val() if initSelection() is not defined'
4089 );
4090 }
4091 this.opts.element.val( val );
4092 this.opts.initSelection( this.opts.element, function ( data ) {
4093 self.opts.element.val( ! data ? '' : self.id( data ) );
4094 self.updateSelection( data );
4095 self.setPlaceholder();
4096 if ( triggerChange ) {
4097 self.triggerChange( { added: data, removed: oldData } );
4098 }
4099 } );
4100 }
4101 },
4102
4103 // single
4104 clearSearch: function () {
4105 this.search.val( '' );
4106 this.focusser.val( '' );
4107 },
4108
4109 // single
4110 data: function ( value ) {
4111 var data,
4112 triggerChange = false;
4113
4114 if ( arguments.length === 0 ) {
4115 data = this.selection.data( 'select2-data' );
4116 if ( data == undefined ) data = null;
4117 return data;
4118 } else {
4119 if ( arguments.length > 1 ) {
4120 triggerChange = arguments[ 1 ];
4121 }
4122 if ( ! value ) {
4123 this.clear( triggerChange );
4124 } else {
4125 data = this.data();
4126 this.opts.element.val( ! value ? '' : this.id( value ) );
4127 this.updateSelection( value );
4128 if ( triggerChange ) {
4129 this.triggerChange( { added: value, removed: data } );
4130 }
4131 }
4132 }
4133 },
4134 } );
4135
4136 MultiSelect2 = clazz( AbstractSelect2, {
4137 // multi
4138 createContainer: function () {
4139 var container = $( document.createElement( 'div' ) )
4140 .attr( {
4141 class: 'select2-container select2-container-multi',
4142 } )
4143 .html(
4144 [
4145 "<ul class='select2-choices'>",
4146 " <li class='select2-search-field'>",
4147 " <label for='' class='select2-offscreen'></label>",
4148 " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
4149 ' </li>',
4150 '</ul>',
4151 "<div class='select2-drop select2-drop-multi select2-display-none'>",
4152 " <ul class='select2-results'>",
4153 ' </ul>',
4154 '</div>',
4155 ].join( '' )
4156 );
4157 return container;
4158 },
4159
4160 // multi
4161 prepareOpts: function () {
4162 var opts = this.parent.prepareOpts.apply( this, arguments ),
4163 self = this;
4164
4165 // TODO validate placeholder is a string if specified
4166 if ( opts.element.get( 0 ).tagName.toLowerCase() === 'select' ) {
4167 // install the selection initializer
4168 opts.initSelection = function ( element, callback ) {
4169 var data = [];
4170
4171 element
4172 .find( 'option' )
4173 .filter( function () {
4174 return this.selected && ! this.disabled;
4175 } )
4176 .each2( function ( i, elm ) {
4177 data.push( self.optionToData( elm ) );
4178 } );
4179 callback( data );
4180 };
4181 } else if ( 'data' in opts ) {
4182 // install default initSelection when applied to hidden input and data is local
4183 opts.initSelection =
4184 opts.initSelection ||
4185 function ( element, callback ) {
4186 var ids = splitVal(
4187 element.val(),
4188 opts.separator,
4189 opts.transformVal
4190 );
4191 //search in data by array of ids, storing matching items in a list
4192 var matches = [];
4193 opts.query( {
4194 matcher: function ( term, text, el ) {
4195 var is_match = $.grep( ids, function ( id ) {
4196 return equal( id, opts.id( el ) );
4197 } ).length;
4198 if ( is_match ) {
4199 matches.push( el );
4200 }
4201 return is_match;
4202 },
4203 callback: ! $.isFunction( callback )
4204 ? $.noop
4205 : function () {
4206 // reorder matches based on the order they appear in the ids array because right now
4207 // they are in the order in which they appear in data array
4208 var ordered = [];
4209 for ( var i = 0; i < ids.length; i++ ) {
4210 var id = ids[ i ];
4211 for (
4212 var j = 0;
4213 j < matches.length;
4214 j++
4215 ) {
4216 var match = matches[ j ];
4217 if (
4218 equal(
4219 id,
4220 opts.id( match )
4221 )
4222 ) {
4223 ordered.push( match );
4224 matches.splice( j, 1 );
4225 break;
4226 }
4227 }
4228 }
4229 callback( ordered );
4230 },
4231 } );
4232 };
4233 }
4234
4235 return opts;
4236 },
4237
4238 // multi
4239 selectChoice: function ( choice ) {
4240 var selected = this.container.find(
4241 '.select2-search-choice-focus'
4242 );
4243 if ( selected.length && choice && choice[ 0 ] == selected[ 0 ] ) {
4244 } else {
4245 if ( selected.length ) {
4246 this.opts.element.trigger( 'choice-deselected', selected );
4247 }
4248 selected.removeClass( 'select2-search-choice-focus' );
4249 if ( choice && choice.length ) {
4250 this.close();
4251 choice.addClass( 'select2-search-choice-focus' );
4252 this.opts.element.trigger( 'choice-selected', choice );
4253 }
4254 }
4255 },
4256
4257 // multi
4258 destroy: function () {
4259 $( "label[for='" + this.search.attr( 'id' ) + "']" ).attr(
4260 'for',
4261 this.opts.element.attr( 'id' )
4262 );
4263 this.parent.destroy.apply( this, arguments );
4264
4265 cleanupJQueryElements.call( this, 'searchContainer', 'selection' );
4266 },
4267
4268 // multi
4269 initContainer: function () {
4270 var selector = '.select2-choices',
4271 selection;
4272
4273 this.searchContainer = this.container.find(
4274 '.select2-search-field'
4275 );
4276 this.selection = selection = this.container.find( selector );
4277
4278 var _this = this;
4279 this.selection.on(
4280 'click',
4281 '.select2-container:not(.select2-container-disabled) .select2-search-choice:not(.select2-locked)',
4282 function ( e ) {
4283 _this.search[ 0 ].focus();
4284 _this.selectChoice( $( this ) );
4285 }
4286 );
4287
4288 // rewrite labels from original element to focusser
4289 this.search.attr( 'id', 's2id_autogen' + nextUid() );
4290
4291 this.search
4292 .prev()
4293 .text(
4294 $(
4295 "label[for='" + this.opts.element.attr( 'id' ) + "']"
4296 ).text()
4297 )
4298 .attr( 'for', this.search.attr( 'id' ) );
4299 this.opts.element.focus(
4300 this.bind( function () {
4301 this.focus();
4302 } )
4303 );
4304
4305 this.search.on(
4306 'input paste',
4307 this.bind( function () {
4308 if (
4309 this.search.attr( 'placeholder' ) &&
4310 this.search.val().length == 0
4311 )
4312 return;
4313 if ( ! this.isInterfaceEnabled() ) return;
4314 if ( ! this.opened() ) {
4315 this.open();
4316 }
4317 } )
4318 );
4319
4320 this.search.attr( 'tabindex', this.elementTabIndex );
4321
4322 this.keydowns = 0;
4323 this.search.on(
4324 'keydown',
4325 this.bind( function ( e ) {
4326 if ( ! this.isInterfaceEnabled() ) return;
4327
4328 ++this.keydowns;
4329 var selected = selection.find(
4330 '.select2-search-choice-focus'
4331 );
4332 var prev = selected.prev(
4333 '.select2-search-choice:not(.select2-locked)'
4334 );
4335 var next = selected.next(
4336 '.select2-search-choice:not(.select2-locked)'
4337 );
4338 var pos = getCursorInfo( this.search );
4339
4340 if (
4341 selected.length &&
4342 ( e.which == KEY.LEFT ||
4343 e.which == KEY.RIGHT ||
4344 e.which == KEY.BACKSPACE ||
4345 e.which == KEY.DELETE ||
4346 e.which == KEY.ENTER )
4347 ) {
4348 var selectedChoice = selected;
4349 if ( e.which == KEY.LEFT && prev.length ) {
4350 selectedChoice = prev;
4351 } else if ( e.which == KEY.RIGHT ) {
4352 selectedChoice = next.length ? next : null;
4353 } else if ( e.which === KEY.BACKSPACE ) {
4354 if ( this.unselect( selected.first() ) ) {
4355 this.search.width( 10 );
4356 selectedChoice = prev.length ? prev : next;
4357 }
4358 } else if ( e.which == KEY.DELETE ) {
4359 if ( this.unselect( selected.first() ) ) {
4360 this.search.width( 10 );
4361 selectedChoice = next.length ? next : null;
4362 }
4363 } else if ( e.which == KEY.ENTER ) {
4364 selectedChoice = null;
4365 }
4366
4367 this.selectChoice( selectedChoice );
4368 killEvent( e );
4369 if ( ! selectedChoice || ! selectedChoice.length ) {
4370 this.open();
4371 }
4372 return;
4373 } else if (
4374 ( ( e.which === KEY.BACKSPACE && this.keydowns == 1 ) ||
4375 e.which == KEY.LEFT ) &&
4376 pos.offset == 0 &&
4377 ! pos.length
4378 ) {
4379 this.selectChoice(
4380 selection
4381 .find(
4382 '.select2-search-choice:not(.select2-locked)'
4383 )
4384 .last()
4385 );
4386 killEvent( e );
4387 return;
4388 } else {
4389 this.selectChoice( null );
4390 }
4391
4392 if ( this.opened() ) {
4393 switch ( e.which ) {
4394 case KEY.UP:
4395 case KEY.DOWN:
4396 this.moveHighlight(
4397 e.which === KEY.UP ? -1 : 1
4398 );
4399 killEvent( e );
4400 return;
4401 case KEY.ENTER:
4402 this.selectHighlighted();
4403 killEvent( e );
4404 return;
4405 case KEY.TAB:
4406 this.selectHighlighted( { noFocus: true } );
4407 this.close();
4408 return;
4409 case KEY.ESC:
4410 this.cancel( e );
4411 killEvent( e );
4412 return;
4413 }
4414 }
4415
4416 if (
4417 e.which === KEY.TAB ||
4418 KEY.isControl( e ) ||
4419 KEY.isFunctionKey( e ) ||
4420 e.which === KEY.BACKSPACE ||
4421 e.which === KEY.ESC
4422 ) {
4423 return;
4424 }
4425
4426 if ( e.which === KEY.ENTER ) {
4427 if ( this.opts.openOnEnter === false ) {
4428 return;
4429 } else if (
4430 e.altKey ||
4431 e.ctrlKey ||
4432 e.shiftKey ||
4433 e.metaKey
4434 ) {
4435 return;
4436 }
4437 }
4438
4439 this.open();
4440
4441 if (
4442 e.which === KEY.PAGE_UP ||
4443 e.which === KEY.PAGE_DOWN
4444 ) {
4445 // prevent the page from scrolling
4446 killEvent( e );
4447 }
4448
4449 if ( e.which === KEY.ENTER ) {
4450 // prevent form from being submitted
4451 killEvent( e );
4452 }
4453 } )
4454 );
4455
4456 this.search.on(
4457 'keyup',
4458 this.bind( function ( e ) {
4459 this.keydowns = 0;
4460 this.resizeSearch();
4461 } )
4462 );
4463
4464 this.search.on(
4465 'blur',
4466 this.bind( function ( e ) {
4467 this.container.removeClass( 'select2-container-active' );
4468 this.search.removeClass( 'select2-focused' );
4469 this.selectChoice( null );
4470 if ( ! this.opened() ) this.clearSearch();
4471 e.stopImmediatePropagation();
4472 this.opts.element.trigger( $.Event( 'select2-blur' ) );
4473 } )
4474 );
4475
4476 this.container.on(
4477 'click',
4478 selector,
4479 this.bind( function ( e ) {
4480 if ( ! this.isInterfaceEnabled() ) return;
4481 if (
4482 $( e.target ).closest( '.select2-search-choice' )
4483 .length > 0
4484 ) {
4485 // clicked inside a select2 search choice, do not open
4486 return;
4487 }
4488 this.selectChoice( null );
4489 this.clearPlaceholder();
4490 if (
4491 ! this.container.hasClass( 'select2-container-active' )
4492 ) {
4493 this.opts.element.trigger( $.Event( 'select2-focus' ) );
4494 }
4495 this.open();
4496 this.focusSearch();
4497 e.preventDefault();
4498 } )
4499 );
4500
4501 this.container.on(
4502 'focus',
4503 selector,
4504 this.bind( function () {
4505 if ( ! this.isInterfaceEnabled() ) return;
4506 if (
4507 ! this.container.hasClass( 'select2-container-active' )
4508 ) {
4509 this.opts.element.trigger( $.Event( 'select2-focus' ) );
4510 }
4511 this.container.addClass( 'select2-container-active' );
4512 this.dropdown.addClass( 'select2-drop-active' );
4513 this.clearPlaceholder();
4514 } )
4515 );
4516
4517 this.initContainerWidth();
4518 this.opts.element.hide();
4519
4520 // set the placeholder if necessary
4521 this.clearSearch();
4522 },
4523
4524 // multi
4525 enableInterface: function () {
4526 if ( this.parent.enableInterface.apply( this, arguments ) ) {
4527 this.search.prop( 'disabled', ! this.isInterfaceEnabled() );
4528 }
4529 },
4530
4531 // multi
4532 initSelection: function () {
4533 var data;
4534 if (
4535 this.opts.element.val() === '' &&
4536 this.opts.element.text() === ''
4537 ) {
4538 this.updateSelection( [] );
4539 this.close();
4540 // set the placeholder if necessary
4541 this.clearSearch();
4542 }
4543 if ( this.select || this.opts.element.val() !== '' ) {
4544 var self = this;
4545 this.opts.initSelection.call(
4546 null,
4547 this.opts.element,
4548 function ( data ) {
4549 if ( data !== undefined && data !== null ) {
4550 self.updateSelection( data );
4551 self.close();
4552 // set the placeholder if necessary
4553 self.clearSearch();
4554 }
4555 }
4556 );
4557 }
4558 },
4559
4560 // multi
4561 clearSearch: function () {
4562 var placeholder = this.getPlaceholder(),
4563 maxWidth = this.getMaxSearchWidth();
4564
4565 if (
4566 placeholder !== undefined &&
4567 this.getVal().length === 0 &&
4568 this.search.hasClass( 'select2-focused' ) === false
4569 ) {
4570 this.search.val( placeholder ).addClass( 'select2-default' );
4571 // stretch the search box to full width of the container so as much of the placeholder is visible as possible
4572 // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944
4573 this.search.width(
4574 maxWidth > 0 ? maxWidth : this.container.css( 'width' )
4575 );
4576 } else {
4577 this.search.val( '' ).width( 10 );
4578 }
4579 },
4580
4581 // multi
4582 clearPlaceholder: function () {
4583 if ( this.search.hasClass( 'select2-default' ) ) {
4584 this.search.val( '' ).removeClass( 'select2-default' );
4585 }
4586 },
4587
4588 // multi
4589 opening: function () {
4590 this.clearPlaceholder(); // should be done before super so placeholder is not used to search
4591 this.resizeSearch();
4592
4593 this.parent.opening.apply( this, arguments );
4594
4595 this.focusSearch();
4596
4597 // initializes search's value with nextSearchTerm (if defined by user)
4598 // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
4599 if ( this.search.val() === '' ) {
4600 if ( this.nextSearchTerm != undefined ) {
4601 this.search.val( this.nextSearchTerm );
4602 this.search.select();
4603 }
4604 }
4605
4606 this.updateResults( true );
4607 if ( this.opts.shouldFocusInput( this ) ) {
4608 this.search.focus();
4609 }
4610 this.opts.element.trigger( $.Event( 'select2-open' ) );
4611 },
4612
4613 // multi
4614 close: function () {
4615 if ( ! this.opened() ) return;
4616 this.parent.close.apply( this, arguments );
4617 },
4618
4619 // multi
4620 focus: function () {
4621 this.close();
4622 this.search.focus();
4623 },
4624
4625 // multi
4626 isFocused: function () {
4627 return this.search.hasClass( 'select2-focused' );
4628 },
4629
4630 // multi
4631 updateSelection: function ( data ) {
4632 var ids = [],
4633 filtered = [],
4634 self = this;
4635
4636 // filter out duplicates
4637 $( data ).each( function () {
4638 if ( indexOf( self.id( this ), ids ) < 0 ) {
4639 ids.push( self.id( this ) );
4640 filtered.push( this );
4641 }
4642 } );
4643 data = filtered;
4644
4645 this.selection.find( '.select2-search-choice' ).remove();
4646 $( data ).each( function () {
4647 self.addSelectedChoice( this );
4648 } );
4649 self.postprocessResults();
4650 },
4651
4652 // multi
4653 tokenize: function () {
4654 var input = this.search.val();
4655 input = this.opts.tokenizer.call(
4656 this,
4657 input,
4658 this.data(),
4659 this.bind( this.onSelect ),
4660 this.opts
4661 );
4662 if ( input != null && input != undefined ) {
4663 this.search.val( input );
4664 if ( input.length > 0 ) {
4665 this.open();
4666 }
4667 }
4668 },
4669
4670 // multi
4671 onSelect: function ( data, options ) {
4672 if ( ! this.triggerSelect( data ) || data.text === '' ) {
4673 return;
4674 }
4675
4676 this.addSelectedChoice( data );
4677
4678 this.opts.element.trigger( {
4679 type: 'selected',
4680 val: this.id( data ),
4681 choice: data,
4682 } );
4683
4684 // keep track of the search's value before it gets cleared
4685 this.nextSearchTerm = this.opts.nextSearchTerm(
4686 data,
4687 this.search.val()
4688 );
4689
4690 this.clearSearch();
4691 this.updateResults();
4692
4693 if ( this.select || ! this.opts.closeOnSelect )
4694 this.postprocessResults(
4695 data,
4696 false,
4697 this.opts.closeOnSelect === true
4698 );
4699
4700 if ( this.opts.closeOnSelect ) {
4701 this.close();
4702 this.search.width( 10 );
4703 } else {
4704 if ( this.countSelectableResults() > 0 ) {
4705 this.search.width( 10 );
4706 this.resizeSearch();
4707 if (
4708 this.getMaximumSelectionSize() > 0 &&
4709 this.val().length >= this.getMaximumSelectionSize()
4710 ) {
4711 // if we reached max selection size repaint the results so choices
4712 // are replaced with the max selection reached message
4713 this.updateResults( true );
4714 } else {
4715 // initializes search's value with nextSearchTerm and update search result
4716 if ( this.nextSearchTerm != undefined ) {
4717 this.search.val( this.nextSearchTerm );
4718 this.updateResults();
4719 this.search.select();
4720 }
4721 }
4722 this.positionDropdown();
4723 } else {
4724 // if nothing left to select close
4725 this.close();
4726 this.search.width( 10 );
4727 }
4728 }
4729
4730 // since its not possible to select an element that has already been
4731 // added we do not need to check if this is a new element before firing change
4732 this.triggerChange( { added: data } );
4733
4734 if ( ! options || ! options.noFocus ) this.focusSearch();
4735 },
4736
4737 // multi
4738 cancel: function () {
4739 this.close();
4740 this.focusSearch();
4741 },
4742
4743 addSelectedChoice: function ( data ) {
4744 var enableChoice = ! data.locked,
4745 enabledItem = $(
4746 "<li class='select2-search-choice'>" +
4747 ' <div></div>' +
4748 " <a href='#' class='select2-search-choice-close' tabindex='-1'></a>" +
4749 '</li>'
4750 ),
4751 disabledItem = $(
4752 "<li class='select2-search-choice select2-locked'>" +
4753 '<div></div>' +
4754 '</li>'
4755 );
4756 var choice = enableChoice ? enabledItem : disabledItem,
4757 id = this.id( data ),
4758 val = this.getVal(),
4759 formatted,
4760 cssClass;
4761
4762 formatted = this.opts.formatSelection(
4763 data,
4764 choice.find( 'div' ),
4765 this.opts.escapeMarkup
4766 );
4767 if ( formatted != undefined ) {
4768 choice
4769 .find( 'div' )
4770 .replaceWith( $( '<div></div>' ).html( formatted ) );
4771 }
4772 cssClass = this.opts.formatSelectionCssClass(
4773 data,
4774 choice.find( 'div' )
4775 );
4776 if ( cssClass != undefined ) {
4777 choice.addClass( cssClass );
4778 }
4779
4780 if ( enableChoice ) {
4781 choice
4782 .find( '.select2-search-choice-close' )
4783 .on( 'mousedown', killEvent )
4784 .on(
4785 'click dblclick',
4786 this.bind( function ( e ) {
4787 if ( ! this.isInterfaceEnabled() ) return;
4788
4789 this.unselect( $( e.target ) );
4790 this.selection
4791 .find( '.select2-search-choice-focus' )
4792 .removeClass( 'select2-search-choice-focus' );
4793 killEvent( e );
4794 this.close();
4795 this.focusSearch();
4796 } )
4797 )
4798 .on(
4799 'focus',
4800 this.bind( function () {
4801 if ( ! this.isInterfaceEnabled() ) return;
4802 this.container.addClass(
4803 'select2-container-active'
4804 );
4805 this.dropdown.addClass( 'select2-drop-active' );
4806 } )
4807 );
4808 }
4809
4810 choice.data( 'select2-data', data );
4811 choice.insertBefore( this.searchContainer );
4812
4813 val.push( id );
4814 this.setVal( val );
4815 },
4816
4817 // multi
4818 unselect: function ( selected ) {
4819 var val = this.getVal(),
4820 data,
4821 index;
4822 selected = selected.closest( '.select2-search-choice' );
4823
4824 if ( selected.length === 0 ) {
4825 throw (
4826 'Invalid argument: ' +
4827 selected +
4828 '. Must be .select2-search-choice'
4829 );
4830 }
4831
4832 data = selected.data( 'select2-data' );
4833
4834 if ( ! data ) {
4835 // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
4836 // and invoked on an element already removed
4837 return;
4838 }
4839
4840 var evt = $.Event( 'select2-removing' );
4841 evt.val = this.id( data );
4842 evt.choice = data;
4843 this.opts.element.trigger( evt );
4844
4845 if ( evt.isDefaultPrevented() ) {
4846 return false;
4847 }
4848
4849 while ( ( index = indexOf( this.id( data ), val ) ) >= 0 ) {
4850 val.splice( index, 1 );
4851 this.setVal( val );
4852 if ( this.select ) this.postprocessResults();
4853 }
4854
4855 selected.remove();
4856
4857 this.opts.element.trigger( {
4858 type: 'select2-removed',
4859 val: this.id( data ),
4860 choice: data,
4861 } );
4862 this.triggerChange( { removed: data } );
4863
4864 return true;
4865 },
4866
4867 // multi
4868 postprocessResults: function ( data, initial, noHighlightUpdate ) {
4869 var val = this.getVal(),
4870 choices = this.results.find( '.select2-result' ),
4871 compound = this.results.find( '.select2-result-with-children' ),
4872 self = this;
4873
4874 choices.each2( function ( i, choice ) {
4875 var id = self.id( choice.data( 'select2-data' ) );
4876 if ( indexOf( id, val ) >= 0 ) {
4877 choice.addClass( 'select2-selected' );
4878 // mark all children of the selected parent as selected
4879 choice
4880 .find( '.select2-result-selectable' )
4881 .addClass( 'select2-selected' );
4882 }
4883 } );
4884
4885 compound.each2( function ( i, choice ) {
4886 // hide an optgroup if it doesn't have any selectable children
4887 if (
4888 ! choice.is( '.select2-result-selectable' ) &&
4889 choice.find(
4890 '.select2-result-selectable:not(.select2-selected)'
4891 ).length === 0
4892 ) {
4893 choice.addClass( 'select2-selected' );
4894 }
4895 } );
4896
4897 if (
4898 this.highlight() == -1 &&
4899 noHighlightUpdate !== false &&
4900 this.opts.closeOnSelect === true
4901 ) {
4902 self.highlight( 0 );
4903 }
4904
4905 //If all results are chosen render formatNoMatches
4906 if (
4907 ! this.opts.createSearchChoice &&
4908 ! choices.filter( '.select2-result:not(.select2-selected)' )
4909 .length > 0
4910 ) {
4911 if (
4912 ! data ||
4913 ( data &&
4914 ! data.more &&
4915 this.results.find( '.select2-no-results' ).length ===
4916 0 )
4917 ) {
4918 if (
4919 checkFormatter(
4920 self.opts.formatNoMatches,
4921 'formatNoMatches'
4922 )
4923 ) {
4924 this.results.append(
4925 "<li class='select2-no-results'>" +
4926 evaluate(
4927 self.opts.formatNoMatches,
4928 self.opts.element,
4929 self.search.val()
4930 ) +
4931 '</li>'
4932 );
4933 }
4934 }
4935 }
4936 },
4937
4938 // multi
4939 getMaxSearchWidth: function () {
4940 return this.selection.width() - getSideBorderPadding( this.search );
4941 },
4942
4943 // multi
4944 resizeSearch: function () {
4945 var minimumWidth,
4946 left,
4947 maxWidth,
4948 containerLeft,
4949 searchWidth,
4950 sideBorderPadding = getSideBorderPadding( this.search );
4951
4952 minimumWidth = measureTextWidth( this.search ) + 10;
4953
4954 left = this.search.offset().left;
4955
4956 maxWidth = this.selection.width();
4957 containerLeft = this.selection.offset().left;
4958
4959 searchWidth =
4960 maxWidth - ( left - containerLeft ) - sideBorderPadding;
4961
4962 if ( searchWidth < minimumWidth ) {
4963 searchWidth = maxWidth - sideBorderPadding;
4964 }
4965
4966 if ( searchWidth < 40 ) {
4967 searchWidth = maxWidth - sideBorderPadding;
4968 }
4969
4970 if ( searchWidth <= 0 ) {
4971 searchWidth = minimumWidth;
4972 }
4973
4974 this.search.width( Math.floor( searchWidth ) );
4975 },
4976
4977 // multi
4978 getVal: function () {
4979 var val;
4980 if ( this.select ) {
4981 val = this.select.val();
4982 return val === null ? [] : val;
4983 } else {
4984 val = this.opts.element.val();
4985 return splitVal(
4986 val,
4987 this.opts.separator,
4988 this.opts.transformVal
4989 );
4990 }
4991 },
4992
4993 // multi
4994 setVal: function ( val ) {
4995 var unique;
4996 if ( this.select ) {
4997 this.select.val( val );
4998 } else {
4999 unique = [];
5000 // filter out duplicates
5001 $( val ).each( function () {
5002 if ( indexOf( this, unique ) < 0 ) unique.push( this );
5003 } );
5004 this.opts.element.val(
5005 unique.length === 0
5006 ? ''
5007 : unique.join( this.opts.separator )
5008 );
5009 }
5010 },
5011
5012 // multi
5013 buildChangeDetails: function ( old, current ) {
5014 var current = current.slice( 0 ),
5015 old = old.slice( 0 );
5016
5017 // remove intersection from each array
5018 for ( var i = 0; i < current.length; i++ ) {
5019 for ( var j = 0; j < old.length; j++ ) {
5020 if (
5021 equal(
5022 this.opts.id( current[ i ] ),
5023 this.opts.id( old[ j ] )
5024 )
5025 ) {
5026 current.splice( i, 1 );
5027 if ( i > 0 ) {
5028 i--;
5029 }
5030 old.splice( j, 1 );
5031 j--;
5032 }
5033 }
5034 }
5035
5036 return { added: current, removed: old };
5037 },
5038
5039 // multi
5040 val: function ( val, triggerChange ) {
5041 var oldData,
5042 self = this;
5043
5044 if ( arguments.length === 0 ) {
5045 return this.getVal();
5046 }
5047
5048 oldData = this.data();
5049 if ( ! oldData.length ) oldData = [];
5050
5051 // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
5052 if ( ! val && val !== 0 ) {
5053 this.opts.element.val( '' );
5054 this.updateSelection( [] );
5055 this.clearSearch();
5056 if ( triggerChange ) {
5057 this.triggerChange( {
5058 added: this.data(),
5059 removed: oldData,
5060 } );
5061 }
5062 return;
5063 }
5064
5065 // val is a list of ids
5066 this.setVal( val );
5067
5068 if ( this.select ) {
5069 this.opts.initSelection(
5070 this.select,
5071 this.bind( this.updateSelection )
5072 );
5073 if ( triggerChange ) {
5074 this.triggerChange(
5075 this.buildChangeDetails( oldData, this.data() )
5076 );
5077 }
5078 } else {
5079 if ( this.opts.initSelection === undefined ) {
5080 throw new Error(
5081 'val() cannot be called if initSelection() is not defined'
5082 );
5083 }
5084
5085 this.opts.initSelection( this.opts.element, function ( data ) {
5086 var ids = $.map( data, self.id );
5087 self.setVal( ids );
5088 self.updateSelection( data );
5089 self.clearSearch();
5090 if ( triggerChange ) {
5091 self.triggerChange(
5092 self.buildChangeDetails( oldData, self.data() )
5093 );
5094 }
5095 } );
5096 }
5097 this.clearSearch();
5098 },
5099
5100 // multi
5101 onSortStart: function () {
5102 if ( this.select ) {
5103 throw new Error(
5104 "Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead."
5105 );
5106 }
5107
5108 // collapse search field into 0 width so its container can be collapsed as well
5109 this.search.width( 0 );
5110 // hide the container
5111 this.searchContainer.hide();
5112 },
5113
5114 // multi
5115 onSortEnd: function () {
5116 var val = [],
5117 self = this;
5118
5119 // show search and move it to the end of the list
5120 this.searchContainer.show();
5121 // make sure the search container is the last item in the list
5122 this.searchContainer.appendTo( this.searchContainer.parent() );
5123 // since we collapsed the width in dragStarted, we resize it here
5124 this.resizeSearch();
5125
5126 // update selection
5127 this.selection.find( '.select2-search-choice' ).each( function () {
5128 val.push( self.opts.id( $( this ).data( 'select2-data' ) ) );
5129 } );
5130 this.setVal( val );
5131 this.triggerChange();
5132 },
5133
5134 // multi
5135 data: function ( values, triggerChange ) {
5136 var self = this,
5137 ids,
5138 old;
5139 if ( arguments.length === 0 ) {
5140 return this.selection
5141 .children( '.select2-search-choice' )
5142 .map( function () {
5143 return $( this ).data( 'select2-data' );
5144 } )
5145 .get();
5146 } else {
5147 old = this.data();
5148 if ( ! values ) {
5149 values = [];
5150 }
5151 ids = $.map( values, function ( e ) {
5152 return self.opts.id( e );
5153 } );
5154 this.setVal( ids );
5155 this.updateSelection( values );
5156 this.clearSearch();
5157 if ( triggerChange ) {
5158 this.triggerChange(
5159 this.buildChangeDetails( old, this.data() )
5160 );
5161 }
5162 }
5163 },
5164 } );
5165
5166 $.fn.select2 = function () {
5167 var args = Array.prototype.slice.call( arguments, 0 ),
5168 opts,
5169 select2,
5170 method,
5171 value,
5172 multiple,
5173 allowedMethods = [
5174 'val',
5175 'destroy',
5176 'opened',
5177 'open',
5178 'close',
5179 'focus',
5180 'isFocused',
5181 'container',
5182 'dropdown',
5183 'onSortStart',
5184 'onSortEnd',
5185 'enable',
5186 'disable',
5187 'readonly',
5188 'positionDropdown',
5189 'data',
5190 'search',
5191 ],
5192 valueMethods = [ 'opened', 'isFocused', 'container', 'dropdown' ],
5193 propertyMethods = [ 'val', 'data' ],
5194 methodsMap = { search: 'externalSearch' };
5195
5196 this.each( function () {
5197 if ( args.length === 0 || typeof args[ 0 ] === 'object' ) {
5198 opts = args.length === 0 ? {} : $.extend( {}, args[ 0 ] );
5199 opts.element = $( this );
5200
5201 if (
5202 opts.element.get( 0 ).tagName.toLowerCase() === 'select'
5203 ) {
5204 multiple = opts.element.prop( 'multiple' );
5205 } else {
5206 multiple = opts.multiple || false;
5207 if ( 'tags' in opts ) {
5208 opts.multiple = multiple = true;
5209 }
5210 }
5211
5212 select2 = multiple
5213 ? new window.Select2[ 'class' ].multi()
5214 : new window.Select2[ 'class' ].single();
5215 select2.init( opts );
5216 } else if ( typeof args[ 0 ] === 'string' ) {
5217 if ( indexOf( args[ 0 ], allowedMethods ) < 0 ) {
5218 throw 'Unknown method: ' + args[ 0 ];
5219 }
5220
5221 value = undefined;
5222 select2 = $( this ).data( 'select2' );
5223 if ( select2 === undefined ) return;
5224
5225 method = args[ 0 ];
5226
5227 if ( method === 'container' ) {
5228 value = select2.container;
5229 } else if ( method === 'dropdown' ) {
5230 value = select2.dropdown;
5231 } else {
5232 if ( methodsMap[ method ] ) method = methodsMap[ method ];
5233
5234 value = select2[ method ].apply( select2, args.slice( 1 ) );
5235 }
5236 if (
5237 indexOf( args[ 0 ], valueMethods ) >= 0 ||
5238 ( indexOf( args[ 0 ], propertyMethods ) >= 0 &&
5239 args.length == 1 )
5240 ) {
5241 return false; // abort the iteration, ready to return first matched value
5242 }
5243 } else {
5244 throw 'Invalid arguments to select2 plugin: ' + args;
5245 }
5246 } );
5247 return value === undefined ? this : value;
5248 };
5249
5250 // plugin defaults, accessible to users
5251 $.fn.select2.defaults = {
5252 width: 'copy',
5253 loadMorePadding: 0,
5254 closeOnSelect: true,
5255 openOnEnter: true,
5256 containerCss: {},
5257 dropdownCss: {},
5258 containerCssClass: '',
5259 dropdownCssClass: '',
5260 formatResult: function ( result, container, query, escapeMarkup ) {
5261 var markup = [];
5262 markMatch( this.text( result ), query.term, markup, escapeMarkup );
5263 return markup.join( '' );
5264 },
5265 transformVal: function ( val ) {
5266 return $.trim( val );
5267 },
5268 formatSelection: function ( data, container, escapeMarkup ) {
5269 return data ? escapeMarkup( this.text( data ) ) : undefined;
5270 },
5271 sortResults: function ( results, container, query ) {
5272 return results;
5273 },
5274 formatResultCssClass: function ( data ) {
5275 return data.css;
5276 },
5277 formatSelectionCssClass: function ( data, container ) {
5278 return undefined;
5279 },
5280 minimumResultsForSearch: 0,
5281 minimumInputLength: 0,
5282 maximumInputLength: null,
5283 maximumSelectionSize: 0,
5284 id: function ( e ) {
5285 return e == undefined ? null : e.id;
5286 },
5287 text: function ( e ) {
5288 if ( e && this.data && this.data.text ) {
5289 if ( $.isFunction( this.data.text ) ) {
5290 return this.data.text( e );
5291 } else {
5292 return e[ this.data.text ];
5293 }
5294 } else {
5295 return e.text;
5296 }
5297 },
5298 matcher: function ( term, text ) {
5299 return (
5300 stripDiacritics( '' + text )
5301 .toUpperCase()
5302 .indexOf( stripDiacritics( '' + term ).toUpperCase() ) >= 0
5303 );
5304 },
5305 separator: ',',
5306 tokenSeparators: [],
5307 tokenizer: defaultTokenizer,
5308 escapeMarkup: defaultEscapeMarkup,
5309 blurOnChange: false,
5310 selectOnBlur: false,
5311 adaptContainerCssClass: function ( c ) {
5312 return c;
5313 },
5314 adaptDropdownCssClass: function ( c ) {
5315 return null;
5316 },
5317 nextSearchTerm: function ( selectedObject, currentSearchTerm ) {
5318 return undefined;
5319 },
5320 searchInputPlaceholder: '',
5321 createSearchChoicePosition: 'top',
5322 shouldFocusInput: function ( instance ) {
5323 // Attempt to detect touch devices
5324 var supportsTouchEvents =
5325 'ontouchstart' in window || navigator.msMaxTouchPoints > 0;
5326
5327 // Only devices which support touch events should be special cased
5328 if ( ! supportsTouchEvents ) {
5329 return true;
5330 }
5331
5332 // Never focus the input if search is disabled
5333 if ( instance.opts.minimumResultsForSearch < 0 ) {
5334 return false;
5335 }
5336
5337 return true;
5338 },
5339 };
5340
5341 $.fn.select2.locales = [];
5342
5343 $.fn.select2.locales[ 'en' ] = {
5344 formatMatches: function ( matches ) {
5345 if ( matches === 1 ) {
5346 return 'One result is available, press enter to select it.';
5347 }
5348 return (
5349 matches +
5350 ' results are available, use up and down arrow keys to navigate.'
5351 );
5352 },
5353 formatNoMatches: function () {
5354 return 'No matches found';
5355 },
5356 formatAjaxError: function ( jqXHR, textStatus, errorThrown ) {
5357 return 'Loading failed';
5358 },
5359 formatInputTooShort: function ( input, min ) {
5360 var n = min - input.length;
5361 return (
5362 'Please enter ' +
5363 n +
5364 ' or more character' +
5365 ( n == 1 ? '' : 's' )
5366 );
5367 },
5368 formatInputTooLong: function ( input, max ) {
5369 var n = input.length - max;
5370 return 'Please delete ' + n + ' character' + ( n == 1 ? '' : 's' );
5371 },
5372 formatSelectionTooBig: function ( limit ) {
5373 return (
5374 'You can only select ' +
5375 limit +
5376 ' item' +
5377 ( limit == 1 ? '' : 's' )
5378 );
5379 },
5380 formatLoadMore: function ( pageNumber ) {
5381 return 'Loading more results…';
5382 },
5383 formatSearching: function () {
5384 return 'Searching…';
5385 },
5386 };
5387
5388 $.extend( $.fn.select2.defaults, $.fn.select2.locales[ 'en' ] );
5389
5390 $.fn.select2.ajaxDefaults = {
5391 transport: $.ajax,
5392 params: {
5393 type: 'GET',
5394 cache: false,
5395 dataType: 'json',
5396 },
5397 };
5398
5399 // exports
5400 window.Select2 = {
5401 query: {
5402 ajax: ajax,
5403 local: local,
5404 tags: tags,
5405 },
5406 util: {
5407 debounce: debounce,
5408 markMatch: markMatch,
5409 escapeMarkup: defaultEscapeMarkup,
5410 stripDiacritics: stripDiacritics,
5411 },
5412 class: {
5413 abstract: AbstractSelect2,
5414 single: SingleSelect2,
5415 multi: MultiSelect2,
5416 },
5417 };
5418 } )( jQuery );
5419