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