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