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