PageSourceSearch

https://www.a1.mk/combo?browserId=other&minifierType=&themeId=A1_W…atorportlet:%2Fjs%2Fmain.js&t=1787739144000

js a1.mk collected 2026-09-24 09:35:54 UTC 124,167 bytes, 3,140 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.