select2-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1471 @@
1
+ /*
2
+ Copyright 2012 Igor Vaynberg
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in
5
+ compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software distributed under the License is
10
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ See the License for the specific language governing permissions and limitations under the License.
12
+ */
13
+ (function ($, undefined) {
14
+ "use strict";
15
+ /*global document, window, jQuery, console */
16
+
17
+ if (window.Select2 !== undefined) {
18
+ return;
19
+ }
20
+
21
+ var KEY, AbstractSelect2, SingleSelect2, MultiSelect2;
22
+
23
+ KEY = {
24
+ TAB: 9,
25
+ ENTER: 13,
26
+ ESC: 27,
27
+ SPACE: 32,
28
+ LEFT: 37,
29
+ UP: 38,
30
+ RIGHT: 39,
31
+ DOWN: 40,
32
+ SHIFT: 16,
33
+ CTRL: 17,
34
+ ALT: 18,
35
+ PAGE_UP: 33,
36
+ PAGE_DOWN: 34,
37
+ HOME: 36,
38
+ END: 35,
39
+ BACKSPACE: 8,
40
+ DELETE: 46,
41
+ isArrow: function (k) {
42
+ k = k.which ? k.which : k;
43
+ switch (k) {
44
+ case KEY.LEFT:
45
+ case KEY.RIGHT:
46
+ case KEY.UP:
47
+ case KEY.DOWN:
48
+ return true;
49
+ }
50
+ return false;
51
+ },
52
+ isControl: function (k) {
53
+ k = k.which ? k.which : k;
54
+ switch (k) {
55
+ case KEY.SHIFT:
56
+ case KEY.CTRL:
57
+ case KEY.ALT:
58
+ return true;
59
+ }
60
+ return false;
61
+ },
62
+ isFunctionKey: function (k) {
63
+ k = k.which ? k.which : k;
64
+ return k >= 112 && k <= 123;
65
+ }
66
+ };
67
+
68
+ function indexOf(value, array) {
69
+ var i = 0, l = array.length, v;
70
+
71
+ if (value.constructor === String) {
72
+ for (; i < l; i = i + 1) if (value.localeCompare(array[i]) === 0) return i;
73
+ } else {
74
+ for (; i < l; i = i + 1) {
75
+ v = array[i];
76
+ if (v.constructor === String) {
77
+ if (v.localeCompare(value) === 0) return i;
78
+ } else {
79
+ if (v === value) return i;
80
+ }
81
+ }
82
+ }
83
+ return -1;
84
+ }
85
+
86
+ /**
87
+ * Compares equality of a and b taking into account that a and b may be strings, in which case localCompare is used
88
+ * @param a
89
+ * @param b
90
+ */
91
+ function equal(a, b) {
92
+ if (a === b) return true;
93
+ if (a === undefined || b === undefined) return false;
94
+ if (a === null || b === null) return false;
95
+ if (a.constructor === String) return a.localeCompare(b) === 0;
96
+ if (b.constructor === String) return b.localeCompare(a) === 0;
97
+ return false;
98
+ }
99
+
100
+ /**
101
+ * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
102
+ * strings
103
+ * @param string
104
+ * @param separator
105
+ */
106
+ function splitVal(string, separator) {
107
+ var val, i, l;
108
+ if (string === null || string.length < 1) return [];
109
+ val = string.split(separator);
110
+ for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
111
+ return val;
112
+ }
113
+
114
+ function getSideBorderPadding(element) {
115
+ return element.outerWidth() - element.width();
116
+ }
117
+
118
+ function installKeyUpChangeEvent(element) {
119
+ element.bind("keydown", function () {
120
+ element.data("keyup-change-value", element.val());
121
+ });
122
+ element.bind("keyup", function () {
123
+ if (element.val() !== element.data("keyup-change-value")) {
124
+ element.trigger("keyup-change");
125
+ }
126
+ });
127
+ }
128
+
129
+ /**
130
+ * filters mouse events so an event is fired only if the mouse moved.
131
+ *
132
+ * filters out mouse events that occur when mouse is stationary but
133
+ * the elements under the pointer are scrolled.
134
+ */
135
+ $(document).delegate("*", "mousemove", function (e) {
136
+ $(document).data("select2-lastpos", {x: e.pageX, y: e.pageY});
137
+ });
138
+ function installFilteredMouseMove(element) {
139
+ element.bind("mousemove", function (e) {
140
+ var lastpos = $(document).data("select2-lastpos");
141
+ if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
142
+ $(e.target).trigger("mousemove-filtered", e);
143
+ }
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
149
+ * within the last quietMillis milliseconds.
150
+ *
151
+ * @param quietMillis number of milliseconds to wait before invoking fn
152
+ * @param fn function to be debounced
153
+ * @return debounced version of fn
154
+ */
155
+ function debounce(quietMillis, fn) {
156
+ var timeout;
157
+ return function () {
158
+ window.clearTimeout(timeout);
159
+ timeout = window.setTimeout(fn, quietMillis);
160
+ };
161
+ }
162
+
163
+ function installDebouncedScroll(threshold, element) {
164
+ var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
165
+ element.bind("scroll", function (e) {
166
+ if (indexOf(e.target, element.get()) >= 0) notify(e);
167
+ });
168
+ }
169
+
170
+ function killEvent(event) {
171
+ event.preventDefault();
172
+ event.stopPropagation();
173
+ }
174
+
175
+ function measureTextWidth(e) {
176
+ var sizer, width;
177
+ sizer = $("<div></div>").css({
178
+ position: "absolute",
179
+ left: "-1000px",
180
+ top: "-1000px",
181
+ display: "none",
182
+ fontSize: e.css("fontSize"),
183
+ fontFamily: e.css("fontFamily"),
184
+ fontStyle: e.css("fontStyle"),
185
+ fontWeight: e.css("fontWeight"),
186
+ letterSpacing: e.css("letterSpacing"),
187
+ textTransform: e.css("textTransform"),
188
+ whiteSpace: "nowrap"
189
+ });
190
+ sizer.text(e.val());
191
+ $("body").append(sizer);
192
+ width = sizer.width();
193
+ sizer.remove();
194
+ return width;
195
+ }
196
+
197
+ /**
198
+ * Produces an ajax-based query function
199
+ *
200
+ * @param options object containing configuration paramters
201
+ * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
202
+ * @param options.url url for the data
203
+ * @param options.data a function(searchTerm, pageNumber) that should return an object containing query string parameters for the above url.
204
+ * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified
205
+ * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
206
+ * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2.
207
+ * The expected format is an object containing the following keys:
208
+ * results array of objects that will be used as choices
209
+ * more (optional) boolean indicating whether there are more results available
210
+ * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
211
+ */
212
+ function ajax(options) {
213
+ var timeout, // current scheduled but not yet executed request
214
+ requestSequence = 0, // sequence used to drop out-of-order responses
215
+ handler = null,
216
+ quietMillis = options.quietMillis || 100;
217
+
218
+ return function (query) {
219
+ window.clearTimeout(timeout);
220
+ timeout = window.setTimeout(function () {
221
+ requestSequence += 1; // increment the sequence
222
+ var requestNumber = requestSequence, // this request's sequence number
223
+ data = options.data, // ajax data function
224
+ transport = options.transport || $.ajax;
225
+
226
+ data = data.call(this, query.term, query.page);
227
+
228
+ if( null !== handler){
229
+ handler.abort();
230
+ }
231
+ handler = transport.call(null, {
232
+ url: options.url,
233
+ dataType: options.dataType,
234
+ data: data,
235
+ success: function (data) {
236
+ if (requestNumber < requestSequence) {
237
+ return;
238
+ }
239
+ // TODO 3.0 - replace query.page with query so users have access to term, page, etc.
240
+ query.callback(options.results(data, query.page));
241
+ }
242
+ });
243
+ }, quietMillis);
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Produces a query function that works with a local array
249
+ *
250
+ * @param options object containing configuration parameters. The options parameter can either be an array or an
251
+ * object.
252
+ *
253
+ * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
254
+ *
255
+ * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
256
+ * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
257
+ * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
258
+ * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
259
+ * the text.
260
+ */
261
+ function local(options) {
262
+ var data = options, // data elements
263
+ text = function (item) { return item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
264
+
265
+ if (!$.isArray(data)) {
266
+ text = data.text;
267
+ // if text is not a function we assume it to be a key name
268
+ if (!$.isFunction(text)) text = function (item) { return item[data.text]; };
269
+ data = data.results;
270
+ }
271
+
272
+ return function (query) {
273
+ var t = query.term.toUpperCase(), filtered = {};
274
+ if (t === "") {
275
+ query.callback({results: data});
276
+ return;
277
+ }
278
+ filtered.results = $(data)
279
+ .filter(function () {return text(this).toUpperCase().indexOf(t) >= 0;})
280
+ .get();
281
+ query.callback(filtered);
282
+ };
283
+ }
284
+
285
+ // TODO javadoc
286
+ function tags(data) {
287
+ // TODO even for a function we should probably return a wrapper that does the same object/string check as
288
+ // the function for arrays. otherwise only functions that return objects are supported.
289
+ if ($.isFunction(data)) {
290
+ return data;
291
+ }
292
+
293
+ // if not a function we assume it to be an array
294
+
295
+ return function (query) {
296
+ var t = query.term.toUpperCase(), filtered = {results: []};
297
+ $(data).each(function () {
298
+ var isObject = this.text !== undefined,
299
+ text = isObject ? this.text : this;
300
+ if (t === "" || text.toUpperCase().indexOf(t) >= 0) {
301
+ filtered.results.push(isObject ? this : {id: this, text: this});
302
+ }
303
+ });
304
+ query.callback(filtered);
305
+ };
306
+ }
307
+
308
+ /**
309
+ * blurs any Select2 container that has focus when an element outside them was clicked or received focus
310
+ */
311
+ $(document).ready(function () {
312
+ $(document).delegate("*", "mousedown focusin", function (e) {
313
+ var target = $(e.target).closest("div.select2-container").get(0);
314
+ $(document).find("div.select2-container-active").each(function () {
315
+ if (this !== target) $(this).data("select2").blur();
316
+ });
317
+ });
318
+ });
319
+
320
+ /**
321
+ * Creates a new class
322
+ *
323
+ * @param superClass
324
+ * @param methods
325
+ */
326
+ function clazz(SuperClass, methods) {
327
+ var constructor = function () {};
328
+ constructor.prototype = new SuperClass;
329
+ constructor.prototype.constructor = constructor;
330
+ constructor.prototype.parent = SuperClass.prototype;
331
+ constructor.prototype = $.extend(constructor.prototype, methods);
332
+ return constructor;
333
+ }
334
+
335
+ AbstractSelect2 = clazz(Object, {
336
+
337
+ bind: function (func) {
338
+ var self = this;
339
+ return function () {
340
+ func.apply(self, arguments);
341
+ };
342
+ },
343
+
344
+ init: function (opts) {
345
+ var results, search, resultsSelector = ".select2-results";
346
+
347
+ // prepare options
348
+ this.opts = opts = this.prepareOpts(opts);
349
+
350
+ this.id=opts.id;
351
+
352
+ // destroy if called on an existing component
353
+ if (opts.element.data("select2") !== undefined) {
354
+ this.destroy();
355
+ }
356
+
357
+ this.container = this.createContainer();
358
+
359
+ if (opts.element.attr("class") !== undefined) {
360
+ this.container.addClass(opts.element.attr("class"));
361
+ }
362
+
363
+ // swap container for the element
364
+ this.opts.element
365
+ .data("select2", this)
366
+ .hide()
367
+ .after(this.container);
368
+ this.container.data("select2", this);
369
+
370
+ this.dropdown = this.container.find(".select2-drop");
371
+ this.results = results = this.container.find(resultsSelector);
372
+ this.search = search = this.container.find("input[type=text]");
373
+
374
+ this.resultsPage = 0;
375
+
376
+ // initialize the container
377
+ this.initContainer();
378
+
379
+ installFilteredMouseMove(this.results);
380
+ this.container.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent));
381
+
382
+ installDebouncedScroll(80, this.results);
383
+ this.container.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded));
384
+
385
+ // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
386
+ if ($.fn.mousewheel) {
387
+ results.mousewheel(function (e, delta, deltaX, deltaY) {
388
+ var top = results.scrollTop(), height;
389
+ if (deltaY > 0 && top - deltaY <= 0) {
390
+ results.scrollTop(0);
391
+ killEvent(e);
392
+ } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
393
+ results.scrollTop(results.get(0).scrollHeight - results.height());
394
+ killEvent(e);
395
+ }
396
+ });
397
+ }
398
+
399
+ installKeyUpChangeEvent(search);
400
+ search.bind("keyup-change", this.bind(this.updateResults));
401
+ search.bind("focus", function () { search.addClass("select2-focused");});
402
+ search.bind("blur", function () { search.removeClass("select2-focused");});
403
+
404
+ this.container.delegate(resultsSelector, "click", this.bind(function (e) {
405
+ if ($(e.target).closest(".select2-result:not(.select2-disabled)").length > 0) {
406
+ this.highlightUnderEvent(e);
407
+ this.selectHighlighted(e);
408
+ } else {
409
+ killEvent(e);
410
+ this.focusSearch();
411
+ }
412
+ }));
413
+
414
+ if ($.isFunction(this.opts.initSelection)) {
415
+ // initialize selection based on the current value of the source element
416
+ this.initSelection();
417
+
418
+ // if the user has provided a function that can set selection based on the value of the source element
419
+ // we monitor the change event on the element and trigger it, allowing for two way synchronization
420
+ this.monitorSource();
421
+ }
422
+ },
423
+
424
+ destroy: function () {
425
+ var select2 = this.opts.element.data("select2");
426
+ if (select2 !== undefined) {
427
+ select2.container.remove();
428
+ select2.opts.element
429
+ .removeData("select2")
430
+ .unbind(".select2")
431
+ .show();
432
+ }
433
+ },
434
+
435
+ prepareOpts: function (opts) {
436
+ var element, select, idKey;
437
+
438
+ element = opts.element;
439
+
440
+ if (element.get(0).tagName.toLowerCase() === "select") {
441
+ this.select = select = opts.element;
442
+ }
443
+
444
+ if (select) {
445
+ // these options are not allowed when attached to a select because they are picked up off the element itself
446
+ $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
447
+ if (this in opts) {
448
+ throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
449
+ }
450
+ });
451
+ }
452
+
453
+ opts = $.extend({}, {
454
+ formatResult: function (data) { return data.text; },
455
+ formatSelection: function (data) { return data.text; },
456
+ formatNoMatches: function () { return "No matches found"; },
457
+ formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; },
458
+ minimumResultsForSearch: 0,
459
+ minimumInputLength: 0,
460
+ id: function (e) { return e.id; }
461
+ }, opts);
462
+
463
+ if (typeof(opts.id) !== "function") {
464
+ idKey = opts.id;
465
+ opts.id = function (e) { return e[idKey]; };
466
+ }
467
+
468
+ if (select) {
469
+ opts.query = this.bind(function (query) {
470
+ var data = {results: [], more: false},
471
+ term = query.term.toUpperCase(),
472
+ placeholder = this.getPlaceholder();
473
+ element.find("option").each(function (i) {
474
+ var e = $(this),
475
+ text = e.text();
476
+
477
+ if (i === 0 && placeholder !== undefined && text === "") return true;
478
+
479
+ if (text.toUpperCase().indexOf(term) >= 0) {
480
+ data.results.push({id: e.attr("value"), text: text});
481
+ }
482
+ });
483
+ query.callback(data);
484
+ });
485
+ // this is needed because inside val() we construct choices from options and there id is hardcoded
486
+ opts.id=function(e) { return e.id; };
487
+ } else {
488
+ if (!("query" in opts)) {
489
+ if ("ajax" in opts) {
490
+ opts.query = ajax(opts.ajax);
491
+ } else if ("data" in opts) {
492
+ opts.query = local(opts.data);
493
+ } else if ("tags" in opts) {
494
+ opts.query = tags(opts.tags);
495
+ opts.createSearchChoice = function (term) { return {id: term, text: term}; };
496
+ opts.initSelection = function (element) {
497
+ var data = [];
498
+ $(splitVal(element.val(), ",")).each(function () {
499
+ data.push({id: this, text: this});
500
+ });
501
+ return data;
502
+ };
503
+ }
504
+ }
505
+ }
506
+ if (typeof(opts.query) !== "function") {
507
+ throw "query function not defined for Select2 " + opts.element.attr("id");
508
+ }
509
+
510
+ return opts;
511
+ },
512
+
513
+ /**
514
+ * Monitor the original element for changes and update select2 accordingly
515
+ */
516
+ monitorSource: function () {
517
+ this.opts.element.bind("change.select2", this.bind(function (e) {
518
+ if (this.opts.element.data("select2-change-triggered") !== true) {
519
+ this.initSelection();
520
+ }
521
+ }));
522
+ },
523
+
524
+ /**
525
+ * Triggers the change event on the source element
526
+ */
527
+ triggerChange: function () {
528
+ // Prevents recursive triggering
529
+ this.opts.element.data("select2-change-triggered", true);
530
+ this.opts.element.trigger("change");
531
+ this.opts.element.data("select2-change-triggered", false);
532
+ },
533
+
534
+ opened: function () {
535
+ return this.container.hasClass("select2-dropdown-open");
536
+ },
537
+
538
+ open: function () {
539
+ if (this.opened()) return;
540
+
541
+ this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
542
+
543
+ this.updateResults(true);
544
+ this.dropdown.show();
545
+ this.focusSearch();
546
+ },
547
+
548
+ close: function () {
549
+ if (!this.opened()) return;
550
+
551
+ this.dropdown.hide();
552
+ this.container.removeClass("select2-dropdown-open");
553
+ this.results.empty();
554
+ this.clearSearch();
555
+ },
556
+
557
+ clearSearch: function () {
558
+
559
+ },
560
+
561
+ ensureHighlightVisible: function () {
562
+ var results = this.results, children, index, child, hb, rb, y, more;
563
+
564
+ children = results.children(".select2-result");
565
+ index = this.highlight();
566
+
567
+ if (index < 0) return;
568
+
569
+ child = $(children[index]);
570
+
571
+ hb = child.offset().top + child.outerHeight();
572
+
573
+ // if this is the last child lets also make sure select2-more-results is visible
574
+ if (index === children.length - 1) {
575
+ more = results.find("li.select2-more-results");
576
+ if (more.length > 0) {
577
+ hb = more.offset().top + more.outerHeight();
578
+ }
579
+ }
580
+
581
+ rb = results.offset().top + results.outerHeight();
582
+ if (hb > rb) {
583
+ results.scrollTop(results.scrollTop() + (hb - rb));
584
+ }
585
+ y = child.offset().top - results.offset().top;
586
+
587
+ // make sure the top of the element is visible
588
+ if (y < 0) {
589
+ results.scrollTop(results.scrollTop() + y); // y is negative
590
+ }
591
+ },
592
+
593
+ moveHighlight: function (delta) {
594
+ var choices = this.results.children(".select2-result"),
595
+ index = this.highlight();
596
+
597
+ while (index > -1 && index < choices.length) {
598
+ index += delta;
599
+ if (!$(choices[index]).hasClass("select2-disabled")) {
600
+ this.highlight(index);
601
+ break;
602
+ }
603
+ }
604
+ },
605
+
606
+ highlight: function (index) {
607
+ var choices = this.results.children(".select2-result");
608
+
609
+ if (arguments.length === 0) {
610
+ return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
611
+ }
612
+
613
+ choices.removeClass("select2-highlighted");
614
+
615
+ if (index >= choices.length) index = choices.length - 1;
616
+ if (index < 0) index = 0;
617
+
618
+ $(choices[index]).addClass("select2-highlighted");
619
+ this.ensureHighlightVisible();
620
+
621
+ if (this.opened()) this.focusSearch();
622
+ },
623
+
624
+ highlightUnderEvent: function (event) {
625
+ var el = $(event.target).closest(".select2-result");
626
+ if (el.length > 0) {
627
+ this.highlight(el.index());
628
+ }
629
+ },
630
+
631
+ loadMoreIfNeeded: function () {
632
+ var results = this.results,
633
+ more = results.find("li.select2-more-results"),
634
+ below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
635
+ offset = -1, // index of first element without data
636
+ page = this.resultsPage + 1;
637
+
638
+ if (more.length === 0) return;
639
+
640
+ below = more.offset().top - results.offset().top - results.height();
641
+
642
+ if (below <= 0) {
643
+ more.addClass("select2-active");
644
+ this.opts.query({term: this.search.val(), page: page, callback: this.bind(function (data) {
645
+ var parts = [], self = this;
646
+ $(data.results).each(function () {
647
+ parts.push("<li class='select2-result'>");
648
+ parts.push(self.opts.formatResult(this));
649
+ parts.push("</li>");
650
+ });
651
+ more.before(parts.join(""));
652
+ results.find(".select2-result").each(function (i) {
653
+ var e = $(this);
654
+ if (e.data("select2-data") !== undefined) {
655
+ offset = i;
656
+ } else {
657
+ e.data("select2-data", data.results[i - offset - 1]);
658
+ }
659
+ });
660
+ if (data.more) {
661
+ more.removeClass("select2-active");
662
+ } else {
663
+ more.remove();
664
+ }
665
+ this.resultsPage = page;
666
+ })});
667
+ }
668
+ },
669
+
670
+ /**
671
+ * @param initial whether or not this is the call to this method right after the dropdown has been opened
672
+ */
673
+ updateResults: function (initial) {
674
+ var search = this.search, results = this.results, opts = this.opts, self=this;
675
+
676
+ search.addClass("select2-active");
677
+
678
+ function render(html) {
679
+ results.html(html);
680
+ results.scrollTop(0);
681
+ search.removeClass("select2-active");
682
+ }
683
+
684
+ if (search.val().length < opts.minimumInputLength) {
685
+ render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
686
+ return;
687
+ }
688
+
689
+ this.resultsPage = 1;
690
+ opts.query({term: search.val(), page: this.resultsPage, callback: this.bind(function (data) {
691
+ var parts = [], // html parts
692
+ def; // default choice
693
+
694
+ // create a default choice and prepend it to the list
695
+ if (this.opts.createSearchChoice && search.val() !== "") {
696
+ def = this.opts.createSearchChoice.call(null, search.val(), data.results);
697
+ if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
698
+ if ($(data.results).filter(
699
+ function () {
700
+ return equal(self.id(this), self.id(def));
701
+ }).length === 0) {
702
+ data.results.unshift(def);
703
+ }
704
+ }
705
+ }
706
+
707
+ if (data.results.length === 0) {
708
+ render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
709
+ return;
710
+ }
711
+
712
+ $(data.results).each(function () {
713
+ parts.push("<li class='select2-result'>");
714
+ parts.push(opts.formatResult(this));
715
+ parts.push("</li>");
716
+ });
717
+
718
+ if (data.more === true) {
719
+ parts.push("<li class='select2-more-results'>Loading more results...</li>");
720
+ }
721
+
722
+ render(parts.join(""));
723
+ results.children(".select2-result").each(function (i) {
724
+ var d = data.results[i];
725
+ $(this).data("select2-data", d);
726
+ });
727
+ this.postprocessResults(data, initial);
728
+ })});
729
+ },
730
+
731
+ cancel: function () {
732
+ this.close();
733
+ },
734
+
735
+ blur: function () {
736
+ /* we do this in a timeout so that current event processing can complete before this code is executed.
737
+ this allows tab index to be preserved even if this code blurs the textfield */
738
+ window.setTimeout(this.bind(function () {
739
+ this.close();
740
+ this.container.removeClass("select2-container-active");
741
+ this.clearSearch();
742
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
743
+ this.search.blur();
744
+ }), 10);
745
+ },
746
+
747
+ focusSearch: function () {
748
+ /* we do this in a timeout so that current event processing can complete before this code is executed.
749
+ this makes sure the search field is focussed even if the current event would blur it */
750
+ window.setTimeout(this.bind(function () {
751
+ this.search.focus();
752
+ }), 10);
753
+ },
754
+
755
+ selectHighlighted: function () {
756
+ var data = this.results.find(".select2-highlighted:not(.select2-disabled)").data("select2-data");
757
+ if (data) {
758
+ this.onSelect(data);
759
+ }
760
+ },
761
+
762
+ getPlaceholder: function () {
763
+ return this.opts.element.attr("placeholder") || this.opts.element.data("placeholder") || this.opts.placeholder;
764
+ },
765
+
766
+ /**
767
+ * Get the desired width for the container element. This is
768
+ * derived first from option `width` passed to select2, then
769
+ * the inline 'style' on the original element, and finally
770
+ * falls back to the jQuery calculated element width.
771
+ *
772
+ * @returns The width string (with units) for the container.
773
+ */
774
+ getContainerWidth: function () {
775
+ var style, attrs, matches, i, l;
776
+ if (this.opts.width !== undefined)
777
+ return this.opts.width;
778
+
779
+ style = this.opts.element.attr('style');
780
+ if (style !== undefined) {
781
+ attrs = style.split(';');
782
+ for (i = 0, l = attrs.length; i < l; i = i + 1) {
783
+ matches = attrs[i].replace(/\s/g, '')
784
+ .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/);
785
+ if (matches !== null && matches.length >= 1)
786
+ return matches[1];
787
+ }
788
+ }
789
+ return this.opts.element.width() + 'px';
790
+ }
791
+ });
792
+
793
+ SingleSelect2 = clazz(AbstractSelect2, {
794
+
795
+ createContainer: function () {
796
+ return $("<div></div>", {
797
+ "class": "select2-container",
798
+ "style": "width: " + this.getContainerWidth()
799
+ }).html([
800
+ " <a href='javascript:void(0)' class='select2-choice'>",
801
+ " <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>",
802
+ " <div><b></b></div>" ,
803
+ "</a>",
804
+ " <div class='select2-drop' style='display:none;'>" ,
805
+ " <div class='select2-search'>" ,
806
+ " <input type='text' autocomplete='off'/>" ,
807
+ " </div>" ,
808
+ " <ul class='select2-results'>" ,
809
+ " </ul>" ,
810
+ "</div>"].join(""));
811
+ },
812
+
813
+ open: function () {
814
+
815
+ if (this.opened()) return;
816
+
817
+ this.parent.open.apply(this, arguments);
818
+
819
+ },
820
+
821
+ close: function () {
822
+ if (!this.opened()) return;
823
+ this.parent.close.apply(this, arguments);
824
+ },
825
+
826
+ focus: function () {
827
+ this.close();
828
+ this.selection.focus();
829
+ },
830
+
831
+ isFocused: function () {
832
+ return this.selection.is(":focus");
833
+ },
834
+
835
+ cancel: function () {
836
+ this.parent.cancel.apply(this, arguments);
837
+ this.selection.focus();
838
+ },
839
+
840
+ initContainer: function () {
841
+
842
+ var selection, container = this.container, clickingInside = false,
843
+ selector = ".select2-choice";
844
+
845
+ this.selection = selection = container.find(selector);
846
+
847
+ this.search.bind("keydown", this.bind(function (e) {
848
+ switch (e.which) {
849
+ case KEY.UP:
850
+ case KEY.DOWN:
851
+ this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
852
+ killEvent(e);
853
+ return;
854
+ case KEY.TAB:
855
+ case KEY.ENTER:
856
+ this.selectHighlighted();
857
+ killEvent(e);
858
+ return;
859
+ case KEY.ESC:
860
+ this.cancel(e);
861
+ e.preventDefault();
862
+ return;
863
+ }
864
+ }));
865
+
866
+ container.delegate(selector, "click", this.bind(function (e) {
867
+ clickingInside = true;
868
+
869
+ if (this.opened()) {
870
+ this.close();
871
+ selection.focus();
872
+ } else {
873
+ this.open();
874
+ }
875
+ e.preventDefault();
876
+
877
+ clickingInside = false;
878
+ }));
879
+ container.delegate(selector, "keydown", this.bind(function (e) {
880
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
881
+ return;
882
+ }
883
+ this.open();
884
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN || e.which === KEY.SPACE) {
885
+ // prevent the page from scrolling
886
+ killEvent(e);
887
+ }
888
+ if (e.which === KEY.ENTER) {
889
+ // do not propagate the event otherwise we open, and propagate enter which closes
890
+ killEvent(e);
891
+ }
892
+ }));
893
+ container.delegate(selector, "focus", function () { container.addClass("select2-container-active"); });
894
+ container.delegate(selector, "blur", this.bind(function () {
895
+ if (clickingInside) return;
896
+ if (!this.opened()) this.blur();
897
+ }));
898
+
899
+ selection.delegate("abbr", "click", this.bind(function (e) {
900
+ this.val("");
901
+ killEvent(e);
902
+ this.close();
903
+ this.triggerChange();
904
+ }));
905
+
906
+ this.setPlaceholder();
907
+ },
908
+
909
+ /**
910
+ * Sets selection based on source element's value
911
+ */
912
+ initSelection: function () {
913
+ var selected;
914
+ if (this.opts.element.val() === "") {
915
+ this.updateSelection({id: "", text: ""});
916
+ } else {
917
+ selected = this.opts.initSelection.call(null, this.opts.element);
918
+ if (selected !== undefined && selected !== null) {
919
+ this.updateSelection(selected);
920
+ }
921
+ }
922
+
923
+ this.close();
924
+ this.setPlaceholder();
925
+ },
926
+
927
+ prepareOpts: function () {
928
+ var opts = this.parent.prepareOpts.apply(this, arguments);
929
+
930
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
931
+ // install sthe selection initializer
932
+ opts.initSelection = function (element) {
933
+ var selected = element.find(":selected");
934
+ // a single select box always has a value, no need to null check 'selected'
935
+ return {id: selected.attr("value"), text: selected.text()};
936
+ };
937
+ }
938
+
939
+ return opts;
940
+ },
941
+
942
+ setPlaceholder: function () {
943
+ var placeholder = this.getPlaceholder();
944
+
945
+ if (this.opts.element.val() === "" && placeholder !== undefined) {
946
+
947
+ // check for a first blank option if attached to a select
948
+ if (this.select && this.select.find("option:first").text() !== "") return;
949
+
950
+ if (typeof(placeholder) === "object") {
951
+ this.updateSelection(placeholder);
952
+ } else {
953
+ this.selection.find("span").html(placeholder);
954
+ }
955
+ this.selection.addClass("select2-default");
956
+
957
+ this.selection.find("abbr").hide();
958
+ }
959
+ },
960
+
961
+ postprocessResults: function (data, initial) {
962
+ var selected = 0, self = this, showSearchInput = true;
963
+
964
+ // find the selected element in the result list
965
+
966
+ this.results.find(".select2-result").each(function (i) {
967
+ if (equal(self.id($(this).data("select2-data")), self.opts.element.val())) {
968
+ selected = i;
969
+ return false;
970
+ }
971
+ });
972
+
973
+ // and highlight it
974
+
975
+ this.highlight(selected);
976
+
977
+ // hide the search box if this is the first we got the results and there are a few of them
978
+
979
+ if (initial === true) {
980
+ showSearchInput = data.results.length >= this.opts.minimumResultsForSearch;
981
+ this.search.parent().toggle(showSearchInput);
982
+
983
+ //add "select2-with-searchbox" to the container if search box is shown
984
+ this.container[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox");
985
+ }
986
+
987
+ },
988
+
989
+ onSelect: function (data) {
990
+ var old = this.opts.element.val();
991
+
992
+ this.opts.element.val(this.id(data));
993
+ this.updateSelection(data);
994
+ this.close();
995
+ this.selection.focus();
996
+
997
+ if (!equal(old, this.id(data))) { this.triggerChange(); }
998
+ },
999
+
1000
+ updateSelection: function (data) {
1001
+ this.selection
1002
+ .find("span")
1003
+ .html(this.opts.formatSelection(data));
1004
+
1005
+ this.selection.removeClass("select2-default");
1006
+
1007
+ if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
1008
+ this.selection.find("abbr").show();
1009
+ }
1010
+ },
1011
+
1012
+ val: function () {
1013
+ var val, data = null;
1014
+
1015
+ if (arguments.length === 0) {
1016
+ return this.opts.element.val();
1017
+ }
1018
+
1019
+ val = arguments[0];
1020
+
1021
+ if (this.select) {
1022
+ // val is an id
1023
+ this.select
1024
+ .val(val)
1025
+ .find(":selected").each(function () {
1026
+ data = {id: $(this).attr("value"), text: $(this).text()};
1027
+ return false;
1028
+ });
1029
+ this.updateSelection(data);
1030
+ } else {
1031
+ // val is an object. !val is true for [undefined,null,'']
1032
+ this.opts.element.val(!val ? "" : this.id(val));
1033
+ this.updateSelection(val);
1034
+ }
1035
+ this.setPlaceholder();
1036
+
1037
+ },
1038
+
1039
+ clearSearch: function () {
1040
+ this.search.val("");
1041
+ }
1042
+ });
1043
+
1044
+ MultiSelect2 = clazz(AbstractSelect2, {
1045
+
1046
+ createContainer: function () {
1047
+ return $("<div></div>", {
1048
+ "class": "select2-container select2-container-multi",
1049
+ "style": "width: " + this.getContainerWidth()
1050
+ }).html([
1051
+ " <ul class='select2-choices'>",
1052
+ //"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" ,
1053
+ " <li class='select2-search-field'>" ,
1054
+ " <input type='text' autocomplete='off' style='width: 25px;'>" ,
1055
+ " </li>" ,
1056
+ "</ul>" ,
1057
+ "<div class='select2-drop' style='display:none;'>" ,
1058
+ " <ul class='select2-results'>" ,
1059
+ " </ul>" ,
1060
+ "</div>"].join(""));
1061
+ },
1062
+
1063
+ prepareOpts: function () {
1064
+ var opts = this.parent.prepareOpts.apply(this, arguments);
1065
+
1066
+ opts = $.extend({}, {
1067
+ closeOnSelect: true
1068
+ }, opts);
1069
+
1070
+ // TODO validate placeholder is a string if specified
1071
+
1072
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
1073
+ // install sthe selection initializer
1074
+ opts.initSelection = function (element) {
1075
+ var data = [];
1076
+ element.find(":selected").each(function () {
1077
+ data.push({id: $(this).attr("value"), text: $(this).text()});
1078
+ });
1079
+ return data;
1080
+ };
1081
+ }
1082
+
1083
+ return opts;
1084
+ },
1085
+
1086
+ initContainer: function () {
1087
+
1088
+ var selector = ".select2-choices", selection;
1089
+
1090
+ this.searchContainer = this.container.find(".select2-search-field");
1091
+ this.selection = selection = this.container.find(selector);
1092
+
1093
+ this.search.bind("keydown", this.bind(function (e) {
1094
+ if (e.which === KEY.BACKSPACE && this.search.val() === "") {
1095
+ this.close();
1096
+
1097
+ var choices,
1098
+ selected = selection.find(".select2-search-choice-focus");
1099
+ if (selected.length > 0) {
1100
+ this.unselect(selected.first());
1101
+ this.search.width(10);
1102
+ killEvent(e);
1103
+ return;
1104
+ }
1105
+
1106
+ choices = selection.find(".select2-search-choice");
1107
+ if (choices.length > 0) {
1108
+ choices.last().addClass("select2-search-choice-focus");
1109
+ }
1110
+ } else {
1111
+ selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
1112
+ }
1113
+
1114
+ if (this.opened()) {
1115
+ switch (e.which) {
1116
+ case KEY.UP:
1117
+ case KEY.DOWN:
1118
+ this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
1119
+ killEvent(e);
1120
+ return;
1121
+ case KEY.ENTER:
1122
+ this.selectHighlighted();
1123
+ killEvent(e);
1124
+ return;
1125
+ case KEY.ESC:
1126
+ this.cancel(e);
1127
+ e.preventDefault();
1128
+ return;
1129
+ }
1130
+ }
1131
+
1132
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
1133
+ return;
1134
+ }
1135
+
1136
+ this.open();
1137
+
1138
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
1139
+ // prevent the page from scrolling
1140
+ killEvent(e);
1141
+ }
1142
+ }));
1143
+
1144
+ this.search.bind("keyup", this.bind(this.resizeSearch));
1145
+
1146
+ this.container.delegate(selector, "click", this.bind(function (e) {
1147
+ this.open();
1148
+ this.focusSearch();
1149
+ e.preventDefault();
1150
+ }));
1151
+
1152
+ this.container.delegate(selector, "focus", this.bind(function () {
1153
+ this.container.addClass("select2-container-active");
1154
+ this.clearPlaceholder();
1155
+ }));
1156
+
1157
+ // set the placeholder if necessary
1158
+ this.clearSearch();
1159
+ },
1160
+
1161
+ initSelection: function () {
1162
+ var data;
1163
+ if (this.opts.element.val() === "") {
1164
+ this.updateSelection([]);
1165
+ }
1166
+ if (this.select || this.opts.element.val() !== "") {
1167
+ data = this.opts.initSelection.call(null, this.opts.element);
1168
+ if (data !== undefined && data !== null) {
1169
+ this.updateSelection(data);
1170
+ }
1171
+ }
1172
+
1173
+ this.close();
1174
+
1175
+ // set the placeholder if necessary
1176
+ this.clearSearch();
1177
+ },
1178
+
1179
+ clearSearch: function () {
1180
+ var placeholder = this.getPlaceholder();
1181
+
1182
+ if (placeholder !== undefined
1183
+ && this.getVal().length === 0
1184
+ && this.search.hasClass("select2-focused") === false) {
1185
+
1186
+ this.search.val(placeholder).addClass("select2-default");
1187
+ // stretch the search box to full width of the container so as much of the placeholder is visible as possible
1188
+ this.search.width(this.getContainerWidth());
1189
+ } else {
1190
+ this.search.val("").width(10);
1191
+ }
1192
+ },
1193
+
1194
+ clearPlaceholder: function () {
1195
+ if (this.search.hasClass("select2-default")) {
1196
+ this.search.val("").removeClass("select2-default");
1197
+ }
1198
+ },
1199
+
1200
+ open: function () {
1201
+ if (this.opened()) return;
1202
+ this.parent.open.apply(this, arguments);
1203
+ this.resizeSearch();
1204
+ this.focusSearch();
1205
+ },
1206
+
1207
+ close: function () {
1208
+ if (!this.opened()) return;
1209
+ this.parent.close.apply(this, arguments);
1210
+ },
1211
+
1212
+ focus: function () {
1213
+ this.close();
1214
+ this.search.focus();
1215
+ },
1216
+
1217
+ isFocused: function () {
1218
+ return this.search.hasClass("select2-focused");
1219
+ },
1220
+
1221
+ updateSelection: function (data) {
1222
+ var ids = [], filtered = [], self = this;
1223
+
1224
+ // filter out duplicates
1225
+ $(data).each(function () {
1226
+ if (indexOf(self.id(this), ids) < 0) {
1227
+ ids.push(self.id(this));
1228
+ filtered.push(this);
1229
+ }
1230
+ });
1231
+ data = filtered;
1232
+
1233
+ this.selection.find(".select2-search-choice").remove();
1234
+ $(data).each(function () {
1235
+ self.addSelectedChoice(this);
1236
+ });
1237
+ self.postprocessResults();
1238
+ },
1239
+
1240
+ onSelect: function (data) {
1241
+ this.addSelectedChoice(data);
1242
+ if (this.select) { this.postprocessResults(); }
1243
+
1244
+ if (this.opts.closeOnSelect) {
1245
+ this.close();
1246
+ this.search.width(10);
1247
+ } else {
1248
+ this.search.width(10);
1249
+ this.resizeSearch();
1250
+ }
1251
+
1252
+ // since its not possible to select an element that has already been
1253
+ // added we do not need to check if this is a new element before firing change
1254
+ this.triggerChange();
1255
+
1256
+ this.focusSearch();
1257
+ },
1258
+
1259
+ cancel: function () {
1260
+ this.close();
1261
+ this.focusSearch();
1262
+ },
1263
+
1264
+ addSelectedChoice: function (data) {
1265
+ var choice,
1266
+ id = this.id(data),
1267
+ parts,
1268
+ val = this.getVal();
1269
+
1270
+ parts = ["<li class='select2-search-choice'>",
1271
+ this.opts.formatSelection(data),
1272
+ "<a href='javascript:void(0)' class='select2-search-choice-close' tabindex='-1'></a>",
1273
+ "</li>"
1274
+ ];
1275
+
1276
+ choice = $(parts.join(""));
1277
+ choice.find("a")
1278
+ .bind("click dblclick", this.bind(function (e) {
1279
+ this.unselect($(e.target));
1280
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
1281
+ killEvent(e);
1282
+ this.close();
1283
+ this.focusSearch();
1284
+ })).bind("focus", this.bind(function () {
1285
+ this.container.addClass("select2-container-active");
1286
+ }));
1287
+
1288
+ choice.data("select2-data", data);
1289
+ choice.insertBefore(this.searchContainer);
1290
+
1291
+ val.push(id);
1292
+ this.setVal(val);
1293
+ },
1294
+
1295
+ unselect: function (selected) {
1296
+ var val = this.getVal(),
1297
+ index;
1298
+
1299
+ selected = selected.closest(".select2-search-choice");
1300
+
1301
+ if (selected.length === 0) {
1302
+ throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
1303
+ }
1304
+
1305
+ index = indexOf(this.id(selected.data("select2-data")), val);
1306
+
1307
+ if (index >= 0) {
1308
+ val.splice(index, 1);
1309
+ this.setVal(val);
1310
+ if (this.select) this.postprocessResults();
1311
+ }
1312
+ selected.remove();
1313
+ this.triggerChange();
1314
+ },
1315
+
1316
+ postprocessResults: function () {
1317
+ var val = this.getVal(),
1318
+ choices = this.results.find(".select2-result"),
1319
+ self = this;
1320
+
1321
+ choices.each(function () {
1322
+ var choice = $(this), id = self.id(choice.data("select2-data"));
1323
+ if (indexOf(id, val) >= 0) {
1324
+ choice.addClass("select2-disabled");
1325
+ } else {
1326
+ choice.removeClass("select2-disabled");
1327
+ }
1328
+ });
1329
+
1330
+ choices.each(function (i) {
1331
+ if (!$(this).hasClass("select2-disabled")) {
1332
+ self.highlight(i);
1333
+ return false;
1334
+ }
1335
+ });
1336
+
1337
+ },
1338
+
1339
+ resizeSearch: function () {
1340
+
1341
+ var minimumWidth, left, maxWidth, containerLeft, searchWidth;
1342
+
1343
+ minimumWidth = measureTextWidth(this.search) + 10;
1344
+
1345
+ left = this.search.offset().left;
1346
+
1347
+ maxWidth = this.selection.width();
1348
+ containerLeft = this.selection.offset().left;
1349
+
1350
+ searchWidth = maxWidth - (left - containerLeft) - getSideBorderPadding(this.search);
1351
+
1352
+ if (searchWidth < minimumWidth) {
1353
+ searchWidth = maxWidth - getSideBorderPadding(this.search);
1354
+ }
1355
+
1356
+ if (searchWidth < 40) {
1357
+ searchWidth = maxWidth - getSideBorderPadding(this.search);
1358
+ }
1359
+ this.search.width(searchWidth);
1360
+ },
1361
+
1362
+ getVal: function () {
1363
+ var val;
1364
+ if (this.select) {
1365
+ val = this.select.val();
1366
+ return val === null ? [] : val;
1367
+ } else {
1368
+ val = this.opts.element.val();
1369
+ return splitVal(val, ",");
1370
+ }
1371
+ },
1372
+
1373
+ setVal: function (val) {
1374
+ var unique = [];
1375
+ if (this.select) {
1376
+ this.select.val(val);
1377
+ } else {
1378
+ // filter out duplicates
1379
+ $(val).each(function () {
1380
+ if (indexOf(this, unique) < 0) unique.push(this);
1381
+ });
1382
+
1383
+ this.opts.element.val(unique.length === 0 ? "" : unique.join(","));
1384
+ }
1385
+ },
1386
+
1387
+ val: function () {
1388
+ var val, data = [], self=this;
1389
+
1390
+ if (arguments.length === 0) {
1391
+ return this.getVal();
1392
+ }
1393
+
1394
+ val = arguments[0];
1395
+
1396
+ if (this.select) {
1397
+ // val is a list of ids
1398
+ this.setVal(val);
1399
+ this.select.find(":selected").each(function () {
1400
+ data.push({id: $(this).attr("value"), text: $(this).text()});
1401
+ });
1402
+ this.updateSelection(data);
1403
+ } else {
1404
+ val = (val === null) ? [] : val;
1405
+ this.setVal(val);
1406
+ // val is a list of objects
1407
+
1408
+ $(val).each(function () { data.push(self.id(this)); });
1409
+ this.setVal(data);
1410
+ this.updateSelection(val);
1411
+ }
1412
+
1413
+ this.clearSearch();
1414
+ }
1415
+ });
1416
+
1417
+ $.fn.select2 = function () {
1418
+
1419
+ var args = Array.prototype.slice.call(arguments, 0),
1420
+ opts,
1421
+ select2,
1422
+ value, multiple, allowedMethods = ["val", "destroy", "open", "close", "focus", "isFocused"];
1423
+
1424
+ this.each(function () {
1425
+ if (args.length === 0 || typeof(args[0]) === "object") {
1426
+ opts = args.length === 0 ? {} : $.extend({}, args[0]);
1427
+ opts.element = $(this);
1428
+
1429
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
1430
+ multiple = opts.element.attr("multiple");
1431
+ } else {
1432
+ multiple = opts.multiple || false;
1433
+ if ("tags" in opts) {opts.multiple = multiple = true;}
1434
+ }
1435
+
1436
+ select2 = multiple ? new MultiSelect2() : new SingleSelect2();
1437
+ select2.init(opts);
1438
+ } else if (typeof(args[0]) === "string") {
1439
+
1440
+ if (indexOf(args[0], allowedMethods) < 0) {
1441
+ throw "Unknown method: " + args[0];
1442
+ }
1443
+
1444
+ value = undefined;
1445
+ select2 = $(this).data("select2");
1446
+ if (select2 === undefined) return;
1447
+ value = select2[args[0]].apply(select2, args.slice(1));
1448
+ if (value !== undefined) {return false;}
1449
+ } else {
1450
+ throw "Invalid arguments to select2 plugin: " + args;
1451
+ }
1452
+ });
1453
+ return (value === undefined) ? this : value;
1454
+ };
1455
+
1456
+ // exports
1457
+ window.Select2 = {
1458
+ query: {
1459
+ ajax: ajax,
1460
+ local: local,
1461
+ tags: tags
1462
+ }, util: {
1463
+ debounce: debounce
1464
+ }, "class": {
1465
+ "abstract": AbstractSelect2,
1466
+ "single": SingleSelect2,
1467
+ "multi": MultiSelect2
1468
+ }
1469
+ };
1470
+
1471
+ }(jQuery));