vendor: 111,449 bytes, lines 1-3161
1/*1790832618000*/ 2(function ($) { 3 'use strict'; 4 5 var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']; 6 7 var uriAttrs = [ 8 'background', 9 'cite', 10 'href', 11 'itemtype', 12 'longdesc', 13 'poster', 14 'src', 15 'xlink:href' 16 ]; 17 18 var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; 19 20 var DefaultWhitelist = { 21 // Global attributes allowed on any supplied element below. 22 '*': ['class', 'dir', 'id', 'lang', 'role', 'tabindex', 'style', ARIA_ATTRIBUTE_PATTERN], 23 a: ['target', 'href', 'title', 'rel'], 24 area: [], 25 b: [], 26 br: [], 27 col: [], 28 code: [], 29 div: [], 30 em: [], 31 hr: [], 32 h1: [], 33 h2: [], 34 h3: [], 35 h4: [], 36 h5: [], 37 h6: [], 38 i: [], 39 img: ['src', 'alt', 'title', 'width', 'height'], 40 li: [], 41 ol: [], 42 p: [], 43 pre: [], 44 s: [], 45 small: [], 46 span: [], 47 sub: [], 48 sup: [], 49 strong: [], 50 u: [], 51 ul: [] 52 } 53 54 /** 55 * A pattern that recognizes a commonly useful subset of URLs that are safe. 56 * 57 * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts 58 */ 59 var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi; 60 61 /** 62 * A pattern that matches safe data URLs. Only matches image, video and audio types. 63 * 64 * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts 65 */ 66 var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i; 67 68 function allowedAttribute (attr, allowedAttributeList) { 69 var attrName = attr.nodeName.toLowerCase() 70 71 if ($.inArray(attrName, allowedAttributeList) !== -1) { 72 if ($.inArray(attrName, uriAttrs) !== -1) { 73 return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)) 74 } 75 76 return true 77 } 78 79 var regExp = $(allowedAttributeList).filter(function (index, value) { 80 return value instanceof RegExp 81 }) 82 83 // Check if a regular expression validates the attribute. 84 for (var i = 0, l = regExp.length; i < l; i++) { 85 if (attrName.match(regExp[i])) { 86 return true 87 } 88 } 89 90 return false 91 } 92 93 function sanitizeHtml (unsafeElements, whiteList, sanitizeFn) { 94 if (sanitizeFn && typeof sanitizeFn === 'function') { 95 return sanitizeFn(unsafeElements); 96 } 97 98 var whitelistKeys = Object.keys(whiteList); 99 100 for (var i = 0, len = unsafeElements.length; i < len; i++) { 101 var elements = unsafeElements[i].querySelectorAll('*'); 102 103 for (var j = 0, len2 = elements.length; j < len2; j++) { 104 var el = elements[j]; 105 var elName = el.nodeName.toLowerCase(); 106 107 if (whitelistKeys.indexOf(elName) === -1) { 108 el.parentNode.removeChild(el); 109 110 continue; 111 } 112 113 var attributeList = [].slice.call(el.attributes); 114 var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []); 115 116 for (var k = 0, len3 = attributeList.length; k < len3; k++) { 117 var attr = attributeList[k]; 118 119 if (!allowedAttribute(attr, whitelistedAttributes)) { 120 el.removeAttribute(attr.nodeName); 121 } 122 } 123 } 124 } 125 } 126 127 // Polyfill for browsers with no classList support 128 // Remove in v2 129 if (!('classList' in document.createElement('_'))) { 130 (function (view) { 131 if (!('Element' in view)) return; 132 133 var classListProp = 'classList', 134 protoProp = 'prototype', 135 elemCtrProto = view.Element[protoProp], 136 objCtr = Object, 137 classListGetter = function () { 138 var $elem = $(this); 139 140 return { 141 add: function (classes) { 142 classes = Array.prototype.slice.call(arguments).join(' '); 143 return $elem.addClass(classes); 144 }, 145 remove: function (classes) { 146 classes = Array.prototype.slice.call(arguments).join(' '); 147 return $elem.removeClass(classes); 148 }, 149 toggle: function (classes, force) { 150 return $elem.toggleClass(classes, force); 151 }, 152 contains: function (classes) { 153 return $elem.hasClass(classes); 154 } 155 } 156 }; 157 158 if (objCtr.defineProperty) { 159 var classListPropDesc = { 160 get: classListGetter, 161 enumerable: true, 162 configurable: true 163 }; 164 try { 165 objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 166 } catch (ex) { // IE 8 doesn't support enumerable:true 167 // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36 168 // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected 169 if (ex.number === undefined || ex.number === -0x7FF5EC54) { 170 classListPropDesc.enumerable = false; 171 objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 172 } 173 } 174 } else if (objCtr[protoProp].__defineGetter__) { 175 elemCtrProto.__defineGetter__(classListProp, classListGetter); 176 } 177 }(window)); 178 } 179 180 var testElement = document.createElement('_'); 181 182 testElement.classList.add('c1', 'c2'); 183 184 if (!testElement.classList.contains('c2')) { 185 var _add = DOMTokenList.prototype.add, 186 _remove = DOMTokenList.prototype.remove; 187 188 DOMTokenList.prototype.add = function () { 189 Array.prototype.forEach.call(arguments, _add.bind(this)); 190 } 191 192 DOMTokenList.prototype.remove = function () { 193 Array.prototype.forEach.call(arguments, _remove.bind(this)); 194 } 195 } 196 197 testElement.classList.toggle('c3', false); 198 199 // Polyfill for IE 10 and Firefox <24, where classList.toggle does not 200 // support the second argument. 201 if (testElement.classList.contains('c3')) { 202 var _toggle = DOMTokenList.prototype.toggle; 203 204 DOMTokenList.prototype.toggle = function (token, force) { 205 if (1 in arguments && !this.contains(token) === !force) { 206 return force; 207 } else { 208 return _toggle.call(this, token); 209 } 210 }; 211 } 212 213 testElement = null; 214 215 // shallow array comparison 216 function isEqual (array1, array2) { 217 return array1.length === array2.length && array1.every(function (element, index) { 218 return element === array2[index]; 219 }); 220 }; 221 222 // <editor-fold desc="Shims"> 223 if (!String.prototype.startsWith) { 224 (function () { 225 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` 226 var defineProperty = (function () { 227 // IE 8 only supports `Object.defineProperty` on DOM elements 228 try { 229 var object = {}; 230 var $defineProperty = Object.defineProperty; 231 var result = $defineProperty(object, object, object) && $defineProperty; 232 } catch (error) { 233 } 234 return result; 235 }()); 236 var toString = {}.toString; 237 var startsWith = function (search) { 238 if (this == null) { 239 throw new TypeError(); 240 } 241 var string = String(this); 242 if (search && toString.call(search) == '[object RegExp]') { 243 throw new TypeError(); 244 } 245 var stringLength = string.length; 246 var searchString = String(search); 247 var searchLength = searchString.length; 248 var position = arguments.length > 1 ? arguments[1] : undefined; 249 // `ToInteger` 250 var pos = position ? Number(position) : 0; 251 if (pos != pos) { // better `isNaN` 252 pos = 0; 253 } 254 var start = Math.min(Math.max(pos, 0), stringLength); 255 // Avoid the `indexOf` call if no match is possible 256 if (searchLength + start > stringLength) { 257 return false; 258 } 259 var index = -1; 260 while (++index < searchLength) { 261 if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { 262 return false; 263 } 264 } 265 return true; 266 }; 267 if (defineProperty) { 268 defineProperty(String.prototype, 'startsWith', { 269 'value': startsWith, 270 'configurable': true, 271 'writable': true 272 }); 273 } else { 274 String.prototype.startsWith = startsWith; 275 } 276 }()); 277 } 278 279 if (!Object.keys) { 280 Object.keys = function ( 281 o, // object 282 k, // key 283 r // result array 284 ) { 285 // initialize object and result 286 r = []; 287 // iterate over object keys 288 for (k in o) { 289 // fill result array with non-prototypical keys 290 r.hasOwnProperty.call(o, k) && r.push(k); 291 } 292 // return result 293 return r; 294 }; 295 } 296 297 if (HTMLSelectElement && !HTMLSelectElement.prototype.hasOwnProperty('selectedOptions')) { 298 Object.defineProperty(HTMLSelectElement.prototype, 'selectedOptions', { 299 get: function () { 300 return this.querySelectorAll(':checked'); 301 } 302 }); 303 } 304 305 function getSelectedOptions (select, ignoreDisabled) { 306 var selectedOptions = select.selectedOptions, 307 options = [], 308 opt; 309 310 if (ignoreDisabled) { 311 for (var i = 0, len = selectedOptions.length; i < len; i++) { 312 opt = selectedOptions[i]; 313 314 if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) { 315 options.push(opt); 316 } 317 } 318 319 return options; 320 } 321 322 return selectedOptions; 323 } 324 325 // much faster than $.val() 326 function getSelectValues (select, selectedOptions) { 327 var value = [], 328 options = selectedOptions || select.selectedOptions, 329 opt; 330 331 for (var i = 0, len = options.length; i < len; i++) { 332 opt = options[i]; 333 334 if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) { 335 value.push(opt.value); 336 } 337 } 338 339 if (!select.multiple) { 340 return !value.length ? null : value[0]; 341 } 342 343 return value; 344 } 345 346 // set data-selected on select element if the value has been programmatically selected 347 // prior to initialization of bootstrap-select 348 // * consider removing or replacing an alternative method * 349 var valHooks = { 350 useDefault: false, 351 _set: $.valHooks.select.set 352 }; 353 354 $.valHooks.select.set = function (elem, value) { 355 if (value && !valHooks.useDefault) $(elem).data('selected', true); 356 357 return valHooks._set.apply(this, arguments); 358 }; 359 360 var changedArguments = null; 361 362 var EventIsSupported = (function () { 363 try { 364 new Event('change'); 365 return true; 366 } catch (e) { 367 return false; 368 } 369 })(); 370 371 $.fn.triggerNative = function (eventName) { 372 var el = this[0], 373 event; 374 375 if (el.dispatchEvent) { // for modern browsers & IE9+ 376 if (EventIsSupported) { 377 // For modern browsers 378 event = new Event(eventName, { 379 bubbles: true 380 }); 381 } else { 382 // For IE since it doesn't support Event constructor 383 event = document.createEvent('Event'); 384 event.initEvent(eventName, true, false); 385 } 386 387 el.dispatchEvent(event); 388 } else if (el.fireEvent) { // for IE8 389 event = document.createEventObject(); 390 event.eventType = eventName; 391 el.fireEvent('on' + eventName, event); 392 } else { 393 // fall back to jQuery.trigger 394 this.trigger(eventName); 395 } 396 }; 397 // </editor-fold> 398 399 function stringSearch (li, searchString, method, normalize) { 400 var stringTypes = [ 401 'display', 402 'subtext', 403 'tokens' 404 ], 405 searchSuccess = false; 406 407 for (var i = 0; i < stringTypes.length; i++) { 408 var stringType = stringTypes[i], 409 string = li[stringType]; 410 411 if (string) { 412 string = string.toString(); 413 414 // Strip HTML tags. This isn't perfect, but it's much faster than any other method 415 if (stringType === 'display') { 416 string = string.replace(/<[^>]+>/g, ''); 417 } 418 419 if (normalize) string = normalizeToBase(string); 420 string = string.toUpperCase(); 421 422 if (method === 'contains') { 423 searchSuccess = string.indexOf(searchString) >= 0; 424 } else { 425 searchSuccess = string.startsWith(searchString); 426 } 427 428 if (searchSuccess) break; 429 } 430 } 431 432 return searchSuccess; 433 } 434 435 function toInteger (value) { 436 return parseInt(value, 10) || 0; 437 } 438 439 // Borrowed from Lodash (_.deburr) 440 /** Used to map Latin Unicode letters to basic Latin letters. */ 441 var deburredLetters = { 442 // Latin-1 Supplement block. 443 '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', 444 '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', 445 '\xc7': 'C', '\xe7': 'c', 446 '\xd0': 'D', '\xf0': 'd', 447 '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', 448 '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', 449 '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', 450 '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', 451 '\xd1': 'N', '\xf1': 'n', 452 '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', 453 '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', 454 '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', 455 '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', 456 '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', 457 '\xc6': 'Ae', '\xe6': 'ae', 458 '\xde': 'Th', '\xfe': 'th', 459 '\xdf': 'ss', 460 // Latin Extended-A block. 461 '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', 462 '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', 463 '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', 464 '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', 465 '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', 466 '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', 467 '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', 468 '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', 469 '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', 470 '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', 471 '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', 472 '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', 473 '\u0134': 'J', '\u0135': 'j', 474 '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', 475 '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', 476 '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', 477 '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', 478 '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', 479 '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', 480 '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', 481 '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', 482 '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', 483 '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', 484 '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', 485 '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', 486 '\u0163': 't', '\u0165': 't', '\u0167': 't', 487 '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', 488 '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', 489 '\u0174': 'W', '\u0175': 'w', 490 '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', 491 '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', 492 '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', 493 '\u0132': 'IJ', '\u0133': 'ij', 494 '\u0152': 'Oe', '\u0153': 'oe', 495 '\u0149': "'n", '\u017f': 's' 496 }; 497 498 /** Used to match Latin Unicode letters (excluding mathematical operators). */ 499 var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; 500 501 /** Used to compose unicode character classes. */ 502 var rsComboMarksRange = '\\u0300-\\u036f', 503 reComboHalfMarksRange = '\\ufe20-\\ufe2f', 504 rsComboSymbolsRange = '\\u20d0-\\u20ff', 505 rsComboMarksExtendedRange = '\\u1ab0-\\u1aff', 506 rsComboMarksSupplementRange = '\\u1dc0-\\u1dff', 507 rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange; 508 509 /** Used to compose unicode capture groups. */ 510 var rsCombo = '[' + rsComboRange + ']'; 511 512 /** 513 * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and 514 * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). 515 */ 516 var reComboMark = RegExp(rsCombo, 'g'); 517 518 function deburrLetter (key) { 519 return deburredLetters[key]; 520 }; 521 522 function normalizeToBase (string) { 523 string = string.toString(); 524 return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); 525 } 526 527 // List of HTML entities for escaping. 528 var escapeMap = { 529 '&': '&', 530 '<': '<', 531 '>': '>', 532 '"': '"', 533 "'": ''', 534 '`': '`' 535 }; 536 537 // Functions for escaping and unescaping strings to/from HTML interpolation. 538 var createEscaper = function (map) { 539 var escaper = function (match) { 540 return map[match]; 541 }; 542 // Regexes for identifying a key that needs to be escaped. 543 var source = '(?:' + Object.keys(map).join('|') + ')'; 544 var testRegexp = RegExp(source); 545 var replaceRegexp = RegExp(source, 'g'); 546 return function (string) { 547 string = string == null ? '' : '' + string; 548 return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; 549 }; 550 }; 551 552 var htmlEscape = createEscaper(escapeMap); 553 554 /** 555 * ------------------------------------------------------------------------ 556 * Constants 557 * ------------------------------------------------------------------------ 558 */ 559 560 var keyCodeMap = { 561 32: ' ', 562 48: '0', 563 49: '1', 564 50: '2', 565 51: '3', 566 52: '4', 567 53: '5', 568 54: '6', 569 55: '7', 570 56: '8', 571 57: '9', 572 59: ';', 573 65: 'A', 574 66: 'B', 575 67: 'C', 576 68: 'D', 577 69: 'E', 578 70: 'F', 579 71: 'G', 580 72: 'H', 581 73: 'I', 582 74: 'J', 583 75: 'K', 584 76: 'L', 585 77: 'M', 586 78: 'N', 587 79: 'O', 588 80: 'P', 589 81: 'Q', 590 82: 'R', 591 83: 'S', 592 84: 'T', 593 85: 'U', 594 86: 'V', 595 87: 'W', 596 88: 'X', 597 89: 'Y', 598 90: 'Z', 599 96: '0', 600 97: '1', 601 98: '2', 602 99: '3', 603 100: '4', 604 101: '5', 605 102: '6', 606 103: '7', 607 104: '8', 608 105: '9' 609 }; 610 611 var keyCodes = { 612 ESCAPE: 27, // KeyboardEvent.which value for Escape (Esc) key 613 ENTER: 13, // KeyboardEvent.which value for Enter key 614 SPACE: 32, // KeyboardEvent.which value for space key 615 TAB: 9, // KeyboardEvent.which value for tab key 616 ARROW_UP: 38, // KeyboardEvent.which value for up arrow key 617 ARROW_DOWN: 40 // KeyboardEvent.which value for down arrow key 618 } 619 620 var version = { 621 success: false, 622 major: '3' 623 }; 624 625 try { 626 version.full = ('4.0.0').split(' ')[0].split('.'); 627 version.major = version.full[0]; 628 version.success = true; 629 } catch (err) { 630 // do nothing 631 } 632 633 var selectId = 0; 634 635 var EVENT_KEY = '.bs.select'; 636 637 var classNames = { 638 DISABLED: 'disabled', 639 DIVIDER: 'divider', 640 SHOW: 'open', 641 DROPUP: 'dropup', 642 MENU: 'dropdown-menu', 643 MENURIGHT: 'dropdown-menu-right', 644 MENULEFT: 'dropdown-menu-left', 645 // to-do: replace with more advanced template/customization options 646 BUTTONCLASS: 'btn-default', 647 POPOVERHEADER: 'popover-title', 648 ICONBASE: 'glyphicon', 649 TICKICON: 'glyphicon-ok' 650 } 651 652 var Selector = { 653 MENU: '.' + classNames.MENU 654 } 655 656 var elementTemplates = { 657 span: document.createElement('span'), 658 i: document.createElement('i'), 659 subtext: document.createElement('small'), 660 a: document.createElement('a'), 661 li: document.createElement('li'), 662 whitespace: document.createTextNode('\u00A0'), 663 fragment: document.createDocumentFragment() 664 } 665 666 elementTemplates.a.setAttribute('role', 'option'); 667 if (version.major === '4') elementTemplates.a.className = 'dropdown-item'; 668 669 elementTemplates.subtext.className = 'text-muted'; 670 671 elementTemplates.text = elementTemplates.span.cloneNode(false); 672 elementTemplates.text.className = 'text'; 673 674 elementTemplates.checkMark = elementTemplates.span.cloneNode(false); 675 676 var REGEXP_ARROW = new RegExp(keyCodes.ARROW_UP + '|' + keyCodes.ARROW_DOWN); 677 var REGEXP_TAB_OR_ESCAPE = new RegExp('^' + keyCodes.TAB + '$|' + keyCodes.ESCAPE); 678 679 var generateOption = { 680 li: function (content, classes, optgroup) { 681 var li = elementTemplates.li.cloneNode(false); 682 683 if (content) { 684 if (content.nodeType === 1 || content.nodeType === 11) { 685 li.appendChild(content); 686 } else { 687 li.innerHTML = content; 688 } 689 } 690 691 if (typeof classes !== 'undefined' && classes !== '') li.className = classes; 692 if (typeof optgroup !== 'undefined' && optgroup !== null) li.classList.add('optgroup-' + optgroup); 693 694 return li; 695 }, 696 697 a: function (text, classes, inline) { 698 var a = elementTemplates.a.cloneNode(true); 699 700 if (text) { 701 if (text.nodeType === 11) { 702 a.appendChild(text); 703 } else { 704 a.insertAdjacentHTML('beforeend', text); 705 } 706 } 707 708 if (typeof classes !== 'undefined' && classes !== '') a.classList.add.apply(a.classList, classes.split(' ')); 709 if (inline) a.setAttribute('style', inline); 710 711 return a; 712 }, 713 714 text: function (options, useFragment) { 715 var textElement = elementTemplates.text.cloneNode(false), 716 subtextElement, 717 iconElement; 718 719 if (options.content) { 720 textElement.innerHTML = options.content; 721 } else { 722 textElement.textContent = options.text; 723 724 if (options.icon) { 725 var whitespace = elementTemplates.whitespace.cloneNode(false); 726 727 // need to use <i> for icons in the button to prevent a breaking change 728 // note: switch to span in next major release 729 iconElement = (useFragment === true ? elementTemplates.i : elementTemplates.span).cloneNode(false); 730 iconElement.className = this.options.iconBase + ' ' + options.icon; 731 732 elementTemplates.fragment.appendChild(iconElement); 733 elementTemplates.fragment.appendChild(whitespace); 734 } 735 736 if (options.subtext) { 737 subtextElement = elementTemplates.subtext.cloneNode(false); 738 subtextElement.textContent = options.subtext; 739 textElement.appendChild(subtextElement); 740 } 741 } 742 743 if (useFragment === true) { 744 while (textElement.childNodes.length > 0) { 745 elementTemplates.fragment.appendChild(textElement.childNodes[0]); 746 } 747 } else { 748 elementTemplates.fragment.appendChild(textElement); 749 } 750 751 return elementTemplates.fragment; 752 }, 753 754 label: function (options) { 755 var textElement = elementTemplates.text.cloneNode(false), 756 subtextElement, 757 iconElement; 758 759 textElement.innerHTML = options.display; 760 761 if (options.icon) { 762 var whitespace = elementTemplates.whitespace.cloneNode(false); 763 764 iconElement = elementTemplates.span.cloneNode(false); 765 iconElement.className = this.options.iconBase + ' ' + options.icon; 766 767 elementTemplates.fragment.appendChild(iconElement); 768 elementTemplates.fragment.appendChild(whitespace); 769 } 770 771 if (options.subtext) { 772 subtextElement = elementTemplates.subtext.cloneNode(false); 773 subtextElement.textContent = options.subtext; 774 textElement.appendChild(subtextElement); 775 } 776 777 elementTemplates.fragment.appendChild(textElement); 778 779 return elementTemplates.fragment; 780 } 781 } 782 783 var Selectpicker = function (element, options) { 784 var that = this; 785 786 // bootstrap-select has been initialized - revert valHooks.select.set back to its original function 787 if (!valHooks.useDefault) { 788 $.valHooks.select.set = valHooks._set; 789 valHooks.useDefault = true; 790 } 791 792 this.$element = $(element); 793 this.$newElement = null; 794 this.$button = null; 795 this.$menu = null; 796 this.options = options; 797 this.selectpicker = { 798 main: {}, 799 search: {}, 800 current: {}, // current changes if a search is in progress 801 view: {}, 802 isSearching: false, 803 keydown: { 804 keyHistory: '', 805 resetKeyHistory: { 806 start: function () { 807 return setTimeout(function () { 808 that.selectpicker.keydown.keyHistory = ''; 809 }, 800); 810 } 811 } 812 } 813 }; 814 815 this.sizeInfo = {}; 816 817 // If we have no title yet, try to pull it from the html title attribute (jQuery doesnt' pick it up as it's not a 818 // data-attribute) 819 if (this.options.title === null) { 820 this.options.title = this.$element.attr('title'); 821 } 822 823 // Format window padding 824 var winPad = this.options.windowPadding; 825 if (typeof winPad === 'number') { 826 this.options.windowPadding = [winPad, winPad, winPad, winPad]; 827 } 828 829 // Expose public methods 830 this.val = Selectpicker.prototype.val; 831 this.render = Selectpicker.prototype.render; 832 this.refresh = Selectpicker.prototype.refresh; 833 this.setStyle = Selectpicker.prototype.setStyle; 834 this.selectAll = Selectpicker.prototype.selectAll; 835 this.deselectAll = Selectpicker.prototype.deselectAll; 836 this.destroy = Selectpicker.prototype.destroy; 837 this.remove = Selectpicker.prototype.remove; 838 this.show = Selectpicker.prototype.show; 839 this.hide = Selectpicker.prototype.hide; 840 841 this.init(); 842 }; 843 844 Selectpicker.VERSION = '1.13.14'; 845 846 // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. 847 Selectpicker.DEFAULTS = { 848 noneSelectedText: 'Nothing selected', 849 noneResultsText: 'No results matched {0}', 850 countSelectedText: function (numSelected, numTotal) { 851 return (numSelected == 1) ? '{0} item selected' : '{0} items selected'; 852 }, 853 maxOptionsText: function (numAll, numGroup) { 854 return [ 855 (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)', 856 (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)' 857 ]; 858 }, 859 selectAllText: 'Select All', 860 deselectAllText: 'Deselect All', 861 doneButton: false, 862 doneButtonText: 'Close', 863 multipleSeparator: ', ', 864 styleBase: 'btn', 865 style: classNames.BUTTONCLASS, 866 size: 'auto', 867 title: null, 868 selectedTextFormat: 'values', 869 width: false, 870 container: false, 871 hideDisabled: false, 872 showSubtext: false, 873 showIcon: true, 874 showContent: true, 875 dropupAuto: true, 876 header: false, 877 liveSearch: false, 878 liveSearchPlaceholder: null, 879 liveSearchNormalize: false, 880 liveSearchStyle: 'contains', 881 actionsBox: false, 882 iconBase: classNames.ICONBASE, 883 tickIcon: classNames.TICKICON, 884 showTick: false, 885 template: { 886 caret: '<span class="caret"></span>' 887 }, 888 maxOptions: false, 889 mobile: false, 890 selectOnTab: false, 891 dropdownAlignRight: false, 892 windowPadding: 0, 893 virtualScroll: 600, 894 display: false, 895 sanitize: true, 896 sanitizeFn: null, 897 whiteList: DefaultWhitelist 898 }; 899 900 Selectpicker.prototype = { 901 902 constructor: Selectpicker, 903 904 init: function () { 905 var that = this, 906 id = this.$element.attr('id'); 907 908 selectId++; 909 this.selectId = 'bs-select-' + selectId; 910 911 this.$element[0].classList.add('bs-select-hidden'); 912 913 this.multiple = this.$element.prop('multiple'); 914 this.autofocus = this.$element.prop('autofocus'); 915 916 if (this.$element[0].classList.contains('show-tick')) { 917 this.options.showTick = true; 918 } 919 920 this.$newElement = this.createDropdown(); 921 this.buildData(); 922 this.$element 923 .after(this.$newElement) 924 .prependTo(this.$newElement); 925 926 this.$button = this.$newElement.children('button'); 927 this.$menu = this.$newElement.children(Selector.MENU); 928 this.$menuInner = this.$menu.children('.inner'); 929 this.$searchbox = this.$menu.find('input'); 930 931 this.$element[0].classList.remove('bs-select-hidden'); 932 933 if (this.options.dropdownAlignRight === true) this.$menu[0].classList.add(classNames.MENURIGHT); 934 935 if (typeof id !== 'undefined') { 936 this.$button.attr('data-id', id); 937 } 938 939 this.checkDisabled(); 940 this.clickListener(); 941 942 if (this.options.liveSearch) { 943 this.liveSearchListener(); 944 this.focusedParent = this.$searchbox[0]; 945 } else { 946 this.focusedParent = this.$menuInner[0]; 947 } 948 949 this.setStyle(); 950 this.render(); 951 this.setWidth(); 952 if (this.options.container) { 953 this.selectPosition(); 954 } else { 955 this.$element.on('hide' + EVENT_KEY, function () { 956 if (that.isVirtual()) { 957 // empty menu on close 958 var menuInner = that.$menuInner[0], 959 emptyMenu = menuInner.firstChild.cloneNode(false); 960 961 // replace the existing UL with an empty one - this is faster than $.empty() or innerHTML = '' 962 menuInner.replaceChild(emptyMenu, menuInner.firstChild); 963 menuInner.scrollTop = 0; 964 } 965 }); 966 } 967 this.$menu.data('this', this); 968 this.$newElement.data('this', this); 969 if (this.options.mobile) this.mobile(); 970 971 this.$newElement.on({ 972 'hide.bs.dropdown': function (e) { 973 that.$element.trigger('hide' + EVENT_KEY, e); 974 }, 975 'hidden.bs.dropdown': function (e) { 976 that.$element.trigger('hidden' + EVENT_KEY, e); 977 }, 978 'show.bs.dropdown': function (e) { 979 that.$element.trigger('show' + EVENT_KEY, e); 980 }, 981 'shown.bs.dropdown': function (e) { 982 that.$element.trigger('shown' + EVENT_KEY, e); 983 } 984 }); 985 986 if (that.$element[0].hasAttribute('required')) { 987 this.$element.on('invalid' + EVENT_KEY, function () { 988 that.$button[0].classList.add('bs-invalid'); 989 990 that.$element 991 .on('shown' + EVENT_KEY + '.invalid', function () { 992 that.$element 993 .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened 994 .off('shown' + EVENT_KEY + '.invalid'); 995 }) 996 .on('rendered' + EVENT_KEY, function () { 997 // if select is no longer invalid, remove the bs-invalid class 998 if (this.validity.valid) that.$button[0].classList.remove('bs-invalid'); 999 that.$element.off('rendered' + EVENT_KEY); 1000 }); 1001 1002 that.$button.on('blur' + EVENT_KEY, function () { 1003 that.$element.trigger('focus').trigger('blur'); 1004 that.$button.off('blur' + EVENT_KEY); 1005 }); 1006 }); 1007 } 1008 1009 setTimeout(function () { 1010 that.buildList(); 1011 that.$element.trigger('loaded' + EVENT_KEY); 1012 }); 1013 }, 1014 1015 createDropdown: function () { 1016 // Options 1017 // If we are multiple or showTick option is set, then add the show-tick class 1018 var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '', 1019 multiselectable = this.multiple ? ' aria-multiselectable="true"' : '', 1020 inputGroup = '', 1021 autofocus = this.autofocus ? ' autofocus' : ''; 1022 1023 if (version.major < 4 && this.$element.parent().hasClass('input-group')) { 1024 inputGroup = ' input-group-btn'; 1025 } 1026 1027 // Elements 1028 var drop, 1029 header = '', 1030 searchbox = '', 1031 actionsbox = '', 1032 donebutton = ''; 1033 1034 if (this.options.header) { 1035 header = 1036 '<div class="' + classNames.POPOVERHEADER + '">' + 1037 '<button type="button" class="close" aria-hidden="true">×</button>' + 1038 this.options.header + 1039 '</div>'; 1040 } 1041 1042 if (this.options.liveSearch) { 1043 searchbox = 1044 '<div class="bs-searchbox">' + 1045 '<input type="search" class="form-control" autocomplete="off"' + 1046 ( 1047 this.options.liveSearchPlaceholder === null ? '' 1048 : 1049 ' placeholder="' + htmlEscape(this.options.liveSearchPlaceholder) + '"' 1050 ) + 1051 ' role="combobox" aria-label="Search" aria-controls="' + this.selectId + '" aria-autocomplete="list">' + 1052 '</div>'; 1053 } 1054 1055 if (this.multiple && this.options.actionsBox) { 1056 actionsbox = 1057 '<div class="bs-actionsbox">' + 1058 '<div class="btn-group btn-group-sm btn-block">' + 1059 '<button type="button" class="actions-btn bs-select-all btn ' + classNames.BUTTONCLASS + '">' + 1060 this.options.selectAllText + 1061 '</button>' + 1062 '<button type="button" class="actions-btn bs-deselect-all btn ' + classNames.BUTTONCLASS + '">' + 1063 this.options.deselectAllText + 1064 '</button>' + 1065 '</div>' + 1066 '</div>'; 1067 } 1068 1069 if (this.multiple && this.options.doneButton) { 1070 donebutton = 1071 '<div class="bs-donebutton">' + 1072 '<div class="btn-group btn-block">' + 1073 '<button type="button" class="btn btn-sm ' + classNames.BUTTONCLASS + '">' + 1074 this.options.doneButtonText + 1075 '</button>' + 1076 '</div>' + 1077 '</div>'; 1078 } 1079 1080 drop = 1081 '<div class="dropdown bootstrap-select' + showTick + inputGroup + '">' + 1082 '<button type="button" class="' + this.options.styleBase + ' dropdown-toggle" ' + (this.options.display === 'static' ? 'data-display="static"' : '') + 'data-toggle="dropdown"' + autofocus + ' role="combobox" aria-owns="' + this.selectId + '" aria-haspopup="listbox" aria-expanded="false">' + 1083 '<div class="filter-option">' + 1084 '<div class="filter-option-inner">' + 1085 '<div class="filter-option-inner-inner"></div>' + 1086 '</div> ' + 1087 '</div>' + 1088 ( 1089 version.major === '4' ? '' 1090 : 1091 '<span class="bs-caret">' + 1092 this.options.template.caret + 1093 '</span>' 1094 ) + 1095 '</button>' + 1096 '<div class="' + classNames.MENU + ' ' + (version.major === '4' ? '' : classNames.SHOW) + '">' + 1097 header + 1098 searchbox + 1099 actionsbox + 1100 '<div class="inner ' + classNames.SHOW + '" role="listbox" id="' + this.selectId + '" tabindex="-1" ' + multiselectable + '>' + 1101 '<ul class="' + classNames.MENU + ' inner ' + (version.major === '4' ? classNames.SHOW : '') + '" role="presentation">' + 1102 '</ul>' + 1103 '</div>' + 1104 donebutton + 1105 '</div>' + 1106 '</div>'; 1107 1108 return $(drop); 1109 }, 1110 1111 setPositionData: function () { 1112 this.selectpicker.view.canHighlight = []; 1113 this.selectpicker.view.size = 0; 1114 1115 for (var i = 0; i < this.selectpicker.current.data.length; i++) { 1116 var li = this.selectpicker.current.data[i], 1117 canHighlight = true; 1118 1119 if (li.type === 'divider') { 1120 canHighlight = false; 1121 li.height = this.sizeInfo.dividerHeight; 1122 } else if (li.type === 'optgroup-label') { 1123 canHighlight = false; 1124 li.height = this.sizeInfo.dropdownHeaderHeight; 1125 } else { 1126 li.height = this.sizeInfo.liHeight; 1127 } 1128 1129 if (li.disabled) canHighlight = false; 1130 1131 this.selectpicker.view.canHighlight.push(canHighlight); 1132 1133 if (canHighlight) { 1134 this.selectpicker.view.size++; 1135 li.posinset = this.selectpicker.view.size; 1136 } 1137 1138 li.position = (i === 0 ? 0 : this.selectpicker.current.data[i - 1].position) + li.height; 1139 } 1140 }, 1141 1142 isVirtual: function () { 1143 return (this.options.virtualScroll !== false) && (this.selectpicker.main.elements.length >= this.options.virtualScroll) || this.options.virtualScroll === true; 1144 }, 1145 1146 createView: function (isSearching, setSize, refresh) { 1147 var that = this, 1148 scrollTop = 0, 1149 active = [], 1150 selected, 1151 prevActive; 1152 1153 this.selectpicker.isSearching = isSearching; 1154 this.selectpicker.current = isSearching ? this.selectpicker.search : this.selectpicker.main; 1155 1156 this.setPositionData(); 1157 1158 if (setSize) { 1159 if (refresh) { 1160 scrollTop = this.$menuInner[0].scrollTop; 1161 } else if (!that.multiple) { 1162 var element = that.$element[0], 1163 selectedIndex = (element.options[element.selectedIndex] || {}).liIndex; 1164 1165 if (typeof selectedIndex === 'number' && that.options.size !== false) { 1166 var selectedData = that.selectpicker.main.data[selectedIndex], 1167 position = selectedData && selectedData.position; 1168 1169 if (position) { 1170 scrollTop = position - ((that.sizeInfo.menuInnerHeight + that.sizeInfo.liHeight) / 2); 1171 } 1172 } 1173 } 1174 } 1175 1176 scroll(scrollTop, true); 1177 1178 this.$menuInner.off('scroll.createView').on('scroll.createView', function (e, updateValue) { 1179 if (!that.noScroll) scroll(this.scrollTop, updateValue); 1180 that.noScroll = false; 1181 }); 1182 1183 function scroll (scrollTop, init) { 1184 var size = that.selectpicker.current.elements.length, 1185 chunks = [], 1186 chunkSize, 1187 chunkCount, 1188 firstChunk, 1189 lastChunk, 1190 currentChunk, 1191 prevPositions, 1192 positionIsDifferent, 1193 previousElements, 1194 menuIsDifferent = true, 1195 isVirtual = that.isVirtual(); 1196 1197 that.selectpicker.view.scrollTop = scrollTop; 1198 1199 chunkSize = Math.ceil(that.sizeInfo.menuInnerHeight / that.sizeInfo.liHeight * 1.5); // number of options in a chunk 1200 chunkCount = Math.round(size / chunkSize) || 1; // number of chunks 1201 1202 for (var i = 0; i < chunkCount; i++) { 1203 var endOfChunk = (i + 1) * chunkSize; 1204 1205 if (i === chunkCount - 1) { 1206 endOfChunk = size; 1207 } 1208 1209 chunks[i] = [ 1210 (i) * chunkSize + (!i ? 0 : 1), 1211 endOfChunk 1212 ]; 1213 1214 if (!size) break; 1215 1216 if (currentChunk === undefined && scrollTop - 1 <= that.selectpicker.current.data[endOfChunk - 1].position - that.sizeInfo.menuInnerHeight) { 1217 currentChunk = i; 1218 } 1219 } 1220 1221 if (currentChunk === undefined) currentChunk = 0; 1222 1223 prevPositions = [that.selectpicker.view.position0, that.selectpicker.view.position1]; 1224 1225 // always display previous, current, and next chunks 1226 firstChunk = Math.max(0, currentChunk - 1); 1227 lastChunk = Math.min(chunkCount - 1, currentChunk + 1); 1228 1229 that.selectpicker.view.position0 = isVirtual === false ? 0 : (Math.max(0, chunks[firstChunk][0]) || 0); 1230 that.selectpicker.view.position1 = isVirtual === false ? size : (Math.min(size, chunks[lastChunk][1]) || 0); 1231 1232 positionIsDifferent = prevPositions[0] !== that.selectpicker.view.position0 || prevPositions[1] !== that.selectpicker.view.position1; 1233 1234 if (that.activeIndex !== undefined) { 1235 prevActive = that.selectpicker.main.elements[that.prevActiveIndex]; 1236 active = that.selectpicker.main.elements[that.activeIndex]; 1237 selected = that.selectpicker.main.elements[that.selectedIndex]; 1238 1239 if (init) { 1240 if (that.activeIndex !== that.selectedIndex) { 1241 that.defocusItem(active); 1242 } 1243 that.activeIndex = undefined; 1244 } 1245 1246 if (that.activeIndex && that.activeIndex !== that.selectedIndex) { 1247 that.defocusItem(selected); 1248 } 1249 } 1250 1251 if (that.prevActiveIndex !== undefined && that.prevActiveIndex !== that.activeIndex && that.prevActiveIndex !== that.selectedIndex) { 1252 that.defocusItem(prevActive); 1253 } 1254 1255 if (init || positionIsDifferent) { 1256 previousElements = that.selectpicker.view.visibleElements ? that.selectpicker.view.visibleElements.slice() : []; 1257 1258 if (isVirtual === false) { 1259 that.selectpicker.view.visibleElements = that.selectpicker.current.elements; 1260 } else { 1261 that.selectpicker.view.visibleElements = that.selectpicker.current.elements.slice(that.selectpicker.view.position0, that.selectpicker.view.position1); 1262 } 1263 1264 that.setOptionStatus(); 1265 1266 // if searching, check to make sure the list has actually been updated before updating DOM 1267 // this prevents unnecessary repaints 1268 if (isSearching || (isVirtual === false && init)) menuIsDifferent = !isEqual(previousElements, that.selectpicker.view.visibleElements); 1269 1270 // if virtual scroll is disabled and not searching, 1271 // menu should never need to be updated more than once 1272 if ((init || isVirtual === true) && menuIsDifferent) { 1273 var menuInner = that.$menuInner[0], 1274 menuFragment = document.createDocumentFragment(), 1275 emptyMenu = menuInner.firstChild.cloneNode(false), 1276 marginTop, 1277 marginBottom, 1278 elements = that.selectpicker.view.visibleElements, 1279 toSanitize = []; 1280 1281 // replace the existing UL with an empty one - this is faster than $.empty() 1282 menuInner.replaceChild(emptyMenu, menuInner.firstChild); 1283 1284 for (var i = 0, visibleElementsLen = elements.length; i < visibleElementsLen; i++) { 1285 var element = elements[i], 1286 elText, 1287 elementData; 1288 1289 if (that.options.sanitize) { 1290 elText = element.lastChild; 1291 1292 if (elText) { 1293 elementData = that.selectpicker.current.data[i + that.selectpicker.view.position0]; 1294 1295 if (elementData && elementData.content && !elementData.sanitized) { 1296 toSanitize.push(elText); 1297 elementData.sanitized = true; 1298 } 1299 } 1300 } 1301 1302 menuFragment.appendChild(element); 1303 } 1304 1305 if (that.options.sanitize && toSanitize.length) { 1306 sanitizeHtml(toSanitize, that.options.whiteList, that.options.sanitizeFn); 1307 } 1308 1309 if (isVirtual === true) { 1310 marginTop = (that.selectpicker.view.position0 === 0 ? 0 : that.selectpicker.current.data[that.selectpicker.view.position0 - 1].position); 1311 marginBottom = (that.selectpicker.view.position1 > size - 1 ? 0 : that.selectpicker.current.data[size - 1].position - that.selectpicker.current.data[that.selectpicker.view.position1 - 1].position); 1312 1313 menuInner.firstChild.style.marginTop = marginTop + 'px'; 1314 menuInner.firstChild.style.marginBottom = marginBottom + 'px'; 1315 } else { 1316 menuInner.firstChild.style.marginTop = 0; 1317 menuInner.firstChild.style.marginBottom = 0; 1318 } 1319 1320 menuInner.firstChild.appendChild(menuFragment); 1321 1322 // if an option is encountered that is wider than the current menu width, update the menu width accordingly 1323 // switch to ResizeObserver with increased browser support 1324 if (isVirtual === true && that.sizeInfo.hasScrollBar) { 1325 var menuInnerInnerWidth = menuInner.firstChild.offsetWidth; 1326 1327 if (init && menuInnerInnerWidth < that.sizeInfo.menuInnerInnerWidth && that.sizeInfo.totalMenuWidth > that.sizeInfo.selectWidth) { 1328 menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; 1329 } else if (menuInnerInnerWidth > that.sizeInfo.menuInnerInnerWidth) { 1330 // set to 0 to get actual width of menu 1331 that.$menu[0].style.minWidth = 0; 1332 1333 var actualMenuWidth = menuInner.firstChild.offsetWidth; 1334 1335 if (actualMenuWidth > that.sizeInfo.menuInnerInnerWidth) { 1336 that.sizeInfo.menuInnerInnerWidth = actualMenuWidth; 1337 menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; 1338 } 1339 1340 // reset to default CSS styling 1341 that.$menu[0].style.minWidth = ''; 1342 } 1343 } 1344 } 1345 } 1346 1347 that.prevActiveIndex = that.activeIndex; 1348 1349 if (!that.options.liveSearch) { 1350 that.$menuInner.trigger('focus'); 1351 } else if (isSearching && init) { 1352 var index = 0, 1353 newActive; 1354 1355 if (!that.selectpicker.view.canHighlight[index]) { 1356 index = 1 + that.selectpicker.view.canHighlight.slice(1).indexOf(true); 1357 } 1358 1359 newActive = that.selectpicker.view.visibleElements[index]; 1360 1361 that.defocusItem(that.selectpicker.view.currentActive); 1362 1363 that.activeIndex = (that.selectpicker.current.data[index] || {}).index; 1364 1365 that.focusItem(newActive); 1366 } 1367 } 1368 1369 $(window) 1370 .off('resize' + EVENT_KEY + '.' + this.selectId + '.createView') 1371 .on('resize' + EVENT_KEY + '.' + this.selectId + '.createView', function () { 1372 var isActive = that.$newElement.hasClass(classNames.SHOW); 1373 1374 if (isActive) scroll(that.$menuInner[0].scrollTop); 1375 }); 1376 }, 1377 1378 focusItem: function (li, liData, noStyle) { 1379 if (li) { 1380 liData = liData || this.selectpicker.main.data[this.activeIndex]; 1381 var a = li.firstChild; 1382 1383 if (a) { 1384 a.setAttribute('aria-setsize', this.selectpicker.view.size); 1385 a.setAttribute('aria-posinset', liData.posinset); 1386 1387 if (noStyle !== true) { 1388 this.focusedParent.setAttribute('aria-activedescendant', a.id); 1389 li.classList.add('active'); 1390 a.classList.add('active'); 1391 } 1392 } 1393 } 1394 }, 1395 1396 defocusItem: function (li) { 1397 if (li) { 1398 li.classList.remove('active'); 1399 if (li.firstChild) li.firstChild.classList.remove('active'); 1400 } 1401 }, 1402 1403 setPlaceholder: function () { 1404 var updateIndex = false; 1405 1406 if (this.options.title && !this.multiple) { 1407 if (!this.selectpicker.view.titleOption) this.selectpicker.view.titleOption = document.createElement('option'); 1408 1409 // this option doesn't create a new <li> element, but does add a new option at the start, 1410 // so startIndex should increase to prevent having to check every option for the bs-title-option class 1411 updateIndex = true; 1412 1413 var element = this.$element[0], 1414 isSelected = false, 1415 titleNotAppended = !this.selectpicker.view.titleOption.parentNode; 1416 1417 if (titleNotAppended) { 1418 // Use native JS to prepend option (faster) 1419 this.selectpicker.view.titleOption.className = 'bs-title-option'; 1420 this.selectpicker.view.titleOption.value = ''; 1421 1422 // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option. 1423 // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs, 1424 // if so, the select will have the data-selected attribute 1425 var $opt = $(element.options[element.selectedIndex]); 1426 isSelected = $opt.attr('selected') === undefined && this.$element.data('selected') === undefined; 1427 } 1428 1429 if (titleNotAppended || this.selectpicker.view.titleOption.index !== 0) { 1430 element.insertBefore(this.selectpicker.view.titleOption, element.firstChild); 1431 } 1432 1433 // Set selected *after* appending to select, 1434 // otherwise the option doesn't get selected in IE 1435 // set using selectedIndex, as setting the selected attr to true here doesn't work in IE11 1436 if (isSelected) element.selectedIndex = 0; 1437 } 1438 1439 return updateIndex; 1440 }, 1441 1442 buildData: function () { 1443 var optionSelector = ':not([hidden]):not([data-hidden="true"])', 1444 mainData = [], 1445 optID = 0, 1446 startIndex = this.setPlaceholder() ? 1 : 0; // append the titleOption if necessary and skip the first option in the loop 1447 1448 if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; 1449 1450 var selectOptions = this.$element[0].querySelectorAll('select > *' + optionSelector); 1451 1452 function addDivider (config) { 1453 var previousData = mainData[mainData.length - 1]; 1454 1455 // ensure optgroup doesn't create back-to-back dividers 1456 if ( 1457 previousData && 1458 previousData.type === 'divider' && 1459 (previousData.optID || config.optID) 1460 ) { 1461 return; 1462 } 1463 1464 config = config || {}; 1465 config.type = 'divider'; 1466 1467 mainData.push(config); 1468 } 1469 1470 function addOption (option, config) { 1471 config = config || {}; 1472 1473 config.divider = option.getAttribute('data-divider') === 'true'; 1474 1475 if (config.divider) { 1476 addDivider({ 1477 optID: config.optID 1478 }); 1479 } else { 1480 var liIndex = mainData.length, 1481 cssText = option.style.cssText, 1482 inlineStyle = cssText ? htmlEscape(cssText) : '', 1483 optionClass = (option.className || '') + (config.optgroupClass || ''); 1484 1485 if (config.optID) optionClass = 'opt ' + optionClass; 1486 1487 config.optionClass = optionClass.trim(); 1488 config.inlineStyle = inlineStyle; 1489 config.text = option.textContent; 1490 1491 config.content = option.getAttribute('data-content'); 1492 config.tokens = option.getAttribute('data-tokens'); 1493 config.subtext = option.getAttribute('data-subtext'); 1494 config.icon = option.getAttribute('data-icon'); 1495 1496 option.liIndex = liIndex; 1497 1498 config.display = config.content || config.text; 1499 config.type = 'option'; 1500 config.index = liIndex; 1501 config.option = option; 1502 config.selected = !!option.selected; 1503 config.disabled = config.disabled || !!option.disabled; 1504 1505 mainData.push(config); 1506 } 1507 } 1508 1509 function addOptgroup (index, selectOptions) { 1510 var optgroup = selectOptions[index], 1511 previous = selectOptions[index - 1], 1512 next = selectOptions[index + 1], 1513 options = optgroup.querySelectorAll('option' + optionSelector); 1514 1515 if (!options.length) return; 1516 1517 var config = { 1518 display: htmlEscape(optgroup.label), 1519 subtext: optgroup.getAttribute('data-subtext'), 1520 icon: optgroup.getAttribute('data-icon'), 1521 type: 'optgroup-label', 1522 optgroupClass: ' ' + (optgroup.className || '') 1523 }, 1524 headerIndex, 1525 lastIndex; 1526 1527 optID++; 1528 1529 if (previous) { 1530 addDivider({ optID: optID }); 1531 } 1532 1533 config.optID = optID; 1534 1535 mainData.push(config); 1536 1537 for (var j = 0, len = options.length; j < len; j++) { 1538 var option = options[j]; 1539 1540 if (j === 0) { 1541 headerIndex = mainData.length - 1; 1542 lastIndex = headerIndex + len; 1543 } 1544 1545 addOption(option, { 1546 headerIndex: headerIndex, 1547 lastIndex: lastIndex, 1548 optID: config.optID, 1549 optgroupClass: config.optgroupClass, 1550 disabled: optgroup.disabled 1551 }); 1552 } 1553 1554 if (next) { 1555 addDivider({ optID: optID }); 1556 } 1557 } 1558 1559 for (var len = selectOptions.length; startIndex < len; startIndex++) { 1560 var item = selectOptions[startIndex]; 1561 1562 if (item.tagName !== 'OPTGROUP') { 1563 addOption(item, {}); 1564 } else { 1565 addOptgroup(startIndex, selectOptions); 1566 } 1567 } 1568 1569 this.selectpicker.main.data = this.selectpicker.current.data = mainData; 1570 }, 1571 1572 buildList: function () { 1573 var that = this, 1574 selectData = this.selectpicker.main.data, 1575 mainElements = [], 1576 widestOptionLength = 0; 1577 1578 if ((that.options.showTick || that.multiple) && !elementTemplates.checkMark.parentNode) { 1579 elementTemplates.checkMark.className = this.options.iconBase + ' ' + that.options.tickIcon + ' check-mark'; 1580 elementTemplates.a.appendChild(elementTemplates.checkMark); 1581 } 1582 1583 function buildElement (item) { 1584 var liElement, 1585 combinedLength = 0; 1586 1587 switch (item.type) { 1588 case 'divider': 1589 liElement = generateOption.li( 1590 false, 1591 classNames.DIVIDER, 1592 (item.optID ? item.optID + 'div' : undefined) 1593 ); 1594 1595 break; 1596 1597 case 'option': 1598 liElement = generateOption.li( 1599 generateOption.a( 1600 generateOption.text.call(that, item), 1601 item.optionClass, 1602 item.inlineStyle 1603 ), 1604 '', 1605 item.optID 1606 ); 1607 1608 if (liElement.firstChild) { 1609 liElement.firstChild.id = that.selectId + '-' + item.index; 1610 } 1611 1612 break; 1613 1614 case 'optgroup-label': 1615 liElement = generateOption.li( 1616 generateOption.label.call(that, item), 1617 'dropdown-header' + item.optgroupClass, 1618 item.optID 1619 ); 1620 1621 break; 1622 } 1623 1624 mainElements.push(liElement); 1625 1626 // count the number of characters in the option - not perfect, but should work in most cases 1627 if (item.display) combinedLength += item.display.length; 1628 if (item.subtext) combinedLength += item.subtext.length; 1629 // if there is an icon, ensure this option's width is checked 1630 if (item.icon) combinedLength += 1; 1631 1632 if (combinedLength > widestOptionLength) { 1633 widestOptionLength = combinedLength; 1634 1635 // guess which option is the widest 1636 // use this when calculating menu width 1637 // not perfect, but it's fast, and the width will be updating accordingly when scrolling 1638 that.selectpicker.view.widestOption = mainElements[mainElements.length - 1]; 1639 } 1640 } 1641 1642 for (var len = selectData.length, i = 0; i < len; i++) { 1643 var item = selectData[i]; 1644 1645 buildElement(item); 1646 } 1647 1648 this.selectpicker.main.elements = this.selectpicker.current.elements = mainElements; 1649 }, 1650 1651 findLis: function () { 1652 return this.$menuInner.find('.inner > li'); 1653 }, 1654 1655 render: function () { 1656 var that = this, 1657 element = this.$element[0], 1658 // ensure titleOption is appended and selected (if necessary) before getting selectedOptions 1659 placeholderSelected = this.setPlaceholder() && element.selectedIndex === 0, 1660 selectedOptions = getSelectedOptions(element, this.options.hideDisabled), 1661 selectedCount = selectedOptions.length, 1662 button = this.$button[0], 1663 buttonInner = button.querySelector('.filter-option-inner-inner'), 1664 multipleSeparator = document.createTextNode(this.options.multipleSeparator), 1665 titleFragment = elementTemplates.fragment.cloneNode(false), 1666 showCount, 1667 countMax, 1668 hasContent = false; 1669 1670 button.classList.toggle('bs-placeholder', that.multiple ? !selectedCount : !getSelectValues(element, selectedOptions)); 1671 1672 this.tabIndex(); 1673 1674 if (this.options.selectedTextFormat === 'static') { 1675 titleFragment = generateOption.text.call(this, { text: this.options.title }, true); 1676 } else { 1677 showCount = this.multiple && this.options.selectedTextFormat.indexOf('count') !== -1 && selectedCount > 1; 1678 1679 // determine if the number of selected options will be shown (showCount === true) 1680 if (showCount) { 1681 countMax = this.options.selectedTextFormat.split('>'); 1682 showCount = (countMax.length > 1 && selectedCount > countMax[1]) || (countMax.length === 1 && selectedCount >= 2); 1683 } 1684 1685 // only loop through all selected options if the count won't be shown 1686 if (showCount === false) { 1687 if (!placeholderSelected) { 1688 for (var selectedIndex = 0; selectedIndex < selectedCount; selectedIndex++) { 1689 if (selectedIndex < 50) { 1690 var option = selectedOptions[selectedIndex], 1691 thisData = this.selectpicker.main.data[option.liIndex], 1692 titleOptions = {}; 1693 1694 if (this.multiple && selectedIndex > 0) { 1695 titleFragment.appendChild(multipleSeparator.cloneNode(false)); 1696 } 1697 1698 if (option.title) { 1699 titleOptions.text = option.title; 1700 } else if (thisData) { 1701 if (thisData.content && that.options.showContent) { 1702 titleOptions.content = thisData.content.toString(); 1703 hasContent = true; 1704 } else { 1705 if (that.options.showIcon) { 1706 titleOptions.icon = thisData.icon; 1707 } 1708 if (that.options.showSubtext && !that.multiple && thisData.subtext) titleOptions.subtext = ' ' + thisData.subtext; 1709 titleOptions.text = option.textContent.trim(); 1710 } 1711 } 1712 1713 titleFragment.appendChild(generateOption.text.call(this, titleOptions, true)); 1714 } else { 1715 break; 1716 } 1717 } 1718 1719 // add ellipsis 1720 if (selectedCount > 49) { 1721 titleFragment.appendChild(document.createTextNode('...')); 1722 } 1723 } 1724 } else { 1725 var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([data-divider="true"])'; 1726 if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; 1727 1728 // If this is a multiselect, and selectedTextFormat is count, then show 1 of 2 selected, etc. 1729 var totalCount = this.$element[0].querySelectorAll('select > option' + optionSelector + ', optgroup' + optionSelector + ' option' + optionSelector).length, 1730 tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedCount, totalCount) : this.options.countSelectedText; 1731 1732 titleFragment = generateOption.text.call(this, { 1733 text: tr8nText.replace('{0}', selectedCount.toString()).replace('{1}', totalCount.toString()) 1734 }, true); 1735 } 1736 } 1737 1738 if (this.options.title == undefined) { 1739 // use .attr to ensure undefined is returned if title attribute is not set 1740 this.options.title = this.$element.attr('title'); 1741 } 1742 1743 // If the select doesn't have a title, then use the default, or if nothing is set at all, use noneSelectedText 1744 if (!titleFragment.childNodes.length) { 1745 titleFragment = generateOption.text.call(this, { 1746 text: typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText 1747 }, true); 1748 } 1749 1750 // strip all HTML tags and trim the result, then unescape any escaped tags 1751 button.title = titleFragment.textContent.replace(/<[^>]*>?/g, '').trim(); 1752 1753 if (this.options.sanitize && hasContent) { 1754 sanitizeHtml([titleFragment], that.options.whiteList, that.options.sanitizeFn); 1755 } 1756 1757 buttonInner.innerHTML = ''; 1758 buttonInner.appendChild(titleFragment); 1759 1760 if (version.major < 4 && this.$newElement[0].classList.contains('bs3-has-addon')) { 1761 var filterExpand = button.querySelector('.filter-expand'), 1762 clone = buttonInner.cloneNode(true); 1763 1764 clone.className = 'filter-expand'; 1765 1766 if (filterExpand) { 1767 button.replaceChild(clone, filterExpand); 1768 } else { 1769 button.appendChild(clone); 1770 } 1771 } 1772 1773 this.$element.trigger('rendered' + EVENT_KEY); 1774 }, 1775 1776 /** 1777 * @param [style] 1778 * @param [status] 1779 */ 1780 setStyle: function (newStyle, status) { 1781 var button = this.$button[0], 1782 newElement = this.$newElement[0], 1783 style = this.options.style.trim(), 1784 buttonClass; 1785 1786 if (this.$element.attr('class')) { 1787 this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, '')); 1788 } 1789 1790 if (version.major < 4) { 1791 newElement.classList.add('bs3'); 1792 1793 if (newElement.parentNode.classList.contains('input-group') && 1794 (newElement.previousElementSibling || newElement.nextElementSibling) && 1795 (newElement.previousElementSibling || newElement.nextElementSibling).classList.contains('input-group-addon') 1796 ) { 1797 newElement.classList.add('bs3-has-addon'); 1798 } 1799 } 1800 1801 if (newStyle) { 1802 buttonClass = newStyle.trim(); 1803 } else { 1804 buttonClass = style; 1805 } 1806 1807 if (status == 'add') { 1808 if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); 1809 } else if (status == 'remove') { 1810 if (buttonClass) button.classList.remove.apply(button.classList, buttonClass.split(' ')); 1811 } else { 1812 if (style) button.classList.remove.apply(button.classList, style.split(' ')); 1813 if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); 1814 } 1815 }, 1816 1817 liHeight: function (refresh) { 1818 if (!refresh && (this.options.size === false || Object.keys(this.sizeInfo).length)) return; 1819 1820 var newElement = document.createElement('div'), 1821 menu = document.createElement('div'), 1822 menuInner = document.createElement('div'), 1823 menuInnerInner = document.createElement('ul'), 1824 divider = document.createElement('li'), 1825 dropdownHeader = document.createElement('li'), 1826 li = document.createElement('li'), 1827 a = document.createElement('a'), 1828 text = document.createElement('span'), 1829 header = this.options.header && this.$menu.find('.' + classNames.POPOVERHEADER).length > 0 ? this.$menu.find('.' + classNames.POPOVERHEADER)[0].cloneNode(true) : null, 1830 search = this.options.liveSearch ? document.createElement('div') : null, 1831 actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null, 1832 doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null, 1833 firstOption = this.$element.find('option')[0]; 1834 1835 this.sizeInfo.selectWidth = this.$newElement[0].offsetWidth; 1836 1837 text.className = 'text'; 1838 a.className = 'dropdown-item ' + (firstOption ? firstOption.className : ''); 1839 newElement.className = this.$menu[0].parentNode.className + ' ' + classNames.SHOW; 1840 newElement.style.width = 0; // ensure button width doesn't affect natural width of menu when calculating 1841 if (this.options.width === 'auto') menu.style.minWidth = 0; 1842 menu.className = classNames.MENU + ' ' + classNames.SHOW; 1843 menuInner.className = 'inner ' + classNames.SHOW; 1844 menuInnerInner.className = classNames.MENU + ' inner ' + (version.major === '4' ? classNames.SHOW : ''); 1845 divider.className = classNames.DIVIDER; 1846 dropdownHeader.className = 'dropdown-header'; 1847 1848 text.appendChild(document.createTextNode('\u200b')); 1849 a.appendChild(text); 1850 li.appendChild(a); 1851 dropdownHeader.appendChild(text.cloneNode(true)); 1852 1853 if (this.selectpicker.view.widestOption) { 1854 menuInnerInner.appendChild(this.selectpicker.view.widestOption.cloneNode(true)); 1855 } 1856 1857 menuInnerInner.appendChild(li); 1858 menuInnerInner.appendChild(divider); 1859 menuInnerInner.appendChild(dropdownHeader); 1860 if (header) menu.appendChild(header); 1861 if (search) { 1862 var input = document.createElement('input'); 1863 search.className = 'bs-searchbox'; 1864 input.className = 'form-control'; 1865 search.appendChild(input); 1866 menu.appendChild(search); 1867 } 1868 if (actions) menu.appendChild(actions); 1869 menuInner.appendChild(menuInnerInner); 1870 menu.appendChild(menuInner); 1871 if (doneButton) menu.appendChild(doneButton); 1872 newElement.appendChild(menu); 1873 1874 document.body.appendChild(newElement); 1875 1876 var liHeight = li.offsetHeight, 1877 dropdownHeaderHeight = dropdownHeader ? dropdownHeader.offsetHeight : 0, 1878 headerHeight = header ? header.offsetHeight : 0, 1879 searchHeight = search ? search.offsetHeight : 0, 1880 actionsHeight = actions ? actions.offsetHeight : 0, 1881 doneButtonHeight = doneButton ? doneButton.offsetHeight : 0, 1882 dividerHeight = $(divider).outerHeight(true), 1883 // fall back to jQuery if getComputedStyle is not supported 1884 menuStyle = window.getComputedStyle ? window.getComputedStyle(menu) : false, 1885 menuWidth = menu.offsetWidth, 1886 $menu = menuStyle ? null : $(menu), 1887 menuPadding = { 1888 vert: toInteger(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) + 1889 toInteger(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) + 1890 toInteger(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) + 1891 toInteger(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')), 1892 horiz: toInteger(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) + 1893 toInteger(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) + 1894 toInteger(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) + 1895 toInteger(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth')) 1896 }, 1897 menuExtras = { 1898 vert: menuPadding.vert + 1899 toInteger(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) + 1900 toInteger(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2, 1901 horiz: menuPadding.horiz + 1902 toInteger(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) + 1903 toInteger(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2 1904 }, 1905 scrollBarWidth; 1906 1907 menuInner.style.overflowY = 'scroll'; 1908 1909 scrollBarWidth = menu.offsetWidth - menuWidth; 1910 1911 document.body.removeChild(newElement); 1912 1913 this.sizeInfo.liHeight = liHeight; 1914 this.sizeInfo.dropdownHeaderHeight = dropdownHeaderHeight; 1915 this.sizeInfo.headerHeight = headerHeight; 1916 this.sizeInfo.searchHeight = searchHeight; 1917 this.sizeInfo.actionsHeight = actionsHeight; 1918 this.sizeInfo.doneButtonHeight = doneButtonHeight; 1919 this.sizeInfo.dividerHeight = dividerHeight; 1920 this.sizeInfo.menuPadding = menuPadding; 1921 this.sizeInfo.menuExtras = menuExtras; 1922 this.sizeInfo.menuWidth = menuWidth; 1923 this.sizeInfo.menuInnerInnerWidth = menuWidth - menuPadding.horiz; 1924 this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth; 1925 this.sizeInfo.scrollBarWidth = scrollBarWidth; 1926 this.sizeInfo.selectHeight = this.$newElement[0].offsetHeight; 1927 1928 this.setPositionData(); 1929 }, 1930 1931 getSelectPosition: function () { 1932 var that = this, 1933 $window = $(window), 1934 pos = that.$newElement.offset(), 1935 $container = $(that.options.container), 1936 containerPos; 1937 1938 if (that.options.container && $container.length && !$container.is('body')) { 1939 containerPos = $container.offset(); 1940 containerPos.top += parseInt($container.css('borderTopWidth')); 1941 containerPos.left += parseInt($container.css('borderLeftWidth')); 1942 } else { 1943 containerPos = { top: 0, left: 0 }; 1944 } 1945 1946 var winPad = that.options.windowPadding; 1947 1948 this.sizeInfo.selectOffsetTop = pos.top - containerPos.top - $window.scrollTop(); 1949 this.sizeInfo.selectOffsetBot = $window.height() - this.sizeInfo.selectOffsetTop - this.sizeInfo.selectHeight - containerPos.top - winPad[2]; 1950 this.sizeInfo.selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft(); 1951 this.sizeInfo.selectOffsetRight = $window.width() - this.sizeInfo.selectOffsetLeft - this.sizeInfo.selectWidth - containerPos.left - winPad[1]; 1952 this.sizeInfo.selectOffsetTop -= winPad[0]; 1953 this.sizeInfo.selectOffsetLeft -= winPad[3]; 1954 }, 1955 1956 setMenuSize: function (isAuto) { 1957 this.getSelectPosition(); 1958 1959 var selectWidth = this.sizeInfo.selectWidth, 1960 liHeight = this.sizeInfo.liHeight, 1961 headerHeight = this.sizeInfo.headerHeight, 1962 searchHeight = this.sizeInfo.searchHeight, 1963 actionsHeight = this.sizeInfo.actionsHeight, 1964 doneButtonHeight = this.sizeInfo.doneButtonHeight, 1965 divHeight = this.sizeInfo.dividerHeight, 1966 menuPadding = this.sizeInfo.menuPadding, 1967 menuInnerHeight, 1968 menuHeight, 1969 divLength = 0, 1970 minHeight, 1971 _minHeight, 1972 maxHeight, 1973 menuInnerMinHeight, 1974 estimate, 1975 isDropup; 1976 1977 if (this.options.dropupAuto) { 1978 // Get the estimated height of the menu without scrollbars. 1979 // This is useful for smaller menus, where there might be plenty of room 1980 // below the button without setting dropup, but we can't know 1981 // the exact height of the menu until createView is called later 1982 estimate = liHeight * this.selectpicker.current.elements.length + menuPadding.vert; 1983 1984 isDropup = this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert && estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot; 1985 1986 // ensure dropup doesn't change while searching (so menu doesn't bounce back and forth) 1987 if (this.selectpicker.isSearching === true) { 1988 isDropup = this.selectpicker.dropup; 1989 } 1990 1991 this.$newElement.toggleClass(classNames.DROPUP, isDropup); 1992 this.selectpicker.dropup = isDropup; 1993 } 1994 1995 if (this.options.size === 'auto') { 1996 _minHeight = this.selectpicker.current.elements.length > 3 ? this.sizeInfo.liHeight * 3 + this.sizeInfo.menuExtras.vert - 2 : 0; 1997 menuHeight = this.sizeInfo.selectOffsetBot - this.sizeInfo.menuExtras.vert; 1998 minHeight = _minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; 1999 menuInnerMinHeight = Math.max(_minHeight - menuPadding.vert, 0); 2000 2001 if (this.$newElement.hasClass(classNames.DROPUP)) { 2002 menuHeight = this.sizeInfo.selectOffsetTop - this.sizeInfo.menuExtras.vert; 2003 } 2004 2005 maxHeight = menuHeight; 2006 menuInnerHeight = menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert; 2007 } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { 2008 for (var i = 0; i < this.options.size; i++) { 2009 if (this.selectpicker.current.data[i].type === 'divider') divLength++; 2010 } 2011 2012 menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert; 2013 menuInnerHeight = menuHeight - menuPadding.vert; 2014 maxHeight = menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; 2015 minHeight = menuInnerMinHeight = ''; 2016 } 2017 2018 this.$menu.css({ 2019 'max-height': maxHeight + 'px', 2020 'overflow': 'hidden', 2021 'min-height': minHeight + 'px' 2022 }); 2023 2024 this.$menuInner.css({ 2025 'max-height': menuInnerHeight + 'px', 2026 'overflow-y': 'auto', 2027 'min-height': menuInnerMinHeight + 'px' 2028 }); 2029 2030 // ensure menuInnerHeight is always a positive number to prevent issues calculating chunkSize in createView 2031 this.sizeInfo.menuInnerHeight = Math.max(menuInnerHeight, 1); 2032 2033 if (this.selectpicker.current.data.length && this.selectpicker.current.data[this.selectpicker.current.data.length - 1].position > this.sizeInfo.menuInnerHeight) { 2034 this.sizeInfo.hasScrollBar = true; 2035 this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth + this.sizeInfo.scrollBarWidth; 2036 } 2037 2038 if (this.options.dropdownAlignRight === 'auto') { 2039 this.$menu.toggleClass(classNames.MENURIGHT, this.sizeInfo.selectOffsetLeft > this.sizeInfo.selectOffsetRight && this.sizeInfo.selectOffsetRight < (this.sizeInfo.totalMenuWidth - selectWidth)); 2040 } 2041 2042 if (this.dropdown && this.dropdown._popper) this.dropdown._popper.update(); 2043 }, 2044 2045 setSize: function (refresh) { 2046 this.liHeight(refresh); 2047 2048 if (this.options.header) this.$menu.css('padding-top', 0); 2049 2050 if (this.options.size !== false) { 2051 var that = this, 2052 $window = $(window); 2053 2054 this.setMenuSize(); 2055 2056 if (this.options.liveSearch) { 2057 this.$searchbox 2058 .off('input.setMenuSize propertychange.setMenuSize') 2059 .on('input.setMenuSize propertychange.setMenuSize', function () { 2060 return that.setMenuSize(); 2061 }); 2062 } 2063 2064 if (this.options.size === 'auto') { 2065 $window 2066 .off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize') 2067 .on('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize', function () { 2068 return that.setMenuSize(); 2069 }); 2070 } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { 2071 $window.off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize'); 2072 } 2073 } 2074 2075 this.createView(false, true, refresh); 2076 }, 2077 2078 setWidth: function () { 2079 var that = this; 2080 2081 if (this.options.width === 'auto') { 2082 requestAnimationFrame(function () { 2083 that.$menu.css('min-width', '0'); 2084 2085 that.$element.on('loaded' + EVENT_KEY, function () { 2086 that.liHeight(); 2087 that.setMenuSize(); 2088 2089 // Get correct width if element is hidden 2090 var $selectClone = that.$newElement.clone().appendTo('body'), 2091 btnWidth = $selectClone.css('width', 'auto').children('button').outerWidth(); 2092 2093 $selectClone.remove(); 2094 2095 // Set width to whatever's larger, button title or longest option 2096 that.sizeInfo.selectWidth = Math.max(that.sizeInfo.totalMenuWidth, btnWidth); 2097 that.$newElement.css('width', that.sizeInfo.selectWidth + 'px'); 2098 }); 2099 }); 2100 } else if (this.options.width === 'fit') { 2101 // Remove inline min-width so width can be changed from 'auto' 2102 this.$menu.css('min-width', ''); 2103 this.$newElement.css('width', '').addClass('fit-width'); 2104 } else if (this.options.width) { 2105 // Remove inline min-width so width can be changed from 'auto' 2106 this.$menu.css('min-width', ''); 2107 this.$newElement.css('width', this.options.width); 2108 } else { 2109 // Remove inline min-width/width so width can be changed 2110 this.$menu.css('min-width', ''); 2111 this.$newElement.css('width', ''); 2112 } 2113 // Remove fit-width class if width is changed programmatically 2114 if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { 2115 this.$newElement[0].classList.remove('fit-width'); 2116 } 2117 }, 2118 2119 selectPosition: function () { 2120 this.$bsContainer = $('<div class="bs-container" />'); 2121 2122 var that = this, 2123 $container = $(this.options.container), 2124 pos, 2125 containerPos, 2126 actualHeight, 2127 getPlacement = function ($element) { 2128 var containerPosition = {}, 2129 // fall back to dropdown's default display setting if display is not manually set 2130 display = that.options.display || ( 2131 // Bootstrap 3 doesn't have $.fn.dropdown.Constructor.Default 2132 $.fn.dropdown.Constructor.Default ? $.fn.dropdown.Constructor.Default.display 2133 : false 2134 ); 2135 2136 that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass(classNames.DROPUP, $element.hasClass(classNames.DROPUP)); 2137 pos = $element.offset(); 2138 2139 if (!$container.is('body')) { 2140 containerPos = $container.offset(); 2141 containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop(); 2142 containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft(); 2143 } else { 2144 containerPos = { top: 0, left: 0 }; 2145 } 2146 2147 actualHeight = $element.hasClass(classNames.DROPUP) ? 0 : $element[0].offsetHeight; 2148 2149 // Bootstrap 4+ uses Popper for menu positioning 2150 if (version.major < 4 || display === 'static') { 2151 containerPosition.top = pos.top - containerPos.top + actualHeight; 2152 containerPosition.left = pos.left - containerPos.left; 2153 } 2154 2155 containerPosition.width = $element[0].offsetWidth; 2156 2157 that.$bsContainer.css(containerPosition); 2158 }; 2159 2160 this.$button.on('click.bs.dropdown.data-api', function () { 2161 if (that.isDisabled()) { 2162 return; 2163 } 2164 2165 getPlacement(that.$newElement); 2166 2167 that.$bsContainer 2168 .appendTo(that.options.container) 2169 .toggleClass(classNames.SHOW, !that.$button.hasClass(classNames.SHOW)) 2170 .append(that.$menu); 2171 }); 2172 2173 $(window) 2174 .off('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId) 2175 .on('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId, function () { 2176 var isActive = that.$newElement.hasClass(classNames.SHOW); 2177 2178 if (isActive) getPlacement(that.$newElement); 2179 }); 2180 2181 this.$element.on('hide' + EVENT_KEY, function () { 2182 that.$menu.data('height', that.$menu.height()); 2183 that.$bsContainer.detach(); 2184 }); 2185 }, 2186 2187 setOptionStatus: function (selectedOnly) { 2188 var that = this; 2189 2190 that.noScroll = false; 2191 2192 if (that.selectpicker.view.visibleElements && that.selectpicker.view.visibleElements.length) { 2193 for (var i = 0; i < that.selectpicker.view.visibleElements.length; i++) { 2194 var liData = that.selectpicker.current.data[i + that.selectpicker.view.position0], 2195 option = liData.option; 2196 2197 if (option) { 2198 if (selectedOnly !== true) { 2199 that.setDisabled( 2200 liData.index, 2201 liData.disabled 2202 ); 2203 } 2204 2205 that.setSelected( 2206 liData.index, 2207 option.selected 2208 ); 2209 } 2210 } 2211 } 2212 }, 2213 2214 /** 2215 * @param {number} index - the index of the option that is being changed 2216 * @param {boolean} selected - true if the option is being selected, false if being deselected 2217 */ 2218 setSelected: function (index, selected) { 2219 var li = this.selectpicker.main.elements[index], 2220 liData = this.selectpicker.main.data[index], 2221 activeIndexIsSet = this.activeIndex !== undefined, 2222 thisIsActive = this.activeIndex === index, 2223 prevActive, 2224 a, 2225 // if current option is already active 2226 // OR 2227 // if the current option is being selected, it's NOT multiple, and 2228 // activeIndex is undefined: 2229 // - when the menu is first being opened, OR 2230 // - after a search has been performed, OR 2231 // - when retainActive is false when selecting a new option (i.e. index of the newly selected option is not the same as the current activeIndex) 2232 keepActive = thisIsActive || (selected && !this.multiple && !activeIndexIsSet); 2233 2234 liData.selected = selected; 2235 2236 a = li.firstChild; 2237 2238 if (selected) { 2239 this.selectedIndex = index; 2240 } 2241 2242 li.classList.toggle('selected', selected); 2243 2244 if (keepActive) { 2245 this.focusItem(li, liData); 2246 this.selectpicker.view.currentActive = li; 2247 this.activeIndex = index; 2248 } else { 2249 this.defocusItem(li); 2250 } 2251 2252 if (a) { 2253 a.classList.toggle('selected', selected); 2254 2255 if (selected) { 2256 a.setAttribute('aria-selected', true); 2257 } else { 2258 if (this.multiple) { 2259 a.setAttribute('aria-selected', false); 2260 } else { 2261 a.removeAttribute('aria-selected'); 2262 } 2263 } 2264 } 2265 2266 if (!keepActive && !activeIndexIsSet && selected && this.prevActiveIndex !== undefined) { 2267 prevActive = this.selectpicker.main.elements[this.prevActiveIndex]; 2268 2269 this.defocusItem(prevActive); 2270 } 2271 }, 2272 2273 /** 2274 * @param {number} index - the index of the option that is being disabled 2275 * @param {boolean} disabled - true if the option is being disabled, false if being enabled 2276 */ 2277 setDisabled: function (index, disabled) { 2278 var li = this.selectpicker.main.elements[index], 2279 a; 2280 2281 this.selectpicker.main.data[index].disabled = disabled; 2282 2283 a = li.firstChild; 2284 2285 li.classList.toggle(classNames.DISABLED, disabled); 2286 2287 if (a) { 2288 if (version.major === '4') a.classList.toggle(classNames.DISABLED, disabled); 2289 2290 if (disabled) { 2291 a.setAttribute('aria-disabled', disabled); 2292 a.setAttribute('tabindex', -1); 2293 } else { 2294 a.removeAttribute('aria-disabled'); 2295 a.setAttribute('tabindex', 0); 2296 } 2297 } 2298 }, 2299 2300 isDisabled: function () { 2301 return this.$element[0].disabled; 2302 }, 2303 2304 checkDisabled: function () { 2305 if (this.isDisabled()) { 2306 this.$newElement[0].classList.add(classNames.DISABLED); 2307 this.$button.addClass(classNames.DISABLED).attr('tabindex', -1).attr('aria-disabled', true); 2308 } else { 2309 if (this.$button[0].classList.contains(classNames.DISABLED)) { 2310 this.$newElement[0].classList.remove(classNames.DISABLED); 2311 this.$button.removeClass(classNames.DISABLED).attr('aria-disabled', false); 2312 } 2313 2314 if (this.$button.attr('tabindex') == -1 && !this.$element.data('tabindex')) { 2315 this.$button.removeAttr('tabindex'); 2316 } 2317 } 2318 }, 2319 2320 tabIndex: function () { 2321 if (this.$element.data('tabindex') !== this.$element.attr('tabindex') && 2322 (this.$element.attr('tabindex') !== -98 && this.$element.attr('tabindex') !== '-98')) { 2323 this.$element.data('tabindex', this.$element.attr('tabindex')); 2324 this.$button.attr('tabindex', this.$element.data('tabindex')); 2325 } 2326 2327 this.$element.attr('tabindex', -98); 2328 }, 2329 2330 clickListener: function () { 2331 var that = this, 2332 $document = $(document); 2333 2334 $document.data('spaceSelect', false); 2335 2336 this.$button.on('keyup', function (e) { 2337 if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) { 2338 e.preventDefault(); 2339 $document.data('spaceSelect', false); 2340 } 2341 }); 2342 2343 this.$newElement.on('show.bs.dropdown', function () { 2344 if (version.major > 3 && !that.dropdown) { 2345 that.dropdown = that.$button.data('bs.dropdown'); 2346 that.dropdown._menu = that.$menu[0]; 2347 } 2348 }); 2349 2350 this.$button.on('click.bs.dropdown.data-api', function () { 2351 if (!that.$newElement.hasClass(classNames.SHOW)) { 2352 that.setSize(); 2353 } 2354 }); 2355 2356 function setFocus () { 2357 if (that.options.liveSearch) { 2358 that.$searchbox.trigger('focus'); 2359 } else { 2360 that.$menuInner.trigger('focus'); 2361 } 2362 } 2363 2364 function checkPopperExists () { 2365 if (that.dropdown && that.dropdown._popper && that.dropdown._popper.state.isCreated) { 2366 setFocus(); 2367 } else { 2368 requestAnimationFrame(checkPopperExists); 2369 } 2370 } 2371 2372 this.$element.on('shown' + EVENT_KEY, function () { 2373 if (that.$menuInner[0].scrollTop !== that.selectpicker.view.scrollTop) { 2374 that.$menuInner[0].scrollTop = that.selectpicker.view.scrollTop; 2375 } 2376 2377 if (version.major > 3) { 2378 requestAnimationFrame(checkPopperExists); 2379 } else { 2380 setFocus(); 2381 } 2382 }); 2383 2384 // ensure posinset and setsize are correct before selecting an option via a click 2385 this.$menuInner.on('mouseenter', 'li a', function (e) { 2386 var hoverLi = this.parentElement, 2387 position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, 2388 index = Array.prototype.indexOf.call(hoverLi.parentElement.children, hoverLi), 2389 hoverData = that.selectpicker.current.data[index + position0]; 2390 2391 that.focusItem(hoverLi, hoverData, true); 2392 }); 2393 2394 this.$menuInner.on('click', 'li a', function (e, retainActive) { 2395 var $this = $(this), 2396 element = that.$element[0], 2397 position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, 2398 clickedData = that.selectpicker.current.data[$this.parent().index() + position0], 2399 clickedIndex = clickedData.index, 2400 prevValue = getSelectValues(element), 2401 prevIndex = element.selectedIndex, 2402 prevOption = element.options[prevIndex], 2403 triggerChange = true; 2404 2405 // Don't close on multi choice menu 2406 if (that.multiple && that.options.maxOptions !== 1) { 2407 e.stopPropagation(); 2408 } 2409 2410 e.preventDefault(); 2411 2412 // Don't run if the select is disabled 2413 if (!that.isDisabled() && !$this.parent().hasClass(classNames.DISABLED)) { 2414 var option = clickedData.option, 2415 $option = $(option), 2416 state = option.selected, 2417 $optgroup = $option.parent('optgroup'), 2418 $optgroupOptions = $optgroup.find('option'), 2419 maxOptions = that.options.maxOptions, 2420 maxOptionsGrp = $optgroup.data('maxOptions') || false; 2421 2422 if (clickedIndex === that.activeIndex) retainActive = true; 2423 2424 if (!retainActive) { 2425 that.prevActiveIndex = that.activeIndex; 2426 that.activeIndex = undefined; 2427 } 2428 2429 if (!that.multiple) { // Deselect all others if not multi select box 2430 if (prevOption) prevOption.selected = false; 2431 option.selected = true; 2432 that.setSelected(clickedIndex, true); 2433 } else { // Toggle the one we have chosen if we are multi select. 2434 option.selected = !state; 2435 2436 that.setSelected(clickedIndex, !state); 2437 $this.trigger('blur'); 2438 2439 if (maxOptions !== false || maxOptionsGrp !== false) { 2440 var maxReached = maxOptions < getSelectedOptions(element).length, 2441 maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; 2442 2443 if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { 2444 if (maxOptions && maxOptions == 1) { 2445 element.selectedIndex = -1; 2446 option.selected = true; 2447 that.setOptionStatus(true); 2448 } else if (maxOptionsGrp && maxOptionsGrp == 1) { 2449 for (var i = 0; i < $optgroupOptions.length; i++) { 2450 var _option = $optgroupOptions[i]; 2451 _option.selected = false; 2452 that.setSelected(_option.liIndex, false); 2453 } 2454 2455 option.selected = true; 2456 that.setSelected(clickedIndex, true); 2457 } else { 2458 var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText, 2459 maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText, 2460 maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), 2461 maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), 2462 $notify = $('<div class="notify"></div>'); 2463 // If {var} is set in array, replace it 2464 /** @deprecated */ 2465 if (maxOptionsArr[2]) { 2466 maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); 2467 maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); 2468 } 2469 2470 option.selected = false; 2471 2472 that.$menu.append($notify); 2473 2474 if (maxOptions && maxReached) { 2475 $notify.append($('<div>' + maxTxt + '</div>')); 2476 triggerChange = false; 2477 that.$element.trigger('maxReached' + EVENT_KEY); 2478 } 2479 2480 if (maxOptionsGrp && maxReachedGrp) { 2481 $notify.append($('<div>' + maxTxtGrp + '</div>')); 2482 triggerChange = false; 2483 that.$element.trigger('maxReachedGrp' + EVENT_KEY); 2484 } 2485 2486 setTimeout(function () { 2487 that.setSelected(clickedIndex, false); 2488 }, 10); 2489 2490 $notify[0].classList.add('fadeOut'); 2491 2492 setTimeout(function () { 2493 $notify.remove(); 2494 }, 1050); 2495 } 2496 } 2497 } 2498 } 2499 2500 if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) { 2501 that.$button.trigger('focus'); 2502 } else if (that.options.liveSearch) { 2503 that.$searchbox.trigger('focus'); 2504 } 2505 2506 // Trigger select 'change' 2507 if (triggerChange) { 2508 if (that.multiple || prevIndex !== element.selectedIndex) { 2509 // $option.prop('selected') is current option state (selected/unselected). prevValue is the value of the select prior to being changed. 2510 changedArguments = [option.index, $option.prop('selected'), prevValue]; 2511 that.$element 2512 .triggerNative('change'); 2513 } 2514 } 2515 } 2516 }); 2517 2518 this.$menu.on('click', 'li.' + classNames.DISABLED + ' a, .' + classNames.POPOVERHEADER + ', .' + classNames.POPOVERHEADER + ' :not(.close)', function (e) { 2519 if (e.currentTarget == this) { 2520 e.preventDefault(); 2521 e.stopPropagation(); 2522 if (that.options.liveSearch && !$(e.target).hasClass('close')) { 2523 that.$searchbox.trigger('focus'); 2524 } else { 2525 that.$button.trigger('focus'); 2526 } 2527 } 2528 }); 2529 2530 this.$menuInner.on('click', '.divider, .dropdown-header', function (e) { 2531 e.preventDefault(); 2532 e.stopPropagation(); 2533 if (that.options.liveSearch) { 2534 that.$searchbox.trigger('focus'); 2535 } else { 2536 that.$button.trigger('focus'); 2537 } 2538 }); 2539 2540 this.$menu.on('click', '.' + classNames.POPOVERHEADER + ' .close', function () { 2541 that.$button.trigger('click'); 2542 }); 2543 2544 this.$searchbox.on('click', function (e) { 2545 e.stopPropagation(); 2546 }); 2547 2548 this.$menu.on('click', '.actions-btn', function (e) { 2549 if (that.options.liveSearch) { 2550 that.$searchbox.trigger('focus'); 2551 } else { 2552 that.$button.trigger('focus'); 2553 } 2554 2555 e.preventDefault(); 2556 e.stopPropagation(); 2557 2558 if ($(this).hasClass('bs-select-all')) { 2559 that.selectAll(); 2560 } else { 2561 that.deselectAll(); 2562 } 2563 }); 2564 2565 this.$element 2566 .on('change' + EVENT_KEY, function () { 2567 that.render(); 2568 that.$element.trigger('changed' + EVENT_KEY, changedArguments); 2569 changedArguments = null; 2570 }) 2571 .on('focus' + EVENT_KEY, function () { 2572 if (!that.options.mobile) that.$button.trigger('focus'); 2573 }); 2574 }, 2575 2576 liveSearchListener: function () { 2577 var that = this, 2578 noResults = document.createElement('li'); 2579 2580 this.$button.on('click.bs.dropdown.data-api', function () { 2581 if (!!that.$searchbox.val()) { 2582 that.$searchbox.val(''); 2583 } 2584 }); 2585 2586 this.$searchbox.on('click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api', function (e) { 2587 e.stopPropagation(); 2588 }); 2589 2590 this.$searchbox.on('input propertychange', function () { 2591 var searchValue = that.$searchbox.val(); 2592 2593 that.selectpicker.search.elements = []; 2594 that.selectpicker.search.data = []; 2595 2596 if (searchValue) { 2597 var i, 2598 searchMatch = [], 2599 q = searchValue.toUpperCase(), 2600 cache = {}, 2601 cacheArr = [], 2602 searchStyle = that._searchStyle(), 2603 normalizeSearch = that.options.liveSearchNormalize; 2604 2605 if (normalizeSearch) q = normalizeToBase(q); 2606 2607 for (var i = 0; i < that.selectpicker.main.data.length; i++) { 2608 var li = that.selectpicker.main.data[i]; 2609 2610 if (!cache[i]) { 2611 cache[i] = stringSearch(li, q, searchStyle, normalizeSearch); 2612 } 2613 2614 if (cache[i] && li.headerIndex !== undefined && cacheArr.indexOf(li.headerIndex) === -1) { 2615 if (li.headerIndex > 0) { 2616 cache[li.headerIndex - 1] = true; 2617 cacheArr.push(li.headerIndex - 1); 2618 } 2619 2620 cache[li.headerIndex] = true; 2621 cacheArr.push(li.headerIndex); 2622 2623 cache[li.lastIndex + 1] = true; 2624 } 2625 2626 if (cache[i] && li.type !== 'optgroup-label') cacheArr.push(i); 2627 } 2628 2629 for (var i = 0, cacheLen = cacheArr.length; i < cacheLen; i++) { 2630 var index = cacheArr[i], 2631 prevIndex = cacheArr[i - 1], 2632 li = that.selectpicker.main.data[index], 2633 liPrev = that.selectpicker.main.data[prevIndex]; 2634 2635 if (li.type !== 'divider' || (li.type === 'divider' && liPrev && liPrev.type !== 'divider' && cacheLen - 1 !== i)) { 2636 that.selectpicker.search.data.push(li); 2637 searchMatch.push(that.selectpicker.main.elements[index]); 2638 } 2639 } 2640 2641 that.activeIndex = undefined; 2642 that.noScroll = true; 2643 that.$menuInner.scrollTop(0); 2644 that.selectpicker.search.elements = searchMatch; 2645 that.createView(true); 2646 2647 if (!searchMatch.length) { 2648 noResults.className = 'no-results'; 2649 noResults.innerHTML = that.options.noneResultsText.replace('{0}', '"' + htmlEscape(searchValue) + '"'); 2650 that.$menuInner[0].firstChild.appendChild(noResults); 2651 } 2652 } else { 2653 that.$menuInner.scrollTop(0); 2654 that.createView(false); 2655 } 2656 }); 2657 }, 2658 2659 _searchStyle: function () { 2660 return this.options.liveSearchStyle || 'contains'; 2661 }, 2662 2663 val: function (value) { 2664 var element = this.$element[0]; 2665 2666 if (typeof value !== 'undefined') { 2667 var prevValue = getSelectValues(element); 2668 2669 changedArguments = [null, null, prevValue]; 2670 2671 this.$element 2672 .val(value) 2673 .trigger('changed' + EVENT_KEY, changedArguments); 2674 2675 if (this.$newElement.hasClass(classNames.SHOW)) { 2676 if (this.multiple) { 2677 this.setOptionStatus(true); 2678 } else { 2679 var liSelectedIndex = (element.options[element.selectedIndex] || {}).liIndex; 2680 2681 if (typeof liSelectedIndex === 'number') { 2682 this.setSelected(this.selectedIndex, false); 2683 this.setSelected(liSelectedIndex, true); 2684 } 2685 } 2686 } 2687 2688 this.render(); 2689 2690 changedArguments = null; 2691 2692 return this.$element; 2693 } else { 2694 return this.$element.val(); 2695 } 2696 }, 2697 2698 changeAll: function (status) { 2699 if (!this.multiple) return; 2700 if (typeof status === 'undefined') status = true; 2701 2702 var element = this.$element[0], 2703 previousSelected = 0, 2704 currentSelected = 0, 2705 prevValue = getSelectValues(element); 2706 2707 element.classList.add('bs-select-hidden'); 2708 2709 for (var i = 0, data = this.selectpicker.current.data, len = data.length; i < len; i++) { 2710 var liData = data[i], 2711 option = liData.option; 2712 2713 if (option && !liData.disabled && liData.type !== 'divider') { 2714 if (liData.selected) previousSelected++; 2715 option.selected = status; 2716 if (status === true) currentSelected++; 2717 } 2718 } 2719 2720 element.classList.remove('bs-select-hidden'); 2721 2722 if (previousSelected === currentSelected) return; 2723 2724 this.setOptionStatus(); 2725 2726 changedArguments = [null, null, prevValue]; 2727 2728 this.$element 2729 .triggerNative('change'); 2730 }, 2731 2732 selectAll: function () { 2733 return this.changeAll(true); 2734 }, 2735 2736 deselectAll: function () { 2737 return this.changeAll(false); 2738 }, 2739 2740 toggle: function (e) { 2741 e = e || window.event; 2742 2743 if (e) e.stopPropagation(); 2744 2745 this.$button.trigger('click.bs.dropdown.data-api'); 2746 }, 2747 2748 keydown: function (e) { 2749 var $this = $(this), 2750 isToggle = $this.hasClass('dropdown-toggle'), 2751 $parent = isToggle ? $this.closest('.dropdown') : $this.closest(Selector.MENU), 2752 that = $parent.data('this'), 2753 $items = that.findLis(), 2754 index, 2755 isActive, 2756 liActive, 2757 activeLi, 2758 offset, 2759 updateScroll = false, 2760 downOnTab = e.which === keyCodes.TAB && !isToggle && !that.options.selectOnTab, 2761 isArrowKey = REGEXP_ARROW.test(e.which) || downOnTab, 2762 scrollTop = that.$menuInner[0].scrollTop, 2763 isVirtual = that.isVirtual(), 2764 position0 = isVirtual === true ? that.selectpicker.view.position0 : 0; 2765 2766 // do nothing if a function key is pressed 2767 if (e.which >= 112 && e.which <= 123) return; 2768 2769 isActive = that.$newElement.hasClass(classNames.SHOW); 2770 2771 if ( 2772 !isActive && 2773 ( 2774 isArrowKey || 2775 (e.which >= 48 && e.which <= 57) || 2776 (e.which >= 96 && e.which <= 105) || 2777 (e.which >= 65 && e.which <= 90) 2778 ) 2779 ) { 2780 that.$button.trigger('click.bs.dropdown.data-api'); 2781 2782 if (that.options.liveSearch) { 2783 that.$searchbox.trigger('focus'); 2784 return; 2785 } 2786 } 2787 2788 if (e.which === keyCodes.ESCAPE && isActive) { 2789 e.preventDefault(); 2790 that.$button.trigger('click.bs.dropdown.data-api').trigger('focus'); 2791 } 2792 2793 if (isArrowKey) { // if up or down 2794 if (!$items.length) return; 2795 2796 liActive = that.selectpicker.main.elements[that.activeIndex]; 2797 index = liActive ? Array.prototype.indexOf.call(liActive.parentElement.children, liActive) : -1; 2798 2799 if (index !== -1) { 2800 that.defocusItem(liActive); 2801 } 2802 2803 if (e.which === keyCodes.ARROW_UP) { // up 2804 if (index !== -1) index--; 2805 if (index + position0 < 0) index += $items.length; 2806 2807 if (!that.selectpicker.view.canHighlight[index + position0]) { 2808 index = that.selectpicker.view.canHighlight.slice(0, index + position0).lastIndexOf(true) - position0; 2809 if (index === -1) index = $items.length - 1; 2810 } 2811 } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down 2812 index++; 2813 if (index + position0 >= that.selectpicker.view.canHighlight.length) index = 0; 2814 2815 if (!that.selectpicker.view.canHighlight[index + position0]) { 2816 index = index + 1 + that.selectpicker.view.canHighlight.slice(index + position0 + 1).indexOf(true); 2817 } 2818 } 2819 2820 e.preventDefault(); 2821 2822 var liActiveIndex = position0 + index; 2823 2824 if (e.which === keyCodes.ARROW_UP) { // up 2825 // scroll to bottom and highlight last option 2826 if (position0 === 0 && index === $items.length - 1) { 2827 that.$menuInner[0].scrollTop = that.$menuInner[0].scrollHeight; 2828 2829 liActiveIndex = that.selectpicker.current.elements.length - 1; 2830 } else { 2831 activeLi = that.selectpicker.current.data[liActiveIndex]; 2832 offset = activeLi.position - activeLi.height; 2833 2834 updateScroll = offset < scrollTop; 2835 } 2836 } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down 2837 // scroll to top and highlight first option 2838 if (index === 0) { 2839 that.$menuInner[0].scrollTop = 0; 2840 2841 liActiveIndex = 0; 2842 } else { 2843 activeLi = that.selectpicker.current.data[liActiveIndex]; 2844 offset = activeLi.position - that.sizeInfo.menuInnerHeight; 2845 2846 updateScroll = offset > scrollTop; 2847 } 2848 } 2849 2850 liActive = that.selectpicker.current.elements[liActiveIndex]; 2851 2852 that.activeIndex = that.selectpicker.current.data[liActiveIndex].index; 2853 2854 that.focusItem(liActive); 2855 2856 that.selectpicker.view.currentActive = liActive; 2857 2858 if (updateScroll) that.$menuInner[0].scrollTop = offset; 2859 2860 if (that.options.liveSearch) { 2861 that.$searchbox.trigger('focus'); 2862 } else { 2863 $this.trigger('focus'); 2864 } 2865 } else if ( 2866 (!$this.is('input') && !REGEXP_TAB_OR_ESCAPE.test(e.which)) || 2867 (e.which === keyCodes.SPACE && that.selectpicker.keydown.keyHistory) 2868 ) { 2869 var searchMatch, 2870 matches = [], 2871 keyHistory; 2872 2873 e.preventDefault(); 2874 2875 that.selectpicker.keydown.keyHistory += keyCodeMap[e.which]; 2876 2877 if (that.selectpicker.keydown.resetKeyHistory.cancel) clearTimeout(that.selectpicker.keydown.resetKeyHistory.cancel); 2878 that.selectpicker.keydown.resetKeyHistory.cancel = that.selectpicker.keydown.resetKeyHistory.start(); 2879 2880 keyHistory = that.selectpicker.keydown.keyHistory; 2881 2882 // if all letters are the same, set keyHistory to just the first character when searching 2883 if (/^(.)\1+$/.test(keyHistory)) { 2884 keyHistory = keyHistory.charAt(0); 2885 } 2886 2887 // find matches 2888 for (var i = 0; i < that.selectpicker.current.data.length; i++) { 2889 var li = that.selectpicker.current.data[i], 2890 hasMatch; 2891 2892 hasMatch = stringSearch(li, keyHistory, 'startsWith', true); 2893 2894 if (hasMatch && that.selectpicker.view.canHighlight[i]) { 2895 matches.push(li.index); 2896 } 2897 } 2898 2899 if (matches.length) { 2900 var matchIndex = 0; 2901 2902 $items.removeClass('active').find('a').removeClass('active'); 2903 2904 // either only one key has been pressed or they are all the same key 2905 if (keyHistory.length === 1) { 2906 matchIndex = matches.indexOf(that.activeIndex); 2907 2908 if (matchIndex === -1 || matchIndex === matches.length - 1) { 2909 matchIndex = 0; 2910 } else { 2911 matchIndex++; 2912 } 2913 } 2914 2915 searchMatch = matches[matchIndex]; 2916 2917 activeLi = that.selectpicker.main.data[searchMatch]; 2918 2919 if (scrollTop - activeLi.position > 0) { 2920 offset = activeLi.position - activeLi.height; 2921 updateScroll = true; 2922 } else { 2923 offset = activeLi.position - that.sizeInfo.menuInnerHeight; 2924 // if the option is already visible at the current scroll position, just keep it the same 2925 updateScroll = activeLi.position > scrollTop + that.sizeInfo.menuInnerHeight; 2926 } 2927 2928 liActive = that.selectpicker.main.elements[searchMatch]; 2929 2930 that.activeIndex = matches[matchIndex]; 2931 2932 that.focusItem(liActive); 2933 2934 if (liActive) liActive.firstChild.focus(); 2935 2936 if (updateScroll) that.$menuInner[0].scrollTop = offset; 2937 2938 $this.trigger('focus'); 2939 } 2940 } 2941 2942 // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. 2943 if ( 2944 isActive && 2945 ( 2946 (e.which === keyCodes.SPACE && !that.selectpicker.keydown.keyHistory) || 2947 e.which === keyCodes.ENTER || 2948 (e.which === keyCodes.TAB && that.options.selectOnTab) 2949 ) 2950 ) { 2951 if (e.which !== keyCodes.SPACE) e.preventDefault(); 2952 2953 if (!that.options.liveSearch || e.which !== keyCodes.SPACE) { 2954 that.$menuInner.find('.active a').trigger('click', true); // retain active class 2955 $this.trigger('focus'); 2956 2957 if (!that.options.liveSearch) { 2958 // Prevent screen from scrolling if the user hits the spacebar 2959 e.preventDefault(); 2960 // Fixes spacebar selection of dropdown items in FF & IE 2961 $(document).data('spaceSelect', true); 2962 } 2963 } 2964 } 2965 }, 2966 2967 mobile: function () { 2968 this.$element[0].classList.add('mobile-device'); 2969 }, 2970 2971 refresh: function () { 2972 // update options if data attributes have been changed 2973 var config = $.extend({}, this.options, this.$element.data()); 2974 this.options = config; 2975 2976 this.checkDisabled(); 2977 this.setStyle(); 2978 this.render(); 2979 this.buildData(); 2980 this.buildList(); 2981 this.setWidth(); 2982 2983 this.setSize(true); 2984 2985 this.$element.trigger('refreshed' + EVENT_KEY); 2986 }, 2987 2988 hide: function () { 2989 this.$newElement.hide(); 2990 }, 2991 2992 show: function () { 2993 this.$newElement.show(); 2994 }, 2995 2996 remove: function () { 2997 this.$newElement.remove(); 2998 this.$element.remove(); 2999 }, 3000 3001 destroy: function () { 3002 this.$newElement.before(this.$element).remove(); 3003 3004 if (this.$bsContainer) { 3005 this.$bsContainer.remove(); 3006 } else { 3007 this.$menu.remove(); 3008 } 3009 3010 this.$element 3011 .off(EVENT_KEY) 3012 .removeData('selectpicker') 3013 .removeClass('bs-select-hidden selectpicker'); 3014 3015 $(window).off(EVENT_KEY + '.' + this.selectId); 3016 } 3017 }; 3018 3019 // SELECTPICKER PLUGIN DEFINITION 3020 // ============================== 3021 function Plugin (option) { 3022 // get the args of the outer function.. 3023 var args = arguments; 3024 // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them 3025 // to get lost/corrupted in android 2.3 and IE9 #715 #775 3026 var _option = option; 3027 3028 [].shift.apply(args); 3029 3030 // if the version was not set successfully 3031 if (!version.success) { 3032 // try to retreive it again 3033 try { 3034 version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.'); 3035 } catch (err) { 3036 // fall back to use BootstrapVersion if set 3037 if (Selectpicker.BootstrapVersion) { 3038 version.full = Selectpicker.BootstrapVersion.split(' ')[0].split('.'); 3039 } else { 3040 version.full = [version.major, '0', '0']; 3041 3042 console.warn( 3043 'There was an issue retrieving Bootstrap\'s version. ' + 3044 'Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. ' + 3045 'If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.', 3046 err 3047 ); 3048 } 3049 } 3050 3051 version.major = version.full[0]; 3052 version.success = true; 3053 } 3054 3055 if (version.major === '4') { 3056 // some defaults need to be changed if using Bootstrap 4 3057 // check to see if they have already been manually changed before forcing them to update 3058 var toUpdate = []; 3059 3060 if (Selectpicker.DEFAULTS.style === classNames.BUTTONCLASS) toUpdate.push({ name: 'style', className: 'BUTTONCLASS' }); 3061 if (Selectpicker.DEFAULTS.iconBase === classNames.ICONBASE) toUpdate.push({ name: 'iconBase', className: 'ICONBASE' }); 3062 if (Selectpicker.DEFAULTS.tickIcon === classNames.TICKICON) toUpdate.push({ name: 'tickIcon', className: 'TICKICON' }); 3063 3064 classNames.DIVIDER = 'dropdown-divider'; 3065 classNames.SHOW = 'show'; 3066 classNames.BUTTONCLASS = 'btn-light'; 3067 classNames.POPOVERHEADER = 'popover-header'; 3068 classNames.ICONBASE = ''; 3069 classNames.TICKICON = 'bs-ok-default'; 3070 3071 for (var i = 0; i < toUpdate.length; i++) { 3072 var option = toUpdate[i]; 3073 Selectpicker.DEFAULTS[option.name] = classNames[option.className]; 3074 } 3075 } 3076 3077 var value; 3078 var chain = this.each(function () { 3079 var $this = $(this); 3080 if ($this.is('select')) { 3081 var data = $this.data('selectpicker'), 3082 options = typeof _option == 'object' && _option; 3083 3084 if (!data) { 3085 var dataAttributes = $this.data(); 3086 3087 for (var dataAttr in dataAttributes) { 3088 if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) { 3089 delete dataAttributes[dataAttr]; 3090 } 3091 } 3092 3093 var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, dataAttributes, options); 3094 config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), dataAttributes.template, options.template); 3095 $this.data('selectpicker', (data = new Selectpicker(this, config))); 3096 } else if (options) { 3097 for (var i in options) { 3098 if (options.hasOwnProperty(i)) { 3099 data.options[i] = options[i]; 3100 } 3101 } 3102 } 3103 3104 if (typeof _option == 'string') { 3105 if (data[_option] instanceof Function) { 3106 value = data[_option].apply(data, args); 3107 } else { 3108 value = data.options[_option]; 3109 } 3110 } 3111 } 3112 }); 3113 3114 if (typeof value !== 'undefined') { 3115 // noinspection JSUnusedAssignment 3116 return value; 3117 } else { 3118 return chain; 3119 } 3120 } 3121 3122 var old = $.fn.selectpicker; 3123 $.fn.selectpicker = Plugin; 3124 $.fn.selectpicker.Constructor = Selectpicker; 3125 3126 // SELECTPICKER NO CONFLICT 3127 // ======================== 3128 $.fn.selectpicker.noConflict = function () { 3129 $.fn.selectpicker = old; 3130 return this; 3131 }; 3132 3133 3134 $(document) 3135 .off('keydown.bs.dropdown.data-api') 3136 .on('keydown' + EVENT_KEY, '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', Selectpicker.prototype.keydown) 3137 .on('focusin.modal', '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', function (e) { 3138 e.stopPropagation(); 3139 }); 3140 3141 // SELECTPICKER DATA-API 3142 // ===================== 3143 $(window).on('load' + EVENT_KEY + '.data-api', function () { 3144 $('.selectpicker').each(function () { 3145 var $selectpicker = $(this); 3146 Plugin.call($selectpicker, $selectpicker.data()); 3147 }) 3148 }); 3149})(jQuery); 3150 3151(function ($) { 3152 $.fn.selectpicker.defaults = { 3153 noneSelectedText: 'No hay selección', 3154 noneResultsText: 'No hay resultados {0}', 3155 countSelectedText: 'Seleccionados {0} de {1}', 3156 maxOptionsText: ['LÃmite alcanzado ({n} {var} max)', 'LÃmite del grupo alcanzado({n} {var} max)', ['elementos', 'element']], 3157 multipleSeparator: ', ', 3158 selectAllText: 'Todos', 3159 deselectAllText: 'Limpiar' 3160 }; 3161})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.