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