##// END OF EJS Templates
Import some of the files from Select2 3.5.0 Javascript system....
Bradley M. Kuhn -
r4128:bbbb013a rhodecode-2.2.5-gpl
parent child Browse files
Show More
1 NO CONTENT: new file 100644, binary diff hidden
This diff has been collapsed as it changes many lines, (700 lines changed) Show them Hide them
@@ -0,0 +1,700 b''
1 /*
2 Version: 3.5.0 Timestamp: Mon Jun 16 19:29:44 EDT 2014
3 */
4 .select2-container {
5 margin: 0;
6 position: relative;
7 display: inline-block;
8 /* inline-block for ie7 */
9 zoom: 1;
10 *display: inline;
11 vertical-align: middle;
12 }
13
14 .select2-container,
15 .select2-drop,
16 .select2-search,
17 .select2-search input {
18 /*
19 Force border-box so that % widths fit the parent
20 container without overlap because of margin/padding.
21 More Info : http://www.quirksmode.org/css/box.html
22 */
23 -webkit-box-sizing: border-box; /* webkit */
24 -moz-box-sizing: border-box; /* firefox */
25 box-sizing: border-box; /* css3 */
26 }
27
28 .select2-container .select2-choice {
29 display: block;
30 height: 26px;
31 padding: 0 0 0 8px;
32 overflow: hidden;
33 position: relative;
34
35 border: 1px solid #aaa;
36 white-space: nowrap;
37 line-height: 26px;
38 color: #444;
39 text-decoration: none;
40
41 border-radius: 4px;
42
43 background-clip: padding-box;
44
45 -webkit-touch-callout: none;
46 -webkit-user-select: none;
47 -moz-user-select: none;
48 -ms-user-select: none;
49 user-select: none;
50
51 background-color: #fff;
52 background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
53 background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
54 background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
55 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
56 background-image: linear-gradient(to top, #eee 0%, #fff 50%);
57 }
58
59 html[dir="rtl"] .select2-container .select2-choice {
60 padding: 0 8px 0 0;
61 }
62
63 .select2-container.select2-drop-above .select2-choice {
64 border-bottom-color: #aaa;
65
66 border-radius: 0 0 4px 4px;
67
68 background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
69 background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
70 background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
71 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
72 background-image: linear-gradient(to bottom, #eee 0%, #fff 90%);
73 }
74
75 .select2-container.select2-allowclear .select2-choice .select2-chosen {
76 margin-right: 42px;
77 }
78
79 .select2-container .select2-choice > .select2-chosen {
80 margin-right: 26px;
81 display: block;
82 overflow: hidden;
83
84 white-space: nowrap;
85
86 text-overflow: ellipsis;
87 float: none;
88 width: auto;
89 }
90
91 html[dir="rtl"] .select2-container .select2-choice > .select2-chosen {
92 margin-left: 26px;
93 margin-right: 0;
94 }
95
96 .select2-container .select2-choice abbr {
97 display: none;
98 width: 12px;
99 height: 12px;
100 position: absolute;
101 right: 24px;
102 top: 8px;
103
104 font-size: 1px;
105 text-decoration: none;
106
107 border: 0;
108 background: url('select2.png') right top no-repeat;
109 cursor: pointer;
110 outline: 0;
111 }
112
113 .select2-container.select2-allowclear .select2-choice abbr {
114 display: inline-block;
115 }
116
117 .select2-container .select2-choice abbr:hover {
118 background-position: right -11px;
119 cursor: pointer;
120 }
121
122 .select2-drop-mask {
123 border: 0;
124 margin: 0;
125 padding: 0;
126 position: fixed;
127 left: 0;
128 top: 0;
129 min-height: 100%;
130 min-width: 100%;
131 height: auto;
132 width: auto;
133 opacity: 0;
134 z-index: 9998;
135 /* styles required for IE to work */
136 background-color: #fff;
137 filter: alpha(opacity=0);
138 }
139
140 .select2-drop {
141 width: 100%;
142 margin-top: -1px;
143 position: absolute;
144 z-index: 9999;
145 top: 100%;
146
147 background: #fff;
148 color: #000;
149 border: 1px solid #aaa;
150 border-top: 0;
151
152 border-radius: 0 0 4px 4px;
153
154 -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
155 box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
156 }
157
158 .select2-drop.select2-drop-above {
159 margin-top: 1px;
160 border-top: 1px solid #aaa;
161 border-bottom: 0;
162
163 border-radius: 4px 4px 0 0;
164
165 -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
166 box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
167 }
168
169 .select2-drop-active {
170 border: 1px solid #5897fb;
171 border-top: none;
172 }
173
174 .select2-drop.select2-drop-above.select2-drop-active {
175 border-top: 1px solid #5897fb;
176 }
177
178 .select2-drop-auto-width {
179 border-top: 1px solid #aaa;
180 width: auto;
181 }
182
183 .select2-drop-auto-width .select2-search {
184 padding-top: 4px;
185 }
186
187 .select2-container .select2-choice .select2-arrow {
188 display: inline-block;
189 width: 18px;
190 height: 100%;
191 position: absolute;
192 right: 0;
193 top: 0;
194
195 border-left: 1px solid #aaa;
196 border-radius: 0 4px 4px 0;
197
198 background-clip: padding-box;
199
200 background: #ccc;
201 background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
202 background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
203 background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
204 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
205 background-image: linear-gradient(to top, #ccc 0%, #eee 60%);
206 }
207
208 html[dir="rtl"] .select2-container .select2-choice .select2-arrow {
209 left: 0;
210 right: auto;
211
212 border-left: none;
213 border-right: 1px solid #aaa;
214 border-radius: 4px 0 0 4px;
215 }
216
217 .select2-container .select2-choice .select2-arrow b {
218 display: block;
219 width: 100%;
220 height: 100%;
221 background: url('select2.png') no-repeat 0 1px;
222 }
223
224 html[dir="rtl"] .select2-container .select2-choice .select2-arrow b {
225 background-position: 2px 1px;
226 }
227
228 .select2-search {
229 display: inline-block;
230 width: 100%;
231 min-height: 26px;
232 margin: 0;
233 padding-left: 4px;
234 padding-right: 4px;
235
236 position: relative;
237 z-index: 10000;
238
239 white-space: nowrap;
240 }
241
242 .select2-search input {
243 width: 100%;
244 height: auto !important;
245 min-height: 26px;
246 padding: 4px 20px 4px 5px;
247 margin: 0;
248
249 outline: 0;
250 font-family: sans-serif;
251 font-size: 1em;
252
253 border: 1px solid #aaa;
254 border-radius: 0;
255
256 -webkit-box-shadow: none;
257 box-shadow: none;
258
259 background: #fff url('select2.png') no-repeat 100% -22px;
260 background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
261 background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
262 background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
263 background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
264 }
265
266 html[dir="rtl"] .select2-search input {
267 padding: 4px 5px 4px 20px;
268
269 background: #fff url('select2.png') no-repeat -37px -22px;
270 background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
271 background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
272 background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
273 background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
274 }
275
276 .select2-drop.select2-drop-above .select2-search input {
277 margin-top: 4px;
278 }
279
280 .select2-search input.select2-active {
281 background: #fff url('select2-spinner.gif') no-repeat 100%;
282 background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
283 background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
284 background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
285 background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
286 }
287
288 .select2-container-active .select2-choice,
289 .select2-container-active .select2-choices {
290 border: 1px solid #5897fb;
291 outline: none;
292
293 -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
294 box-shadow: 0 0 5px rgba(0, 0, 0, .3);
295 }
296
297 .select2-dropdown-open .select2-choice {
298 border-bottom-color: transparent;
299 -webkit-box-shadow: 0 1px 0 #fff inset;
300 box-shadow: 0 1px 0 #fff inset;
301
302 border-bottom-left-radius: 0;
303 border-bottom-right-radius: 0;
304
305 background-color: #eee;
306 background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
307 background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
308 background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
309 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
310 background-image: linear-gradient(to top, #fff 0%, #eee 50%);
311 }
312
313 .select2-dropdown-open.select2-drop-above .select2-choice,
314 .select2-dropdown-open.select2-drop-above .select2-choices {
315 border: 1px solid #5897fb;
316 border-top-color: transparent;
317
318 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
319 background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
320 background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
321 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
322 background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
323 }
324
325 .select2-dropdown-open .select2-choice .select2-arrow {
326 background: transparent;
327 border-left: none;
328 filter: none;
329 }
330 html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow {
331 border-right: none;
332 }
333
334 .select2-dropdown-open .select2-choice .select2-arrow b {
335 background-position: -18px 1px;
336 }
337
338 html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b {
339 background-position: -16px 1px;
340 }
341
342 .select2-hidden-accessible {
343 border: 0;
344 clip: rect(0 0 0 0);
345 height: 1px;
346 margin: -1px;
347 overflow: hidden;
348 padding: 0;
349 position: absolute;
350 width: 1px;
351 }
352
353 /* results */
354 .select2-results {
355 max-height: 200px;
356 padding: 0 0 0 4px;
357 margin: 4px 4px 4px 0;
358 position: relative;
359 overflow-x: hidden;
360 overflow-y: auto;
361 -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
362 }
363
364 html[dir="rtl"] .select2-results {
365 padding: 0 4px 0 0;
366 margin: 4px 0 4px 4px;
367 }
368
369 .select2-results ul.select2-result-sub {
370 margin: 0;
371 padding-left: 0;
372 }
373
374 .select2-results li {
375 list-style: none;
376 display: list-item;
377 background-image: none;
378 }
379
380 .select2-results li.select2-result-with-children > .select2-result-label {
381 font-weight: bold;
382 }
383
384 .select2-results .select2-result-label {
385 padding: 3px 7px 4px;
386 margin: 0;
387 cursor: pointer;
388
389 min-height: 1em;
390
391 -webkit-touch-callout: none;
392 -webkit-user-select: none;
393 -moz-user-select: none;
394 -ms-user-select: none;
395 user-select: none;
396 }
397
398 .select2-results-dept-1 .select2-result-label { padding-left: 20px }
399 .select2-results-dept-2 .select2-result-label { padding-left: 40px }
400 .select2-results-dept-3 .select2-result-label { padding-left: 60px }
401 .select2-results-dept-4 .select2-result-label { padding-left: 80px }
402 .select2-results-dept-5 .select2-result-label { padding-left: 100px }
403 .select2-results-dept-6 .select2-result-label { padding-left: 110px }
404 .select2-results-dept-7 .select2-result-label { padding-left: 120px }
405
406 .select2-results .select2-highlighted {
407 background: #3875d7;
408 color: #fff;
409 }
410
411 .select2-results li em {
412 background: #feffde;
413 font-style: normal;
414 }
415
416 .select2-results .select2-highlighted em {
417 background: transparent;
418 }
419
420 .select2-results .select2-highlighted ul {
421 background: #fff;
422 color: #000;
423 }
424
425
426 .select2-results .select2-no-results,
427 .select2-results .select2-searching,
428 .select2-results .select2-selection-limit {
429 background: #f4f4f4;
430 display: list-item;
431 padding-left: 5px;
432 }
433
434 /*
435 disabled look for disabled choices in the results dropdown
436 */
437 .select2-results .select2-disabled.select2-highlighted {
438 color: #666;
439 background: #f4f4f4;
440 display: list-item;
441 cursor: default;
442 }
443 .select2-results .select2-disabled {
444 background: #f4f4f4;
445 display: list-item;
446 cursor: default;
447 }
448
449 .select2-results .select2-selected {
450 display: none;
451 }
452
453 .select2-more-results.select2-active {
454 background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
455 }
456
457 .select2-more-results {
458 background: #f4f4f4;
459 display: list-item;
460 }
461
462 /* disabled styles */
463
464 .select2-container.select2-container-disabled .select2-choice {
465 background-color: #f4f4f4;
466 background-image: none;
467 border: 1px solid #ddd;
468 cursor: default;
469 }
470
471 .select2-container.select2-container-disabled .select2-choice .select2-arrow {
472 background-color: #f4f4f4;
473 background-image: none;
474 border-left: 0;
475 }
476
477 .select2-container.select2-container-disabled .select2-choice abbr {
478 display: none;
479 }
480
481
482 /* multiselect */
483
484 .select2-container-multi .select2-choices {
485 height: auto !important;
486 height: 1%;
487 margin: 0;
488 padding: 0 5px 0 0;
489 position: relative;
490
491 border: 1px solid #aaa;
492 cursor: text;
493 overflow: hidden;
494
495 background-color: #fff;
496 background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
497 background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
498 background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
499 background-image: linear-gradient(to bottom, #eee 1%, #fff 15%);
500 }
501
502 html[dir="rtl"] .select2-container-multi .select2-choices {
503 padding: 0 0 0 5px;
504 }
505
506 .select2-locked {
507 padding: 3px 5px 3px 5px !important;
508 }
509
510 .select2-container-multi .select2-choices {
511 min-height: 26px;
512 }
513
514 .select2-container-multi.select2-container-active .select2-choices {
515 border: 1px solid #5897fb;
516 outline: none;
517
518 -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
519 box-shadow: 0 0 5px rgba(0, 0, 0, .3);
520 }
521 .select2-container-multi .select2-choices li {
522 float: left;
523 list-style: none;
524 }
525 html[dir="rtl"] .select2-container-multi .select2-choices li
526 {
527 float: right;
528 }
529 .select2-container-multi .select2-choices .select2-search-field {
530 margin: 0;
531 padding: 0;
532 white-space: nowrap;
533 }
534
535 .select2-container-multi .select2-choices .select2-search-field input {
536 padding: 5px;
537 margin: 1px 0;
538
539 font-family: sans-serif;
540 font-size: 100%;
541 color: #666;
542 outline: 0;
543 border: 0;
544 -webkit-box-shadow: none;
545 box-shadow: none;
546 background: transparent !important;
547 }
548
549 .select2-container-multi .select2-choices .select2-search-field input.select2-active {
550 background: #fff url('select2-spinner.gif') no-repeat 100% !important;
551 }
552
553 .select2-default {
554 color: #999 !important;
555 }
556
557 .select2-container-multi .select2-choices .select2-search-choice {
558 padding: 3px 5px 3px 18px;
559 margin: 3px 0 3px 5px;
560 position: relative;
561
562 line-height: 13px;
563 color: #333;
564 cursor: default;
565 border: 1px solid #aaaaaa;
566
567 border-radius: 3px;
568
569 -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
570 box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
571
572 background-clip: padding-box;
573
574 -webkit-touch-callout: none;
575 -webkit-user-select: none;
576 -moz-user-select: none;
577 -ms-user-select: none;
578 user-select: none;
579
580 background-color: #e4e4e4;
581 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
582 background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
583 background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
584 background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
585 background-image: linear-gradient(to top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
586 }
587 html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice
588 {
589 margin: 3px 5px 3px 0;
590 padding: 3px 18px 3px 5px;
591 }
592 .select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
593 cursor: default;
594 }
595 .select2-container-multi .select2-choices .select2-search-choice-focus {
596 background: #d4d4d4;
597 }
598
599 .select2-search-choice-close {
600 display: block;
601 width: 12px;
602 height: 13px;
603 position: absolute;
604 right: 3px;
605 top: 4px;
606
607 font-size: 1px;
608 outline: none;
609 background: url('select2.png') right top no-repeat;
610 }
611 html[dir="rtl"] .select2-search-choice-close {
612 right: auto;
613 left: 3px;
614 }
615
616 .select2-container-multi .select2-search-choice-close {
617 left: 3px;
618 }
619
620 html[dir="rtl"] .select2-container-multi .select2-search-choice-close {
621 left: auto;
622 right: 2px;
623 }
624
625 .select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
626 background-position: right -11px;
627 }
628 .select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
629 background-position: right -11px;
630 }
631
632 /* disabled styles */
633 .select2-container-multi.select2-container-disabled .select2-choices {
634 background-color: #f4f4f4;
635 background-image: none;
636 border: 1px solid #ddd;
637 cursor: default;
638 }
639
640 .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
641 padding: 3px 5px 3px 5px;
642 border: 1px solid #ddd;
643 background-image: none;
644 background-color: #f4f4f4;
645 }
646
647 .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
648 background: none;
649 }
650 /* end multiselect */
651
652
653 .select2-result-selectable .select2-match,
654 .select2-result-unselectable .select2-match {
655 text-decoration: underline;
656 }
657
658 .select2-offscreen, .select2-offscreen:focus {
659 clip: rect(0 0 0 0) !important;
660 width: 1px !important;
661 height: 1px !important;
662 border: 0 !important;
663 margin: 0 !important;
664 padding: 0 !important;
665 overflow: hidden !important;
666 position: absolute !important;
667 outline: 0 !important;
668 left: 0px !important;
669 top: 0px !important;
670 }
671
672 .select2-display-none {
673 display: none;
674 }
675
676 .select2-measure-scrollbar {
677 position: absolute;
678 top: -10000px;
679 left: -10000px;
680 width: 100px;
681 height: 100px;
682 overflow: scroll;
683 }
684
685 /* Retina-ize icons */
686
687 @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) {
688 .select2-search input,
689 .select2-search-choice-close,
690 .select2-container .select2-choice abbr,
691 .select2-container .select2-choice .select2-arrow b {
692 background-image: url('select2x2.png') !important;
693 background-repeat: no-repeat !important;
694 background-size: 60px 40px !important;
695 }
696
697 .select2-search input {
698 background-position: 100% -21px !important;
699 }
700 }
This diff has been collapsed as it changes many lines, (3481 lines changed) Show them Hide them
@@ -0,0 +1,3481 b''
1 /*
2 Copyright 2012 Igor Vaynberg
3
4 Version: 3.5.0 Timestamp: Mon Jun 16 19:29:44 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]), i = -1, l = this.length;
30 while (
31 ++i < l
32 && (j.context = j[0] = this[i])
33 && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
34 );
35 return this;
36 }
37 });
38 }
39 })(jQuery);
40
41 (function ($, undefined) {
42 "use strict";
43 /*global document, window, jQuery, console */
44
45 if (window.Select2 !== undefined) {
46 return;
47 }
48
49 var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
50 lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
51
52 KEY = {
53 TAB: 9,
54 ENTER: 13,
55 ESC: 27,
56 SPACE: 32,
57 LEFT: 37,
58 UP: 38,
59 RIGHT: 39,
60 DOWN: 40,
61 SHIFT: 16,
62 CTRL: 17,
63 ALT: 18,
64 PAGE_UP: 33,
65 PAGE_DOWN: 34,
66 HOME: 36,
67 END: 35,
68 BACKSPACE: 8,
69 DELETE: 46,
70 isArrow: function (k) {
71 k = k.which ? k.which : k;
72 switch (k) {
73 case KEY.LEFT:
74 case KEY.RIGHT:
75 case KEY.UP:
76 case KEY.DOWN:
77 return true;
78 }
79 return false;
80 },
81 isControl: function (e) {
82 var k = e.which;
83 switch (k) {
84 case KEY.SHIFT:
85 case KEY.CTRL:
86 case KEY.ALT:
87 return true;
88 }
89
90 if (e.metaKey) return true;
91
92 return false;
93 },
94 isFunctionKey: function (k) {
95 k = k.which ? k.which : k;
96 return k >= 112 && k <= 123;
97 }
98 },
99 MEASURE_SCROLLBAR_TEMPLATE = "<div class='select2-measure-scrollbar'></div>",
100
101 DIACRITICS = {"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18":"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC5":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z","\u0386":"\u0391","\u0388":"\u0395","\u0389":"\u0397","\u038A":"\u0399","\u03AA":"\u0399","\u038C":"\u039F","\u038E":"\u03A5","\u03AB":"\u03A5","\u038F":"\u03A9","\u03AC":"\u03B1","\u03AD":"\u03B5","\u03AE":"\u03B7","\u03AF":"\u03B9","\u03CA":"\u03B9","\u0390":"\u03B9","\u03CC":"\u03BF","\u03CD":"\u03C5","\u03CB":"\u03C5","\u03B0":"\u03C5","\u03C9":"\u03C9","\u03C2":"\u03C3"};
102
103 $document = $(document);
104
105 nextUid=(function() { var counter=1; return function() { return counter++; }; }());
106
107
108 function reinsertElement(element) {
109 var placeholder = $(document.createTextNode(''));
110
111 element.before(placeholder);
112 placeholder.before(element);
113 placeholder.remove();
114 }
115
116 function stripDiacritics(str) {
117 // Used 'uni range + named function' from http://jsperf.com/diacritics/18
118 function match(a) {
119 return DIACRITICS[a] || a;
120 }
121
122 return str.replace(/[^\u0000-\u007E]/g, match);
123 }
124
125 function indexOf(value, array) {
126 var i = 0, l = array.length;
127 for (; i < l; i = i + 1) {
128 if (equal(value, array[i])) return i;
129 }
130 return -1;
131 }
132
133 function measureScrollbar () {
134 var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
135 $template.appendTo('body');
136
137 var dim = {
138 width: $template.width() - $template[0].clientWidth,
139 height: $template.height() - $template[0].clientHeight
140 };
141 $template.remove();
142
143 return dim;
144 }
145
146 /**
147 * Compares equality of a and b
148 * @param a
149 * @param b
150 */
151 function equal(a, b) {
152 if (a === b) return true;
153 if (a === undefined || b === undefined) return false;
154 if (a === null || b === null) return false;
155 // Check whether 'a' or 'b' is a string (primitive or object).
156 // The concatenation of an empty string (+'') converts its argument to a string's primitive.
157 if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
158 if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
159 return false;
160 }
161
162 /**
163 * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
164 * strings
165 * @param string
166 * @param separator
167 */
168 function splitVal(string, separator) {
169 var val, i, l;
170 if (string === null || string.length < 1) return [];
171 val = string.split(separator);
172 for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
173 return val;
174 }
175
176 function getSideBorderPadding(element) {
177 return element.outerWidth(false) - element.width();
178 }
179
180 function installKeyUpChangeEvent(element) {
181 var key="keyup-change-value";
182 element.on("keydown", function () {
183 if ($.data(element, key) === undefined) {
184 $.data(element, key, element.val());
185 }
186 });
187 element.on("keyup", function () {
188 var val= $.data(element, key);
189 if (val !== undefined && element.val() !== val) {
190 $.removeData(element, key);
191 element.trigger("keyup-change");
192 }
193 });
194 }
195
196
197 /**
198 * filters mouse events so an event is fired only if the mouse moved.
199 *
200 * filters out mouse events that occur when mouse is stationary but
201 * the elements under the pointer are scrolled.
202 */
203 function installFilteredMouseMove(element) {
204 element.on("mousemove", function (e) {
205 var lastpos = lastMousePosition;
206 if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
207 $(e.target).trigger("mousemove-filtered", e);
208 }
209 });
210 }
211
212 /**
213 * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
214 * within the last quietMillis milliseconds.
215 *
216 * @param quietMillis number of milliseconds to wait before invoking fn
217 * @param fn function to be debounced
218 * @param ctx object to be used as this reference within fn
219 * @return debounced version of fn
220 */
221 function debounce(quietMillis, fn, ctx) {
222 ctx = ctx || undefined;
223 var timeout;
224 return function () {
225 var args = arguments;
226 window.clearTimeout(timeout);
227 timeout = window.setTimeout(function() {
228 fn.apply(ctx, args);
229 }, quietMillis);
230 };
231 }
232
233 function installDebouncedScroll(threshold, element) {
234 var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
235 element.on("scroll", function (e) {
236 if (indexOf(e.target, element.get()) >= 0) notify(e);
237 });
238 }
239
240 function focus($el) {
241 if ($el[0] === document.activeElement) return;
242
243 /* set the focus in a 0 timeout - that way the focus is set after the processing
244 of the current event has finished - which seems like the only reliable way
245 to set focus */
246 window.setTimeout(function() {
247 var el=$el[0], pos=$el.val().length, range;
248
249 $el.focus();
250
251 /* make sure el received focus so we do not error out when trying to manipulate the caret.
252 sometimes modals or others listeners may steal it after its set */
253 var isVisible = (el.offsetWidth > 0 || el.offsetHeight > 0);
254 if (isVisible && el === document.activeElement) {
255
256 /* after the focus is set move the caret to the end, necessary when we val()
257 just before setting focus */
258 if(el.setSelectionRange)
259 {
260 el.setSelectionRange(pos, pos);
261 }
262 else if (el.createTextRange) {
263 range = el.createTextRange();
264 range.collapse(false);
265 range.select();
266 }
267 }
268 }, 0);
269 }
270
271 function getCursorInfo(el) {
272 el = $(el)[0];
273 var offset = 0;
274 var length = 0;
275 if ('selectionStart' in el) {
276 offset = el.selectionStart;
277 length = el.selectionEnd - offset;
278 } else if ('selection' in document) {
279 el.focus();
280 var sel = document.selection.createRange();
281 length = document.selection.createRange().text.length;
282 sel.moveStart('character', -el.value.length);
283 offset = sel.text.length - length;
284 }
285 return { offset: offset, length: length };
286 }
287
288 function killEvent(event) {
289 event.preventDefault();
290 event.stopPropagation();
291 }
292 function killEventImmediately(event) {
293 event.preventDefault();
294 event.stopImmediatePropagation();
295 }
296
297 function measureTextWidth(e) {
298 if (!sizer){
299 var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
300 sizer = $(document.createElement("div")).css({
301 position: "absolute",
302 left: "-10000px",
303 top: "-10000px",
304 display: "none",
305 fontSize: style.fontSize,
306 fontFamily: style.fontFamily,
307 fontStyle: style.fontStyle,
308 fontWeight: style.fontWeight,
309 letterSpacing: style.letterSpacing,
310 textTransform: style.textTransform,
311 whiteSpace: "nowrap"
312 });
313 sizer.attr("class","select2-sizer");
314 $("body").append(sizer);
315 }
316 sizer.text(e.val());
317 return sizer.width();
318 }
319
320 function syncCssClasses(dest, src, adapter) {
321 var classes, replacements = [], adapted;
322
323 classes = $.trim(dest.attr("class"));
324
325 if (classes) {
326 classes = '' + classes; // for IE which returns object
327
328 $(classes.split(/\s+/)).each2(function() {
329 if (this.indexOf("select2-") === 0) {
330 replacements.push(this);
331 }
332 });
333 }
334
335 classes = $.trim(src.attr("class"));
336
337 if (classes) {
338 classes = '' + classes; // for IE which returns object
339
340 $(classes.split(/\s+/)).each2(function() {
341 if (this.indexOf("select2-") !== 0) {
342 adapted = adapter(this);
343
344 if (adapted) {
345 replacements.push(adapted);
346 }
347 }
348 });
349 }
350
351 dest.attr("class", replacements.join(" "));
352 }
353
354
355 function markMatch(text, term, markup, escapeMarkup) {
356 var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())),
357 tl=term.length;
358
359 if (match<0) {
360 markup.push(escapeMarkup(text));
361 return;
362 }
363
364 markup.push(escapeMarkup(text.substring(0, match)));
365 markup.push("<span class='select2-match'>");
366 markup.push(escapeMarkup(text.substring(match, match + tl)));
367 markup.push("</span>");
368 markup.push(escapeMarkup(text.substring(match + tl, text.length)));
369 }
370
371 function defaultEscapeMarkup(markup) {
372 var replace_map = {
373 '\\': '&#92;',
374 '&': '&amp;',
375 '<': '&lt;',
376 '>': '&gt;',
377 '"': '&quot;',
378 "'": '&#39;',
379 "/": '&#47;'
380 };
381
382 return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
383 return replace_map[match];
384 });
385 }
386
387 /**
388 * Produces an ajax-based query function
389 *
390 * @param options object containing configuration parameters
391 * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
392 * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
393 * @param options.url url for the data
394 * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
395 * @param options.dataType request data type: ajax, jsonp, other datatypes supported by jQuery's $.ajax function or the transport function if specified
396 * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
397 * @param options.results a function(remoteData, pageNumber, query) that converts data returned form the remote request to the format expected by Select2.
398 * The expected format is an object containing the following keys:
399 * results array of objects that will be used as choices
400 * more (optional) boolean indicating whether there are more results available
401 * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
402 */
403 function ajax(options) {
404 var timeout, // current scheduled but not yet executed request
405 handler = null,
406 quietMillis = options.quietMillis || 100,
407 ajaxUrl = options.url,
408 self = this;
409
410 return function (query) {
411 window.clearTimeout(timeout);
412 timeout = window.setTimeout(function () {
413 var data = options.data, // ajax data function
414 url = ajaxUrl, // ajax url string or function
415 transport = options.transport || $.fn.select2.ajaxDefaults.transport,
416 // deprecated - to be removed in 4.0 - use params instead
417 deprecated = {
418 type: options.type || 'GET', // set type of request (GET or POST)
419 cache: options.cache || false,
420 jsonpCallback: options.jsonpCallback||undefined,
421 dataType: options.dataType||"json"
422 },
423 params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated);
424
425 data = data ? data.call(self, query.term, query.page, query.context) : null;
426 url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url;
427
428 if (handler && typeof handler.abort === "function") { handler.abort(); }
429
430 if (options.params) {
431 if ($.isFunction(options.params)) {
432 $.extend(params, options.params.call(self));
433 } else {
434 $.extend(params, options.params);
435 }
436 }
437
438 $.extend(params, {
439 url: url,
440 dataType: options.dataType,
441 data: data,
442 success: function (data) {
443 // TODO - replace query.page with query so users have access to term, page, etc.
444 // added query as third paramter to keep backwards compatibility
445 var results = options.results(data, query.page, query);
446 query.callback(results);
447 }
448 });
449 handler = transport.call(self, params);
450 }, quietMillis);
451 };
452 }
453
454 /**
455 * Produces a query function that works with a local array
456 *
457 * @param options object containing configuration parameters. The options parameter can either be an array or an
458 * object.
459 *
460 * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
461 *
462 * If the object form is used it is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
463 * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
464 * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
465 * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
466 * the text.
467 */
468 function local(options) {
469 var data = options, // data elements
470 dataText,
471 tmp,
472 text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
473
474 if ($.isArray(data)) {
475 tmp = data;
476 data = { results: tmp };
477 }
478
479 if ($.isFunction(data) === false) {
480 tmp = data;
481 data = function() { return tmp; };
482 }
483
484 var dataItem = data();
485 if (dataItem.text) {
486 text = dataItem.text;
487 // if text is not a function we assume it to be a key name
488 if (!$.isFunction(text)) {
489 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
490 text = function (item) { return item[dataText]; };
491 }
492 }
493
494 return function (query) {
495 var t = query.term, filtered = { results: [] }, process;
496 if (t === "") {
497 query.callback(data());
498 return;
499 }
500
501 process = function(datum, collection) {
502 var group, attr;
503 datum = datum[0];
504 if (datum.children) {
505 group = {};
506 for (attr in datum) {
507 if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
508 }
509 group.children=[];
510 $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
511 if (group.children.length || query.matcher(t, text(group), datum)) {
512 collection.push(group);
513 }
514 } else {
515 if (query.matcher(t, text(datum), datum)) {
516 collection.push(datum);
517 }
518 }
519 };
520
521 $(data().results).each2(function(i, datum) { process(datum, filtered.results); });
522 query.callback(filtered);
523 };
524 }
525
526 // TODO javadoc
527 function tags(data) {
528 var isFunc = $.isFunction(data);
529 return function (query) {
530 var t = query.term, filtered = {results: []};
531 var result = isFunc ? data(query) : data;
532 if ($.isArray(result)) {
533 $(result).each(function () {
534 var isObject = this.text !== undefined,
535 text = isObject ? this.text : this;
536 if (t === "" || query.matcher(t, text)) {
537 filtered.results.push(isObject ? this : {id: this, text: this});
538 }
539 });
540 query.callback(filtered);
541 }
542 };
543 }
544
545 /**
546 * Checks if the formatter function should be used.
547 *
548 * Throws an error if it is not a function. Returns true if it should be used,
549 * false if no formatting should be performed.
550 *
551 * @param formatter
552 */
553 function checkFormatter(formatter, formatterName) {
554 if ($.isFunction(formatter)) return true;
555 if (!formatter) return false;
556 if (typeof(formatter) === 'string') return true;
557 throw new Error(formatterName +" must be a string, function, or falsy value");
558 }
559
560 /**
561 * Returns a given value
562 * If given a function, returns its output
563 *
564 * @param val string|function
565 * @param context value of "this" to be passed to function
566 * @returns {*}
567 */
568 function evaluate(val, context) {
569 if ($.isFunction(val)) {
570 var args = Array.prototype.slice.call(arguments, 2);
571 return val.apply(context, args);
572 }
573 return val;
574 }
575
576 function countResults(results) {
577 var count = 0;
578 $.each(results, function(i, item) {
579 if (item.children) {
580 count += countResults(item.children);
581 } else {
582 count++;
583 }
584 });
585 return count;
586 }
587
588 /**
589 * Default tokenizer. This function uses breaks the input on substring match of any string from the
590 * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
591 * two options have to be defined in order for the tokenizer to work.
592 *
593 * @param input text user has typed so far or pasted into the search field
594 * @param selection currently selected choices
595 * @param selectCallback function(choice) callback tho add the choice to selection
596 * @param opts select2's opts
597 * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
598 */
599 function defaultTokenizer(input, selection, selectCallback, opts) {
600 var original = input, // store the original so we can compare and know if we need to tell the search to update its text
601 dupe = false, // check for whether a token we extracted represents a duplicate selected choice
602 token, // token
603 index, // position at which the separator was found
604 i, l, // looping variables
605 separator; // the matched separator
606
607 if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
608
609 while (true) {
610 index = -1;
611
612 for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
613 separator = opts.tokenSeparators[i];
614 index = input.indexOf(separator);
615 if (index >= 0) break;
616 }
617
618 if (index < 0) break; // did not find any token separator in the input string, bail
619
620 token = input.substring(0, index);
621 input = input.substring(index + separator.length);
622
623 if (token.length > 0) {
624 token = opts.createSearchChoice.call(this, token, selection);
625 if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
626 dupe = false;
627 for (i = 0, l = selection.length; i < l; i++) {
628 if (equal(opts.id(token), opts.id(selection[i]))) {
629 dupe = true; break;
630 }
631 }
632
633 if (!dupe) selectCallback(token);
634 }
635 }
636 }
637
638 if (original!==input) return input;
639 }
640
641 function cleanupJQueryElements() {
642 var self = this;
643
644 $.each(arguments, function (i, element) {
645 self[element].remove();
646 self[element] = null;
647 });
648 }
649
650 /**
651 * Creates a new class
652 *
653 * @param superClass
654 * @param methods
655 */
656 function clazz(SuperClass, methods) {
657 var constructor = function () {};
658 constructor.prototype = new SuperClass;
659 constructor.prototype.constructor = constructor;
660 constructor.prototype.parent = SuperClass.prototype;
661 constructor.prototype = $.extend(constructor.prototype, methods);
662 return constructor;
663 }
664
665 AbstractSelect2 = clazz(Object, {
666
667 // abstract
668 bind: function (func) {
669 var self = this;
670 return function () {
671 func.apply(self, arguments);
672 };
673 },
674
675 // abstract
676 init: function (opts) {
677 var results, search, resultsSelector = ".select2-results";
678
679 // prepare options
680 this.opts = opts = this.prepareOpts(opts);
681
682 this.id=opts.id;
683
684 // destroy if called on an existing component
685 if (opts.element.data("select2") !== undefined &&
686 opts.element.data("select2") !== null) {
687 opts.element.data("select2").destroy();
688 }
689
690 this.container = this.createContainer();
691
692 this.liveRegion = $("<span>", {
693 role: "status",
694 "aria-live": "polite"
695 })
696 .addClass("select2-hidden-accessible")
697 .appendTo(document.body);
698
699 this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
700 this.containerEventName= this.containerId
701 .replace(/([.])/g, '_')
702 .replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
703 this.container.attr("id", this.containerId);
704
705 this.container.attr("title", opts.element.attr("title"));
706
707 this.body = $("body");
708
709 syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
710
711 this.container.attr("style", opts.element.attr("style"));
712 this.container.css(evaluate(opts.containerCss, this.opts.element));
713 this.container.addClass(evaluate(opts.containerCssClass, this.opts.element));
714
715 this.elementTabIndex = this.opts.element.attr("tabindex");
716
717 // swap container for the element
718 this.opts.element
719 .data("select2", this)
720 .attr("tabindex", "-1")
721 .before(this.container)
722 .on("click.select2", killEvent); // do not leak click events
723
724 this.container.data("select2", this);
725
726 this.dropdown = this.container.find(".select2-drop");
727
728 syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
729
730 this.dropdown.addClass(evaluate(opts.dropdownCssClass, this.opts.element));
731 this.dropdown.data("select2", this);
732 this.dropdown.on("click", killEvent);
733
734 this.results = results = this.container.find(resultsSelector);
735 this.search = search = this.container.find("input.select2-input");
736
737 this.queryCount = 0;
738 this.resultsPage = 0;
739 this.context = null;
740
741 // initialize the container
742 this.initContainer();
743
744 this.container.on("click", killEvent);
745
746 installFilteredMouseMove(this.results);
747
748 this.dropdown.on("mousemove-filtered", resultsSelector, this.bind(this.highlightUnderEvent));
749 this.dropdown.on("touchstart touchmove touchend", resultsSelector, this.bind(function (event) {
750 this._touchEvent = true;
751 this.highlightUnderEvent(event);
752 }));
753 this.dropdown.on("touchmove", resultsSelector, this.bind(this.touchMoved));
754 this.dropdown.on("touchstart touchend", resultsSelector, this.bind(this.clearTouchMoved));
755
756 // Waiting for a click event on touch devices to select option and hide dropdown
757 // otherwise click will be triggered on an underlying element
758 this.dropdown.on('click', this.bind(function (event) {
759 if (this._touchEvent) {
760 this._touchEvent = false;
761 this.selectHighlighted();
762 }
763 }));
764
765 installDebouncedScroll(80, this.results);
766 this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
767
768 // do not propagate change event from the search field out of the component
769 $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
770 $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
771
772 // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
773 if ($.fn.mousewheel) {
774 results.mousewheel(function (e, delta, deltaX, deltaY) {
775 var top = results.scrollTop();
776 if (deltaY > 0 && top - deltaY <= 0) {
777 results.scrollTop(0);
778 killEvent(e);
779 } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
780 results.scrollTop(results.get(0).scrollHeight - results.height());
781 killEvent(e);
782 }
783 });
784 }
785
786 installKeyUpChangeEvent(search);
787 search.on("keyup-change input paste", this.bind(this.updateResults));
788 search.on("focus", function () { search.addClass("select2-focused"); });
789 search.on("blur", function () { search.removeClass("select2-focused");});
790
791 this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
792 if ($(e.target).closest(".select2-result-selectable").length > 0) {
793 this.highlightUnderEvent(e);
794 this.selectHighlighted(e);
795 }
796 }));
797
798 // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
799 // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
800 // dom it will trigger the popup close, which is not what we want
801 // focusin can cause focus wars between modals and select2 since the dropdown is outside the modal.
802 this.dropdown.on("click mouseup mousedown touchstart touchend focusin", function (e) { e.stopPropagation(); });
803
804 this.nextSearchTerm = undefined;
805
806 if ($.isFunction(this.opts.initSelection)) {
807 // initialize selection based on the current value of the source element
808 this.initSelection();
809
810 // if the user has provided a function that can set selection based on the value of the source element
811 // we monitor the change event on the element and trigger it, allowing for two way synchronization
812 this.monitorSource();
813 }
814
815 if (opts.maximumInputLength !== null) {
816 this.search.attr("maxlength", opts.maximumInputLength);
817 }
818
819 var disabled = opts.element.prop("disabled");
820 if (disabled === undefined) disabled = false;
821 this.enable(!disabled);
822
823 var readonly = opts.element.prop("readonly");
824 if (readonly === undefined) readonly = false;
825 this.readonly(readonly);
826
827 // Calculate size of scrollbar
828 scrollBarDimensions = scrollBarDimensions || measureScrollbar();
829
830 this.autofocus = opts.element.prop("autofocus");
831 opts.element.prop("autofocus", false);
832 if (this.autofocus) this.focus();
833
834 this.search.attr("placeholder", opts.searchInputPlaceholder);
835 },
836
837 // abstract
838 destroy: function () {
839 var element=this.opts.element, select2 = element.data("select2");
840
841 this.close();
842
843 if (element.length && element[0].detachEvent) {
844 element.each(function () {
845 this.detachEvent("onpropertychange", this._sync);
846 });
847 }
848 if (this.propertyObserver) {
849 this.propertyObserver.disconnect();
850 this.propertyObserver = null;
851 }
852 this._sync = null;
853
854 if (select2 !== undefined) {
855 select2.container.remove();
856 select2.liveRegion.remove();
857 select2.dropdown.remove();
858 element
859 .removeClass("select2-offscreen")
860 .removeData("select2")
861 .off(".select2")
862 .prop("autofocus", this.autofocus || false);
863 if (this.elementTabIndex) {
864 element.attr({tabindex: this.elementTabIndex});
865 } else {
866 element.removeAttr("tabindex");
867 }
868 element.show();
869 }
870
871 cleanupJQueryElements.call(this,
872 "container",
873 "liveRegion",
874 "dropdown",
875 "results",
876 "search"
877 );
878 },
879
880 // abstract
881 optionToData: function(element) {
882 if (element.is("option")) {
883 return {
884 id:element.prop("value"),
885 text:element.text(),
886 element: element.get(),
887 css: element.attr("class"),
888 disabled: element.prop("disabled"),
889 locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true)
890 };
891 } else if (element.is("optgroup")) {
892 return {
893 text:element.attr("label"),
894 children:[],
895 element: element.get(),
896 css: element.attr("class")
897 };
898 }
899 },
900
901 // abstract
902 prepareOpts: function (opts) {
903 var element, select, idKey, ajaxUrl, self = this;
904
905 element = opts.element;
906
907 if (element.get(0).tagName.toLowerCase() === "select") {
908 this.select = select = opts.element;
909 }
910
911 if (select) {
912 // these options are not allowed when attached to a select because they are picked up off the element itself
913 $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
914 if (this in opts) {
915 throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
916 }
917 });
918 }
919
920 opts = $.extend({}, {
921 populateResults: function(container, results, query) {
922 var populate, id=this.opts.id, liveRegion=this.liveRegion;
923
924 populate=function(results, container, depth) {
925
926 var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
927
928 results = opts.sortResults(results, container, query);
929
930 // collect the created nodes for bulk append
931 var nodes = [];
932 for (i = 0, l = results.length; i < l; i = i + 1) {
933
934 result=results[i];
935
936 disabled = (result.disabled === true);
937 selectable = (!disabled) && (id(result) !== undefined);
938
939 compound=result.children && result.children.length > 0;
940
941 node=$("<li></li>");
942 node.addClass("select2-results-dept-"+depth);
943 node.addClass("select2-result");
944 node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
945 if (disabled) { node.addClass("select2-disabled"); }
946 if (compound) { node.addClass("select2-result-with-children"); }
947 node.addClass(self.opts.formatResultCssClass(result));
948 node.attr("role", "presentation");
949
950 label=$(document.createElement("div"));
951 label.addClass("select2-result-label");
952 label.attr("id", "select2-result-label-" + nextUid());
953 label.attr("role", "option");
954
955 formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
956 if (formatted!==undefined) {
957 label.html(formatted);
958 node.append(label);
959 }
960
961
962 if (compound) {
963
964 innerContainer=$("<ul></ul>");
965 innerContainer.addClass("select2-result-sub");
966 populate(result.children, innerContainer, depth+1);
967 node.append(innerContainer);
968 }
969
970 node.data("select2-data", result);
971 nodes.push(node[0]);
972 }
973
974 // bulk append the created nodes
975 container.append(nodes);
976 liveRegion.text(opts.formatMatches(results.length));
977 };
978
979 populate(results, container, 0);
980 }
981 }, $.fn.select2.defaults, opts);
982
983 if (typeof(opts.id) !== "function") {
984 idKey = opts.id;
985 opts.id = function (e) { return e[idKey]; };
986 }
987
988 if ($.isArray(opts.element.data("select2Tags"))) {
989 if ("tags" in opts) {
990 throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
991 }
992 opts.tags=opts.element.data("select2Tags");
993 }
994
995 if (select) {
996 opts.query = this.bind(function (query) {
997 var data = { results: [], more: false },
998 term = query.term,
999 children, placeholderOption, process;
1000
1001 process=function(element, collection) {
1002 var group;
1003 if (element.is("option")) {
1004 if (query.matcher(term, element.text(), element)) {
1005 collection.push(self.optionToData(element));
1006 }
1007 } else if (element.is("optgroup")) {
1008 group=self.optionToData(element);
1009 element.children().each2(function(i, elm) { process(elm, group.children); });
1010 if (group.children.length>0) {
1011 collection.push(group);
1012 }
1013 }
1014 };
1015
1016 children=element.children();
1017
1018 // ignore the placeholder option if there is one
1019 if (this.getPlaceholder() !== undefined && children.length > 0) {
1020 placeholderOption = this.getPlaceholderOption();
1021 if (placeholderOption) {
1022 children=children.not(placeholderOption);
1023 }
1024 }
1025
1026 children.each2(function(i, elm) { process(elm, data.results); });
1027
1028 query.callback(data);
1029 });
1030 // this is needed because inside val() we construct choices from options and there id is hardcoded
1031 opts.id=function(e) { return e.id; };
1032 } else {
1033 if (!("query" in opts)) {
1034
1035 if ("ajax" in opts) {
1036 ajaxUrl = opts.element.data("ajax-url");
1037 if (ajaxUrl && ajaxUrl.length > 0) {
1038 opts.ajax.url = ajaxUrl;
1039 }
1040 opts.query = ajax.call(opts.element, opts.ajax);
1041 } else if ("data" in opts) {
1042 opts.query = local(opts.data);
1043 } else if ("tags" in opts) {
1044 opts.query = tags(opts.tags);
1045 if (opts.createSearchChoice === undefined) {
1046 opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; };
1047 }
1048 if (opts.initSelection === undefined) {
1049 opts.initSelection = function (element, callback) {
1050 var data = [];
1051 $(splitVal(element.val(), opts.separator)).each(function () {
1052 var obj = { id: this, text: this },
1053 tags = opts.tags;
1054 if ($.isFunction(tags)) tags=tags();
1055 $(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } });
1056 data.push(obj);
1057 });
1058
1059 callback(data);
1060 };
1061 }
1062 }
1063 }
1064 }
1065 if (typeof(opts.query) !== "function") {
1066 throw "query function not defined for Select2 " + opts.element.attr("id");
1067 }
1068
1069 if (opts.createSearchChoicePosition === 'top') {
1070 opts.createSearchChoicePosition = function(list, item) { list.unshift(item); };
1071 }
1072 else if (opts.createSearchChoicePosition === 'bottom') {
1073 opts.createSearchChoicePosition = function(list, item) { list.push(item); };
1074 }
1075 else if (typeof(opts.createSearchChoicePosition) !== "function") {
1076 throw "invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";
1077 }
1078
1079 return opts;
1080 },
1081
1082 /**
1083 * Monitor the original element for changes and update select2 accordingly
1084 */
1085 // abstract
1086 monitorSource: function () {
1087 var el = this.opts.element, observer, self = this;
1088
1089 el.on("change.select2", this.bind(function (e) {
1090 if (this.opts.element.data("select2-change-triggered") !== true) {
1091 this.initSelection();
1092 }
1093 }));
1094
1095 this._sync = this.bind(function () {
1096
1097 // sync enabled state
1098 var disabled = el.prop("disabled");
1099 if (disabled === undefined) disabled = false;
1100 this.enable(!disabled);
1101
1102 var readonly = el.prop("readonly");
1103 if (readonly === undefined) readonly = false;
1104 this.readonly(readonly);
1105
1106 syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
1107 this.container.addClass(evaluate(this.opts.containerCssClass, this.opts.element));
1108
1109 syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
1110 this.dropdown.addClass(evaluate(this.opts.dropdownCssClass, this.opts.element));
1111
1112 });
1113
1114 // IE8-10 (IE9/10 won't fire propertyChange via attachEventListener)
1115 if (el.length && el[0].attachEvent) {
1116 el.each(function() {
1117 this.attachEvent("onpropertychange", self._sync);
1118 });
1119 }
1120
1121 // safari, chrome, firefox, IE11
1122 observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver;
1123 if (observer !== undefined) {
1124 if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
1125 this.propertyObserver = new observer(function (mutations) {
1126 $.each(mutations, self._sync);
1127 });
1128 this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
1129 }
1130 },
1131
1132 // abstract
1133 triggerSelect: function(data) {
1134 var evt = $.Event("select2-selecting", { val: this.id(data), object: data, choice: data });
1135 this.opts.element.trigger(evt);
1136 return !evt.isDefaultPrevented();
1137 },
1138
1139 /**
1140 * Triggers the change event on the source element
1141 */
1142 // abstract
1143 triggerChange: function (details) {
1144
1145 details = details || {};
1146 details= $.extend({}, details, { type: "change", val: this.val() });
1147 // prevents recursive triggering
1148 this.opts.element.data("select2-change-triggered", true);
1149 this.opts.element.trigger(details);
1150 this.opts.element.data("select2-change-triggered", false);
1151
1152 // some validation frameworks ignore the change event and listen instead to keyup, click for selects
1153 // so here we trigger the click event manually
1154 this.opts.element.click();
1155
1156 // ValidationEngine ignores the change event and listens instead to blur
1157 // so here we trigger the blur event manually if so desired
1158 if (this.opts.blurOnChange)
1159 this.opts.element.blur();
1160 },
1161
1162 //abstract
1163 isInterfaceEnabled: function()
1164 {
1165 return this.enabledInterface === true;
1166 },
1167
1168 // abstract
1169 enableInterface: function() {
1170 var enabled = this._enabled && !this._readonly,
1171 disabled = !enabled;
1172
1173 if (enabled === this.enabledInterface) return false;
1174
1175 this.container.toggleClass("select2-container-disabled", disabled);
1176 this.close();
1177 this.enabledInterface = enabled;
1178
1179 return true;
1180 },
1181
1182 // abstract
1183 enable: function(enabled) {
1184 if (enabled === undefined) enabled = true;
1185 if (this._enabled === enabled) return;
1186 this._enabled = enabled;
1187
1188 this.opts.element.prop("disabled", !enabled);
1189 this.enableInterface();
1190 },
1191
1192 // abstract
1193 disable: function() {
1194 this.enable(false);
1195 },
1196
1197 // abstract
1198 readonly: function(enabled) {
1199 if (enabled === undefined) enabled = false;
1200 if (this._readonly === enabled) return;
1201 this._readonly = enabled;
1202
1203 this.opts.element.prop("readonly", enabled);
1204 this.enableInterface();
1205 },
1206
1207 // abstract
1208 opened: function () {
1209 return (this.container) ? this.container.hasClass("select2-dropdown-open") : false;
1210 },
1211
1212 // abstract
1213 positionDropdown: function() {
1214 var $dropdown = this.dropdown,
1215 offset = this.container.offset(),
1216 height = this.container.outerHeight(false),
1217 width = this.container.outerWidth(false),
1218 dropHeight = $dropdown.outerHeight(false),
1219 $window = $(window),
1220 windowWidth = $window.width(),
1221 windowHeight = $window.height(),
1222 viewPortRight = $window.scrollLeft() + windowWidth,
1223 viewportBottom = $window.scrollTop() + windowHeight,
1224 dropTop = offset.top + height,
1225 dropLeft = offset.left,
1226 enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
1227 enoughRoomAbove = (offset.top - dropHeight) >= $window.scrollTop(),
1228 dropWidth = $dropdown.outerWidth(false),
1229 enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
1230 aboveNow = $dropdown.hasClass("select2-drop-above"),
1231 bodyOffset,
1232 above,
1233 changeDirection,
1234 css,
1235 resultsListNode;
1236
1237 // always prefer the current above/below alignment, unless there is not enough room
1238 if (aboveNow) {
1239 above = true;
1240 if (!enoughRoomAbove && enoughRoomBelow) {
1241 changeDirection = true;
1242 above = false;
1243 }
1244 } else {
1245 above = false;
1246 if (!enoughRoomBelow && enoughRoomAbove) {
1247 changeDirection = true;
1248 above = true;
1249 }
1250 }
1251
1252 //if we are changing direction we need to get positions when dropdown is hidden;
1253 if (changeDirection) {
1254 $dropdown.hide();
1255 offset = this.container.offset();
1256 height = this.container.outerHeight(false);
1257 width = this.container.outerWidth(false);
1258 dropHeight = $dropdown.outerHeight(false);
1259 viewPortRight = $window.scrollLeft() + windowWidth;
1260 viewportBottom = $window.scrollTop() + windowHeight;
1261 dropTop = offset.top + height;
1262 dropLeft = offset.left;
1263 dropWidth = $dropdown.outerWidth(false);
1264 enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
1265 $dropdown.show();
1266
1267 // fix so the cursor does not move to the left within the search-textbox in IE
1268 this.focusSearch();
1269 }
1270
1271 if (this.opts.dropdownAutoWidth) {
1272 resultsListNode = $('.select2-results', $dropdown)[0];
1273 $dropdown.addClass('select2-drop-auto-width');
1274 $dropdown.css('width', '');
1275 // Add scrollbar width to dropdown if vertical scrollbar is present
1276 dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
1277 dropWidth > width ? width = dropWidth : dropWidth = width;
1278 dropHeight = $dropdown.outerHeight(false);
1279 enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
1280 }
1281 else {
1282 this.container.removeClass('select2-drop-auto-width');
1283 }
1284
1285 //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
1286 //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body.scrollTop(), "enough?", enoughRoomAbove);
1287
1288 // fix positioning when body has an offset and is not position: static
1289 if (this.body.css('position') !== 'static') {
1290 bodyOffset = this.body.offset();
1291 dropTop -= bodyOffset.top;
1292 dropLeft -= bodyOffset.left;
1293 }
1294
1295 if (!enoughRoomOnRight) {
1296 dropLeft = offset.left + this.container.outerWidth(false) - dropWidth;
1297 }
1298
1299 css = {
1300 left: dropLeft,
1301 width: width
1302 };
1303
1304 if (above) {
1305 css.top = offset.top - dropHeight;
1306 css.bottom = 'auto';
1307 this.container.addClass("select2-drop-above");
1308 $dropdown.addClass("select2-drop-above");
1309 }
1310 else {
1311 css.top = dropTop;
1312 css.bottom = 'auto';
1313 this.container.removeClass("select2-drop-above");
1314 $dropdown.removeClass("select2-drop-above");
1315 }
1316 css = $.extend(css, evaluate(this.opts.dropdownCss, this.opts.element));
1317
1318 $dropdown.css(css);
1319 },
1320
1321 // abstract
1322 shouldOpen: function() {
1323 var event;
1324
1325 if (this.opened()) return false;
1326
1327 if (this._enabled === false || this._readonly === true) return false;
1328
1329 event = $.Event("select2-opening");
1330 this.opts.element.trigger(event);
1331 return !event.isDefaultPrevented();
1332 },
1333
1334 // abstract
1335 clearDropdownAlignmentPreference: function() {
1336 // clear the classes used to figure out the preference of where the dropdown should be opened
1337 this.container.removeClass("select2-drop-above");
1338 this.dropdown.removeClass("select2-drop-above");
1339 },
1340
1341 /**
1342 * Opens the dropdown
1343 *
1344 * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
1345 * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
1346 */
1347 // abstract
1348 open: function () {
1349
1350 if (!this.shouldOpen()) return false;
1351
1352 this.opening();
1353
1354 // Only bind the document mousemove when the dropdown is visible
1355 $document.on("mousemove.select2Event", function (e) {
1356 lastMousePosition.x = e.pageX;
1357 lastMousePosition.y = e.pageY;
1358 });
1359
1360 return true;
1361 },
1362
1363 /**
1364 * Performs the opening of the dropdown
1365 */
1366 // abstract
1367 opening: function() {
1368 var cid = this.containerEventName,
1369 scroll = "scroll." + cid,
1370 resize = "resize."+cid,
1371 orient = "orientationchange."+cid,
1372 mask;
1373
1374 this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
1375
1376 this.clearDropdownAlignmentPreference();
1377
1378 if(this.dropdown[0] !== this.body.children().last()[0]) {
1379 this.dropdown.detach().appendTo(this.body);
1380 }
1381
1382 // create the dropdown mask if doesn't already exist
1383 mask = $("#select2-drop-mask");
1384 if (mask.length == 0) {
1385 mask = $(document.createElement("div"));
1386 mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
1387 mask.hide();
1388 mask.appendTo(this.body);
1389 mask.on("mousedown touchstart click", function (e) {
1390 // Prevent IE from generating a click event on the body
1391 reinsertElement(mask);
1392
1393 var dropdown = $("#select2-drop"), self;
1394 if (dropdown.length > 0) {
1395 self=dropdown.data("select2");
1396 if (self.opts.selectOnBlur) {
1397 self.selectHighlighted({noFocus: true});
1398 }
1399 self.close();
1400 e.preventDefault();
1401 e.stopPropagation();
1402 }
1403 });
1404 }
1405
1406 // ensure the mask is always right before the dropdown
1407 if (this.dropdown.prev()[0] !== mask[0]) {
1408 this.dropdown.before(mask);
1409 }
1410
1411 // move the global id to the correct dropdown
1412 $("#select2-drop").removeAttr("id");
1413 this.dropdown.attr("id", "select2-drop");
1414
1415 // show the elements
1416 mask.show();
1417
1418 this.positionDropdown();
1419 this.dropdown.show();
1420 this.positionDropdown();
1421
1422 this.dropdown.addClass("select2-drop-active");
1423
1424 // attach listeners to events that can change the position of the container and thus require
1425 // the position of the dropdown to be updated as well so it does not come unglued from the container
1426 var that = this;
1427 this.container.parents().add(window).each(function () {
1428 $(this).on(resize+" "+scroll+" "+orient, function (e) {
1429 if (that.opened()) that.positionDropdown();
1430 });
1431 });
1432
1433
1434 },
1435
1436 // abstract
1437 close: function () {
1438 if (!this.opened()) return;
1439
1440 var cid = this.containerEventName,
1441 scroll = "scroll." + cid,
1442 resize = "resize."+cid,
1443 orient = "orientationchange."+cid;
1444
1445 // unbind event listeners
1446 this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
1447
1448 this.clearDropdownAlignmentPreference();
1449
1450 $("#select2-drop-mask").hide();
1451 this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
1452 this.dropdown.hide();
1453 this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
1454 this.results.empty();
1455
1456 // Now that the dropdown is closed, unbind the global document mousemove event
1457 $document.off("mousemove.select2Event");
1458
1459 this.clearSearch();
1460 this.search.removeClass("select2-active");
1461 this.opts.element.trigger($.Event("select2-close"));
1462 },
1463
1464 /**
1465 * Opens control, sets input value, and updates results.
1466 */
1467 // abstract
1468 externalSearch: function (term) {
1469 this.open();
1470 this.search.val(term);
1471 this.updateResults(false);
1472 },
1473
1474 // abstract
1475 clearSearch: function () {
1476
1477 },
1478
1479 //abstract
1480 getMaximumSelectionSize: function() {
1481 return evaluate(this.opts.maximumSelectionSize, this.opts.element);
1482 },
1483
1484 // abstract
1485 ensureHighlightVisible: function () {
1486 var results = this.results, children, index, child, hb, rb, y, more, topOffset;
1487
1488 index = this.highlight();
1489
1490 if (index < 0) return;
1491
1492 if (index == 0) {
1493
1494 // if the first element is highlighted scroll all the way to the top,
1495 // that way any unselectable headers above it will also be scrolled
1496 // into view
1497
1498 results.scrollTop(0);
1499 return;
1500 }
1501
1502 children = this.findHighlightableChoices().find('.select2-result-label');
1503
1504 child = $(children[index]);
1505
1506 topOffset = (child.offset() || {}).top || 0;
1507
1508 hb = topOffset + child.outerHeight(true);
1509
1510 // if this is the last child lets also make sure select2-more-results is visible
1511 if (index === children.length - 1) {
1512 more = results.find("li.select2-more-results");
1513 if (more.length > 0) {
1514 hb = more.offset().top + more.outerHeight(true);
1515 }
1516 }
1517
1518 rb = results.offset().top + results.outerHeight(true);
1519 if (hb > rb) {
1520 results.scrollTop(results.scrollTop() + (hb - rb));
1521 }
1522 y = topOffset - results.offset().top;
1523
1524 // make sure the top of the element is visible
1525 if (y < 0 && child.css('display') != 'none' ) {
1526 results.scrollTop(results.scrollTop() + y); // y is negative
1527 }
1528 },
1529
1530 // abstract
1531 findHighlightableChoices: function() {
1532 return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)");
1533 },
1534
1535 // abstract
1536 moveHighlight: function (delta) {
1537 var choices = this.findHighlightableChoices(),
1538 index = this.highlight();
1539
1540 while (index > -1 && index < choices.length) {
1541 index += delta;
1542 var choice = $(choices[index]);
1543 if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
1544 this.highlight(index);
1545 break;
1546 }
1547 }
1548 },
1549
1550 // abstract
1551 highlight: function (index) {
1552 var choices = this.findHighlightableChoices(),
1553 choice,
1554 data;
1555
1556 if (arguments.length === 0) {
1557 return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
1558 }
1559
1560 if (index >= choices.length) index = choices.length - 1;
1561 if (index < 0) index = 0;
1562
1563 this.removeHighlight();
1564
1565 choice = $(choices[index]);
1566 choice.addClass("select2-highlighted");
1567
1568 // ensure assistive technology can determine the active choice
1569 this.search.attr("aria-activedescendant", choice.find(".select2-result-label").attr("id"));
1570
1571 this.ensureHighlightVisible();
1572
1573 this.liveRegion.text(choice.text());
1574
1575 data = choice.data("select2-data");
1576 if (data) {
1577 this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
1578 }
1579 },
1580
1581 removeHighlight: function() {
1582 this.results.find(".select2-highlighted").removeClass("select2-highlighted");
1583 },
1584
1585 touchMoved: function() {
1586 this._touchMoved = true;
1587 },
1588
1589 clearTouchMoved: function() {
1590 this._touchMoved = false;
1591 },
1592
1593 // abstract
1594 countSelectableResults: function() {
1595 return this.findHighlightableChoices().length;
1596 },
1597
1598 // abstract
1599 highlightUnderEvent: function (event) {
1600 var el = $(event.target).closest(".select2-result-selectable");
1601 if (el.length > 0 && !el.is(".select2-highlighted")) {
1602 var choices = this.findHighlightableChoices();
1603 this.highlight(choices.index(el));
1604 } else if (el.length == 0) {
1605 // if we are over an unselectable item remove all highlights
1606 this.removeHighlight();
1607 }
1608 },
1609
1610 // abstract
1611 loadMoreIfNeeded: function () {
1612 var results = this.results,
1613 more = results.find("li.select2-more-results"),
1614 below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
1615 page = this.resultsPage + 1,
1616 self=this,
1617 term=this.search.val(),
1618 context=this.context;
1619
1620 if (more.length === 0) return;
1621 below = more.offset().top - results.offset().top - results.height();
1622
1623 if (below <= this.opts.loadMorePadding) {
1624 more.addClass("select2-active");
1625 this.opts.query({
1626 element: this.opts.element,
1627 term: term,
1628 page: page,
1629 context: context,
1630 matcher: this.opts.matcher,
1631 callback: this.bind(function (data) {
1632
1633 // ignore a response if the select2 has been closed before it was received
1634 if (!self.opened()) return;
1635
1636
1637 self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
1638 self.postprocessResults(data, false, false);
1639
1640 if (data.more===true) {
1641 more.detach().appendTo(results).text(evaluate(self.opts.formatLoadMore, self.opts.element, page+1));
1642 window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1643 } else {
1644 more.remove();
1645 }
1646 self.positionDropdown();
1647 self.resultsPage = page;
1648 self.context = data.context;
1649 this.opts.element.trigger({ type: "select2-loaded", items: data });
1650 })});
1651 }
1652 },
1653
1654 /**
1655 * Default tokenizer function which does nothing
1656 */
1657 tokenize: function() {
1658
1659 },
1660
1661 /**
1662 * @param initial whether or not this is the call to this method right after the dropdown has been opened
1663 */
1664 // abstract
1665 updateResults: function (initial) {
1666 var search = this.search,
1667 results = this.results,
1668 opts = this.opts,
1669 data,
1670 self = this,
1671 input,
1672 term = search.val(),
1673 lastTerm = $.data(this.container, "select2-last-term"),
1674 // sequence number used to drop out-of-order responses
1675 queryNumber;
1676
1677 // prevent duplicate queries against the same term
1678 if (initial !== true && lastTerm && equal(term, lastTerm)) return;
1679
1680 $.data(this.container, "select2-last-term", term);
1681
1682 // if the search is currently hidden we do not alter the results
1683 if (initial !== true && (this.showSearchInput === false || !this.opened())) {
1684 return;
1685 }
1686
1687 function postRender() {
1688 search.removeClass("select2-active");
1689 self.positionDropdown();
1690 if (results.find('.select2-no-results,.select2-selection-limit,.select2-searching').length) {
1691 self.liveRegion.text(results.text());
1692 }
1693 else {
1694 self.liveRegion.text(self.opts.formatMatches(results.find('.select2-result-selectable').length));
1695 }
1696 }
1697
1698 function render(html) {
1699 results.html(html);
1700 postRender();
1701 }
1702
1703 queryNumber = ++this.queryCount;
1704
1705 var maxSelSize = this.getMaximumSelectionSize();
1706 if (maxSelSize >=1) {
1707 data = this.data();
1708 if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
1709 render("<li class='select2-selection-limit'>" + evaluate(opts.formatSelectionTooBig, opts.element, maxSelSize) + "</li>");
1710 return;
1711 }
1712 }
1713
1714 if (search.val().length < opts.minimumInputLength) {
1715 if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
1716 render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooShort, opts.element, search.val(), opts.minimumInputLength) + "</li>");
1717 } else {
1718 render("");
1719 }
1720 if (initial && this.showSearch) this.showSearch(true);
1721 return;
1722 }
1723
1724 if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
1725 if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
1726 render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooLong, opts.element, search.val(), opts.maximumInputLength) + "</li>");
1727 } else {
1728 render("");
1729 }
1730 return;
1731 }
1732
1733 if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
1734 render("<li class='select2-searching'>" + evaluate(opts.formatSearching, opts.element) + "</li>");
1735 }
1736
1737 search.addClass("select2-active");
1738
1739 this.removeHighlight();
1740
1741 // give the tokenizer a chance to pre-process the input
1742 input = this.tokenize();
1743 if (input != undefined && input != null) {
1744 search.val(input);
1745 }
1746
1747 this.resultsPage = 1;
1748
1749 opts.query({
1750 element: opts.element,
1751 term: search.val(),
1752 page: this.resultsPage,
1753 context: null,
1754 matcher: opts.matcher,
1755 callback: this.bind(function (data) {
1756 var def; // default choice
1757
1758 // ignore old responses
1759 if (queryNumber != this.queryCount) {
1760 return;
1761 }
1762
1763 // ignore a response if the select2 has been closed before it was received
1764 if (!this.opened()) {
1765 this.search.removeClass("select2-active");
1766 return;
1767 }
1768
1769 // save context, if any
1770 this.context = (data.context===undefined) ? null : data.context;
1771 // create a default choice and prepend it to the list
1772 if (this.opts.createSearchChoice && search.val() !== "") {
1773 def = this.opts.createSearchChoice.call(self, search.val(), data.results);
1774 if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
1775 if ($(data.results).filter(
1776 function () {
1777 return equal(self.id(this), self.id(def));
1778 }).length === 0) {
1779 this.opts.createSearchChoicePosition(data.results, def);
1780 }
1781 }
1782 }
1783
1784 if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
1785 render("<li class='select2-no-results'>" + evaluate(opts.formatNoMatches, opts.element, search.val()) + "</li>");
1786 return;
1787 }
1788
1789 results.empty();
1790 self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
1791
1792 if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
1793 results.append("<li class='select2-more-results'>" + opts.escapeMarkup(evaluate(opts.formatLoadMore, opts.element, this.resultsPage)) + "</li>");
1794 window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1795 }
1796
1797 this.postprocessResults(data, initial);
1798
1799 postRender();
1800
1801 this.opts.element.trigger({ type: "select2-loaded", items: data });
1802 })});
1803 },
1804
1805 // abstract
1806 cancel: function () {
1807 this.close();
1808 },
1809
1810 // abstract
1811 blur: function () {
1812 // if selectOnBlur == true, select the currently highlighted option
1813 if (this.opts.selectOnBlur)
1814 this.selectHighlighted({noFocus: true});
1815
1816 this.close();
1817 this.container.removeClass("select2-container-active");
1818 // synonymous to .is(':focus'), which is available in jquery >= 1.6
1819 if (this.search[0] === document.activeElement) { this.search.blur(); }
1820 this.clearSearch();
1821 this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
1822 },
1823
1824 // abstract
1825 focusSearch: function () {
1826 focus(this.search);
1827 },
1828
1829 // abstract
1830 selectHighlighted: function (options) {
1831 if (this._touchMoved) {
1832 this.clearTouchMoved();
1833 return;
1834 }
1835 var index=this.highlight(),
1836 highlighted=this.results.find(".select2-highlighted"),
1837 data = highlighted.closest('.select2-result').data("select2-data");
1838
1839 if (data) {
1840 this.highlight(index);
1841 this.onSelect(data, options);
1842 } else if (options && options.noFocus) {
1843 this.close();
1844 }
1845 },
1846
1847 // abstract
1848 getPlaceholder: function () {
1849 var placeholderOption;
1850 return this.opts.element.attr("placeholder") ||
1851 this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
1852 this.opts.element.data("placeholder") ||
1853 this.opts.placeholder ||
1854 ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
1855 },
1856
1857 // abstract
1858 getPlaceholderOption: function() {
1859 if (this.select) {
1860 var firstOption = this.select.children('option').first();
1861 if (this.opts.placeholderOption !== undefined ) {
1862 //Determine the placeholder option based on the specified placeholderOption setting
1863 return (this.opts.placeholderOption === "first" && firstOption) ||
1864 (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
1865 } else if ($.trim(firstOption.text()) === "" && firstOption.val() === "") {
1866 //No explicit placeholder option specified, use the first if it's blank
1867 return firstOption;
1868 }
1869 }
1870 },
1871
1872 /**
1873 * Get the desired width for the container element. This is
1874 * derived first from option `width` passed to select2, then
1875 * the inline 'style' on the original element, and finally
1876 * falls back to the jQuery calculated element width.
1877 */
1878 // abstract
1879 initContainerWidth: function () {
1880 function resolveContainerWidth() {
1881 var style, attrs, matches, i, l, attr;
1882
1883 if (this.opts.width === "off") {
1884 return null;
1885 } else if (this.opts.width === "element"){
1886 return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
1887 } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
1888 // check if there is inline style on the element that contains width
1889 style = this.opts.element.attr('style');
1890 if (style !== undefined) {
1891 attrs = style.split(';');
1892 for (i = 0, l = attrs.length; i < l; i = i + 1) {
1893 attr = attrs[i].replace(/\s/g, '');
1894 matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
1895 if (matches !== null && matches.length >= 1)
1896 return matches[1];
1897 }
1898 }
1899
1900 if (this.opts.width === "resolve") {
1901 // next check if css('width') can resolve a width that is percent based, this is sometimes possible
1902 // when attached to input type=hidden or elements hidden via css
1903 style = this.opts.element.css('width');
1904 if (style.indexOf("%") > 0) return style;
1905
1906 // finally, fallback on the calculated width of the element
1907 return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
1908 }
1909
1910 return null;
1911 } else if ($.isFunction(this.opts.width)) {
1912 return this.opts.width();
1913 } else {
1914 return this.opts.width;
1915 }
1916 };
1917
1918 var width = resolveContainerWidth.call(this);
1919 if (width !== null) {
1920 this.container.css("width", width);
1921 }
1922 }
1923 });
1924
1925 SingleSelect2 = clazz(AbstractSelect2, {
1926
1927 // single
1928
1929 createContainer: function () {
1930 var container = $(document.createElement("div")).attr({
1931 "class": "select2-container"
1932 }).html([
1933 "<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>",
1934 " <span class='select2-chosen'>&#160;</span><abbr class='select2-search-choice-close'></abbr>",
1935 " <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>",
1936 "</a>",
1937 "<label for='' class='select2-offscreen'></label>",
1938 "<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />",
1939 "<div class='select2-drop select2-display-none'>",
1940 " <div class='select2-search'>",
1941 " <label for='' class='select2-offscreen'></label>",
1942 " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'",
1943 " aria-autocomplete='list' />",
1944 " </div>",
1945 " <ul class='select2-results' role='listbox'>",
1946 " </ul>",
1947 "</div>"].join(""));
1948 return container;
1949 },
1950
1951 // single
1952 enableInterface: function() {
1953 if (this.parent.enableInterface.apply(this, arguments)) {
1954 this.focusser.prop("disabled", !this.isInterfaceEnabled());
1955 }
1956 },
1957
1958 // single
1959 opening: function () {
1960 var el, range, len;
1961
1962 if (this.opts.minimumResultsForSearch >= 0) {
1963 this.showSearch(true);
1964 }
1965
1966 this.parent.opening.apply(this, arguments);
1967
1968 if (this.showSearchInput !== false) {
1969 // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
1970 // all other browsers handle this just fine
1971
1972 this.search.val(this.focusser.val());
1973 }
1974 if (this.opts.shouldFocusInput(this)) {
1975 this.search.focus();
1976 // move the cursor to the end after focussing, otherwise it will be at the beginning and
1977 // new text will appear *before* focusser.val()
1978 el = this.search.get(0);
1979 if (el.createTextRange) {
1980 range = el.createTextRange();
1981 range.collapse(false);
1982 range.select();
1983 } else if (el.setSelectionRange) {
1984 len = this.search.val().length;
1985 el.setSelectionRange(len, len);
1986 }
1987 }
1988
1989 // initializes search's value with nextSearchTerm (if defined by user)
1990 // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
1991 if(this.search.val() === "") {
1992 if(this.nextSearchTerm != undefined){
1993 this.search.val(this.nextSearchTerm);
1994 this.search.select();
1995 }
1996 }
1997
1998 this.focusser.prop("disabled", true).val("");
1999 this.updateResults(true);
2000 this.opts.element.trigger($.Event("select2-open"));
2001 },
2002
2003 // single
2004 close: function () {
2005 if (!this.opened()) return;
2006 this.parent.close.apply(this, arguments);
2007
2008 this.focusser.prop("disabled", false);
2009
2010 if (this.opts.shouldFocusInput(this)) {
2011 this.focusser.focus();
2012 }
2013 },
2014
2015 // single
2016 focus: function () {
2017 if (this.opened()) {
2018 this.close();
2019 } else {
2020 this.focusser.prop("disabled", false);
2021 if (this.opts.shouldFocusInput(this)) {
2022 this.focusser.focus();
2023 }
2024 }
2025 },
2026
2027 // single
2028 isFocused: function () {
2029 return this.container.hasClass("select2-container-active");
2030 },
2031
2032 // single
2033 cancel: function () {
2034 this.parent.cancel.apply(this, arguments);
2035 this.focusser.prop("disabled", false);
2036
2037 if (this.opts.shouldFocusInput(this)) {
2038 this.focusser.focus();
2039 }
2040 },
2041
2042 // single
2043 destroy: function() {
2044 $("label[for='" + this.focusser.attr('id') + "']")
2045 .attr('for', this.opts.element.attr("id"));
2046 this.parent.destroy.apply(this, arguments);
2047
2048 cleanupJQueryElements.call(this,
2049 "selection",
2050 "focusser"
2051 );
2052 },
2053
2054 // single
2055 initContainer: function () {
2056
2057 var selection,
2058 container = this.container,
2059 dropdown = this.dropdown,
2060 idSuffix = nextUid(),
2061 elementLabel;
2062
2063 if (this.opts.minimumResultsForSearch < 0) {
2064 this.showSearch(false);
2065 } else {
2066 this.showSearch(true);
2067 }
2068
2069 this.selection = selection = container.find(".select2-choice");
2070
2071 this.focusser = container.find(".select2-focusser");
2072
2073 // add aria associations
2074 selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix);
2075 this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix);
2076 this.results.attr("id", "select2-results-"+idSuffix);
2077 this.search.attr("aria-owns", "select2-results-"+idSuffix);
2078
2079 // rewrite labels from original element to focusser
2080 this.focusser.attr("id", "s2id_autogen"+idSuffix);
2081
2082 elementLabel = $("label[for='" + this.opts.element.attr("id") + "']");
2083
2084 this.focusser.prev()
2085 .text(elementLabel.text())
2086 .attr('for', this.focusser.attr('id'));
2087
2088 // Ensure the original element retains an accessible name
2089 var originalTitle = this.opts.element.attr("title");
2090 this.opts.element.attr("title", (originalTitle || elementLabel.text()));
2091
2092 this.focusser.attr("tabindex", this.elementTabIndex);
2093
2094 // write label for search field using the label from the focusser element
2095 this.search.attr("id", this.focusser.attr('id') + '_search');
2096
2097 this.search.prev()
2098 .text($("label[for='" + this.focusser.attr('id') + "']").text())
2099 .attr('for', this.search.attr('id'));
2100
2101 this.search.on("keydown", this.bind(function (e) {
2102 if (!this.isInterfaceEnabled()) return;
2103
2104 if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2105 // prevent the page from scrolling
2106 killEvent(e);
2107 return;
2108 }
2109
2110 switch (e.which) {
2111 case KEY.UP:
2112 case KEY.DOWN:
2113 this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2114 killEvent(e);
2115 return;
2116 case KEY.ENTER:
2117 this.selectHighlighted();
2118 killEvent(e);
2119 return;
2120 case KEY.TAB:
2121 this.selectHighlighted({noFocus: true});
2122 return;
2123 case KEY.ESC:
2124 this.cancel(e);
2125 killEvent(e);
2126 return;
2127 }
2128 }));
2129
2130 this.search.on("blur", this.bind(function(e) {
2131 // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
2132 // without this the search field loses focus which is annoying
2133 if (document.activeElement === this.body.get(0)) {
2134 window.setTimeout(this.bind(function() {
2135 if (this.opened()) {
2136 this.search.focus();
2137 }
2138 }), 0);
2139 }
2140 }));
2141
2142 this.focusser.on("keydown", this.bind(function (e) {
2143 if (!this.isInterfaceEnabled()) return;
2144
2145 if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
2146 return;
2147 }
2148
2149 if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
2150 killEvent(e);
2151 return;
2152 }
2153
2154 if (e.which == KEY.DOWN || e.which == KEY.UP
2155 || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
2156
2157 if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
2158
2159 this.open();
2160 killEvent(e);
2161 return;
2162 }
2163
2164 if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
2165 if (this.opts.allowClear) {
2166 this.clear();
2167 }
2168 killEvent(e);
2169 return;
2170 }
2171 }));
2172
2173
2174 installKeyUpChangeEvent(this.focusser);
2175 this.focusser.on("keyup-change input", this.bind(function(e) {
2176 if (this.opts.minimumResultsForSearch >= 0) {
2177 e.stopPropagation();
2178 if (this.opened()) return;
2179 this.open();
2180 }
2181 }));
2182
2183 selection.on("mousedown touchstart", "abbr", this.bind(function (e) {
2184 if (!this.isInterfaceEnabled()) return;
2185 this.clear();
2186 killEventImmediately(e);
2187 this.close();
2188 this.selection.focus();
2189 }));
2190
2191 selection.on("mousedown touchstart", this.bind(function (e) {
2192 // Prevent IE from generating a click event on the body
2193 reinsertElement(selection);
2194
2195 if (!this.container.hasClass("select2-container-active")) {
2196 this.opts.element.trigger($.Event("select2-focus"));
2197 }
2198
2199 if (this.opened()) {
2200 this.close();
2201 } else if (this.isInterfaceEnabled()) {
2202 this.open();
2203 }
2204
2205 killEvent(e);
2206 }));
2207
2208 dropdown.on("mousedown touchstart", this.bind(function() {
2209 if (this.opts.shouldFocusInput(this)) {
2210 this.search.focus();
2211 }
2212 }));
2213
2214 selection.on("focus", this.bind(function(e) {
2215 killEvent(e);
2216 }));
2217
2218 this.focusser.on("focus", this.bind(function(){
2219 if (!this.container.hasClass("select2-container-active")) {
2220 this.opts.element.trigger($.Event("select2-focus"));
2221 }
2222 this.container.addClass("select2-container-active");
2223 })).on("blur", this.bind(function() {
2224 if (!this.opened()) {
2225 this.container.removeClass("select2-container-active");
2226 this.opts.element.trigger($.Event("select2-blur"));
2227 }
2228 }));
2229 this.search.on("focus", this.bind(function(){
2230 if (!this.container.hasClass("select2-container-active")) {
2231 this.opts.element.trigger($.Event("select2-focus"));
2232 }
2233 this.container.addClass("select2-container-active");
2234 }));
2235
2236 this.initContainerWidth();
2237 this.opts.element.addClass("select2-offscreen");
2238 this.setPlaceholder();
2239
2240 },
2241
2242 // single
2243 clear: function(triggerChange) {
2244 var data=this.selection.data("select2-data");
2245 if (data) { // guard against queued quick consecutive clicks
2246 var evt = $.Event("select2-clearing");
2247 this.opts.element.trigger(evt);
2248 if (evt.isDefaultPrevented()) {
2249 return;
2250 }
2251 var placeholderOption = this.getPlaceholderOption();
2252 this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
2253 this.selection.find(".select2-chosen").empty();
2254 this.selection.removeData("select2-data");
2255 this.setPlaceholder();
2256
2257 if (triggerChange !== false){
2258 this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
2259 this.triggerChange({removed:data});
2260 }
2261 }
2262 },
2263
2264 /**
2265 * Sets selection based on source element's value
2266 */
2267 // single
2268 initSelection: function () {
2269 var selected;
2270 if (this.isPlaceholderOptionSelected()) {
2271 this.updateSelection(null);
2272 this.close();
2273 this.setPlaceholder();
2274 } else {
2275 var self = this;
2276 this.opts.initSelection.call(null, this.opts.element, function(selected){
2277 if (selected !== undefined && selected !== null) {
2278 self.updateSelection(selected);
2279 self.close();
2280 self.setPlaceholder();
2281 self.nextSearchTerm = self.opts.nextSearchTerm(selected, self.search.val());
2282 }
2283 });
2284 }
2285 },
2286
2287 isPlaceholderOptionSelected: function() {
2288 var placeholderOption;
2289 if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered
2290 return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected"))
2291 || (this.opts.element.val() === "")
2292 || (this.opts.element.val() === undefined)
2293 || (this.opts.element.val() === null);
2294 },
2295
2296 // single
2297 prepareOpts: function () {
2298 var opts = this.parent.prepareOpts.apply(this, arguments),
2299 self=this;
2300
2301 if (opts.element.get(0).tagName.toLowerCase() === "select") {
2302 // install the selection initializer
2303 opts.initSelection = function (element, callback) {
2304 var selected = element.find("option").filter(function() { return this.selected && !this.disabled });
2305 // a single select box always has a value, no need to null check 'selected'
2306 callback(self.optionToData(selected));
2307 };
2308 } else if ("data" in opts) {
2309 // install default initSelection when applied to hidden input and data is local
2310 opts.initSelection = opts.initSelection || function (element, callback) {
2311 var id = element.val();
2312 //search in data by id, storing the actual matching item
2313 var match = null;
2314 opts.query({
2315 matcher: function(term, text, el){
2316 var is_match = equal(id, opts.id(el));
2317 if (is_match) {
2318 match = el;
2319 }
2320 return is_match;
2321 },
2322 callback: !$.isFunction(callback) ? $.noop : function() {
2323 callback(match);
2324 }
2325 });
2326 };
2327 }
2328
2329 return opts;
2330 },
2331
2332 // single
2333 getPlaceholder: function() {
2334 // if a placeholder is specified on a single select without a valid placeholder option ignore it
2335 if (this.select) {
2336 if (this.getPlaceholderOption() === undefined) {
2337 return undefined;
2338 }
2339 }
2340
2341 return this.parent.getPlaceholder.apply(this, arguments);
2342 },
2343
2344 // single
2345 setPlaceholder: function () {
2346 var placeholder = this.getPlaceholder();
2347
2348 if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
2349
2350 // check for a placeholder option if attached to a select
2351 if (this.select && this.getPlaceholderOption() === undefined) return;
2352
2353 this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
2354
2355 this.selection.addClass("select2-default");
2356
2357 this.container.removeClass("select2-allowclear");
2358 }
2359 },
2360
2361 // single
2362 postprocessResults: function (data, initial, noHighlightUpdate) {
2363 var selected = 0, self = this, showSearchInput = true;
2364
2365 // find the selected element in the result list
2366
2367 this.findHighlightableChoices().each2(function (i, elm) {
2368 if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
2369 selected = i;
2370 return false;
2371 }
2372 });
2373
2374 // and highlight it
2375 if (noHighlightUpdate !== false) {
2376 if (initial === true && selected >= 0) {
2377 this.highlight(selected);
2378 } else {
2379 this.highlight(0);
2380 }
2381 }
2382
2383 // hide the search box if this is the first we got the results and there are enough of them for search
2384
2385 if (initial === true) {
2386 var min = this.opts.minimumResultsForSearch;
2387 if (min >= 0) {
2388 this.showSearch(countResults(data.results) >= min);
2389 }
2390 }
2391 },
2392
2393 // single
2394 showSearch: function(showSearchInput) {
2395 if (this.showSearchInput === showSearchInput) return;
2396
2397 this.showSearchInput = showSearchInput;
2398
2399 this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
2400 this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
2401 //add "select2-with-searchbox" to the container if search box is shown
2402 $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
2403 },
2404
2405 // single
2406 onSelect: function (data, options) {
2407
2408 if (!this.triggerSelect(data)) { return; }
2409
2410 var old = this.opts.element.val(),
2411 oldData = this.data();
2412
2413 this.opts.element.val(this.id(data));
2414 this.updateSelection(data);
2415
2416 this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
2417
2418 this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
2419 this.close();
2420
2421 if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) {
2422 this.focusser.focus();
2423 }
2424
2425 if (!equal(old, this.id(data))) {
2426 this.triggerChange({ added: data, removed: oldData });
2427 }
2428 },
2429
2430 // single
2431 updateSelection: function (data) {
2432
2433 var container=this.selection.find(".select2-chosen"), formatted, cssClass;
2434
2435 this.selection.data("select2-data", data);
2436
2437 container.empty();
2438 if (data !== null) {
2439 formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
2440 }
2441 if (formatted !== undefined) {
2442 container.append(formatted);
2443 }
2444 cssClass=this.opts.formatSelectionCssClass(data, container);
2445 if (cssClass !== undefined) {
2446 container.addClass(cssClass);
2447 }
2448
2449 this.selection.removeClass("select2-default");
2450
2451 if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
2452 this.container.addClass("select2-allowclear");
2453 }
2454 },
2455
2456 // single
2457 val: function () {
2458 var val,
2459 triggerChange = false,
2460 data = null,
2461 self = this,
2462 oldData = this.data();
2463
2464 if (arguments.length === 0) {
2465 return this.opts.element.val();
2466 }
2467
2468 val = arguments[0];
2469
2470 if (arguments.length > 1) {
2471 triggerChange = arguments[1];
2472 }
2473
2474 if (this.select) {
2475 this.select
2476 .val(val)
2477 .find("option").filter(function() { return this.selected }).each2(function (i, elm) {
2478 data = self.optionToData(elm);
2479 return false;
2480 });
2481 this.updateSelection(data);
2482 this.setPlaceholder();
2483 if (triggerChange) {
2484 this.triggerChange({added: data, removed:oldData});
2485 }
2486 } else {
2487 // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
2488 if (!val && val !== 0) {
2489 this.clear(triggerChange);
2490 return;
2491 }
2492 if (this.opts.initSelection === undefined) {
2493 throw new Error("cannot call val() if initSelection() is not defined");
2494 }
2495 this.opts.element.val(val);
2496 this.opts.initSelection(this.opts.element, function(data){
2497 self.opts.element.val(!data ? "" : self.id(data));
2498 self.updateSelection(data);
2499 self.setPlaceholder();
2500 if (triggerChange) {
2501 self.triggerChange({added: data, removed:oldData});
2502 }
2503 });
2504 }
2505 },
2506
2507 // single
2508 clearSearch: function () {
2509 this.search.val("");
2510 this.focusser.val("");
2511 },
2512
2513 // single
2514 data: function(value) {
2515 var data,
2516 triggerChange = false;
2517
2518 if (arguments.length === 0) {
2519 data = this.selection.data("select2-data");
2520 if (data == undefined) data = null;
2521 return data;
2522 } else {
2523 if (arguments.length > 1) {
2524 triggerChange = arguments[1];
2525 }
2526 if (!value) {
2527 this.clear(triggerChange);
2528 } else {
2529 data = this.data();
2530 this.opts.element.val(!value ? "" : this.id(value));
2531 this.updateSelection(value);
2532 if (triggerChange) {
2533 this.triggerChange({added: value, removed:data});
2534 }
2535 }
2536 }
2537 }
2538 });
2539
2540 MultiSelect2 = clazz(AbstractSelect2, {
2541
2542 // multi
2543 createContainer: function () {
2544 var container = $(document.createElement("div")).attr({
2545 "class": "select2-container select2-container-multi"
2546 }).html([
2547 "<ul class='select2-choices'>",
2548 " <li class='select2-search-field'>",
2549 " <label for='' class='select2-offscreen'></label>",
2550 " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
2551 " </li>",
2552 "</ul>",
2553 "<div class='select2-drop select2-drop-multi select2-display-none'>",
2554 " <ul class='select2-results'>",
2555 " </ul>",
2556 "</div>"].join(""));
2557 return container;
2558 },
2559
2560 // multi
2561 prepareOpts: function () {
2562 var opts = this.parent.prepareOpts.apply(this, arguments),
2563 self=this;
2564
2565 // TODO validate placeholder is a string if specified
2566
2567 if (opts.element.get(0).tagName.toLowerCase() === "select") {
2568 // install the selection initializer
2569 opts.initSelection = function (element, callback) {
2570
2571 var data = [];
2572
2573 element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) {
2574 data.push(self.optionToData(elm));
2575 });
2576 callback(data);
2577 };
2578 } else if ("data" in opts) {
2579 // install default initSelection when applied to hidden input and data is local
2580 opts.initSelection = opts.initSelection || function (element, callback) {
2581 var ids = splitVal(element.val(), opts.separator);
2582 //search in data by array of ids, storing matching items in a list
2583 var matches = [];
2584 opts.query({
2585 matcher: function(term, text, el){
2586 var is_match = $.grep(ids, function(id) {
2587 return equal(id, opts.id(el));
2588 }).length;
2589 if (is_match) {
2590 matches.push(el);
2591 }
2592 return is_match;
2593 },
2594 callback: !$.isFunction(callback) ? $.noop : function() {
2595 // reorder matches based on the order they appear in the ids array because right now
2596 // they are in the order in which they appear in data array
2597 var ordered = [];
2598 for (var i = 0; i < ids.length; i++) {
2599 var id = ids[i];
2600 for (var j = 0; j < matches.length; j++) {
2601 var match = matches[j];
2602 if (equal(id, opts.id(match))) {
2603 ordered.push(match);
2604 matches.splice(j, 1);
2605 break;
2606 }
2607 }
2608 }
2609 callback(ordered);
2610 }
2611 });
2612 };
2613 }
2614
2615 return opts;
2616 },
2617
2618 // multi
2619 selectChoice: function (choice) {
2620
2621 var selected = this.container.find(".select2-search-choice-focus");
2622 if (selected.length && choice && choice[0] == selected[0]) {
2623
2624 } else {
2625 if (selected.length) {
2626 this.opts.element.trigger("choice-deselected", selected);
2627 }
2628 selected.removeClass("select2-search-choice-focus");
2629 if (choice && choice.length) {
2630 this.close();
2631 choice.addClass("select2-search-choice-focus");
2632 this.opts.element.trigger("choice-selected", choice);
2633 }
2634 }
2635 },
2636
2637 // multi
2638 destroy: function() {
2639 $("label[for='" + this.search.attr('id') + "']")
2640 .attr('for', this.opts.element.attr("id"));
2641 this.parent.destroy.apply(this, arguments);
2642
2643 cleanupJQueryElements.call(this,
2644 "searchContainer",
2645 "selection"
2646 );
2647 },
2648
2649 // multi
2650 initContainer: function () {
2651
2652 var selector = ".select2-choices", selection;
2653
2654 this.searchContainer = this.container.find(".select2-search-field");
2655 this.selection = selection = this.container.find(selector);
2656
2657 var _this = this;
2658 this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) {
2659 //killEvent(e);
2660 _this.search[0].focus();
2661 _this.selectChoice($(this));
2662 });
2663
2664 // rewrite labels from original element to focusser
2665 this.search.attr("id", "s2id_autogen"+nextUid());
2666
2667 this.search.prev()
2668 .text($("label[for='" + this.opts.element.attr("id") + "']").text())
2669 .attr('for', this.search.attr('id'));
2670
2671 this.search.on("input paste", this.bind(function() {
2672 if (this.search.attr('placeholder') && this.search.val().length == 0) return;
2673 if (!this.isInterfaceEnabled()) return;
2674 if (!this.opened()) {
2675 this.open();
2676 }
2677 }));
2678
2679 this.search.attr("tabindex", this.elementTabIndex);
2680
2681 this.keydowns = 0;
2682 this.search.on("keydown", this.bind(function (e) {
2683 if (!this.isInterfaceEnabled()) return;
2684
2685 ++this.keydowns;
2686 var selected = selection.find(".select2-search-choice-focus");
2687 var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
2688 var next = selected.next(".select2-search-choice:not(.select2-locked)");
2689 var pos = getCursorInfo(this.search);
2690
2691 if (selected.length &&
2692 (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
2693 var selectedChoice = selected;
2694 if (e.which == KEY.LEFT && prev.length) {
2695 selectedChoice = prev;
2696 }
2697 else if (e.which == KEY.RIGHT) {
2698 selectedChoice = next.length ? next : null;
2699 }
2700 else if (e.which === KEY.BACKSPACE) {
2701 if (this.unselect(selected.first())) {
2702 this.search.width(10);
2703 selectedChoice = prev.length ? prev : next;
2704 }
2705 } else if (e.which == KEY.DELETE) {
2706 if (this.unselect(selected.first())) {
2707 this.search.width(10);
2708 selectedChoice = next.length ? next : null;
2709 }
2710 } else if (e.which == KEY.ENTER) {
2711 selectedChoice = null;
2712 }
2713
2714 this.selectChoice(selectedChoice);
2715 killEvent(e);
2716 if (!selectedChoice || !selectedChoice.length) {
2717 this.open();
2718 }
2719 return;
2720 } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
2721 || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
2722
2723 this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
2724 killEvent(e);
2725 return;
2726 } else {
2727 this.selectChoice(null);
2728 }
2729
2730 if (this.opened()) {
2731 switch (e.which) {
2732 case KEY.UP:
2733 case KEY.DOWN:
2734 this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2735 killEvent(e);
2736 return;
2737 case KEY.ENTER:
2738 this.selectHighlighted();
2739 killEvent(e);
2740 return;
2741 case KEY.TAB:
2742 this.selectHighlighted({noFocus:true});
2743 this.close();
2744 return;
2745 case KEY.ESC:
2746 this.cancel(e);
2747 killEvent(e);
2748 return;
2749 }
2750 }
2751
2752 if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
2753 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
2754 return;
2755 }
2756
2757 if (e.which === KEY.ENTER) {
2758 if (this.opts.openOnEnter === false) {
2759 return;
2760 } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2761 return;
2762 }
2763 }
2764
2765 this.open();
2766
2767 if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2768 // prevent the page from scrolling
2769 killEvent(e);
2770 }
2771
2772 if (e.which === KEY.ENTER) {
2773 // prevent form from being submitted
2774 killEvent(e);
2775 }
2776
2777 }));
2778
2779 this.search.on("keyup", this.bind(function (e) {
2780 this.keydowns = 0;
2781 this.resizeSearch();
2782 })
2783 );
2784
2785 this.search.on("blur", this.bind(function(e) {
2786 this.container.removeClass("select2-container-active");
2787 this.search.removeClass("select2-focused");
2788 this.selectChoice(null);
2789 if (!this.opened()) this.clearSearch();
2790 e.stopImmediatePropagation();
2791 this.opts.element.trigger($.Event("select2-blur"));
2792 }));
2793
2794 this.container.on("click", selector, this.bind(function (e) {
2795 if (!this.isInterfaceEnabled()) return;
2796 if ($(e.target).closest(".select2-search-choice").length > 0) {
2797 // clicked inside a select2 search choice, do not open
2798 return;
2799 }
2800 this.selectChoice(null);
2801 this.clearPlaceholder();
2802 if (!this.container.hasClass("select2-container-active")) {
2803 this.opts.element.trigger($.Event("select2-focus"));
2804 }
2805 this.open();
2806 this.focusSearch();
2807 e.preventDefault();
2808 }));
2809
2810 this.container.on("focus", selector, this.bind(function () {
2811 if (!this.isInterfaceEnabled()) return;
2812 if (!this.container.hasClass("select2-container-active")) {
2813 this.opts.element.trigger($.Event("select2-focus"));
2814 }
2815 this.container.addClass("select2-container-active");
2816 this.dropdown.addClass("select2-drop-active");
2817 this.clearPlaceholder();
2818 }));
2819
2820 this.initContainerWidth();
2821 this.opts.element.addClass("select2-offscreen");
2822
2823 // set the placeholder if necessary
2824 this.clearSearch();
2825 },
2826
2827 // multi
2828 enableInterface: function() {
2829 if (this.parent.enableInterface.apply(this, arguments)) {
2830 this.search.prop("disabled", !this.isInterfaceEnabled());
2831 }
2832 },
2833
2834 // multi
2835 initSelection: function () {
2836 var data;
2837 if (this.opts.element.val() === "" && this.opts.element.text() === "") {
2838 this.updateSelection([]);
2839 this.close();
2840 // set the placeholder if necessary
2841 this.clearSearch();
2842 }
2843 if (this.select || this.opts.element.val() !== "") {
2844 var self = this;
2845 this.opts.initSelection.call(null, this.opts.element, function(data){
2846 if (data !== undefined && data !== null) {
2847 self.updateSelection(data);
2848 self.close();
2849 // set the placeholder if necessary
2850 self.clearSearch();
2851 }
2852 });
2853 }
2854 },
2855
2856 // multi
2857 clearSearch: function () {
2858 var placeholder = this.getPlaceholder(),
2859 maxWidth = this.getMaxSearchWidth();
2860
2861 if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
2862 this.search.val(placeholder).addClass("select2-default");
2863 // stretch the search box to full width of the container so as much of the placeholder is visible as possible
2864 // 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
2865 this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
2866 } else {
2867 this.search.val("").width(10);
2868 }
2869 },
2870
2871 // multi
2872 clearPlaceholder: function () {
2873 if (this.search.hasClass("select2-default")) {
2874 this.search.val("").removeClass("select2-default");
2875 }
2876 },
2877
2878 // multi
2879 opening: function () {
2880 this.clearPlaceholder(); // should be done before super so placeholder is not used to search
2881 this.resizeSearch();
2882
2883 this.parent.opening.apply(this, arguments);
2884
2885 this.focusSearch();
2886
2887 // initializes search's value with nextSearchTerm (if defined by user)
2888 // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
2889 if(this.search.val() === "") {
2890 if(this.nextSearchTerm != undefined){
2891 this.search.val(this.nextSearchTerm);
2892 this.search.select();
2893 }
2894 }
2895
2896 this.updateResults(true);
2897 if (this.opts.shouldFocusInput(this)) {
2898 this.search.focus();
2899 }
2900 this.opts.element.trigger($.Event("select2-open"));
2901 },
2902
2903 // multi
2904 close: function () {
2905 if (!this.opened()) return;
2906 this.parent.close.apply(this, arguments);
2907 },
2908
2909 // multi
2910 focus: function () {
2911 this.close();
2912 this.search.focus();
2913 },
2914
2915 // multi
2916 isFocused: function () {
2917 return this.search.hasClass("select2-focused");
2918 },
2919
2920 // multi
2921 updateSelection: function (data) {
2922 var ids = [], filtered = [], self = this;
2923
2924 // filter out duplicates
2925 $(data).each(function () {
2926 if (indexOf(self.id(this), ids) < 0) {
2927 ids.push(self.id(this));
2928 filtered.push(this);
2929 }
2930 });
2931 data = filtered;
2932
2933 this.selection.find(".select2-search-choice").remove();
2934 $(data).each(function () {
2935 self.addSelectedChoice(this);
2936 });
2937 self.postprocessResults();
2938 },
2939
2940 // multi
2941 tokenize: function() {
2942 var input = this.search.val();
2943 input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
2944 if (input != null && input != undefined) {
2945 this.search.val(input);
2946 if (input.length > 0) {
2947 this.open();
2948 }
2949 }
2950
2951 },
2952
2953 // multi
2954 onSelect: function (data, options) {
2955
2956 if (!this.triggerSelect(data)) { return; }
2957
2958 this.addSelectedChoice(data);
2959
2960 this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
2961
2962 // keep track of the search's value before it gets cleared
2963 this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
2964
2965 this.clearSearch();
2966 this.updateResults();
2967
2968 if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
2969
2970 if (this.opts.closeOnSelect) {
2971 this.close();
2972 this.search.width(10);
2973 } else {
2974 if (this.countSelectableResults()>0) {
2975 this.search.width(10);
2976 this.resizeSearch();
2977 if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
2978 // if we reached max selection size repaint the results so choices
2979 // are replaced with the max selection reached message
2980 this.updateResults(true);
2981 } else {
2982 // initializes search's value with nextSearchTerm and update search result
2983 if(this.nextSearchTerm != undefined){
2984 this.search.val(this.nextSearchTerm);
2985 this.updateResults();
2986 this.search.select();
2987 }
2988 }
2989 this.positionDropdown();
2990 } else {
2991 // if nothing left to select close
2992 this.close();
2993 this.search.width(10);
2994 }
2995 }
2996
2997 // since its not possible to select an element that has already been
2998 // added we do not need to check if this is a new element before firing change
2999 this.triggerChange({ added: data });
3000
3001 if (!options || !options.noFocus)
3002 this.focusSearch();
3003 },
3004
3005 // multi
3006 cancel: function () {
3007 this.close();
3008 this.focusSearch();
3009 },
3010
3011 addSelectedChoice: function (data) {
3012 var enableChoice = !data.locked,
3013 enabledItem = $(
3014 "<li class='select2-search-choice'>" +
3015 " <div></div>" +
3016 " <a href='#' class='select2-search-choice-close' tabindex='-1'></a>" +
3017 "</li>"),
3018 disabledItem = $(
3019 "<li class='select2-search-choice select2-locked'>" +
3020 "<div></div>" +
3021 "</li>");
3022 var choice = enableChoice ? enabledItem : disabledItem,
3023 id = this.id(data),
3024 val = this.getVal(),
3025 formatted,
3026 cssClass;
3027
3028 formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
3029 if (formatted != undefined) {
3030 choice.find("div").replaceWith("<div>"+formatted+"</div>");
3031 }
3032 cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
3033 if (cssClass != undefined) {
3034 choice.addClass(cssClass);
3035 }
3036
3037 if(enableChoice){
3038 choice.find(".select2-search-choice-close")
3039 .on("mousedown", killEvent)
3040 .on("click dblclick", this.bind(function (e) {
3041 if (!this.isInterfaceEnabled()) return;
3042
3043 this.unselect($(e.target));
3044 this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
3045 killEvent(e);
3046 this.close();
3047 this.focusSearch();
3048 })).on("focus", this.bind(function () {
3049 if (!this.isInterfaceEnabled()) return;
3050 this.container.addClass("select2-container-active");
3051 this.dropdown.addClass("select2-drop-active");
3052 }));
3053 }
3054
3055 choice.data("select2-data", data);
3056 choice.insertBefore(this.searchContainer);
3057
3058 val.push(id);
3059 this.setVal(val);
3060 },
3061
3062 // multi
3063 unselect: function (selected) {
3064 var val = this.getVal(),
3065 data,
3066 index;
3067 selected = selected.closest(".select2-search-choice");
3068
3069 if (selected.length === 0) {
3070 throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
3071 }
3072
3073 data = selected.data("select2-data");
3074
3075 if (!data) {
3076 // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
3077 // and invoked on an element already removed
3078 return;
3079 }
3080
3081 var evt = $.Event("select2-removing");
3082 evt.val = this.id(data);
3083 evt.choice = data;
3084 this.opts.element.trigger(evt);
3085
3086 if (evt.isDefaultPrevented()) {
3087 return false;
3088 }
3089
3090 while((index = indexOf(this.id(data), val)) >= 0) {
3091 val.splice(index, 1);
3092 this.setVal(val);
3093 if (this.select) this.postprocessResults();
3094 }
3095
3096 selected.remove();
3097
3098 this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
3099 this.triggerChange({ removed: data });
3100
3101 return true;
3102 },
3103
3104 // multi
3105 postprocessResults: function (data, initial, noHighlightUpdate) {
3106 var val = this.getVal(),
3107 choices = this.results.find(".select2-result"),
3108 compound = this.results.find(".select2-result-with-children"),
3109 self = this;
3110
3111 choices.each2(function (i, choice) {
3112 var id = self.id(choice.data("select2-data"));
3113 if (indexOf(id, val) >= 0) {
3114 choice.addClass("select2-selected");
3115 // mark all children of the selected parent as selected
3116 choice.find(".select2-result-selectable").addClass("select2-selected");
3117 }
3118 });
3119
3120 compound.each2(function(i, choice) {
3121 // hide an optgroup if it doesn't have any selectable children
3122 if (!choice.is('.select2-result-selectable')
3123 && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
3124 choice.addClass("select2-selected");
3125 }
3126 });
3127
3128 if (this.highlight() == -1 && noHighlightUpdate !== false){
3129 self.highlight(0);
3130 }
3131
3132 //If all results are chosen render formatNoMatches
3133 if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
3134 if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
3135 if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
3136 this.results.append("<li class='select2-no-results'>" + evaluate(self.opts.formatNoMatches, self.opts.element, self.search.val()) + "</li>");
3137 }
3138 }
3139 }
3140
3141 },
3142
3143 // multi
3144 getMaxSearchWidth: function() {
3145 return this.selection.width() - getSideBorderPadding(this.search);
3146 },
3147
3148 // multi
3149 resizeSearch: function () {
3150 var minimumWidth, left, maxWidth, containerLeft, searchWidth,
3151 sideBorderPadding = getSideBorderPadding(this.search);
3152
3153 minimumWidth = measureTextWidth(this.search) + 10;
3154
3155 left = this.search.offset().left;
3156
3157 maxWidth = this.selection.width();
3158 containerLeft = this.selection.offset().left;
3159
3160 searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
3161
3162 if (searchWidth < minimumWidth) {
3163 searchWidth = maxWidth - sideBorderPadding;
3164 }
3165
3166 if (searchWidth < 40) {
3167 searchWidth = maxWidth - sideBorderPadding;
3168 }
3169
3170 if (searchWidth <= 0) {
3171 searchWidth = minimumWidth;
3172 }
3173
3174 this.search.width(Math.floor(searchWidth));
3175 },
3176
3177 // multi
3178 getVal: function () {
3179 var val;
3180 if (this.select) {
3181 val = this.select.val();
3182 return val === null ? [] : val;
3183 } else {
3184 val = this.opts.element.val();
3185 return splitVal(val, this.opts.separator);
3186 }
3187 },
3188
3189 // multi
3190 setVal: function (val) {
3191 var unique;
3192 if (this.select) {
3193 this.select.val(val);
3194 } else {
3195 unique = [];
3196 // filter out duplicates
3197 $(val).each(function () {
3198 if (indexOf(this, unique) < 0) unique.push(this);
3199 });
3200 this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
3201 }
3202 },
3203
3204 // multi
3205 buildChangeDetails: function (old, current) {
3206 var current = current.slice(0),
3207 old = old.slice(0);
3208
3209 // remove intersection from each array
3210 for (var i = 0; i < current.length; i++) {
3211 for (var j = 0; j < old.length; j++) {
3212 if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
3213 current.splice(i, 1);
3214 if(i>0){
3215 i--;
3216 }
3217 old.splice(j, 1);
3218 j--;
3219 }
3220 }
3221 }
3222
3223 return {added: current, removed: old};
3224 },
3225
3226
3227 // multi
3228 val: function (val, triggerChange) {
3229 var oldData, self=this;
3230
3231 if (arguments.length === 0) {
3232 return this.getVal();
3233 }
3234
3235 oldData=this.data();
3236 if (!oldData.length) oldData=[];
3237
3238 // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
3239 if (!val && val !== 0) {
3240 this.opts.element.val("");
3241 this.updateSelection([]);
3242 this.clearSearch();
3243 if (triggerChange) {
3244 this.triggerChange({added: this.data(), removed: oldData});
3245 }
3246 return;
3247 }
3248
3249 // val is a list of ids
3250 this.setVal(val);
3251
3252 if (this.select) {
3253 this.opts.initSelection(this.select, this.bind(this.updateSelection));
3254 if (triggerChange) {
3255 this.triggerChange(this.buildChangeDetails(oldData, this.data()));
3256 }
3257 } else {
3258 if (this.opts.initSelection === undefined) {
3259 throw new Error("val() cannot be called if initSelection() is not defined");
3260 }
3261
3262 this.opts.initSelection(this.opts.element, function(data){
3263 var ids=$.map(data, self.id);
3264 self.setVal(ids);
3265 self.updateSelection(data);
3266 self.clearSearch();
3267 if (triggerChange) {
3268 self.triggerChange(self.buildChangeDetails(oldData, self.data()));
3269 }
3270 });
3271 }
3272 this.clearSearch();
3273 },
3274
3275 // multi
3276 onSortStart: function() {
3277 if (this.select) {
3278 throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
3279 }
3280
3281 // collapse search field into 0 width so its container can be collapsed as well
3282 this.search.width(0);
3283 // hide the container
3284 this.searchContainer.hide();
3285 },
3286
3287 // multi
3288 onSortEnd:function() {
3289
3290 var val=[], self=this;
3291
3292 // show search and move it to the end of the list
3293 this.searchContainer.show();
3294 // make sure the search container is the last item in the list
3295 this.searchContainer.appendTo(this.searchContainer.parent());
3296 // since we collapsed the width in dragStarted, we resize it here
3297 this.resizeSearch();
3298
3299 // update selection
3300 this.selection.find(".select2-search-choice").each(function() {
3301 val.push(self.opts.id($(this).data("select2-data")));
3302 });
3303 this.setVal(val);
3304 this.triggerChange();
3305 },
3306
3307 // multi
3308 data: function(values, triggerChange) {
3309 var self=this, ids, old;
3310 if (arguments.length === 0) {
3311 return this.selection
3312 .children(".select2-search-choice")
3313 .map(function() { return $(this).data("select2-data"); })
3314 .get();
3315 } else {
3316 old = this.data();
3317 if (!values) { values = []; }
3318 ids = $.map(values, function(e) { return self.opts.id(e); });
3319 this.setVal(ids);
3320 this.updateSelection(values);
3321 this.clearSearch();
3322 if (triggerChange) {
3323 this.triggerChange(this.buildChangeDetails(old, this.data()));
3324 }
3325 }
3326 }
3327 });
3328
3329 $.fn.select2 = function () {
3330
3331 var args = Array.prototype.slice.call(arguments, 0),
3332 opts,
3333 select2,
3334 method, value, multiple,
3335 allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"],
3336 valueMethods = ["opened", "isFocused", "container", "dropdown"],
3337 propertyMethods = ["val", "data"],
3338 methodsMap = { search: "externalSearch" };
3339
3340 this.each(function () {
3341 if (args.length === 0 || typeof(args[0]) === "object") {
3342 opts = args.length === 0 ? {} : $.extend({}, args[0]);
3343 opts.element = $(this);
3344
3345 if (opts.element.get(0).tagName.toLowerCase() === "select") {
3346 multiple = opts.element.prop("multiple");
3347 } else {
3348 multiple = opts.multiple || false;
3349 if ("tags" in opts) {opts.multiple = multiple = true;}
3350 }
3351
3352 select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single();
3353 select2.init(opts);
3354 } else if (typeof(args[0]) === "string") {
3355
3356 if (indexOf(args[0], allowedMethods) < 0) {
3357 throw "Unknown method: " + args[0];
3358 }
3359
3360 value = undefined;
3361 select2 = $(this).data("select2");
3362 if (select2 === undefined) return;
3363
3364 method=args[0];
3365
3366 if (method === "container") {
3367 value = select2.container;
3368 } else if (method === "dropdown") {
3369 value = select2.dropdown;
3370 } else {
3371 if (methodsMap[method]) method = methodsMap[method];
3372
3373 value = select2[method].apply(select2, args.slice(1));
3374 }
3375 if (indexOf(args[0], valueMethods) >= 0
3376 || (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) {
3377 return false; // abort the iteration, ready to return first matched value
3378 }
3379 } else {
3380 throw "Invalid arguments to select2 plugin: " + args;
3381 }
3382 });
3383 return (value === undefined) ? this : value;
3384 };
3385
3386 // plugin defaults, accessible to users
3387 $.fn.select2.defaults = {
3388 width: "copy",
3389 loadMorePadding: 0,
3390 closeOnSelect: true,
3391 openOnEnter: true,
3392 containerCss: {},
3393 dropdownCss: {},
3394 containerCssClass: "",
3395 dropdownCssClass: "",
3396 formatResult: function(result, container, query, escapeMarkup) {
3397 var markup=[];
3398 markMatch(result.text, query.term, markup, escapeMarkup);
3399 return markup.join("");
3400 },
3401 formatSelection: function (data, container, escapeMarkup) {
3402 return data ? escapeMarkup(data.text) : undefined;
3403 },
3404 sortResults: function (results, container, query) {
3405 return results;
3406 },
3407 formatResultCssClass: function(data) {return data.css;},
3408 formatSelectionCssClass: function(data, container) {return undefined;},
3409 formatMatches: function (matches) { if (matches === 1) { return "One result is available, press enter to select it."; } return matches + " results are available, use up and down arrow keys to navigate."; },
3410 formatNoMatches: function () { return "No matches found"; },
3411 formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1? "" : "s"); },
3412 formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); },
3413 formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
3414 formatLoadMore: function (pageNumber) { return "Loading more results…"; },
3415 formatSearching: function () { return "Searching…"; },
3416 minimumResultsForSearch: 0,
3417 minimumInputLength: 0,
3418 maximumInputLength: null,
3419 maximumSelectionSize: 0,
3420 id: function (e) { return e == undefined ? null : e.id; },
3421 matcher: function(term, text) {
3422 return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0;
3423 },
3424 separator: ",",
3425 tokenSeparators: [],
3426 tokenizer: defaultTokenizer,
3427 escapeMarkup: defaultEscapeMarkup,
3428 blurOnChange: false,
3429 selectOnBlur: false,
3430 adaptContainerCssClass: function(c) { return c; },
3431 adaptDropdownCssClass: function(c) { return null; },
3432 nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; },
3433 searchInputPlaceholder: '',
3434 createSearchChoicePosition: 'top',
3435 shouldFocusInput: function (instance) {
3436 // Attempt to detect touch devices
3437 var supportsTouchEvents = (('ontouchstart' in window) ||
3438 (navigator.msMaxTouchPoints > 0));
3439
3440 // Only devices which support touch events should be special cased
3441 if (!supportsTouchEvents) {
3442 return true;
3443 }
3444
3445 // Never focus the input if search is disabled
3446 if (instance.opts.minimumResultsForSearch < 0) {
3447 return false;
3448 }
3449
3450 return true;
3451 }
3452 };
3453
3454 $.fn.select2.ajaxDefaults = {
3455 transport: $.ajax,
3456 params: {
3457 type: "GET",
3458 cache: false,
3459 dataType: "json"
3460 }
3461 };
3462
3463 // exports
3464 window.Select2 = {
3465 query: {
3466 ajax: ajax,
3467 local: local,
3468 tags: tags
3469 }, util: {
3470 debounce: debounce,
3471 markMatch: markMatch,
3472 escapeMarkup: defaultEscapeMarkup,
3473 stripDiacritics: stripDiacritics
3474 }, "class": {
3475 "abstract": AbstractSelect2,
3476 "single": SingleSelect2,
3477 "multi": MultiSelect2
3478 }
3479 };
3480
3481 }(jQuery));
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
General Comments 0
You need to be logged in to leave comments. Login now