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