myreplicator 1.0.6 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/app/assets/javascripts/myreplicator/select2.js +2685 -0
  2. data/app/assets/stylesheets/myreplicator/select2-spinner.gif +0 -0
  3. data/app/assets/stylesheets/myreplicator/select2.css +612 -0
  4. data/app/assets/stylesheets/myreplicator/select2.png +0 -0
  5. data/app/models/myreplicator/export.rb +25 -7
  6. data/app/models/myreplicator/vertica_export.rb +5 -0
  7. data/app/views/myreplicator/exports/_form.html.erb +48 -27
  8. data/app/views/myreplicator/exports/index.html.erb +4 -4
  9. data/db/migrate/20130213211927_create_myreplicator_vertica_exports.rb +22 -0
  10. data/db/migrate/20130213211927_create_myreplicator_vertica_exports.rb~ +8 -0
  11. data/lib/exporter/export_metadata.rb +7 -1
  12. data/lib/exporter/mysql_exporter.rb +103 -29
  13. data/lib/exporter/sql_commands.rb +8 -9
  14. data/lib/exporter.rb +1 -0
  15. data/lib/loader/loader.rb +32 -12
  16. data/lib/loader/vertica/source_db.rb +30 -0
  17. data/lib/loader/vertica/source_db.rb~ +30 -0
  18. data/lib/loader/vertica/types.rb +44 -0
  19. data/lib/loader/vertica/vertica_loader.rb +235 -0
  20. data/lib/loader/vertica/vertica_loader.rb~ +175 -0
  21. data/lib/loader/vertica/vertica_sql.rb +59 -0
  22. data/lib/loader/vertica/vertica_sql.rb~ +60 -0
  23. data/lib/loader/vertica.rb +1 -0
  24. data/lib/myreplicator/version.rb +1 -1
  25. data/lib/transporter/transporter.rb +11 -1
  26. data/test/dummy/app/models/vertica_db.rb +7 -0
  27. data/test/dummy/config/database.yml +7 -0
  28. data/test/dummy/config/database.yml~ +2 -2
  29. data/test/dummy/config/myreplicator.yml +5 -0
  30. data/test/dummy/config/myreplicator.yml~ +0 -2
  31. data/test/dummy/log/development.log +1664 -9510
  32. data/test/dummy/tmp/cache/assets/CD5/B90/sprockets%2Fc999d13a6a21113981c0d820e8043bdf +0 -0
  33. data/test/dummy/tmp/cache/assets/CD7/030/sprockets%2F9ba4859590582b8b72a650b2b00b6cd2 +0 -0
  34. data/test/dummy/tmp/cache/assets/CE5/670/sprockets%2Fe9e4122f1706626a21da6f8457f088ce +0 -0
  35. data/test/dummy/tmp/cache/assets/D06/5D0/sprockets%2F91850a20c0ddfa3d8814ca91870fb715 +0 -0
  36. data/test/dummy/tmp/cache/assets/D14/3A0/sprockets%2Fe59a60053fada52e8185281b4ee887a5 +0 -0
  37. data/test/dummy/tmp/cache/assets/D7C/E30/sprockets%2F0ba91e21bddfc7e1de102b22183e1e11 +0 -0
  38. data/test/dummy/tmp/cache/assets/D8B/B60/sprockets%2Faa32227c440a378ccd21218eefeb80bf +0 -0
  39. data/test/dummy/tmp/cache/assets/DA7/E50/sprockets%2F47bf4f2b4afeac775e6d572a83343fb8 +0 -0
  40. data/test/dummy/tmp/cache/assets/DA8/910/sprockets%2Fab5775c4a837bd4d97ac394d473cda9b +0 -0
  41. data/test/dummy/tmp/cache/assets/DAA/060/sprockets%2Facc0d22b9d28123cc1c84d0db630d0ba +0 -0
  42. data/test/dummy/tmp/cache/assets/DF8/5D0/sprockets%2Fb815ed34d61cfed96222daa3bfd1d84d +0 -0
  43. data/test/dummy/tmp/cache/assets/E1C/AC0/sprockets%2Faff544a3a34eb7dab7d46b0cb2cd7b70 +0 -0
  44. data/test/dummy/tmp/cache/assets/E2E/1F0/sprockets%2Fa24e3d7bc5ae4d40adf6f1b8fe94e7c3 +0 -0
  45. data/test/fixtures/myreplicator/vertica_exports.yml +11 -0
  46. data/test/unit/myreplicator/vertica_export_test.rb +9 -0
  47. metadata +43 -13
  48. data/test/dummy/tmp/myreplicator/okl_test_batchy_batches_1358547945.tsv.gz +0 -0
  49. data/test/dummy/tmp/myreplicator/okl_test_batchy_batches_1358547945.tsv.json +0 -1
@@ -0,0 +1,2685 @@
1
+ /*
2
+ Copyright 2012 Igor Vaynberg
3
+
4
+ Version: @@ver@@ Timestamp: @@timestamp@@
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 Licesnse 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
+ $.fn.extend({
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, $document;
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
+
100
+ $document = $(document);
101
+
102
+ nextUid=(function() { var counter=1; return function() { return counter++; }; }());
103
+
104
+ function indexOf(value, array) {
105
+ var i = 0, l = array.length;
106
+ for (; i < l; i = i + 1) {
107
+ if (equal(value, array[i])) return i;
108
+ }
109
+ return -1;
110
+ }
111
+
112
+ /**
113
+ * Compares equality of a and b
114
+ * @param a
115
+ * @param b
116
+ */
117
+ function equal(a, b) {
118
+ if (a === b) return true;
119
+ if (a === undefined || b === undefined) return false;
120
+ if (a === null || b === null) return false;
121
+ if (a.constructor === String) return a === b+'';
122
+ if (b.constructor === String) return b === a+'';
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
128
+ * strings
129
+ * @param string
130
+ * @param separator
131
+ */
132
+ function splitVal(string, separator) {
133
+ var val, i, l;
134
+ if (string === null || string.length < 1) return [];
135
+ val = string.split(separator);
136
+ for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
137
+ return val;
138
+ }
139
+
140
+ function getSideBorderPadding(element) {
141
+ return element.outerWidth(false) - element.width();
142
+ }
143
+
144
+ function installKeyUpChangeEvent(element) {
145
+ var key="keyup-change-value";
146
+ element.bind("keydown", function () {
147
+ if ($.data(element, key) === undefined) {
148
+ $.data(element, key, element.val());
149
+ }
150
+ });
151
+ element.bind("keyup", function () {
152
+ var val= $.data(element, key);
153
+ if (val !== undefined && element.val() !== val) {
154
+ $.removeData(element, key);
155
+ element.trigger("keyup-change");
156
+ }
157
+ });
158
+ }
159
+
160
+ $document.bind("mousemove", function (e) {
161
+ lastMousePosition = {x: e.pageX, y: e.pageY};
162
+ });
163
+
164
+ /**
165
+ * filters mouse events so an event is fired only if the mouse moved.
166
+ *
167
+ * filters out mouse events that occur when mouse is stationary but
168
+ * the elements under the pointer are scrolled.
169
+ */
170
+ function installFilteredMouseMove(element) {
171
+ element.bind("mousemove", function (e) {
172
+ var lastpos = lastMousePosition;
173
+ if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
174
+ $(e.target).trigger("mousemove-filtered", e);
175
+ }
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
181
+ * within the last quietMillis milliseconds.
182
+ *
183
+ * @param quietMillis number of milliseconds to wait before invoking fn
184
+ * @param fn function to be debounced
185
+ * @param ctx object to be used as this reference within fn
186
+ * @return debounced version of fn
187
+ */
188
+ function debounce(quietMillis, fn, ctx) {
189
+ ctx = ctx || undefined;
190
+ var timeout;
191
+ return function () {
192
+ var args = arguments;
193
+ window.clearTimeout(timeout);
194
+ timeout = window.setTimeout(function() {
195
+ fn.apply(ctx, args);
196
+ }, quietMillis);
197
+ };
198
+ }
199
+
200
+ /**
201
+ * A simple implementation of a thunk
202
+ * @param formula function used to lazily initialize the thunk
203
+ * @return {Function}
204
+ */
205
+ function thunk(formula) {
206
+ var evaluated = false,
207
+ value;
208
+ return function() {
209
+ if (evaluated === false) { value = formula(); evaluated = true; }
210
+ return value;
211
+ };
212
+ };
213
+
214
+ function installDebouncedScroll(threshold, element) {
215
+ var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
216
+ element.bind("scroll", function (e) {
217
+ if (indexOf(e.target, element.get()) >= 0) notify(e);
218
+ });
219
+ }
220
+
221
+ function focus($el) {
222
+ if ($el[0] === document.activeElement) return;
223
+
224
+ /* set the focus in a 0 timeout - that way the focus is set after the processing
225
+ of the current event has finished - which seems like the only reliable way
226
+ to set focus */
227
+ window.setTimeout(function() {
228
+ var el=$el[0], pos=$el.val().length, range;
229
+
230
+ $el.focus();
231
+
232
+ /* after the focus is set move the caret to the end, necessary when we val()
233
+ just before setting focus */
234
+ if(el.setSelectionRange)
235
+ {
236
+ el.setSelectionRange(pos, pos);
237
+ }
238
+ else if (el.createTextRange) {
239
+ range = el.createTextRange();
240
+ range.collapse(true);
241
+ range.moveEnd('character', pos);
242
+ range.moveStart('character', pos);
243
+ range.select();
244
+ }
245
+
246
+ }, 0);
247
+ }
248
+
249
+ function killEvent(event) {
250
+ event.preventDefault();
251
+ event.stopPropagation();
252
+ }
253
+ function killEventImmediately(event) {
254
+ event.preventDefault();
255
+ event.stopImmediatePropagation();
256
+ }
257
+
258
+ function measureTextWidth(e) {
259
+ if (!sizer){
260
+ var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
261
+ sizer = $(document.createElement("div")).css({
262
+ position: "absolute",
263
+ left: "-10000px",
264
+ top: "-10000px",
265
+ display: "none",
266
+ fontSize: style.fontSize,
267
+ fontFamily: style.fontFamily,
268
+ fontStyle: style.fontStyle,
269
+ fontWeight: style.fontWeight,
270
+ letterSpacing: style.letterSpacing,
271
+ textTransform: style.textTransform,
272
+ whiteSpace: "nowrap"
273
+ });
274
+ sizer.attr("class","select2-sizer");
275
+ $("body").append(sizer);
276
+ }
277
+ sizer.text(e.val());
278
+ return sizer.width();
279
+ }
280
+
281
+ function syncCssClasses(dest, src, adapter) {
282
+ var classes, replacements = [], adapted;
283
+
284
+ classes = dest.attr("class");
285
+ if (typeof classes === "string") {
286
+ $(classes.split(" ")).each2(function() {
287
+ if (this.indexOf("select2-") === 0) {
288
+ replacements.push(this);
289
+ }
290
+ });
291
+ }
292
+ classes = src.attr("class");
293
+ if (typeof classes === "string") {
294
+ $(classes.split(" ")).each2(function() {
295
+ if (this.indexOf("select2-") !== 0) {
296
+ adapted = adapter(this);
297
+ if (typeof adapted === "string" && adapted.length > 0) {
298
+ replacements.push(this);
299
+ }
300
+ }
301
+ });
302
+ }
303
+ dest.attr("class", replacements.join(" "));
304
+ }
305
+
306
+
307
+ function markMatch(text, term, markup, escapeMarkup) {
308
+ var match=text.toUpperCase().indexOf(term.toUpperCase()),
309
+ tl=term.length;
310
+
311
+ if (match<0) {
312
+ markup.push(escapeMarkup(text));
313
+ return;
314
+ }
315
+
316
+ markup.push(escapeMarkup(text.substring(0, match)));
317
+ markup.push("<span class='select2-match'>");
318
+ markup.push(escapeMarkup(text.substring(match, match + tl)));
319
+ markup.push("</span>");
320
+ markup.push(escapeMarkup(text.substring(match + tl, text.length)));
321
+ }
322
+
323
+ /**
324
+ * Produces an ajax-based query function
325
+ *
326
+ * @param options object containing configuration paramters
327
+ * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
328
+ * @param options.url url for the data
329
+ * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
330
+ * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified
331
+ * @param options.traditional a boolean flag that should be true if you wish to use the traditional style of param serialization for the ajax request
332
+ * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
333
+ * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2.
334
+ * The expected format is an object containing the following keys:
335
+ * results array of objects that will be used as choices
336
+ * more (optional) boolean indicating whether there are more results available
337
+ * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
338
+ */
339
+ function ajax(options) {
340
+ var timeout, // current scheduled but not yet executed request
341
+ requestSequence = 0, // sequence used to drop out-of-order responses
342
+ handler = null,
343
+ quietMillis = options.quietMillis || 100;
344
+
345
+ return function (query) {
346
+ window.clearTimeout(timeout);
347
+ timeout = window.setTimeout(function () {
348
+ requestSequence += 1; // increment the sequence
349
+ var requestNumber = requestSequence, // this request's sequence number
350
+ data = options.data, // ajax data function
351
+ url = options.url, // ajax url string or function
352
+ transport = options.transport || $.ajax,
353
+ type = options.type || 'GET', // set type of request (GET or POST)
354
+ params = {};
355
+
356
+ data = data ? data.call(this, query.term, query.page, query.context) : null;
357
+ url = (typeof url === 'function') ? url.call(this, query.term, query.page, query.context) : url;
358
+
359
+ if( null !== handler) { handler.abort(); }
360
+
361
+ if (options.params) {
362
+ if ($.isFunction(options.params)) {
363
+ $.extend(params, options.params.call(null));
364
+ } else {
365
+ $.extend(params, options.params);
366
+ }
367
+ }
368
+
369
+ $.extend(params, {
370
+ url: url,
371
+ dataType: options.dataType,
372
+ data: data,
373
+ type: type,
374
+ cache: false,
375
+ success: function (data) {
376
+ if (requestNumber < requestSequence) {
377
+ return;
378
+ }
379
+ // TODO - replace query.page with query so users have access to term, page, etc.
380
+ var results = options.results(data, query.page);
381
+ query.callback(results);
382
+ }
383
+ });
384
+ handler = transport.call(null, params);
385
+ }, quietMillis);
386
+ };
387
+ }
388
+
389
+ /**
390
+ * Produces a query function that works with a local array
391
+ *
392
+ * @param options object containing configuration parameters. The options parameter can either be an array or an
393
+ * object.
394
+ *
395
+ * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
396
+ *
397
+ * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
398
+ * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
399
+ * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
400
+ * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
401
+ * the text.
402
+ */
403
+ function local(options) {
404
+ var data = options, // data elements
405
+ dataText,
406
+ text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
407
+
408
+ if (!$.isArray(data)) {
409
+ text = data.text;
410
+ // if text is not a function we assume it to be a key name
411
+ if (!$.isFunction(text)) {
412
+ dataText = data.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
413
+ text = function (item) { return item[dataText]; };
414
+ }
415
+ data = data.results;
416
+ }
417
+
418
+ return function (query) {
419
+ var t = query.term, filtered = { results: [] }, process;
420
+ if (t === "") {
421
+ query.callback({results: data});
422
+ return;
423
+ }
424
+
425
+ process = function(datum, collection) {
426
+ var group, attr;
427
+ datum = datum[0];
428
+ if (datum.children) {
429
+ group = {};
430
+ for (attr in datum) {
431
+ if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
432
+ }
433
+ group.children=[];
434
+ $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
435
+ if (group.children.length || query.matcher(t, text(group), datum)) {
436
+ collection.push(group);
437
+ }
438
+ } else {
439
+ if (query.matcher(t, text(datum), datum)) {
440
+ collection.push(datum);
441
+ }
442
+ }
443
+ };
444
+
445
+ $(data).each2(function(i, datum) { process(datum, filtered.results); });
446
+ query.callback(filtered);
447
+ };
448
+ }
449
+
450
+ // TODO javadoc
451
+ function tags(data) {
452
+ var isFunc = $.isFunction(data);
453
+ return function (query) {
454
+ var t = query.term, filtered = {results: []};
455
+ $(isFunc ? data() : data).each(function () {
456
+ var isObject = this.text !== undefined,
457
+ text = isObject ? this.text : this;
458
+ if (t === "" || query.matcher(t, text)) {
459
+ filtered.results.push(isObject ? this : {id: this, text: this});
460
+ }
461
+ });
462
+ query.callback(filtered);
463
+ };
464
+ }
465
+
466
+ /**
467
+ * Checks if the formatter function should be used.
468
+ *
469
+ * Throws an error if it is not a function. Returns true if it should be used,
470
+ * false if no formatting should be performed.
471
+ *
472
+ * @param formatter
473
+ */
474
+ function checkFormatter(formatter, formatterName) {
475
+ if ($.isFunction(formatter)) return true;
476
+ if (!formatter) return false;
477
+ throw new Error("formatterName must be a function or a falsy value");
478
+ }
479
+
480
+ function evaluate(val) {
481
+ return $.isFunction(val) ? val() : val;
482
+ }
483
+
484
+ function countResults(results) {
485
+ var count = 0;
486
+ $.each(results, function(i, item) {
487
+ if (item.children) {
488
+ count += countResults(item.children);
489
+ } else {
490
+ count++;
491
+ }
492
+ });
493
+ return count;
494
+ }
495
+
496
+ /**
497
+ * Default tokenizer. This function uses breaks the input on substring match of any string from the
498
+ * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
499
+ * two options have to be defined in order for the tokenizer to work.
500
+ *
501
+ * @param input text user has typed so far or pasted into the search field
502
+ * @param selection currently selected choices
503
+ * @param selectCallback function(choice) callback tho add the choice to selection
504
+ * @param opts select2's opts
505
+ * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
506
+ */
507
+ function defaultTokenizer(input, selection, selectCallback, opts) {
508
+ var original = input, // store the original so we can compare and know if we need to tell the search to update its text
509
+ dupe = false, // check for whether a token we extracted represents a duplicate selected choice
510
+ token, // token
511
+ index, // position at which the separator was found
512
+ i, l, // looping variables
513
+ separator; // the matched separator
514
+
515
+ if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
516
+
517
+ while (true) {
518
+ index = -1;
519
+
520
+ for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
521
+ separator = opts.tokenSeparators[i];
522
+ index = input.indexOf(separator);
523
+ if (index >= 0) break;
524
+ }
525
+
526
+ if (index < 0) break; // did not find any token separator in the input string, bail
527
+
528
+ token = input.substring(0, index);
529
+ input = input.substring(index + separator.length);
530
+
531
+ if (token.length > 0) {
532
+ token = opts.createSearchChoice(token, selection);
533
+ if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
534
+ dupe = false;
535
+ for (i = 0, l = selection.length; i < l; i++) {
536
+ if (equal(opts.id(token), opts.id(selection[i]))) {
537
+ dupe = true; break;
538
+ }
539
+ }
540
+
541
+ if (!dupe) selectCallback(token);
542
+ }
543
+ }
544
+ }
545
+
546
+ if (original!==input) return input;
547
+ }
548
+
549
+ /**
550
+ * Creates a new class
551
+ *
552
+ * @param superClass
553
+ * @param methods
554
+ */
555
+ function clazz(SuperClass, methods) {
556
+ var constructor = function () {};
557
+ constructor.prototype = new SuperClass;
558
+ constructor.prototype.constructor = constructor;
559
+ constructor.prototype.parent = SuperClass.prototype;
560
+ constructor.prototype = $.extend(constructor.prototype, methods);
561
+ return constructor;
562
+ }
563
+
564
+ AbstractSelect2 = clazz(Object, {
565
+
566
+ // abstract
567
+ bind: function (func) {
568
+ var self = this;
569
+ return function () {
570
+ func.apply(self, arguments);
571
+ };
572
+ },
573
+
574
+ // abstract
575
+ init: function (opts) {
576
+ var results, search, resultsSelector = ".select2-results", mask;
577
+
578
+ // prepare options
579
+ this.opts = opts = this.prepareOpts(opts);
580
+
581
+ this.id=opts.id;
582
+
583
+ // destroy if called on an existing component
584
+ if (opts.element.data("select2") !== undefined &&
585
+ opts.element.data("select2") !== null) {
586
+ this.destroy();
587
+ }
588
+
589
+ this.enabled=true;
590
+ this.container = this.createContainer();
591
+
592
+ this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
593
+ this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
594
+ this.container.attr("id", this.containerId);
595
+
596
+ // cache the body so future lookups are cheap
597
+ this.body = thunk(function() { return opts.element.closest("body"); });
598
+
599
+ // create the dropdown mask if doesnt already exist
600
+ mask = $("#select2-drop-mask");
601
+ if (mask.length == 0) {
602
+ mask = $(document.createElement("div"));
603
+ mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
604
+ mask.hide();
605
+ mask.appendTo(this.body());
606
+ mask.bind("mousedown touchstart", function (e) {
607
+ var dropdown = $("#select2-drop"), self;
608
+ if (dropdown.length > 0) {
609
+ self=dropdown.data("select2");
610
+ if (self.opts.selectOnBlur) {
611
+ self.selectHighlighted({noFocus: true});
612
+ }
613
+ self.close();
614
+ }
615
+ });
616
+ }
617
+
618
+ syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
619
+
620
+ this.container.css(evaluate(opts.containerCss));
621
+ this.container.addClass(evaluate(opts.containerCssClass));
622
+
623
+ this.elementTabIndex = this.opts.element.attr("tabIndex");
624
+
625
+ // swap container for the element
626
+ this.opts.element
627
+ .data("select2", this)
628
+ .addClass("select2-offscreen")
629
+ .bind("focus.select2", function() { $(this).select2("focus"); })
630
+ .attr("tabIndex", "-1")
631
+ .before(this.container);
632
+ this.container.data("select2", this);
633
+
634
+ this.dropdown = this.container.find(".select2-drop");
635
+ this.dropdown.addClass(evaluate(opts.dropdownCssClass));
636
+ this.dropdown.data("select2", this);
637
+
638
+ this.results = results = this.container.find(resultsSelector);
639
+ this.search = search = this.container.find("input.select2-input");
640
+
641
+ search.attr("tabIndex", this.elementTabIndex);
642
+
643
+ this.resultsPage = 0;
644
+ this.context = null;
645
+
646
+ // initialize the container
647
+ this.initContainer();
648
+ this.initContainerWidth();
649
+
650
+ installFilteredMouseMove(this.results);
651
+ this.dropdown.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent));
652
+
653
+ installDebouncedScroll(80, this.results);
654
+ this.dropdown.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded));
655
+
656
+ // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
657
+ if ($.fn.mousewheel) {
658
+ results.mousewheel(function (e, delta, deltaX, deltaY) {
659
+ var top = results.scrollTop(), height;
660
+ if (deltaY > 0 && top - deltaY <= 0) {
661
+ results.scrollTop(0);
662
+ killEvent(e);
663
+ } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
664
+ results.scrollTop(results.get(0).scrollHeight - results.height());
665
+ killEvent(e);
666
+ }
667
+ });
668
+ }
669
+
670
+ installKeyUpChangeEvent(search);
671
+ search.bind("keyup-change input paste", this.bind(this.updateResults));
672
+ search.bind("focus", function () { search.addClass("select2-focused"); });
673
+ search.bind("blur", function () { search.removeClass("select2-focused");});
674
+
675
+ this.dropdown.delegate(resultsSelector, "mouseup", this.bind(function (e) {
676
+ if ($(e.target).closest(".select2-result-selectable").length > 0) {
677
+ this.highlightUnderEvent(e);
678
+ this.selectHighlighted(e);
679
+ } else {
680
+ this.focusSearch();
681
+ }
682
+ killEvent(e);
683
+ }));
684
+
685
+ // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
686
+ // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
687
+ // dom it will trigger the popup close, which is not what we want
688
+ this.dropdown.bind("click mouseup mousedown", function (e) { e.stopPropagation(); });
689
+
690
+ if ($.isFunction(this.opts.initSelection)) {
691
+ // initialize selection based on the current value of the source element
692
+ this.initSelection();
693
+
694
+ // if the user has provided a function that can set selection based on the value of the source element
695
+ // we monitor the change event on the element and trigger it, allowing for two way synchronization
696
+ this.monitorSource();
697
+ }
698
+
699
+ if (opts.element.is(":disabled") || opts.element.is("[readonly='readonly']")) this.disable();
700
+ },
701
+
702
+ // abstract
703
+ destroy: function () {
704
+ var select2 = this.opts.element.data("select2");
705
+
706
+ if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
707
+
708
+ if (select2 !== undefined) {
709
+
710
+ select2.container.remove();
711
+ select2.dropdown.remove();
712
+ select2.opts.element
713
+ .removeClass("select2-offscreen")
714
+ .removeData("select2")
715
+ .unbind(".select2")
716
+ .attr({"tabIndex": this.elementTabIndex})
717
+ .show();
718
+ }
719
+ },
720
+
721
+ // abstract
722
+ prepareOpts: function (opts) {
723
+ var element, select, idKey, ajaxUrl;
724
+
725
+ element = opts.element;
726
+
727
+ if (element.get(0).tagName.toLowerCase() === "select") {
728
+ this.select = select = opts.element;
729
+ }
730
+
731
+ if (select) {
732
+ // these options are not allowed when attached to a select because they are picked up off the element itself
733
+ $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
734
+ if (this in opts) {
735
+ throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
736
+ }
737
+ });
738
+ }
739
+
740
+ opts = $.extend({}, {
741
+ populateResults: function(container, results, query) {
742
+ var populate, data, result, children, id=this.opts.id, self=this;
743
+
744
+ populate=function(results, container, depth) {
745
+
746
+ var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
747
+
748
+ results = opts.sortResults(results, container, query);
749
+
750
+ for (i = 0, l = results.length; i < l; i = i + 1) {
751
+
752
+ result=results[i];
753
+
754
+ disabled = (result.disabled === true);
755
+ selectable = (!disabled) && (id(result) !== undefined);
756
+
757
+ compound=result.children && result.children.length > 0;
758
+
759
+ node=$("<li></li>");
760
+ node.addClass("select2-results-dept-"+depth);
761
+ node.addClass("select2-result");
762
+ node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
763
+ if (disabled) { node.addClass("select2-disabled"); }
764
+ if (compound) { node.addClass("select2-result-with-children"); }
765
+ node.addClass(self.opts.formatResultCssClass(result));
766
+
767
+ label=$(document.createElement("div"));
768
+ label.addClass("select2-result-label");
769
+
770
+ formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
771
+ if (formatted!==undefined) {
772
+ label.html(formatted);
773
+ }
774
+
775
+ node.append(label);
776
+
777
+ if (compound) {
778
+
779
+ innerContainer=$("<ul></ul>");
780
+ innerContainer.addClass("select2-result-sub");
781
+ populate(result.children, innerContainer, depth+1);
782
+ node.append(innerContainer);
783
+ }
784
+
785
+ node.data("select2-data", result);
786
+ container.append(node);
787
+ }
788
+ };
789
+
790
+ populate(results, container, 0);
791
+ }
792
+ }, $.fn.select2.defaults, opts);
793
+
794
+ if (typeof(opts.id) !== "function") {
795
+ idKey = opts.id;
796
+ opts.id = function (e) { return e[idKey]; };
797
+ }
798
+
799
+ if ($.isArray(opts.element.data("select2Tags"))) {
800
+ if ("tags" in opts) {
801
+ throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
802
+ }
803
+ opts.tags=opts.element.attr("data-select2-tags");
804
+ }
805
+
806
+ if (select) {
807
+ opts.query = this.bind(function (query) {
808
+ var data = { results: [], more: false },
809
+ term = query.term,
810
+ children, firstChild, process;
811
+
812
+ process=function(element, collection) {
813
+ var group;
814
+ if (element.is("option")) {
815
+ if (query.matcher(term, element.text(), element)) {
816
+ collection.push({id:element.attr("value"), text:element.text(), element: element.get(), css: element.attr("class"), disabled: equal(element.attr("disabled"), "disabled") });
817
+ }
818
+ } else if (element.is("optgroup")) {
819
+ group={text:element.attr("label"), children:[], element: element.get(), css: element.attr("class")};
820
+ element.children().each2(function(i, elm) { process(elm, group.children); });
821
+ if (group.children.length>0) {
822
+ collection.push(group);
823
+ }
824
+ }
825
+ };
826
+
827
+ children=element.children();
828
+
829
+ // ignore the placeholder option if there is one
830
+ if (this.getPlaceholder() !== undefined && children.length > 0) {
831
+ firstChild = children[0];
832
+ if ($(firstChild).text() === "") {
833
+ children=children.not(firstChild);
834
+ }
835
+ }
836
+
837
+ children.each2(function(i, elm) { process(elm, data.results); });
838
+
839
+ query.callback(data);
840
+ });
841
+ // this is needed because inside val() we construct choices from options and there id is hardcoded
842
+ opts.id=function(e) { return e.id; };
843
+ opts.formatResultCssClass = function(data) { return data.css; };
844
+ } else {
845
+ if (!("query" in opts)) {
846
+
847
+ if ("ajax" in opts) {
848
+ ajaxUrl = opts.element.data("ajax-url");
849
+ if (ajaxUrl && ajaxUrl.length > 0) {
850
+ opts.ajax.url = ajaxUrl;
851
+ }
852
+ opts.query = ajax(opts.ajax);
853
+ } else if ("data" in opts) {
854
+ opts.query = local(opts.data);
855
+ } else if ("tags" in opts) {
856
+ opts.query = tags(opts.tags);
857
+ if (opts.createSearchChoice === undefined) {
858
+ opts.createSearchChoice = function (term) { return {id: term, text: term}; };
859
+ }
860
+ if (opts.initSelection === undefined) {
861
+ opts.initSelection = function (element, callback) {
862
+ var data = [];
863
+ $(splitVal(element.val(), opts.separator)).each(function () {
864
+ var id = this, text = this, tags=opts.tags;
865
+ if ($.isFunction(tags)) tags=tags();
866
+ $(tags).each(function() { if (equal(this.id, id)) { text = this.text; return false; } });
867
+ data.push({id: id, text: text});
868
+ });
869
+
870
+ callback(data);
871
+ };
872
+ }
873
+ }
874
+ }
875
+ }
876
+ if (typeof(opts.query) !== "function") {
877
+ throw "query function not defined for Select2 " + opts.element.attr("id");
878
+ }
879
+
880
+ return opts;
881
+ },
882
+
883
+ /**
884
+ * Monitor the original element for changes and update select2 accordingly
885
+ */
886
+ // abstract
887
+ monitorSource: function () {
888
+ var el = this.opts.element, sync;
889
+
890
+ el.bind("change.select2", this.bind(function (e) {
891
+ if (this.opts.element.data("select2-change-triggered") !== true) {
892
+ this.initSelection();
893
+ }
894
+ }));
895
+
896
+ sync = this.bind(function () {
897
+
898
+ var enabled, readonly, self = this;
899
+
900
+ // sync enabled state
901
+
902
+ enabled = this.opts.element.attr("disabled") !== "disabled";
903
+ readonly = this.opts.element.attr("readonly") === "readonly";
904
+
905
+ enabled = enabled && !readonly;
906
+
907
+ if (this.enabled !== enabled) {
908
+ if (enabled) {
909
+ this.enable();
910
+ } else {
911
+ this.disable();
912
+ }
913
+ }
914
+
915
+
916
+ syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
917
+ this.container.addClass(evaluate(this.opts.containerCssClass));
918
+
919
+ syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
920
+ this.dropdown.addClass(evaluate(this.opts.dropdownCssClass));
921
+
922
+ });
923
+
924
+ // mozilla and IE
925
+ el.bind("propertychange.select2 DOMAttrModified.select2", sync);
926
+ // safari and chrome
927
+ if (typeof WebKitMutationObserver !== "undefined") {
928
+ if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
929
+ this.propertyObserver = new WebKitMutationObserver(function (mutations) {
930
+ mutations.forEach(sync);
931
+ });
932
+ this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
933
+ }
934
+ },
935
+
936
+ /**
937
+ * Triggers the change event on the source element
938
+ */
939
+ // abstract
940
+ triggerChange: function (details) {
941
+
942
+ details = details || {};
943
+ details= $.extend({}, details, { type: "change", val: this.val() });
944
+ // prevents recursive triggering
945
+ this.opts.element.data("select2-change-triggered", true);
946
+ this.opts.element.trigger(details);
947
+ this.opts.element.data("select2-change-triggered", false);
948
+
949
+ // some validation frameworks ignore the change event and listen instead to keyup, click for selects
950
+ // so here we trigger the click event manually
951
+ this.opts.element.click();
952
+
953
+ // ValidationEngine ignorea the change event and listens instead to blur
954
+ // so here we trigger the blur event manually if so desired
955
+ if (this.opts.blurOnChange)
956
+ this.opts.element.blur();
957
+ },
958
+
959
+ // abstract
960
+ enable: function() {
961
+ if (this.enabled) return;
962
+
963
+ this.enabled=true;
964
+ this.container.removeClass("select2-container-disabled");
965
+ this.opts.element.removeAttr("disabled");
966
+ },
967
+
968
+ // abstract
969
+ disable: function() {
970
+ if (!this.enabled) return;
971
+
972
+ this.close();
973
+
974
+ this.enabled=false;
975
+ this.container.addClass("select2-container-disabled");
976
+ this.opts.element.attr("disabled", "disabled");
977
+ },
978
+
979
+ // abstract
980
+ opened: function () {
981
+ return this.container.hasClass("select2-dropdown-open");
982
+ },
983
+
984
+ // abstract
985
+ positionDropdown: function() {
986
+ var offset = this.container.offset(),
987
+ height = this.container.outerHeight(false),
988
+ width = this.container.outerWidth(false),
989
+ dropHeight = this.dropdown.outerHeight(false),
990
+ viewPortRight = $(window).scrollLeft() + $(window).width(),
991
+ viewportBottom = $(window).scrollTop() + $(window).height(),
992
+ dropTop = offset.top + height,
993
+ dropLeft = offset.left,
994
+ enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
995
+ enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(),
996
+ dropWidth = this.dropdown.outerWidth(false),
997
+ enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
998
+ aboveNow = this.dropdown.hasClass("select2-drop-above"),
999
+ bodyOffset,
1000
+ above,
1001
+ css;
1002
+
1003
+ //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
1004
+ //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove);
1005
+
1006
+ // fix positioning when body has an offset and is not position: static
1007
+
1008
+ if (this.body().css('position') !== 'static') {
1009
+ bodyOffset = this.body().offset();
1010
+ dropTop -= bodyOffset.top;
1011
+ dropLeft -= bodyOffset.left;
1012
+ }
1013
+
1014
+ // always prefer the current above/below alignment, unless there is not enough room
1015
+
1016
+ if (aboveNow) {
1017
+ above = true;
1018
+ if (!enoughRoomAbove && enoughRoomBelow) above = false;
1019
+ } else {
1020
+ above = false;
1021
+ if (!enoughRoomBelow && enoughRoomAbove) above = true;
1022
+ }
1023
+
1024
+ if (!enoughRoomOnRight) {
1025
+ dropLeft = offset.left + width - dropWidth;
1026
+ }
1027
+
1028
+ if (above) {
1029
+ dropTop = offset.top - dropHeight;
1030
+ this.container.addClass("select2-drop-above");
1031
+ this.dropdown.addClass("select2-drop-above");
1032
+ }
1033
+ else {
1034
+ this.container.removeClass("select2-drop-above");
1035
+ this.dropdown.removeClass("select2-drop-above");
1036
+ }
1037
+
1038
+ css = $.extend({
1039
+ top: dropTop,
1040
+ left: dropLeft,
1041
+ width: width
1042
+ }, evaluate(this.opts.dropdownCss));
1043
+
1044
+ this.dropdown.css(css);
1045
+ },
1046
+
1047
+ // abstract
1048
+ shouldOpen: function() {
1049
+ var event;
1050
+
1051
+ if (this.opened()) return false;
1052
+
1053
+ event = $.Event("opening");
1054
+ this.opts.element.trigger(event);
1055
+ return !event.isDefaultPrevented();
1056
+ },
1057
+
1058
+ // abstract
1059
+ clearDropdownAlignmentPreference: function() {
1060
+ // clear the classes used to figure out the preference of where the dropdown should be opened
1061
+ this.container.removeClass("select2-drop-above");
1062
+ this.dropdown.removeClass("select2-drop-above");
1063
+ },
1064
+
1065
+ /**
1066
+ * Opens the dropdown
1067
+ *
1068
+ * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
1069
+ * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
1070
+ */
1071
+ // abstract
1072
+ open: function () {
1073
+
1074
+ if (!this.shouldOpen()) return false;
1075
+
1076
+ window.setTimeout(this.bind(this.opening), 1);
1077
+
1078
+ return true;
1079
+ },
1080
+
1081
+ /**
1082
+ * Performs the opening of the dropdown
1083
+ */
1084
+ // abstract
1085
+ opening: function() {
1086
+ var cid = this.containerId,
1087
+ scroll = "scroll." + cid,
1088
+ resize = "resize."+cid,
1089
+ orient = "orientationchange."+cid,
1090
+ mask;
1091
+
1092
+ this.clearDropdownAlignmentPreference();
1093
+
1094
+ this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
1095
+
1096
+
1097
+ if(this.dropdown[0] !== this.body().children().last()[0]) {
1098
+ this.dropdown.detach().appendTo(this.body());
1099
+ }
1100
+
1101
+ this.updateResults(true);
1102
+
1103
+ mask = $("#select2-drop-mask");
1104
+
1105
+ // ensure the mask is always right before the dropdown
1106
+ if (this.dropdown.prev()[0] !== mask[0]) {
1107
+ this.dropdown.before(mask);
1108
+ }
1109
+
1110
+ // move the global id to the correct dropdown
1111
+ $("#select2-drop").removeAttr("id");
1112
+ this.dropdown.attr("id", "select2-drop");
1113
+
1114
+ // show the elements
1115
+ mask.css({
1116
+ width: document.documentElement.scrollWidth,
1117
+ height: document.documentElement.scrollHeight});
1118
+ mask.show();
1119
+ this.dropdown.show();
1120
+ this.positionDropdown();
1121
+
1122
+ this.dropdown.addClass("select2-drop-active");
1123
+ this.ensureHighlightVisible();
1124
+
1125
+ // attach listeners to events that can change the position of the container and thus require
1126
+ // the position of the dropdown to be updated as well so it does not come unglued from the container
1127
+ this.container.parents().add(window).each(function () {
1128
+ $(this).bind(resize+" "+scroll+" "+orient, function (e) {
1129
+ $("#select2-drop-mask").css({
1130
+ width:document.documentElement.scrollWidth,
1131
+ height:document.documentElement.scrollHeight});
1132
+ $("#select2-drop").data("select2").positionDropdown();
1133
+ });
1134
+ });
1135
+
1136
+ this.focusSearch();
1137
+ },
1138
+
1139
+ // abstract
1140
+ close: function () {
1141
+ if (!this.opened()) return;
1142
+
1143
+ var cid = this.containerId,
1144
+ scroll = "scroll." + cid,
1145
+ resize = "resize."+cid,
1146
+ orient = "orientationchange."+cid;
1147
+
1148
+ // unbind event listeners
1149
+ this.container.parents().add(window).each(function () { $(this).unbind(scroll).unbind(resize).unbind(orient); });
1150
+
1151
+ this.clearDropdownAlignmentPreference();
1152
+
1153
+ $("#select2-drop-mask").hide();
1154
+ this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
1155
+ this.dropdown.hide();
1156
+ this.container.removeClass("select2-dropdown-open");
1157
+ this.results.empty();
1158
+ this.clearSearch();
1159
+
1160
+ this.opts.element.trigger($.Event("close"));
1161
+ },
1162
+
1163
+ // abstract
1164
+ clearSearch: function () {
1165
+
1166
+ },
1167
+
1168
+ //abstract
1169
+ getMaximumSelectionSize: function() {
1170
+ return evaluate(this.opts.maximumSelectionSize);
1171
+ },
1172
+
1173
+ // abstract
1174
+ ensureHighlightVisible: function () {
1175
+ var results = this.results, children, index, child, hb, rb, y, more;
1176
+
1177
+ index = this.highlight();
1178
+
1179
+ if (index < 0) return;
1180
+
1181
+ if (index == 0) {
1182
+
1183
+ // if the first element is highlighted scroll all the way to the top,
1184
+ // that way any unselectable headers above it will also be scrolled
1185
+ // into view
1186
+
1187
+ results.scrollTop(0);
1188
+ return;
1189
+ }
1190
+
1191
+ children = this.findHighlightableChoices();
1192
+
1193
+ child = $(children[index]);
1194
+
1195
+ hb = child.offset().top + child.outerHeight(true);
1196
+
1197
+ // if this is the last child lets also make sure select2-more-results is visible
1198
+ if (index === children.length - 1) {
1199
+ more = results.find("li.select2-more-results");
1200
+ if (more.length > 0) {
1201
+ hb = more.offset().top + more.outerHeight(true);
1202
+ }
1203
+ }
1204
+
1205
+ rb = results.offset().top + results.outerHeight(true);
1206
+ if (hb > rb) {
1207
+ results.scrollTop(results.scrollTop() + (hb - rb));
1208
+ }
1209
+ y = child.offset().top - results.offset().top;
1210
+
1211
+ // make sure the top of the element is visible
1212
+ if (y < 0 && child.css('display') != 'none' ) {
1213
+ results.scrollTop(results.scrollTop() + y); // y is negative
1214
+ }
1215
+ },
1216
+
1217
+ // abstract
1218
+ findHighlightableChoices: function() {
1219
+ var h=this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)");
1220
+ return this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)");
1221
+ },
1222
+
1223
+ // abstract
1224
+ moveHighlight: function (delta) {
1225
+ var choices = this.findHighlightableChoices(),
1226
+ index = this.highlight();
1227
+
1228
+ while (index > -1 && index < choices.length) {
1229
+ index += delta;
1230
+ var choice = $(choices[index]);
1231
+ if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
1232
+ this.highlight(index);
1233
+ break;
1234
+ }
1235
+ }
1236
+ },
1237
+
1238
+ // abstract
1239
+ highlight: function (index) {
1240
+ var choices = this.findHighlightableChoices(),
1241
+ choice,
1242
+ data;
1243
+
1244
+ if (arguments.length === 0) {
1245
+ return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
1246
+ }
1247
+
1248
+ if (index >= choices.length) index = choices.length - 1;
1249
+ if (index < 0) index = 0;
1250
+
1251
+ this.results.find(".select2-highlighted").removeClass("select2-highlighted");
1252
+
1253
+ choice = $(choices[index]);
1254
+ choice.addClass("select2-highlighted");
1255
+
1256
+ this.ensureHighlightVisible();
1257
+
1258
+ data = choice.data("select2-data");
1259
+ if (data) {
1260
+ this.opts.element.trigger({ type: "highlight", val: this.id(data), choice: data });
1261
+ }
1262
+ },
1263
+
1264
+ // abstract
1265
+ countSelectableResults: function() {
1266
+ return this.findHighlightableChoices().length;
1267
+ },
1268
+
1269
+ // abstract
1270
+ highlightUnderEvent: function (event) {
1271
+ var el = $(event.target).closest(".select2-result-selectable");
1272
+ if (el.length > 0 && !el.is(".select2-highlighted")) {
1273
+ var choices = this.findHighlightableChoices();
1274
+ this.highlight(choices.index(el));
1275
+ } else if (el.length == 0) {
1276
+ // if we are over an unselectable item remove al highlights
1277
+ this.results.find(".select2-highlighted").removeClass("select2-highlighted");
1278
+ }
1279
+ },
1280
+
1281
+ // abstract
1282
+ loadMoreIfNeeded: function () {
1283
+ var results = this.results,
1284
+ more = results.find("li.select2-more-results"),
1285
+ below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
1286
+ offset = -1, // index of first element without data
1287
+ page = this.resultsPage + 1,
1288
+ self=this,
1289
+ term=this.search.val(),
1290
+ context=this.context;
1291
+
1292
+ if (more.length === 0) return;
1293
+ below = more.offset().top - results.offset().top - results.height();
1294
+
1295
+ if (below <= this.opts.loadMorePadding) {
1296
+ more.addClass("select2-active");
1297
+ this.opts.query({
1298
+ element: this.opts.element,
1299
+ term: term,
1300
+ page: page,
1301
+ context: context,
1302
+ matcher: this.opts.matcher,
1303
+ callback: this.bind(function (data) {
1304
+
1305
+ // ignore a response if the select2 has been closed before it was received
1306
+ if (!self.opened()) return;
1307
+
1308
+
1309
+ self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
1310
+
1311
+ if (data.more===true) {
1312
+ more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
1313
+ window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1314
+ } else {
1315
+ more.remove();
1316
+ }
1317
+ self.positionDropdown();
1318
+ self.resultsPage = page;
1319
+ self.context = data.context;
1320
+ })});
1321
+ }
1322
+ },
1323
+
1324
+ /**
1325
+ * Default tokenizer function which does nothing
1326
+ */
1327
+ tokenize: function() {
1328
+
1329
+ },
1330
+
1331
+ /**
1332
+ * @param initial whether or not this is the call to this method right after the dropdown has been opened
1333
+ */
1334
+ // abstract
1335
+ updateResults: function (initial) {
1336
+ var search = this.search, results = this.results, opts = this.opts, data, self=this, input;
1337
+
1338
+ // if the search is currently hidden we do not alter the results
1339
+ if (initial !== true && (this.showSearchInput === false || !this.opened())) {
1340
+ return;
1341
+ }
1342
+
1343
+ search.addClass("select2-active");
1344
+
1345
+ function postRender() {
1346
+ results.scrollTop(0);
1347
+ search.removeClass("select2-active");
1348
+ self.positionDropdown();
1349
+ }
1350
+
1351
+ function render(html) {
1352
+ results.html(html);
1353
+ postRender();
1354
+ }
1355
+
1356
+ var maxSelSize = this.getMaximumSelectionSize();
1357
+ if (maxSelSize >=1) {
1358
+ data = this.data();
1359
+ if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
1360
+ render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(maxSelSize) + "</li>");
1361
+ return;
1362
+ }
1363
+ }
1364
+
1365
+ if (search.val().length < opts.minimumInputLength) {
1366
+ if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
1367
+ render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
1368
+ } else {
1369
+ render("");
1370
+ }
1371
+ return;
1372
+ }
1373
+ else if (opts.formatSearching() && initial===true) {
1374
+ render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
1375
+ }
1376
+
1377
+ if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
1378
+ if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
1379
+ render("<li class='select2-no-results'>" + opts.formatInputTooLong(search.val(), opts.maximumInputLength) + "</li>");
1380
+ } else {
1381
+ render("");
1382
+ }
1383
+ return;
1384
+ }
1385
+
1386
+ // give the tokenizer a chance to pre-process the input
1387
+ input = this.tokenize();
1388
+ if (input != undefined && input != null) {
1389
+ search.val(input);
1390
+ }
1391
+
1392
+ this.resultsPage = 1;
1393
+ opts.query({
1394
+ element: opts.element,
1395
+ term: search.val(),
1396
+ page: this.resultsPage,
1397
+ context: null,
1398
+ matcher: opts.matcher,
1399
+ callback: this.bind(function (data) {
1400
+ var def; // default choice
1401
+
1402
+ // ignore a response if the select2 has been closed before it was received
1403
+ if (!this.opened()) return;
1404
+
1405
+ // save context, if any
1406
+ this.context = (data.context===undefined) ? null : data.context;
1407
+
1408
+ // create a default choice and prepend it to the list
1409
+ if (this.opts.createSearchChoice && search.val() !== "") {
1410
+ def = this.opts.createSearchChoice.call(null, search.val(), data.results);
1411
+ if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
1412
+ if ($(data.results).filter(
1413
+ function () {
1414
+ return equal(self.id(this), self.id(def));
1415
+ }).length === 0) {
1416
+ data.results.unshift(def);
1417
+ }
1418
+ }
1419
+ }
1420
+
1421
+ if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
1422
+ render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
1423
+ return;
1424
+ }
1425
+
1426
+ results.empty();
1427
+ self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
1428
+
1429
+ if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
1430
+ results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>");
1431
+ window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1432
+ }
1433
+
1434
+ this.postprocessResults(data, initial);
1435
+
1436
+ postRender();
1437
+ })});
1438
+ },
1439
+
1440
+ // abstract
1441
+ cancel: function () {
1442
+ this.close();
1443
+ },
1444
+
1445
+ // abstract
1446
+ blur: function () {
1447
+ // if selectOnBlur == true, select the currently highlighted option
1448
+ if (this.opts.selectOnBlur)
1449
+ this.selectHighlighted({noFocus: true});
1450
+
1451
+ this.close();
1452
+ this.container.removeClass("select2-container-active");
1453
+ // synonymous to .is(':focus'), which is available in jquery >= 1.6
1454
+ if (this.search[0] === document.activeElement) { this.search.blur(); }
1455
+ this.clearSearch();
1456
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
1457
+ },
1458
+
1459
+ // abstract
1460
+ focusSearch: function () {
1461
+ focus(this.search);
1462
+ },
1463
+
1464
+ // abstract
1465
+ selectHighlighted: function (options) {
1466
+ var index=this.highlight(),
1467
+ highlighted=this.results.find(".select2-highlighted"),
1468
+ data = highlighted.closest('.select2-result').data("select2-data");
1469
+
1470
+ if (data) {
1471
+ this.highlight(index);
1472
+ this.onSelect(data, options);
1473
+ }
1474
+ },
1475
+
1476
+ // abstract
1477
+ getPlaceholder: function () {
1478
+
1479
+ // if a placeholder is specified on a select without the first empty option ignore it
1480
+ if (this.select) {
1481
+ if (this.select.find("option").first().text() !== "") {
1482
+ return undefined;
1483
+ }
1484
+ }
1485
+
1486
+ return this.opts.element.attr("placeholder") ||
1487
+ this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
1488
+ this.opts.element.data("placeholder") ||
1489
+ this.opts.placeholder;
1490
+ },
1491
+
1492
+ /**
1493
+ * Get the desired width for the container element. This is
1494
+ * derived first from option `width` passed to select2, then
1495
+ * the inline 'style' on the original element, and finally
1496
+ * falls back to the jQuery calculated element width.
1497
+ */
1498
+ // abstract
1499
+ initContainerWidth: function () {
1500
+ function resolveContainerWidth() {
1501
+ var style, attrs, matches, i, l;
1502
+
1503
+ if (this.opts.width === "off") {
1504
+ return null;
1505
+ } else if (this.opts.width === "element"){
1506
+ return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
1507
+ } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
1508
+ // check if there is inline style on the element that contains width
1509
+ style = this.opts.element.attr('style');
1510
+ if (style !== undefined) {
1511
+ attrs = style.split(';');
1512
+ for (i = 0, l = attrs.length; i < l; i = i + 1) {
1513
+ matches = attrs[i].replace(/\s/g, '')
1514
+ .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/);
1515
+ if (matches !== null && matches.length >= 1)
1516
+ return matches[1];
1517
+ }
1518
+ }
1519
+
1520
+ if (this.opts.width === "resolve") {
1521
+ // next check if css('width') can resolve a width that is percent based, this is sometimes possible
1522
+ // when attached to input type=hidden or elements hidden via css
1523
+ style = this.opts.element.css('width');
1524
+ if (style.indexOf("%") > 0) return style;
1525
+
1526
+ // finally, fallback on the calculated width of the element
1527
+ return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
1528
+ }
1529
+
1530
+ return null;
1531
+ } else if ($.isFunction(this.opts.width)) {
1532
+ return this.opts.width();
1533
+ } else {
1534
+ return this.opts.width;
1535
+ }
1536
+ };
1537
+
1538
+ var width = resolveContainerWidth.call(this);
1539
+ if (width !== null) {
1540
+ this.container.css("width", width);
1541
+ }
1542
+ }
1543
+ });
1544
+
1545
+ SingleSelect2 = clazz(AbstractSelect2, {
1546
+
1547
+ // single
1548
+
1549
+ createContainer: function () {
1550
+ var container = $(document.createElement("div")).attr({
1551
+ "class": "select2-container"
1552
+ }).html([
1553
+ "<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>",
1554
+ " <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>",
1555
+ " <div><b></b></div>" ,
1556
+ "</a>",
1557
+ "<input class='select2-focusser select2-offscreen' type='text'/>",
1558
+ "<div class='select2-drop' style='display:none'>" ,
1559
+ " <div class='select2-search'>" ,
1560
+ " <input type='text' autocomplete='off' class='select2-input'/>" ,
1561
+ " </div>" ,
1562
+ " <ul class='select2-results'>" ,
1563
+ " </ul>" ,
1564
+ "</div>"].join(""));
1565
+ return container;
1566
+ },
1567
+
1568
+ // single
1569
+ disable: function() {
1570
+ if (!this.enabled) return;
1571
+
1572
+ this.parent.disable.apply(this, arguments);
1573
+
1574
+ this.focusser.attr("disabled", "disabled");
1575
+ },
1576
+
1577
+ // single
1578
+ enable: function() {
1579
+ if (this.enabled) return;
1580
+
1581
+ this.parent.enable.apply(this, arguments);
1582
+
1583
+ this.focusser.removeAttr("disabled");
1584
+ },
1585
+
1586
+ // single
1587
+ opening: function () {
1588
+ this.parent.opening.apply(this, arguments);
1589
+ this.focusser.attr("disabled", "disabled");
1590
+
1591
+ this.opts.element.trigger($.Event("open"));
1592
+ },
1593
+
1594
+ // single
1595
+ close: function () {
1596
+ if (!this.opened()) return;
1597
+ this.parent.close.apply(this, arguments);
1598
+ this.focusser.removeAttr("disabled");
1599
+ focus(this.focusser);
1600
+ },
1601
+
1602
+ // single
1603
+ focus: function () {
1604
+ if (this.opened()) {
1605
+ this.close();
1606
+ } else {
1607
+ this.focusser.removeAttr("disabled");
1608
+ this.focusser.focus();
1609
+ }
1610
+ },
1611
+
1612
+ // single
1613
+ isFocused: function () {
1614
+ return this.container.hasClass("select2-container-active");
1615
+ },
1616
+
1617
+ // single
1618
+ cancel: function () {
1619
+ this.parent.cancel.apply(this, arguments);
1620
+ this.focusser.removeAttr("disabled");
1621
+ this.focusser.focus();
1622
+ },
1623
+
1624
+ // single
1625
+ initContainer: function () {
1626
+
1627
+ var selection,
1628
+ container = this.container,
1629
+ dropdown = this.dropdown,
1630
+ clickingInside = false;
1631
+
1632
+ this.selection = selection = container.find(".select2-choice");
1633
+
1634
+ this.focusser = container.find(".select2-focusser");
1635
+
1636
+ this.search.bind("keydown", this.bind(function (e) {
1637
+ if (!this.enabled) return;
1638
+
1639
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
1640
+ // prevent the page from scrolling
1641
+ killEvent(e);
1642
+ return;
1643
+ }
1644
+
1645
+ switch (e.which) {
1646
+ case KEY.UP:
1647
+ case KEY.DOWN:
1648
+ this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
1649
+ killEvent(e);
1650
+ return;
1651
+ case KEY.TAB:
1652
+ case KEY.ENTER:
1653
+ this.selectHighlighted();
1654
+ killEvent(e);
1655
+ return;
1656
+ case KEY.ESC:
1657
+ this.cancel(e);
1658
+ killEvent(e);
1659
+ return;
1660
+ }
1661
+ }));
1662
+
1663
+ this.focusser.bind("keydown", this.bind(function (e) {
1664
+ if (!this.enabled) return;
1665
+
1666
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
1667
+ return;
1668
+ }
1669
+
1670
+ if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
1671
+ killEvent(e);
1672
+ return;
1673
+ }
1674
+
1675
+ if (e.which == KEY.DOWN || e.which == KEY.UP
1676
+ || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
1677
+ this.open();
1678
+ killEvent(e);
1679
+ return;
1680
+ }
1681
+
1682
+ if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
1683
+ if (this.opts.allowClear) {
1684
+ this.clear();
1685
+ }
1686
+ killEvent(e);
1687
+ return;
1688
+ }
1689
+ }));
1690
+
1691
+
1692
+ installKeyUpChangeEvent(this.focusser);
1693
+ this.focusser.bind("keyup-change input", this.bind(function(e) {
1694
+ if (this.opened()) return;
1695
+ this.open();
1696
+ this.search.val(this.focusser.val());
1697
+ this.focusser.val("");
1698
+ killEvent(e);
1699
+ }));
1700
+
1701
+ selection.delegate("abbr", "mousedown", this.bind(function (e) {
1702
+ if (!this.enabled) return;
1703
+ this.clear();
1704
+ killEventImmediately(e);
1705
+ this.close();
1706
+ this.triggerChange();
1707
+ this.selection.focus();
1708
+ }));
1709
+
1710
+ selection.bind("mousedown", this.bind(function (e) {
1711
+ clickingInside = true;
1712
+
1713
+ if (this.opened()) {
1714
+ this.close();
1715
+ } else if (this.enabled) {
1716
+ this.open();
1717
+ }
1718
+
1719
+ killEvent(e);
1720
+
1721
+ clickingInside = false;
1722
+ }));
1723
+
1724
+ dropdown.bind("mousedown", this.bind(function() { this.search.focus(); }));
1725
+
1726
+ selection.bind("focus", this.bind(function(e) {
1727
+ killEvent(e);
1728
+ }));
1729
+
1730
+ this.focusser.bind("focus", this.bind(function(){
1731
+ this.container.addClass("select2-container-active");
1732
+ })).bind("blur", this.bind(function() {
1733
+ if (!this.opened()) {
1734
+ this.container.removeClass("select2-container-active");
1735
+ }
1736
+ }));
1737
+ this.search.bind("focus", this.bind(function(){
1738
+ this.container.addClass("select2-container-active");
1739
+ }))
1740
+ this.setPlaceholder();
1741
+
1742
+ },
1743
+
1744
+ // single
1745
+ clear: function() {
1746
+ var data=this.selection.data("select2-data");
1747
+ this.opts.element.val("");
1748
+ this.selection.find("span").empty();
1749
+ this.selection.removeData("select2-data");
1750
+ this.setPlaceholder();
1751
+
1752
+ this.opts.element.trigger({ type: "removed", val: this.id(data), choice: data });
1753
+ this.triggerChange({removed:data});
1754
+ },
1755
+
1756
+ /**
1757
+ * Sets selection based on source element's value
1758
+ */
1759
+ // single
1760
+ initSelection: function () {
1761
+ var selected;
1762
+ if (this.opts.element.val() === "" && this.opts.element.text() === "") {
1763
+ this.close();
1764
+ this.setPlaceholder();
1765
+ } else {
1766
+ var self = this;
1767
+ this.opts.initSelection.call(null, this.opts.element, function(selected){
1768
+ if (selected !== undefined && selected !== null) {
1769
+ self.updateSelection(selected);
1770
+ self.close();
1771
+ self.setPlaceholder();
1772
+ }
1773
+ });
1774
+ }
1775
+ },
1776
+
1777
+ // single
1778
+ prepareOpts: function () {
1779
+ var opts = this.parent.prepareOpts.apply(this, arguments);
1780
+
1781
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
1782
+ // install the selection initializer
1783
+ opts.initSelection = function (element, callback) {
1784
+ var selected = element.find(":selected");
1785
+ // a single select box always has a value, no need to null check 'selected'
1786
+ if ($.isFunction(callback))
1787
+ callback({id: selected.attr("value"), text: selected.text(), element:selected});
1788
+ };
1789
+ } else if ("data" in opts) {
1790
+ // install default initSelection when applied to hidden input and data is local
1791
+ opts.initSelection = opts.initSelection || function (element, callback) {
1792
+ var id = element.val();
1793
+ //search in data by id
1794
+ opts.query({
1795
+ matcher: function(term, text, el){
1796
+ return equal(id, opts.id(el));
1797
+ },
1798
+ callback: !$.isFunction(callback) ? $.noop : function(filtered) {
1799
+ callback(filtered.results.length ? filtered.results[0] : null);
1800
+ }
1801
+ });
1802
+ };
1803
+ }
1804
+
1805
+ return opts;
1806
+ },
1807
+
1808
+ // single
1809
+ setPlaceholder: function () {
1810
+ var placeholder = this.getPlaceholder();
1811
+
1812
+ if (this.opts.element.val() === "" && placeholder !== undefined) {
1813
+
1814
+ // check for a first blank option if attached to a select
1815
+ if (this.select && this.select.find("option:first").text() !== "") return;
1816
+
1817
+ this.selection.find("span").html(this.opts.escapeMarkup(placeholder));
1818
+
1819
+ this.selection.addClass("select2-default");
1820
+
1821
+ this.selection.find("abbr").hide();
1822
+ }
1823
+ },
1824
+
1825
+ // single
1826
+ postprocessResults: function (data, initial) {
1827
+ var selected = 0, self = this, showSearchInput = true;
1828
+
1829
+ // find the selected element in the result list
1830
+
1831
+ this.findHighlightableChoices().each2(function (i, elm) {
1832
+ if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
1833
+ selected = i;
1834
+ return false;
1835
+ }
1836
+ });
1837
+
1838
+ // and highlight it
1839
+
1840
+ this.highlight(selected);
1841
+
1842
+ // hide the search box if this is the first we got the results and there are a few of them
1843
+
1844
+ if (initial === true) {
1845
+ showSearchInput = this.showSearchInput = countResults(data.results) >= this.opts.minimumResultsForSearch;
1846
+ this.dropdown.find(".select2-search")[showSearchInput ? "removeClass" : "addClass"]("select2-search-hidden");
1847
+
1848
+ //add "select2-with-searchbox" to the container if search box is shown
1849
+ $(this.dropdown, this.container)[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox");
1850
+ }
1851
+
1852
+ },
1853
+
1854
+ // single
1855
+ onSelect: function (data, options) {
1856
+ var old = this.opts.element.val();
1857
+
1858
+ this.opts.element.val(this.id(data));
1859
+ this.updateSelection(data);
1860
+
1861
+ this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
1862
+
1863
+ this.close();
1864
+
1865
+ if (!options || !options.noFocus)
1866
+ this.selection.focus();
1867
+
1868
+ if (!equal(old, this.id(data))) { this.triggerChange(); }
1869
+ },
1870
+
1871
+ // single
1872
+ updateSelection: function (data) {
1873
+
1874
+ var container=this.selection.find("span"), formatted;
1875
+
1876
+ this.selection.data("select2-data", data);
1877
+
1878
+ container.empty();
1879
+ formatted=this.opts.formatSelection(data, container);
1880
+ if (formatted !== undefined) {
1881
+ container.append(this.opts.escapeMarkup(formatted));
1882
+ }
1883
+
1884
+ this.selection.removeClass("select2-default");
1885
+
1886
+ if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
1887
+ this.selection.find("abbr").show();
1888
+ }
1889
+ },
1890
+
1891
+ // single
1892
+ val: function () {
1893
+ var val, triggerChange = false, data = null, self = this;
1894
+
1895
+ if (arguments.length === 0) {
1896
+ return this.opts.element.val();
1897
+ }
1898
+
1899
+ val = arguments[0];
1900
+
1901
+ if (arguments.length > 1) {
1902
+ triggerChange = arguments[1];
1903
+ }
1904
+
1905
+ if (this.select) {
1906
+ this.select
1907
+ .val(val)
1908
+ .find(":selected").each2(function (i, elm) {
1909
+ data = {id: elm.attr("value"), text: elm.text()};
1910
+ return false;
1911
+ });
1912
+ this.updateSelection(data);
1913
+ this.setPlaceholder();
1914
+ if (triggerChange) {
1915
+ this.triggerChange();
1916
+ }
1917
+ } else {
1918
+ if (this.opts.initSelection === undefined) {
1919
+ throw new Error("cannot call val() if initSelection() is not defined");
1920
+ }
1921
+ // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
1922
+ if (!val && val !== 0) {
1923
+ this.clear();
1924
+ if (triggerChange) {
1925
+ this.triggerChange();
1926
+ }
1927
+ return;
1928
+ }
1929
+ this.opts.element.val(val);
1930
+ this.opts.initSelection(this.opts.element, function(data){
1931
+ self.opts.element.val(!data ? "" : self.id(data));
1932
+ self.updateSelection(data);
1933
+ self.setPlaceholder();
1934
+ if (triggerChange) {
1935
+ self.triggerChange();
1936
+ }
1937
+ });
1938
+ }
1939
+ },
1940
+
1941
+ // single
1942
+ clearSearch: function () {
1943
+ this.search.val("");
1944
+ this.focusser.val("");
1945
+ },
1946
+
1947
+ // single
1948
+ data: function(value) {
1949
+ var data;
1950
+
1951
+ if (arguments.length === 0) {
1952
+ data = this.selection.data("select2-data");
1953
+ if (data == undefined) data = null;
1954
+ return data;
1955
+ } else {
1956
+ if (!value || value === "") {
1957
+ this.clear();
1958
+ } else {
1959
+ this.opts.element.val(!value ? "" : this.id(value));
1960
+ this.updateSelection(value);
1961
+ }
1962
+ }
1963
+ }
1964
+ });
1965
+
1966
+ MultiSelect2 = clazz(AbstractSelect2, {
1967
+
1968
+ // multi
1969
+ createContainer: function () {
1970
+ var container = $(document.createElement("div")).attr({
1971
+ "class": "select2-container select2-container-multi"
1972
+ }).html([
1973
+ " <ul class='select2-choices'>",
1974
+ //"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" ,
1975
+ " <li class='select2-search-field'>" ,
1976
+ " <input type='text' autocomplete='off' class='select2-input'>" ,
1977
+ " </li>" ,
1978
+ "</ul>" ,
1979
+ "<div class='select2-drop select2-drop-multi' style='display:none;'>" ,
1980
+ " <ul class='select2-results'>" ,
1981
+ " </ul>" ,
1982
+ "</div>"].join(""));
1983
+ return container;
1984
+ },
1985
+
1986
+ // multi
1987
+ prepareOpts: function () {
1988
+ var opts = this.parent.prepareOpts.apply(this, arguments);
1989
+
1990
+ // TODO validate placeholder is a string if specified
1991
+
1992
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
1993
+ // install sthe selection initializer
1994
+ opts.initSelection = function (element, callback) {
1995
+
1996
+ var data = [];
1997
+
1998
+ element.find(":selected").each2(function (i, elm) {
1999
+ data.push({id: elm.attr("value"), text: elm.text(), element: elm[0]});
2000
+ });
2001
+ callback(data);
2002
+ };
2003
+ } else if ("data" in opts) {
2004
+ // install default initSelection when applied to hidden input and data is local
2005
+ opts.initSelection = opts.initSelection || function (element, callback) {
2006
+ var ids = splitVal(element.val(), opts.separator);
2007
+ //search in data by array of ids
2008
+ opts.query({
2009
+ matcher: function(term, text, el){
2010
+ return $.grep(ids, function(id) {
2011
+ return equal(id, opts.id(el));
2012
+ }).length;
2013
+ },
2014
+ callback: !$.isFunction(callback) ? $.noop : function(filtered) {
2015
+ callback(filtered.results);
2016
+ }
2017
+ });
2018
+ };
2019
+ }
2020
+
2021
+ return opts;
2022
+ },
2023
+
2024
+ // multi
2025
+ initContainer: function () {
2026
+
2027
+ var selector = ".select2-choices", selection;
2028
+
2029
+ this.searchContainer = this.container.find(".select2-search-field");
2030
+ this.selection = selection = this.container.find(selector);
2031
+
2032
+ this.search.bind("input paste", this.bind(function() {
2033
+ if (!this.enabled) return;
2034
+ if (!this.opened()) {
2035
+ this.open();
2036
+ }
2037
+ }));
2038
+
2039
+ this.search.bind("keydown", this.bind(function (e) {
2040
+ if (!this.enabled) return;
2041
+
2042
+ if (e.which === KEY.BACKSPACE && this.search.val() === "") {
2043
+ this.close();
2044
+
2045
+ var choices,
2046
+ selected = selection.find(".select2-search-choice-focus");
2047
+ if (selected.length > 0) {
2048
+ this.unselect(selected.first());
2049
+ this.search.width(10);
2050
+ killEvent(e);
2051
+ return;
2052
+ }
2053
+
2054
+ choices = selection.find(".select2-search-choice:not(.select2-locked)");
2055
+ if (choices.length > 0) {
2056
+ choices.last().addClass("select2-search-choice-focus");
2057
+ }
2058
+ } else {
2059
+ selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
2060
+ }
2061
+
2062
+ if (this.opened()) {
2063
+ switch (e.which) {
2064
+ case KEY.UP:
2065
+ case KEY.DOWN:
2066
+ this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2067
+ killEvent(e);
2068
+ return;
2069
+ case KEY.ENTER:
2070
+ case KEY.TAB:
2071
+ this.selectHighlighted();
2072
+ killEvent(e);
2073
+ return;
2074
+ case KEY.ESC:
2075
+ this.cancel(e);
2076
+ killEvent(e);
2077
+ return;
2078
+ }
2079
+ }
2080
+
2081
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
2082
+ || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
2083
+ return;
2084
+ }
2085
+
2086
+ if (e.which === KEY.ENTER) {
2087
+ if (this.opts.openOnEnter === false) {
2088
+ return;
2089
+ } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2090
+ return;
2091
+ }
2092
+ }
2093
+
2094
+ this.open();
2095
+
2096
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2097
+ // prevent the page from scrolling
2098
+ killEvent(e);
2099
+ }
2100
+ }));
2101
+
2102
+ this.search.bind("keyup", this.bind(this.resizeSearch));
2103
+
2104
+ this.search.bind("blur", this.bind(function(e) {
2105
+ this.container.removeClass("select2-container-active");
2106
+ this.search.removeClass("select2-focused");
2107
+ if (!this.opened()) this.clearSearch();
2108
+ e.stopImmediatePropagation();
2109
+ }));
2110
+
2111
+ this.container.delegate(selector, "mousedown", this.bind(function (e) {
2112
+ if (!this.enabled) return;
2113
+ if ($(e.target).closest(".select2-search-choice").length > 0) {
2114
+ // clicked inside a select2 search choice, do not open
2115
+ return;
2116
+ }
2117
+ this.clearPlaceholder();
2118
+ this.open();
2119
+ this.focusSearch();
2120
+ e.preventDefault();
2121
+ }));
2122
+
2123
+ this.container.delegate(selector, "focus", this.bind(function () {
2124
+ if (!this.enabled) return;
2125
+ this.container.addClass("select2-container-active");
2126
+ this.dropdown.addClass("select2-drop-active");
2127
+ this.clearPlaceholder();
2128
+ }));
2129
+
2130
+ // set the placeholder if necessary
2131
+ this.clearSearch();
2132
+ },
2133
+
2134
+ // multi
2135
+ enable: function() {
2136
+ if (this.enabled) return;
2137
+
2138
+ this.parent.enable.apply(this, arguments);
2139
+
2140
+ this.search.removeAttr("disabled");
2141
+ },
2142
+
2143
+ // multi
2144
+ disable: function() {
2145
+ if (!this.enabled) return;
2146
+
2147
+ this.parent.disable.apply(this, arguments);
2148
+
2149
+ this.search.attr("disabled", true);
2150
+ },
2151
+
2152
+ // multi
2153
+ initSelection: function () {
2154
+ var data;
2155
+ if (this.opts.element.val() === "" && this.opts.element.text() === "") {
2156
+ this.updateSelection([]);
2157
+ this.close();
2158
+ // set the placeholder if necessary
2159
+ this.clearSearch();
2160
+ }
2161
+ if (this.select || this.opts.element.val() !== "") {
2162
+ var self = this;
2163
+ this.opts.initSelection.call(null, this.opts.element, function(data){
2164
+ if (data !== undefined && data !== null) {
2165
+ self.updateSelection(data);
2166
+ self.close();
2167
+ // set the placeholder if necessary
2168
+ self.clearSearch();
2169
+ }
2170
+ });
2171
+ }
2172
+ },
2173
+
2174
+ // multi
2175
+ clearSearch: function () {
2176
+ var placeholder = this.getPlaceholder();
2177
+
2178
+ if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
2179
+ this.search.val(placeholder).addClass("select2-default");
2180
+ // stretch the search box to full width of the container so as much of the placeholder is visible as possible
2181
+ this.resizeSearch();
2182
+ } else {
2183
+ this.search.val("").width(10);
2184
+ }
2185
+ },
2186
+
2187
+ // multi
2188
+ clearPlaceholder: function () {
2189
+ if (this.search.hasClass("select2-default")) {
2190
+ this.search.val("").removeClass("select2-default");
2191
+ }
2192
+ },
2193
+
2194
+ // multi
2195
+ opening: function () {
2196
+ this.parent.opening.apply(this, arguments);
2197
+
2198
+ this.clearPlaceholder();
2199
+ this.resizeSearch();
2200
+ this.focusSearch();
2201
+
2202
+ this.opts.element.trigger($.Event("open"));
2203
+ },
2204
+
2205
+ // multi
2206
+ close: function () {
2207
+ if (!this.opened()) return;
2208
+ this.parent.close.apply(this, arguments);
2209
+ },
2210
+
2211
+ // multi
2212
+ focus: function () {
2213
+ this.close();
2214
+ this.search.focus();
2215
+ this.opts.element.triggerHandler("focus");
2216
+ },
2217
+
2218
+ // multi
2219
+ isFocused: function () {
2220
+ return this.search.hasClass("select2-focused");
2221
+ },
2222
+
2223
+ // multi
2224
+ updateSelection: function (data) {
2225
+ var ids = [], filtered = [], self = this;
2226
+
2227
+ // filter out duplicates
2228
+ $(data).each(function () {
2229
+ if (indexOf(self.id(this), ids) < 0) {
2230
+ ids.push(self.id(this));
2231
+ filtered.push(this);
2232
+ }
2233
+ });
2234
+ data = filtered;
2235
+
2236
+ this.selection.find(".select2-search-choice").remove();
2237
+ $(data).each(function () {
2238
+ self.addSelectedChoice(this);
2239
+ });
2240
+ self.postprocessResults();
2241
+ },
2242
+
2243
+ tokenize: function() {
2244
+ var input = this.search.val();
2245
+ input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts);
2246
+ if (input != null && input != undefined) {
2247
+ this.search.val(input);
2248
+ if (input.length > 0) {
2249
+ this.open();
2250
+ }
2251
+ }
2252
+
2253
+ },
2254
+
2255
+ // multi
2256
+ onSelect: function (data, options) {
2257
+ this.addSelectedChoice(data);
2258
+
2259
+ this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
2260
+
2261
+ if (this.select || !this.opts.closeOnSelect) this.postprocessResults();
2262
+
2263
+ if (this.opts.closeOnSelect) {
2264
+ this.close();
2265
+ this.search.width(10);
2266
+ } else {
2267
+ if (this.countSelectableResults()>0) {
2268
+ this.search.width(10);
2269
+ this.resizeSearch();
2270
+ if (this.val().length >= this.getMaximumSelectionSize()) {
2271
+ // if we reached max selection size repaint the results so choices
2272
+ // are replaced with the max selection reached message
2273
+ this.updateResults(true);
2274
+ }
2275
+ this.positionDropdown();
2276
+ } else {
2277
+ // if nothing left to select close
2278
+ this.close();
2279
+ this.search.width(10);
2280
+ }
2281
+ }
2282
+
2283
+ // since its not possible to select an element that has already been
2284
+ // added we do not need to check if this is a new element before firing change
2285
+ this.triggerChange({ added: data });
2286
+
2287
+ if (!options || !options.noFocus)
2288
+ this.focusSearch();
2289
+ },
2290
+
2291
+ // multi
2292
+ cancel: function () {
2293
+ this.close();
2294
+ this.focusSearch();
2295
+ },
2296
+
2297
+ addSelectedChoice: function (data) {
2298
+ var enableChoice = !data.locked,
2299
+ enabledItem = $(
2300
+ "<li class='select2-search-choice'>" +
2301
+ " <div></div>" +
2302
+ " <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" +
2303
+ "</li>"),
2304
+ disabledItem = $(
2305
+ "<li class='select2-search-choice select2-locked'>" +
2306
+ "<div></div>" +
2307
+ "</li>");
2308
+ var choice = enableChoice ? enabledItem : disabledItem,
2309
+ id = this.id(data),
2310
+ val = this.getVal(),
2311
+ formatted;
2312
+
2313
+ formatted=this.opts.formatSelection(data, choice.find("div"));
2314
+ if (formatted != undefined) {
2315
+ choice.find("div").replaceWith("<div>"+this.opts.escapeMarkup(formatted)+"</div>");
2316
+ }
2317
+
2318
+ if(enableChoice){
2319
+ choice.find(".select2-search-choice-close")
2320
+ .bind("mousedown", killEvent)
2321
+ .bind("click dblclick", this.bind(function (e) {
2322
+ if (!this.enabled) return;
2323
+
2324
+ $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){
2325
+ this.unselect($(e.target));
2326
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
2327
+ this.close();
2328
+ this.focusSearch();
2329
+ })).dequeue();
2330
+ killEvent(e);
2331
+ })).bind("focus", this.bind(function () {
2332
+ if (!this.enabled) return;
2333
+ this.container.addClass("select2-container-active");
2334
+ this.dropdown.addClass("select2-drop-active");
2335
+ }));
2336
+ }
2337
+
2338
+ choice.data("select2-data", data);
2339
+ choice.insertBefore(this.searchContainer);
2340
+
2341
+ val.push(id);
2342
+ this.setVal(val);
2343
+ },
2344
+
2345
+ // multi
2346
+ unselect: function (selected) {
2347
+ var val = this.getVal(),
2348
+ data,
2349
+ index;
2350
+
2351
+ selected = selected.closest(".select2-search-choice");
2352
+
2353
+ if (selected.length === 0) {
2354
+ throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
2355
+ }
2356
+
2357
+ data = selected.data("select2-data");
2358
+
2359
+ if (!data) {
2360
+ // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
2361
+ // and invoked on an element already removed
2362
+ return;
2363
+ }
2364
+
2365
+ index = indexOf(this.id(data), val);
2366
+
2367
+ if (index >= 0) {
2368
+ val.splice(index, 1);
2369
+ this.setVal(val);
2370
+ if (this.select) this.postprocessResults();
2371
+ }
2372
+ selected.remove();
2373
+
2374
+ this.opts.element.trigger({ type: "removed", val: this.id(data), choice: data });
2375
+ this.triggerChange({ removed: data });
2376
+ },
2377
+
2378
+ // multi
2379
+ postprocessResults: function () {
2380
+ var val = this.getVal(),
2381
+ choices = this.results.find(".select2-result"),
2382
+ compound = this.results.find(".select2-result-with-children"),
2383
+ self = this;
2384
+
2385
+ choices.each2(function (i, choice) {
2386
+ var id = self.id(choice.data("select2-data"));
2387
+ if (indexOf(id, val) >= 0) {
2388
+ choice.addClass("select2-selected");
2389
+ // mark all children of the selected parent as selected
2390
+ choice.find(".select2-result-selectable").addClass("select2-selected");
2391
+ }
2392
+ });
2393
+
2394
+ compound.each2(function(i, choice) {
2395
+ // hide an optgroup if it doesnt have any selectable children
2396
+ if (!choice.is('.select2-result-selectable')
2397
+ && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
2398
+ choice.addClass("select2-selected");
2399
+ }
2400
+ });
2401
+
2402
+ if (this.highlight() == -1){
2403
+ self.highlight(0);
2404
+ }
2405
+
2406
+ },
2407
+
2408
+ // multi
2409
+ resizeSearch: function () {
2410
+
2411
+ var minimumWidth, left, maxWidth, containerLeft, searchWidth,
2412
+ sideBorderPadding = getSideBorderPadding(this.search);
2413
+
2414
+ minimumWidth = measureTextWidth(this.search) + 10;
2415
+
2416
+ left = this.search.offset().left;
2417
+
2418
+ maxWidth = this.selection.width();
2419
+ containerLeft = this.selection.offset().left;
2420
+
2421
+ searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
2422
+ if (searchWidth < minimumWidth) {
2423
+ searchWidth = maxWidth - sideBorderPadding;
2424
+ }
2425
+
2426
+ if (searchWidth < 40) {
2427
+ searchWidth = maxWidth - sideBorderPadding;
2428
+ }
2429
+
2430
+ if (searchWidth <= 0) {
2431
+ searchWidth = minimumWidth;
2432
+ }
2433
+
2434
+ this.search.width(searchWidth);
2435
+ },
2436
+
2437
+ // multi
2438
+ getVal: function () {
2439
+ var val;
2440
+ if (this.select) {
2441
+ val = this.select.val();
2442
+ return val === null ? [] : val;
2443
+ } else {
2444
+ val = this.opts.element.val();
2445
+ return splitVal(val, this.opts.separator);
2446
+ }
2447
+ },
2448
+
2449
+ // multi
2450
+ setVal: function (val) {
2451
+ var unique;
2452
+ if (this.select) {
2453
+ this.select.val(val);
2454
+ } else {
2455
+ unique = [];
2456
+ // filter out duplicates
2457
+ $(val).each(function () {
2458
+ if (indexOf(this, unique) < 0) unique.push(this);
2459
+ });
2460
+ this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
2461
+ }
2462
+ },
2463
+
2464
+ // multi
2465
+ val: function () {
2466
+ var val, triggerChange = false, data = [], self=this;
2467
+
2468
+ if (arguments.length === 0) {
2469
+ return this.getVal();
2470
+ }
2471
+
2472
+ val = arguments[0];
2473
+
2474
+ if (arguments.length > 1) {
2475
+ triggerChange = arguments[1];
2476
+ }
2477
+
2478
+ // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
2479
+ if (!val && val !== 0) {
2480
+ this.opts.element.val("");
2481
+ this.updateSelection([]);
2482
+ this.clearSearch();
2483
+ if (triggerChange) {
2484
+ this.triggerChange();
2485
+ }
2486
+ return;
2487
+ }
2488
+
2489
+ // val is a list of ids
2490
+ this.setVal(val);
2491
+
2492
+ if (this.select) {
2493
+ this.opts.initSelection(this.select, this.bind(this.updateSelection));
2494
+ if (triggerChange) {
2495
+ this.triggerChange();
2496
+ }
2497
+ } else {
2498
+ if (this.opts.initSelection === undefined) {
2499
+ throw new Error("val() cannot be called if initSelection() is not defined");
2500
+ }
2501
+
2502
+ this.opts.initSelection(this.opts.element, function(data){
2503
+ var ids=$(data).map(self.id);
2504
+ self.setVal(ids);
2505
+ self.updateSelection(data);
2506
+ self.clearSearch();
2507
+ if (triggerChange) {
2508
+ self.triggerChange();
2509
+ }
2510
+ });
2511
+ }
2512
+ this.clearSearch();
2513
+ },
2514
+
2515
+ // multi
2516
+ onSortStart: function() {
2517
+ if (this.select) {
2518
+ throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
2519
+ }
2520
+
2521
+ // collapse search field into 0 width so its container can be collapsed as well
2522
+ this.search.width(0);
2523
+ // hide the container
2524
+ this.searchContainer.hide();
2525
+ },
2526
+
2527
+ // multi
2528
+ onSortEnd:function() {
2529
+
2530
+ var val=[], self=this;
2531
+
2532
+ // show search and move it to the end of the list
2533
+ this.searchContainer.show();
2534
+ // make sure the search container is the last item in the list
2535
+ this.searchContainer.appendTo(this.searchContainer.parent());
2536
+ // since we collapsed the width in dragStarted, we resize it here
2537
+ this.resizeSearch();
2538
+
2539
+ // update selection
2540
+
2541
+ this.selection.find(".select2-search-choice").each(function() {
2542
+ val.push(self.opts.id($(this).data("select2-data")));
2543
+ });
2544
+ this.setVal(val);
2545
+ this.triggerChange();
2546
+ },
2547
+
2548
+ // multi
2549
+ data: function(values) {
2550
+ var self=this, ids;
2551
+ if (arguments.length === 0) {
2552
+ return this.selection
2553
+ .find(".select2-search-choice")
2554
+ .map(function() { return $(this).data("select2-data"); })
2555
+ .get();
2556
+ } else {
2557
+ if (!values) { values = []; }
2558
+ ids = $.map(values, function(e) { return self.opts.id(e); });
2559
+ this.setVal(ids);
2560
+ this.updateSelection(values);
2561
+ this.clearSearch();
2562
+ }
2563
+ }
2564
+ });
2565
+
2566
+ $.fn.select2 = function () {
2567
+
2568
+ var args = Array.prototype.slice.call(arguments, 0),
2569
+ opts,
2570
+ select2,
2571
+ value, multiple, allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable", "positionDropdown", "data"];
2572
+
2573
+ this.each(function () {
2574
+ if (args.length === 0 || typeof(args[0]) === "object") {
2575
+ opts = args.length === 0 ? {} : $.extend({}, args[0]);
2576
+ opts.element = $(this);
2577
+
2578
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
2579
+ multiple = opts.element.attr("multiple");
2580
+ } else {
2581
+ multiple = opts.multiple || false;
2582
+ if ("tags" in opts) {opts.multiple = multiple = true;}
2583
+ }
2584
+
2585
+ select2 = multiple ? new MultiSelect2() : new SingleSelect2();
2586
+ select2.init(opts);
2587
+ } else if (typeof(args[0]) === "string") {
2588
+
2589
+ if (indexOf(args[0], allowedMethods) < 0) {
2590
+ throw "Unknown method: " + args[0];
2591
+ }
2592
+
2593
+ value = undefined;
2594
+ select2 = $(this).data("select2");
2595
+ if (select2 === undefined) return;
2596
+ if (args[0] === "container") {
2597
+ value=select2.container;
2598
+ } else {
2599
+ value = select2[args[0]].apply(select2, args.slice(1));
2600
+ }
2601
+ if (value !== undefined) {return false;}
2602
+ } else {
2603
+ throw "Invalid arguments to select2 plugin: " + args;
2604
+ }
2605
+ });
2606
+ return (value === undefined) ? this : value;
2607
+ };
2608
+
2609
+ // plugin defaults, accessible to users
2610
+ $.fn.select2.defaults = {
2611
+ width: "copy",
2612
+ loadMorePadding: 0,
2613
+ closeOnSelect: true,
2614
+ openOnEnter: true,
2615
+ containerCss: {},
2616
+ dropdownCss: {},
2617
+ containerCssClass: "",
2618
+ dropdownCssClass: "",
2619
+ formatResult: function(result, container, query, escapeMarkup) {
2620
+ var markup=[];
2621
+ markMatch(result.text, query.term, markup, escapeMarkup);
2622
+ return markup.join("");
2623
+ },
2624
+ formatSelection: function (data, container) {
2625
+ return data ? data.text : undefined;
2626
+ },
2627
+ sortResults: function (results, container, query) {
2628
+ return results;
2629
+ },
2630
+ formatResultCssClass: function(data) {return undefined;},
2631
+ formatNoMatches: function () { return "No matches found"; },
2632
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); },
2633
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Please enter " + n + " less character" + (n == 1? "" : "s"); },
2634
+ formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
2635
+ formatLoadMore: function (pageNumber) { return "Loading more results..."; },
2636
+ formatSearching: function () { return "Searching..."; },
2637
+ minimumResultsForSearch: 0,
2638
+ minimumInputLength: 0,
2639
+ maximumInputLength: null,
2640
+ maximumSelectionSize: 0,
2641
+ id: function (e) { return e.id; },
2642
+ matcher: function(term, text) {
2643
+ return text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
2644
+ },
2645
+ separator: ",",
2646
+ tokenSeparators: [],
2647
+ tokenizer: defaultTokenizer,
2648
+ escapeMarkup: function (markup) {
2649
+ var replace_map = {
2650
+ '\\': '&#92;',
2651
+ '&': '&amp;',
2652
+ '<': '&lt;',
2653
+ '>': '&gt;',
2654
+ '"': '&quot;',
2655
+ "'": '&apos;',
2656
+ "/": '&#47;'
2657
+ };
2658
+
2659
+ return String(markup).replace(/[&<>"'/\\]/g, function (match) {
2660
+ return replace_map[match[0]];
2661
+ });
2662
+ },
2663
+ blurOnChange: false,
2664
+ selectOnBlur: false,
2665
+ adaptContainerCssClass: function(c) { return c; },
2666
+ adaptDropdownCssClass: function(c) { return null; }
2667
+ };
2668
+
2669
+ // exports
2670
+ window.Select2 = {
2671
+ query: {
2672
+ ajax: ajax,
2673
+ local: local,
2674
+ tags: tags
2675
+ }, util: {
2676
+ debounce: debounce,
2677
+ markMatch: markMatch
2678
+ }, "class": {
2679
+ "abstract": AbstractSelect2,
2680
+ "single": SingleSelect2,
2681
+ "multi": MultiSelect2
2682
+ }
2683
+ };
2684
+
2685
+ }(jQuery));