1(function ($) { 2 'use strict'; 3 4 //<editor-fold desc="Shims"> 5 if (!String.prototype.includes) { 6 (function () { 7 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` 8 var toString = {}.toString; 9 var defineProperty = (function () { 10 // IE 8 only supports `Object.defineProperty` on DOM elements 11 try { 12 var object = {}; 13 var $defineProperty = Object.defineProperty; 14 var result = $defineProperty(object, object, object) && $defineProperty; 15 } catch (error) { 16 } 17 return result; 18 }()); 19 var indexOf = ''.indexOf; 20 var includes = function (search) { 21 if (this == null) { 22 throw TypeError(); 23 } 24 var string = String(this); 25 if (search && toString.call(search) == '[object RegExp]') { 26 throw TypeError(); 27 } 28 var stringLength = string.length; 29 var searchString = String(search); 30 var searchLength = searchString.length; 31 var position = arguments.length > 1 ? arguments[1] : undefined; 32 // `ToInteger` 33 var pos = position ? Number(position) : 0; 34 if (pos != pos) { // better `isNaN` 35 pos = 0; 36 } 37 var start = Math.min(Math.max(pos, 0), stringLength); 38 // Avoid the `indexOf` call if no match is possible 39 if (searchLength + start > stringLength) { 40 return false; 41 } 42 return indexOf.call(string, searchString, pos) != -1; 43 }; 44 if (defineProperty) { 45 defineProperty(String.prototype, 'includes', { 46 'value': includes, 47 'configurable': true, 48 'writable': true 49 }); 50 } else { 51 String.prototype.includes = includes; 52 } 53 }()); 54 } 55 56 if (!String.prototype.startsWith) { 57 (function () { 58 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` 59 var defineProperty = (function () { 60 // IE 8 only supports `Object.defineProperty` on DOM elements 61 try { 62 var object = {}; 63 var $defineProperty = Object.defineProperty; 64 var result = $defineProperty(object, object, object) && $defineProperty; 65 } catch (error) { 66 } 67 return result; 68 }()); 69 var toString = {}.toString; 70 var startsWith = function (search) { 71 if (this == null) { 72 throw TypeError(); 73 } 74 var string = String(this); 75 if (search && toString.call(search) == '[object RegExp]') { 76 throw TypeError(); 77 } 78 var stringLength = string.length; 79 var searchString = String(search); 80 var searchLength = searchString.length; 81 var position = arguments.length > 1 ? arguments[1] : undefined; 82 // `ToInteger` 83 var pos = position ? Number(position) : 0; 84 if (pos != pos) { // better `isNaN` 85 pos = 0; 86 } 87 var start = Math.min(Math.max(pos, 0), stringLength); 88 // Avoid the `indexOf` call if no match is possible 89 if (searchLength + start > stringLength) { 90 return false; 91 } 92 var index = -1; 93 while (++index < searchLength) { 94 if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { 95 return false; 96 } 97 } 98 return true; 99 }; 100 if (defineProperty) { 101 defineProperty(String.prototype, 'startsWith', { 102 'value': startsWith, 103 'configurable': true, 104 'writable': true 105 }); 106 } else { 107 String.prototype.startsWith = startsWith; 108 } 109 }()); 110 } 111 112 if (!Object.keys) { 113 Object.keys = function ( 114 o, // object 115 k, // key 116 r // result array 117 ){ 118 // initialize object and result 119 r=[]; 120 // iterate over object keys 121 for (k in o) 122 // fill result array with non-prototypical keys 123 r.hasOwnProperty.call(o, k) && r.push(k); 124 // return result 125 return r 126 }; 127 } 128 //</editor-fold> 129 130 // Case insensitive contains search 131 $.expr[':'].icontains = function (obj, index, meta) { 132 var $obj = $(obj); 133 var haystack = ($obj.data('tokens') || $obj.text()).toUpperCase(); 134 return haystack.includes(meta[3].toUpperCase()); 135 }; 136 137 // Case insensitive begins search 138 $.expr[':'].ibegins = function (obj, index, meta) { 139 var $obj = $(obj); 140 var haystack = ($obj.data('tokens') || $obj.text()).toUpperCase(); 141 return haystack.startsWith(meta[3].toUpperCase()); 142 }; 143 144 // Case and accent insensitive contains search 145 $.expr[':'].aicontains = function (obj, index, meta) { 146 var $obj = $(obj); 147 var haystack = ($obj.data('tokens') || $obj.data('normalizedText') || $obj.text()).toUpperCase(); 148 return haystack.includes(meta[3].toUpperCase()); 149 }; 150 151 // Case and accent insensitive begins search 152 $.expr[':'].aibegins = function (obj, index, meta) { 153 var $obj = $(obj); 154 var haystack = ($obj.data('tokens') || $obj.data('normalizedText') || $obj.text()).toUpperCase(); 155 return haystack.startsWith(meta[3].toUpperCase()); 156 }; 157 158 /** 159 * Remove all diatrics from the given text. 160 * @access private 161 * @param {String} text 162 * @returns {String} 163 */ 164 function normalizeToBase(text) { 165 var rExps = [ 166 {re: /[\xC0-\xC6]/g, ch: "A"}, 167 {re: /[\xE0-\xE6]/g, ch: "a"}, 168 {re: /[\xC8-\xCB]/g, ch: "E"}, 169 {re: /[\xE8-\xEB]/g, ch: "e"}, 170 {re: /[\xCC-\xCF]/g, ch: "I"}, 171 {re: /[\xEC-\xEF]/g, ch: "i"}, 172 {re: /[\xD2-\xD6]/g, ch: "O"},
173 {re: /[\xF2-\xF6]/g, ch: "o"}, 174 {re: /[\xD9-\xDC]/g, ch: "U"}, 175 {re: /[\xF9-\xFC]/g, ch: "u"}, 176 {re: /[\xC7-\xE7]/g, ch: "c"}, 177 {re: /[\xD1]/g, ch: "N"}, 178 {re: /[\xF1]/g, ch: "n"} 179 ]; 180 $.each(rExps, function () { 181 text = text.replace(this.re, this.ch); 182 }); 183 return text; 184 } 185 186 187 function htmlEscape(html) { 188 var escapeMap = { 189 '&': '&', 190 '<': '<', 191 '>': '>', 192 '"': '"', 193 "'": ''', 194 '`': '`' 195 }; 196 var source = '(?:' + Object.keys(escapeMap).join('|') + ')', 197 testRegexp = new RegExp(source), 198 replaceRegexp = new RegExp(source, 'g'), 199 string = html == null ? '' : '' + html; 200 return testRegexp.test(string) ? string.replace(replaceRegexp, function (match) { 201 return escapeMap[match]; 202 }) : string; 203 } 204 205 var Selectpicker = function (element, options, e) { 206 if (e) { 207 e.stopPropagation(); 208 e.preventDefault(); 209 } 210 211 this.$element = $(element); 212 this.$newElement = null; 213 this.$button = null; 214 this.$menu = null; 215 this.$lis = null; 216 this.options = options; 217 218 // 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 219 // data-attribute) 220 if (this.options.title === null) { 221 this.options.title = this.$element.attr('title'); 222 } 223 224 //Expose public methods 225 this.val = Selectpicker.prototype.val; 226 this.render = Selectpicker.prototype.render; 227 this.refresh = Selectpicker.prototype.refresh; 228 this.setStyle = Selectpicker.prototype.setStyle; 229 this.selectAll = Selectpicker.prototype.selectAll; 230 this.deselectAll = Selectpicker.prototype.deselectAll; 231 this.destroy = Selectpicker.prototype.remove; 232 this.remove = Selectpicker.prototype.remove; 233 this.show = Selectpicker.prototype.show; 234 this.hide = Selectpicker.prototype.hide; 235 236 this.init(); 237 }; 238 239 Selectpicker.VERSION = '1.7.2'; 240 241 // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. 242 Selectpicker.DEFAULTS = { 243 noneSelectedText: 'Nothing selected', 244 noneResultsText: 'No results matched {0}', 245 countSelectedText: function (numSelected, numTotal) { 246 return (numSelected == 1) ? "{0} item selected" : "{0} items selected"; 247 }, 248 maxOptionsText: function (numAll, numGroup) { 249 return [ 250 (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)', 251 (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)' 252 ]; 253 }, 254 selectAllText: 'Select All', 255 deselectAllText: 'Deselect All', 256 doneButton: false, 257 doneButtonText: 'Close', 258 multipleSeparator: ', ', 259 styleBase: 'btn', 260 style: 'btn-default', 261 size: 'auto', 262 title: null, 263 selectedTextFormat: 'values', 264 width: false, 265 container: false, 266 hideDisabled: false, 267 showSubtext: false, 268 showIcon: true, 269 showContent: true, 270 dropupAuto: true, 271 header: false, 272 liveSearch: false, 273 liveSearchPlaceholder: null, 274 liveSearchNormalize: false, 275 liveSearchStyle: 'contains', 276 actionsBox: false, 277 iconBase: 'glyphicon', 278 tickIcon: 'glyphicon-ok', 279 maxOptions: false, 280 mobile: false, 281 selectOnTab: false, 282 dropdownAlignRight: false 283 }; 284 285 Selectpicker.prototype = { 286 287 constructor: Selectpicker, 288 289 init: function () { 290 var that = this, 291 id = this.$element.attr('id'); 292 293 this.$element.addClass('bs-select-hidden'); 294 // store originalIndex (key) and newIndex (value) in this.liObj for fast accessibility 295 // allows us to do this.$lis.eq(that.liObj[index]) instead of this.$lis.filter('[data-original-index="' + index + '"]') 296 this.liObj = {}; 297 this.multiple = this.$element.prop('multiple'); 298 this.autofocus = this.$element.prop('autofocus'); 299 this.$newElement = this.createView(); 300 this.$element.after(this.$newElement); 301 this.$button = this.$newElement.children('button'); 302 this.$menu = this.$newElement.children('.dropdown-menu'); 303 this.$menuInner = this.$menu.children('.inner'); 304 this.$searchbox = this.$menu.find('input'); 305 306 if (this.options.dropdownAlignRight) 307 this.$menu.addClass('dropdown-menu-right'); 308 309 if (typeof id !== 'undefined') { 310 this.$button.attr('data-id', id); 311 $('label[for="' + id + '"]').click(function (e) { 312 e.preventDefault(); 313 that.$button.focus(); 314 }); 315 } 316 317 this.checkDisabled(); 318 this.clickListener(); 319 if (this.options.liveSearch) this.liveSearchListener(); 320 this.render(); 321 this.setStyle(); 322 this.setWidth(); 323 if (this.options.container) this.selectPosition(); 324 this.$menu.data('this', this); 325 this.$newElement.data('this', this); 326 if (this.options.mobile) this.mobile(); 327 328 this.$newElement.on('hide.bs.dropdown', function (e) { 329 that.$element.trigger('hide.bs.select', e); 330 }); 331 332 this.$newElement.on('hidden.bs.dropdown', function (e) { 333 that.$element.trigger('hidden.bs.select', e); 334 }); 335 336 this.$newElement.on('show.bs.dropdown', function (e) { 337 that.$element.trigger('show.bs.select', e); 338 }); 339 340 this.$newElement.on('shown.bs.dropdown', function (e) { 341 that.$element.trigger('shown.bs.select', e); 342 }); 343 344 setTimeout(function () { 345 that.$element.trigger('loaded.bs.select'); 346 }); 347 }, 348 349 createDropdown: function () { 350 // Options 351 // If we are multiple, then add the show-tick class by default 352 var multiple = this.multiple ? ' show-tick' : '', 353 inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '', 354 autofocus = this.autofocus ? ' autofocus' : ''; 355 // Elements 356 var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">×</button>' + this.options.header + '</div>' : ''; 357 var searchbox = this.options.liveSearch ? 358 '<div class="bs-searchbox">' + 359 '<input type="text" class="form-control" autocomplete="off"' + 360 (null === this.options.liveSearchPlaceholder ? '' : ' placeholder="' + htmlEscape(this.options.liveSearchPlaceholder) + '"') + '>
360' + 361 '</div>' 362 : ''; 363 var actionsbox = this.multiple && this.options.actionsBox ? 364 '<div class="bs-actionsbox">' + 365 '<div class="btn-group btn-group-sm btn-block">' + 366 '<button type="button" class="actions-btn bs-select-all btn btn-default">' + 367 this.options.selectAllText + 368 '</button>' + 369 '<button type="button" class="actions-btn bs-deselect-all btn btn-default">' + 370 this.options.deselectAllText + 371 '</button>' + 372 '</div>' + 373 '</div>' 374 : ''; 375 var donebutton = this.multiple && this.options.doneButton ? 376 '<div class="bs-donebutton">' + 377 '<div class="btn-group btn-block">' + 378 '<button type="button" class="btn btn-sm btn-default">' + 379 this.options.doneButtonText + 380 '</button>' + 381 '</div>' + 382 '</div>' 383 : ''; 384 var drop = 385 '<div class="btn-group bootstrap-select' + multiple + inputGroup + '">' + 386 '<button type="button" class="' + this.options.styleBase + ' dropdown-toggle" data-toggle="dropdown"' + autofocus + '>' + 387 '<span class="filter-option pull-left"></span> ' + 388 '<span class="caret"></span>' + 389 '</button>' + 390 '<div class="dropdown-menu open">' + 391 header + 392 searchbox + 393 actionsbox + 394 '<ul class="dropdown-menu inner" role="menu">' + 395 '</ul>' + 396 donebutton + 397 '</div>' + 398 '</div>'; 399 400 return $(drop); 401 }, 402 403 createView: function () { 404 var $drop = this.createDropdown(), 405 li = this.createLi(); 406 407 $drop.find('ul')[0].innerHTML = li; 408 return $drop; 409 }, 410 411 reloadLi: function () { 412 //Remove all children. 413 this.destroyLi(); 414 //Re build 415 var li = this.createLi(); 416 this.$menuInner[0].innerHTML = li; 417 }, 418 419 destroyLi: function () { 420 this.$menu.find('li').remove(); 421 }, 422 423 createLi: function () { 424 var that = this, 425 _li = [], 426 optID = 0, 427 titleOption = document.createElement('option'), 428 liIndex = -1; // increment liIndex whenever a new <li> element is created to ensure liObj is correct 429 430 // Helper functions 431 /** 432 * @param content 433 * @param [index] 434 * @param [classes] 435 * @param [optgroup] 436 * @returns {string} 437 */ 438 var generateLI = function (content, index, classes, optgroup) { 439 return '<li' + 440 ((typeof classes !== 'undefined' & '' !== classes) ? ' class="' + classes + '"' : '') + 441 ((typeof index !== 'undefined' & null !== index) ? ' data-original-index="' + index + '"' : '') + 442 ((typeof optgroup !== 'undefined' & null !== optgroup) ? 'data-optgroup="' + optgroup + '"' : '') + 443 '>' + content + '</li>'; 444 }; 445 446 /** 447 * @param text 448 * @param [classes] 449 * @param [inline] 450 * @param [tokens] 451 * @returns {string} 452 */ 453 var generateA = function (text, classes, inline, tokens) { 454 return '<a tabindex="0"' + 455 (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + 456 (typeof inline !== 'undefined' ? ' style="' + inline + '"' : '') + 457 (that.options.liveSearchNormalize ? ' data-normalized-text="' + normalizeToBase(htmlEscape(text)) + '"' : '') + 458 (typeof tokens !== 'undefined' || tokens !== null ? ' data-tokens="' + tokens + '"' : '') + 459 '>' + text + 460 '<span class="' + that.options.iconBase + ' ' + that.options.tickIcon + ' check-mark"></span>' + 461 '</a>'; 462 }; 463 464 if (this.options.title && !this.multiple) { 465 // this option doesn't create a new <li> element, but does add a new option, so liIndex is decreased 466 // since liObj is recalculated on every refresh, liIndex needs to be decreased even if the titleOption is already appended 467 liIndex--; 468 469 if (!this.$element.find('.bs-title-option').length) { 470 // Use native JS to prepend option (faster) 471 var element = this.$element[0]; 472 titleOption.className = 'bs-title-option'; 473 titleOption.appendChild(document.createTextNode(this.options.title)); 474 titleOption.value = ''; 475 element.insertBefore(titleOption, element.firstChild); 476 // Check if selected attribute is already set on an option. If not, select the titleOption option. 477 if (element.options[element.selectedIndex].getAttribute('selected') === null) titleOption.selected = true; 478 } 479 } 480 481 this.$element.find('option').each(function (index) { 482 var $this = $(this); 483 484 liIndex++; 485 486 if ($this.hasClass('bs-title-option')) return; 487 488 // Get the class and text for the option 489 var optionClass = this.className || '', 490 inline = this.style.cssText, 491 text = $this.data('content') ? $this.data('content') : $this.html(), 492 tokens = $this.data('tokens') ? $this.data('tokens') : null, 493 subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="text-muted">' + $this.data('subtext') + '</small>' : '', 494 icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '', 495 isDisabled = this.disabled || this.parentElement.tagName === 'OPTGROUP' && this.parentElement.disabled; 496 497 if (icon !== '' && isDisabled) { 498 icon = '<span>' + icon + '</span>'; 499 } 500 501 if (that.options.hideDisabled && isDisabled) { 502 liIndex--; 503 return; 504 } 505 506 if (!$this.data('content')) { 507 // Prepend any icon and append any subtext to the main text. 508 text = icon + '<span class="text">
508' + text + subtext + '</span>'; 509 } 510 511 if (this.parentElement.tagName === 'OPTGROUP' && $this.data('divider') !== true) { 512 if ($this.index() === 0) { // Is it the first option of the optgroup? 513 optID += 1; 514 515 // Get the opt group label 516 var label = this.parentElement.label, 517 labelSubtext = typeof $this.parent().data('subtext') !== 'undefined' ? '<small class="text-muted">' + $this.parent().data('subtext') + '</small>' : '', 518 labelIcon = $this.parent().data('icon') ? '<span class="' + that.options.iconBase + ' ' + $this.parent().data('icon') + '"></span> ' : '', 519 optGroupClass = ' ' + this.parentElement.className || ''; 520 521 label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>'; 522 523 if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown? 524 liIndex++; 525 _li.push(generateLI('', null, 'divider', optID + 'div')); 526 } 527 liIndex++; 528 _li.push(generateLI(label, null, 'dropdown-header' + optGroupClass, optID)); 529 } 530 _li.push(generateLI(generateA(text, 'opt ' + optionClass + optGroupClass, inline, tokens), index, '', optID)); 531 } else if ($this.data('divider') === true) { 532 _li.push(generateLI('', index, 'divider')); 533 } else if ($this.data('hidden') === true) { 534 _li.push(generateLI(generateA(text, optionClass, inline, tokens), index, 'hidden is-hidden')); 535 } else { 536 if (this.previousElementSibling && this.previousElementSibling.tagName === 'OPTGROUP') { 537 liIndex++; 538 _li.push(generateLI('', null, 'divider', optID + 'div')); 539 } 540 _li.push(generateLI(generateA(text, optionClass, inline, tokens), index)); 541 } 542 543 that.liObj[index] = liIndex; 544 }); 545 546 //If we are not multiple, we don't have a selected item, and we don't have a title, select the first element so something is set in the button 547 if (!this.multiple && this.$element.find('option:selected').length === 0 && !this.options.title) { 548 this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected'); 549 } 550 551 return _li.join(''); 552 }, 553 554 findLis: function () { 555 if (this.$lis == null) this.$lis = this.$menu.find('li'); 556 return this.$lis; 557 }, 558 559 /** 560 * @param [updateLi] defaults to true 561 */ 562 render: function (updateLi) { 563 var that = this, 564 notDisabled; 565 566 //Update the LI to match the SELECT 567 if (updateLi !== false) { 568 this.$element.find('option').each(function (index) { 569 var $lis = that.findLis().eq(that.liObj[index]); 570 571 that.setDisabled(index, this.disabled || this.parentElement.tagName === 'OPTGROUP' && this.parentElement.disabled, $lis); 572 that.setSelected(index, this.selected, $lis); 573 }); 574 } 575 576 this.tabIndex(); 577 578 var selectedItems = this.$element.find('option').map(function () { 579 if (this.selected) { 580 if (that.options.hideDisabled && (this.disabled || this.parentElement.tagName === 'OPTGROUP' && this.parentElement.disabled)) return false; 581 582 var $this = $(this), 583 icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : '', 584 subtext; 585 586 if (that.options.showSubtext && $this.data('subtext') && !that.multiple) { 587 subtext = ' <small class="text-muted">' + $this.data('subtext') + '</small>'; 588 } else { 589 subtext = ''; 590 } 591 if (typeof $this.attr('title') !== 'undefined') { 592 return $this.attr('title'); 593 } else if ($this.data('content') && that.options.showContent) { 594 return $this.data('content'); 595 } else { 596 return icon + $this.html() + subtext; 597 } 598 } 599 }).toArray(); 600 601 //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled 602 //Convert all the values into a comma delimited string 603 var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator); 604 605 //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc.. 606 if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) { 607 var max = this.options.selectedTextFormat.split('>'); 608 if ((max.length > 1 && selectedItems.length > max[1]) || (max.length == 1 && selectedItems.length >= 2)) { 609 notDisabled = this.options.hideDisabled ? ', [disabled]' : ''; 610 var totalCount = this.$element.find('option').not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length, 611 tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText; 612 title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString()); 613 } 614 } 615 616 if (this.options.title == undefined) { 617 this.options.title = this.$element.attr('title'); 618 } 619 620 if (this.options.selectedTextFormat == 'static') { 621 title = this.options.title; 622 } 623 624 //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text 625 if (!title) { 626 title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText; 627 } 628 629 //strip all html-tags and trim the result 630 this.$button.attr('title', $.trim(title.replace(/<[^>]*>?/g, ''))); 631 this.$button.children('.filter-option').html(title); 632 633 this.$element.trigger('rendered.bs.select'); 634 }, 635 636 /** 637 * @param [style] 638 * @param [status] 639 */ 640 setStyle: function (style, status) { 641 if (this.$element.attr('class')) { 642 this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, '')); 643 } 644 645 var buttonClass = style ? style : this.options.style; 646 647 if (status == 'add') { 648 this.$button.addClass(buttonClass); 649 } else if (status == 'remove') { 650 this.$button.removeClass(buttonClass); 651 } else { 652 this.$button.removeClass(this.options.style); 653 this.$button.addClass(buttonClass); 654 } 655 }, 656 657 liHeight: function (refresh) { 658 if (!refresh && (this.options.size === false || this.sizeInfo)) return; 659 660 var newElement = document.createElement('div'), 661 menu = document.createElement('div'), 662 menuInner = document.createElement('ul'), 663 divider = document.createElement('li'), 664 li = document.createElement('li'), 665 a = document.createElement('a'), 666 text = document.createElement('span'), 667 header = this.options.header ? this.$menu.find('.popover-title')[0].cloneNode(true) : null, 668 search = this.options.liveSearch ? document.createElement('div') : null, 669 actions = this.options.actionsBox && this.multiple ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null, 670 doneButton = this.options.doneButton && this.multiple ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null; 671 672 text.className = 'text'; 673 newElement.className = this.$menu[0].parentNode.className + ' open'; 674 menu.className = 'dropdown-menu open'; 675 menuInner.className = 'dropdown-menu inner'; 676 divider.className = 'divider'; 677 678 text.appendChild(document.createTextNode('Inner text')); 679 a.appendChild(text); 680 li.appendChild(a); 681 menuInner.appendChild(li); 682 menuInner.appendChild(divider); 683 if (header) menu.appendChild(header); 684 if (search) { 685 // create a span instead of input as creating an input element is slower 686 var input = document.createElement('span'); 687 search.className = 'bs-searchbox'; 688 input.className = 'form-control'; 689 search.appendChild(input); 690 menu.appendChild(search); 691 } 692 if (actions) menu.appendChild(actions); 693 menu.appendChild(menuInner); 694 if (doneButton) menu.appendChild(doneButton); 695 newElement.appendChild(menu); 696 697 document.body.appendChild(newElement); 698 699 var liHeight = a.offsetHeight, 700 headerHeight = header ? header.offsetHeight : 0, 701 searchHeight = search ? search.offsetHeight : 0, 702 actionsHeight = actions ? actions.offsetHeight : 0, 703 doneButtonHeight = doneButton ? doneButton.offsetHeight : 0, 704 dividerHeight = $(divider).outerHeight(true), 705 // fall back to jQuery if getComputedStyle is not supported 706 menuStyle = getComputedStyle ? getComputedStyle(menu) : false, 707 $menu = menuStyle ? $(menu) : null, 708 menuPadding = parseInt(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) + 709 parseInt(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) + 710 parseInt(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) + 711 parseInt(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')), 712 menuExtras = menuPadding + 713 parseInt(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) + 714 parseInt(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2; 715 716 document.body.removeChild(newElement); 717 718 this.sizeInfo = { 719 liHeight: liHeight, 720 headerHeight: headerHeight, 721 searchHeight: searchHeight, 722 actionsHeight: actionsHeight, 723 doneButtonHeight: doneButtonHeight,
724 dividerHeight: dividerHeight, 725 menuPadding: menuPadding, 726 menuExtras: menuExtras 727 }; 728 }, 729 730 setSize: function () { 731 this.findLis(); 732 this.liHeight(); 733 var that = this, 734 $menu = this.$menu, 735 $menuInner = this.$menuInner, 736 $window = $(window), 737 selectHeight = this.$newElement[0].offsetHeight, 738 liHeight = this.sizeInfo['liHeight'], 739 headerHeight = this.sizeInfo['headerHeight'], 740 searchHeight = this.sizeInfo['searchHeight'], 741 actionsHeight = this.sizeInfo['actionsHeight'], 742 doneButtonHeight = this.sizeInfo['doneButtonHeight'], 743 divHeight = this.sizeInfo['dividerHeight'], 744 menuPadding = this.sizeInfo['menuPadding'], 745 menuExtras = this.sizeInfo['menuExtras'], 746 notDisabled = this.options.hideDisabled ? '.disabled' : '', 747 menuHeight, 748 getHeight, 749 selectOffsetTop, 750 selectOffsetBot, 751 posVert = function () { 752 selectOffsetTop = that.$newElement.offset().top - $window.scrollTop(); 753 selectOffsetBot = $window.height() - selectOffsetTop - selectHeight; 754 }; 755 756 posVert(); 757 758 if (this.options.header) $menu.css('padding-top', 0); 759 760 if (this.options.size === 'auto') { 761 var getSize = function () { 762 var minHeight, 763 hasClass = function (className, include) { 764 return function (element) { 765 if (include) { 766 return (element.classList ? element.classList.contains(className) : $(element).hasClass(className)); 767 } else { 768 return !(element.classList ? element.classList.contains(className) : $(element).hasClass(className)); 769 } 770 }; 771 }, 772 lis = that.$menuInner[0].getElementsByTagName('li'), 773 lisVisible = Array.prototype.filter ? Array.prototype.filter.call(lis, hasClass('hidden', false)) : that.$lis.not('.hidden'), 774 optGroup = Array.prototype.filter ? Array.prototype.filter.call(lisVisible, hasClass('dropdown-header', true)) : lisVisible.filter('.dropdown-header'); 775 776 posVert(); 777 menuHeight = selectOffsetBot - menuExtras; 778 779 if (that.options.container) { 780 if (!$menu.data('height')) $menu.data('height', $menu.height()); 781 getHeight = $menu.data('height'); 782 } else { 783 getHeight = $menu.height(); 784 } 785 786 if (that.options.dropupAuto) { 787 that.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras) < getHeight); 788 } 789 if (that.$newElement.hasClass('dropup')) { 790 menuHeight = selectOffsetTop - menuExtras; 791 } 792 793 if ((lisVisible.length + optGroup.length) > 3) { 794 minHeight = liHeight * 3 + menuExtras - 2; 795 } else { 796 minHeight = 0; 797 } 798 799 $menu.css({ 800 'max-height': menuHeight + 'px', 801 'overflow': 'hidden', 802 'min-height': minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px' 803 }); 804 $menuInner.css({ 805 'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding + 'px', 806 'overflow-y': 'auto', 807 'min-height': Math.max(minHeight - menuPadding, 0) + 'px' 808 }); 809 }; 810 getSize(); 811 this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize); 812 $window.off('resize.getSize scroll.getSize').on('resize.getSize scroll.getSize', getSize); 813 } else if (this.options.size && this.options.size != 'auto' && this.$lis.not(notDisabled).length > this.options.size) { 814 var optIndex = this.$lis.not('.divider').not(notDisabled).children().slice(0, this.options.size).last().parent().index(), 815 divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length; 816 menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding; 817 818 if (that.options.container) { 819 if (!$menu.data('height')) $menu.data('height', $menu.height()); 820 getHeight = $menu.data('height'); 821 } else { 822 getHeight = $menu.height(); 823 } 824 825 if (that.options.dropupAuto) { 826 //noinspection JSUnusedAssignment 827 this.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras) < getHeight); 828 } 829 $menu.css({ 830 'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px', 831 'overflow': 'hidden', 832 'min-height': '' 833 }); 834 $menuInner.css({ 835 'max-height': menuHeight - menuPadding + 'px', 836 'overflow-y': 'auto', 837 'min-height': '' 838 }); 839 } 840 }, 841 842 setWidth: function () { 843 if (this.options.width === 'auto') { 844 this.$menu.css('min-width', '0'); 845 846 // Get correct width if element is hidden 847 var $selectClone = this.$menu.parent().clone().appendTo('body'), 848 $selectClone2 = this.options.container ? this.$newElement.clone().appendTo('body') : $selectClone, 849 ulWidth = $selectClone.children('.dropdown-menu').outerWidth(), 850 btnWidth = $selectClone2.css('width', 'auto').children('button').outerWidth(); 851 852 $selectClone.remove(); 853 $selectClone2.remove(); 854 855 // Set width to whatever's larger, button title or longest option 856 this.$newElement.css('width', Math.max(ulWidth, btnWidth) + 'px'); 857 } else if (this.options.width === 'fit') { 858 // Remove inline min-width so width can be changed from 'auto' 859 this.$menu.css('min-width', ''); 860 this.$newElement.css('width', '').addClass('fit-width'); 861 } else if (this.options.width) { 862 // Remove inline min-width so width can be changed from 'auto' 863 this.$menu.css('min-width', ''); 864 this.$newElement.css('width', this.options.width); 865 } else { 866 // Remove inline min-width/width so width can be changed 867 this.$menu.css('min-width', ''); 868 this.$newElement.css('width', ''); 869 } 870 // Remove fit-width class if width is changed programmatically 871 if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { 872 this.$newElement.removeClass('fit-width'); 873 } 874 }, 875 876 selectPosition: function () { 877 var that = this, 878 drop = '<div />', 879 $drop = $(drop), 880 pos, 881 actualHeight, 882 getPlacement = function ($element) { 883 $drop.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass('dropup', $element.hasClass('dropup')); 884 pos = $element.offset(); 885 actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight; 886 $drop.css({ 887 'top': pos.top + actualHeight, 888 'left': pos.left, 889 'width': $element[0].offsetWidth, 890 'position': 'absolute' 891 }); 892 }; 893 894 this.$newElement.on('click', function () { 895 if (that.isDisabled()) { 896 return; 897 } 898 getPlacement($(this)); 899 $drop.appendTo(that.options.container); 900 $drop.toggleClass('open', !$(this).hasClass('open')); 901 $drop.append(that.$menu); 902 }); 903 904 $(window).on('resize scroll', function () { 905 getPlacement(that.$newElement); 906 }); 907 908 this.$element.on('hide.bs.select', function () { 909 that.$menu.data('height', that.$menu.height()); 910 $drop.detach(); 911 }); 912 }, 913 914 setSelected: function (index, selected, $lis) { 915 if (!$lis) { 916 var $lis = this.findLis().eq(this.liObj[index]); 917 } 918 919 $lis.toggleClass('selected', selected); 920 }, 921 922 setDisabled: function (index, disabled, $lis) { 923 if (!$lis) { 924 var $lis = this.findLis().eq(this.liObj[index]); 925 } 926 927 if (disabled) { 928 $lis.addClass('disabled').children('a').attr('href', '#').attr('tabindex', -1); 929 } else { 930 $lis.removeClass('disabled').children('a').removeAttr('href').attr('tabindex', 0); 931 } 932 }, 933 934 isDisabled: function () { 935 return this.$element[0].disabled; 936 }, 937 938 checkDisabled: function () { 939 var that = this; 940 941 if (this.isDisabled()) { 942 this.$newElement.addClass('disabled'); 943 this.$button.addClass('disabled').attr('tabindex', -1); 944 } else { 945 if (this.$button.hasClass('disabled')) { 946 this.$newElement.removeClass('disabled'); 947 this.$button.removeClass('disabled'); 948 } 949 950 if (this.$button.attr('tabindex') == -1 && !this.$element.data('tabindex')) { 951 this.$button.removeAttr('tabindex'); 952 } 953 } 954 955 this.$button.click(function () { 956 return !that.isDisabled(); 957 }); 958 }, 959 960 tabIndex: function () { 961 if (this.$element.is('[tabindex]')) { 962 this.$element.data('tabindex', this.$element.attr('tabindex')); 963 this.$button.attr('tabindex', this.$element.data('tabindex')); 964 } 965 }, 966 967 clickListener: function () { 968 var that = this, 969 $document = $(document); 970 971 this.$newElement.on('touchstart.dropdown', '.dropdown-menu', function (e) { 972 e.stopPropagation(); 973 }); 974 975 $document.data('spaceSelect', false); 976 977 this.$button.on('keyup', function (e) { 978 if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) { 979 e.preventDefault(); 980 $document.data('spaceSelect', false); 981 } 982 }); 983 984 this.$newElement.on('click', function () { 985 that.setSize(); 986 that.$element.on('shown.bs.select', function () { 987 if (!that.options.liveSearch && !that.multiple) { 988 that.$menu.find('.selected a').focus(); 989 } else if (!that.multiple) { 990 var selectedIndex = that.liObj[that.$element[0].selectedIndex]; 991 992 if (typeof selectedIndex !== 'number') return; 993 994 // scroll to selected option 995 var offset = that.$lis.eq(selectedIndex)[0].offsetTop - that.$menuInner[0].offsetTop; 996 offset = offset - that.$menuInner[0].offsetHeight/2 + that.sizeInfo.liHeight/2; 997 that.$menuInner[0].scrollTop = offset; 998 } 999 }); 1000 }); 1001 1002 this.$menu.on('click', 'li a', function (e) { 1003 var $this = $(this), 1004 clickedIndex = $this.parent().data('originalIndex'),
1005 prevValue = that.$element.val(), 1006 prevIndex = that.$element.prop('selectedIndex'); 1007 1008 // Don't close on multi choice menu 1009 if (that.multiple) { 1010 e.stopPropagation(); 1011 } 1012 1013 e.preventDefault(); 1014 1015 //Don't run if we have been disabled 1016 if (!that.isDisabled() && !$this.parent().hasClass('disabled')) { 1017 var $options = that.$element.find('option'), 1018 $option = $options.eq(clickedIndex), 1019 state = $option.prop('selected'), 1020 $optgroup = $option.parent('optgroup'), 1021 maxOptions = that.options.maxOptions, 1022 maxOptionsGrp = $optgroup.data('maxOptions') || false; 1023 1024 if (!that.multiple) { // Deselect all others if not multi select box 1025 $options.prop('selected', false); 1026 $option.prop('selected', true); 1027 that.$menu.find('.selected').removeClass('selected'); 1028 that.setSelected(clickedIndex, true); 1029 } else { // Toggle the one we have chosen if we are multi select. 1030 $option.prop('selected', !state); 1031 that.setSelected(clickedIndex, !state); 1032 $this.blur(); 1033 1034 if (maxOptions !== false || maxOptionsGrp !== false) { 1035 var maxReached = maxOptions < $options.filter(':selected').length, 1036 maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; 1037 1038 if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { 1039 if (maxOptions && maxOptions == 1) { 1040 $options.prop('selected', false); 1041 $option.prop('selected', true); 1042 that.$menu.find('.selected').removeClass('selected'); 1043 that.setSelected(clickedIndex, true); 1044 } else if (maxOptionsGrp && maxOptionsGrp == 1) { 1045 $optgroup.find('option:selected').prop('selected', false); 1046 $option.prop('selected', true); 1047 var optgroupID = $this.parent().data('optgroup'); 1048 that.$menu.find('[data-optgroup="' + optgroupID + '"]').removeClass('selected'); 1049 that.setSelected(clickedIndex, true); 1050 } else { 1051 var maxOptionsArr = (typeof that.options.maxOptionsText === 'function') ? 1052 that.options.maxOptionsText(maxOptions, maxOptionsGrp) : that.options.maxOptionsText, 1053 maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), 1054 maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), 1055 $notify = $('<div class="notify"></div>'); 1056 // If {var} is set in array, replace it 1057 /** @deprecated */ 1058 if (maxOptionsArr[2]) { 1059 maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); 1060 maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); 1061 } 1062 1063 $option.prop('selected', false); 1064 1065 that.$menu.append($notify); 1066 1067 if (maxOptions && maxReached) { 1068 $notify.append($('<div>' + maxTxt + '</div>')); 1069 that.$element.trigger('maxReached.bs.select'); 1070 } 1071 1072 if (maxOptionsGrp && maxReachedGrp) { 1073 $notify.append($('<div>' + maxTxtGrp + '</div>')); 1074 that.$element.trigger('maxReachedGrp.bs.select'); 1075 } 1076 1077 setTimeout(function () { 1078 that.setSelected(clickedIndex, false); 1079 }, 10); 1080 1081 $notify.delay(750).fadeOut(300, function () { 1082 $(this).remove(); 1083 }); 1084 } 1085 } 1086 } 1087 } 1088 1089 if (!that.multiple) { 1090 that.$button.focus(); 1091 } else if (that.options.liveSearch) { 1092 that.$searchbox.focus(); 1093 } 1094 1095 // Trigger select 'change' 1096 if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) { 1097 that.$element.change(); 1098 // $option.prop('selected') is current option state (selected/unselected). state is previous option state. 1099 that.$element.trigger('changed.bs.select', [clickedIndex, $option.prop('selected'), state]); 1100 } 1101 } 1102 }); 1103 1104 this.$menu.on('click', 'li.disabled a, .popover-title, .popover-title :not(.close)', function (e) { 1105 if (e.currentTarget == this) { 1106 e.preventDefault(); 1107 e.stopPropagation(); 1108 if (that.options.liveSearch && !$(e.target).hasClass('close')) { 1109 that.$searchbox.focus(); 1110 } else { 1111 that.$button.focus(); 1112 } 1113 } 1114 }); 1115 1116 this.$menu.on('click', 'li.divider, li.dropdown-header', function (e) { 1117 e.preventDefault(); 1118 e.stopPropagation(); 1119 if (that.options.liveSearch) { 1120 that.$searchbox.focus(); 1121 } else { 1122 that.$button.focus(); 1123 } 1124 }); 1125 1126 this.$menu.on('click', '.popover-title .close', function () { 1127 that.$button.click(); 1128 }); 1129 1130 this.$searchbox.on('click', function (e) { 1131 e.stopPropagation(); 1132 }); 1133 1134 this.$menu.on('click', '.actions-btn', function (e) { 1135 if (that.options.liveSearch) { 1136 that.$searchbox.focus(); 1137 } else { 1138 that.$button.focus(); 1139 } 1140 1141 e.preventDefault(); 1142 e.stopPropagation(); 1143 1144 if ($(this).hasClass('bs-select-all')) { 1145 that.selectAll(); 1146 } else { 1147 that.deselectAll(); 1148 } 1149 that.$element.change(); 1150 }); 1151 1152 this.$element.change(function () { 1153 that.render(false); 1154 }); 1155 }, 1156 1157 liveSearchListener: function () { 1158 var that = this, 1159 $no_results = $('<li class="no-results"></li>'); 1160 1161 this.$newElement.on('click.dropdown.data-api touchstart.dropdown.data-api', function () { 1162 that.$menuInner.find('.active').removeClass('active'); 1163 if (!!that.$searchbox.val()) { 1164 that.$searchbox.val(''); 1165 that.$lis.not('.is-hidden').removeClass('hidden'); 1166 if (!!$no_results.parent().length) $no_results.remove(); 1167 } 1168 if (!that.multiple) that.$menuInner.find('.selected').addClass('active'); 1169 setTimeout(function () { 1170 that.$searchbox.focus(); 1171 }, 10); 1172 }); 1173 1174 this.$searchbox.on('click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api', function (e) { 1175 e.stopPropagation(); 1176 }); 1177 1178 this.$searchbox.on('input propertychange', function () { 1179 if (that.$searchbox.val()) { 1180 var $searchBase = that.$lis.not('.is-hidden').removeClass('hidden').children('a'); 1181 if (that.options.liveSearchNormalize) { 1182 $searchBase = $searchBase.not(':a' + that._searchStyle() + '(' + normalizeToBase(that.$searchbox.val()) + ')'); 1183 } else { 1184 $searchBase = $searchBase.not(':' + that._searchStyle() + '(' + that.$searchbox.val() + ')'); 1185 } 1186 $searchBase.parent().addClass('hidden'); 1187 1188 that.$lis.filter('.dropdown-header').each(function () { 1189 var $this = $(this), 1190 optgroup = $this.data('optgroup'); 1191 1192 if (that.$lis.filter('[data-optgroup=' + optgroup + ']').not($this).not('.hidden').length === 0) { 1193 $this.addClass('hidden'); 1194 that.$lis.filter('[data-optgroup=' + optgroup + 'div]').addClass('hidden'); 1195 } 1196 }); 1197 1198 var $lisVisible = that.$lis.not('.hidden'); 1199 1200 // hide divider if first or last visible, or if followed by another divider 1201 $lisVisible.each(function (index) { 1202 var $this = $(this); 1203 1204 if ($this.hasClass('divider') && ( 1205 $this.index() === $lisVisible.eq(0).index() || 1206 $this.index() === $lisVisible.last().index() || 1207 $lisVisible.eq(index + 1).hasClass('divider'))) { 1208 $this.addClass('hidden'); 1209 } 1210 }); 1211 1212 if (!that.$lis.not('.hidden, .no-results').length) { 1213 if (!!$no_results.parent().length) { 1214 $no_results.remove(); 1215 } 1216 $no_results.html(that.options.noneResultsText.replace('{0}', '"' + htmlEscape(that.$searchbox.val()) + '"')).show(); 1217 that.$menuInner.append($no_results); 1218 } else if (!!$no_results.parent().length) { 1219 $no_results.remove(); 1220 } 1221 1222 } else { 1223 that.$lis.not('.is-hidden').removeClass('hidden'); 1224 if (!!$no_results.parent().length) { 1225 $no_results.remove(); 1226 } 1227 } 1228 1229 that.$lis.filter('.active').removeClass('active'); 1230 that.$lis.not('.hidden, .divider, .dropdown-header').eq(0).addClass('active').children('a').focus(); 1231 $(this).focus(); 1232 }); 1233 }, 1234 1235 _searchStyle: function () { 1236 var style = 'icontains'; 1237 switch (this.options.liveSearchStyle) { 1238 case 'begins': 1239 case 'startsWith': 1240 style = 'ibegins'; 1241 break; 1242 case 'contains': 1243 default: 1244 break; //no need to change the default 1245 } 1246 1247 return style; 1248 }, 1249 1250 val: function (value) { 1251 if (typeof value !== 'undefined') { 1252 this.$element.val(value); 1253 this.render(); 1254 1255 return this.$element; 1256 } else { 1257 return this.$element.val(); 1258 } 1259 }, 1260 1261 selectAll: function () { 1262 this.findLis(); 1263 this.$element.find('option:enabled').not('[data-divider], [data-hidden]').prop('selected', true); 1264 this.$lis.not('.divider, .dropdown-header, .disabled, .hidden').addClass('selected'); 1265 this.render(false); 1266 }, 1267 1268 deselectAll: function () { 1269 this.findLis(); 1270 this.$element.find('option:enabled').not('[data-divider], [data-hidden]').prop('selected', false); 1271 this.$lis.not('.divider, .dropdown-header, .disabled, .hidden').removeClass('selected'); 1272 this.render(false); 1273 }, 1274 1275 keydown: function (e) { 1276 var $this = $(this), 1277 $parent = $this.is('input') ? $this.parent().parent() : $this.parent(), 1278 $items, 1279 that = $parent.data('this'), 1280 index, 1281 next, 1282 first, 1283 last, 1284 prev, 1285 nextPrev, 1286 prevIndex, 1287 isActive, 1288 selector = ':not(.disabled, .hidden, .dropdown-header, .divider)', 1289 keyCodeMap = { 1290 32: ' ', 1291 48: '0', 1292 49: '1', 1293 50: '2', 1294 51: '3', 1295 52: '4', 1296 53: '5', 1297 54: '6', 1298 55: '7', 1299 56: '8', 1300 57: '9', 1301 59: ';', 1302 65: 'a', 1303 66: 'b', 1304 67: 'c', 1305 68: 'd', 1306 69: 'e', 1307 70: 'f', 1308 71: 'g', 1309 72: 'h', 1310 73: 'i', 1311 74: 'j', 1312 75: 'k', 1313 76: 'l', 1314 77: 'm', 1315 78: 'n', 1316 79: 'o', 1317 80: 'p', 1318 81: 'q', 1319 82: 'r', 1320 83: 's', 1321 84: 't', 1322 85: 'u', 1323 86: 'v',
1324 87: 'w', 1325 88: 'x', 1326 89: 'y', 1327 90: 'z', 1328 96: '0', 1329 97: '1', 1330 98: '2', 1331 99: '3', 1332 100: '4', 1333 101: '5', 1334 102: '6', 1335 103: '7', 1336 104: '8', 1337 105: '9' 1338 }; 1339 1340 if (that.options.liveSearch) $parent = $this.parent().parent(); 1341 1342 if (that.options.container) $parent = that.$menu; 1343 1344 $items = $('[role=menu] li a', $parent); 1345 1346 isActive = that.$menu.parent().hasClass('open'); 1347 1348 if (!isActive && (e.keyCode >= 48 && e.keyCode <= 57 || event.keyCode >= 65 && event.keyCode <= 90)) { 1349 if (!that.options.container) { 1350 that.setSize(); 1351 that.$menu.parent().addClass('open'); 1352 isActive = true; 1353 } else { 1354 that.$newElement.trigger('click'); 1355 } 1356 that.$searchbox.focus(); 1357 } 1358 1359 if (that.options.liveSearch) { 1360 if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && that.$menu.find('.active').length === 0) { 1361 e.preventDefault(); 1362 that.$menu.parent().removeClass('open'); 1363 if (that.options.container) that.$newElement.removeClass('open'); 1364 that.$button.focus(); 1365 } 1366 // $items contains li elements when liveSearch is enabled 1367 $items = $('[role=menu] li:not(.disabled, .hidden, .dropdown-header, .divider)', $parent); 1368 if (!$this.val() && !/(38|40)/.test(e.keyCode.toString(10))) { 1369 if ($items.filter('.active').length === 0) { 1370 $items = that.$newElement.find('li'); 1371 if (that.options.liveSearchNormalize) { 1372 $items = $items.filter(':a' + that._searchStyle() + '(' + normalizeToBase(keyCodeMap[e.keyCode]) + ')'); 1373 } else { 1374 $items = $items.filter(':' + that._searchStyle() + '(' + keyCodeMap[e.keyCode] + ')'); 1375 } 1376 } 1377 } 1378 } 1379 1380 if (!$items.length) return; 1381 1382 if (/(38|40)/.test(e.keyCode.toString(10))) { 1383 index = $items.index($items.filter(':focus')); 1384 first = $items.parent(selector).first().data('originalIndex'); 1385 last = $items.parent(selector).last().data('originalIndex'); 1386 next = $items.eq(index).parent().nextAll(selector).eq(0).data('originalIndex'); 1387 prev = $items.eq(index).parent().prevAll(selector).eq(0).data('originalIndex'); 1388 nextPrev = $items.eq(next).parent().prevAll(selector).eq(0).data('originalIndex'); 1389 1390 if (that.options.liveSearch) { 1391 $items.each(function (i) { 1392 if (!$(this).hasClass('disabled')) { 1393 $(this).data('index', i); 1394 } 1395 }); 1396 index = $items.index($items.filter('.active')); 1397 first = $items.first().data('index'); 1398 last = $items.last().data('index'); 1399 next = $items.eq(index).nextAll().eq(0).data('index'); 1400 prev = $items.eq(index).prevAll().eq(0).data('index'); 1401 nextPrev = $items.eq(next).prevAll().eq(0).data('index'); 1402 } 1403 1404 prevIndex = $this.data('prevIndex'); 1405 1406 if (e.keyCode == 38) { 1407 if (that.options.liveSearch) index -= 1; 1408 if (index != nextPrev && index > prev) index = prev; 1409 if (index < first) index = first; 1410 if (index == prevIndex) index = last; 1411 } else if (e.keyCode == 40) { 1412 if (that.options.liveSearch) index += 1; 1413 if (index == -1) index = 0; 1414 if (index != nextPrev && index < next) index = next; 1415 if (index > last) index = last; 1416 if (index == prevIndex) index = first; 1417 } 1418 1419 $this.data('prevIndex', index); 1420 1421 if (!that.options.liveSearch) { 1422 $items.eq(index).focus(); 1423 } else { 1424 e.preventDefault(); 1425 if (!$this.hasClass('dropdown-toggle')) { 1426 $items.removeClass('active').eq(index).addClass('active').children('a').focus(); 1427 $this.focus(); 1428 } 1429 } 1430 1431 } else if (!$this.is('input')) { 1432 var keyIndex = [], 1433 count, 1434 prevKey; 1435 1436 $items.each(function () { 1437 if (!$(this).parent().hasClass('disabled')) { 1438 if ($.trim($(this).text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) { 1439 keyIndex.push($(this).parent().index()); 1440 } 1441 } 1442 }); 1443 1444 count = $(document).data('keycount'); 1445 count++; 1446 $(document).data('keycount', count); 1447 1448 prevKey = $.trim($(':focus').text().toLowerCase()).substring(0, 1); 1449 1450 if (prevKey != keyCodeMap[e.keyCode]) { 1451 count = 1; 1452 $(document).data('keycount', count); 1453 } else if (count >= keyIndex.length) { 1454 $(document).data('keycount', 0); 1455 if (count > keyIndex.length) count = 1; 1456 } 1457 1458 $items.eq(keyIndex[count - 1]).focus(); 1459 } 1460 1461 // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. 1462 if ((/(13|32)/.test(e.keyCode.toString(10)) || (/(^9$)/.test(e.keyCode.toString(10)) && that.options.selectOnTab)) && isActive) { 1463 if (!/(32)/.test(e.keyCode.toString(10))) e.preventDefault(); 1464 if (!that.options.liveSearch) { 1465 var elem = $(':focus'); 1466 elem.click(); 1467 // Bring back focus for multiselects 1468 elem.focus(); 1469 // Prevent screen from scrolling if the user hit the spacebar 1470 e.preventDefault(); 1471 // Fixes spacebar selection of dropdown items in FF & IE 1472 $(document).data('spaceSelect', true); 1473 } else if (!/(32)/.test(e.keyCode.toString(10))) { 1474 that.$menu.find('.active a').click(); 1475 $this.focus(); 1476 } 1477 $(document).data('keycount', 0); 1478 } 1479 1480 if ((/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode.toString(10)) && !isActive)) { 1481 that.$menu.parent().removeClass('open'); 1482 if (that.options.container) that.$newElement.removeClass('open'); 1483 that.$button.focus(); 1484 } 1485 }, 1486 1487 mobile: function () { 1488 this.$element.addClass('mobile-device').appendTo(this.$newElement); 1489 if (this.options.container) this.$menu.hide(); 1490 }, 1491 1492 refresh: function () { 1493 this.$lis = null; 1494 this.reloadLi(); 1495 this.render(); 1496 this.checkDisabled(); 1497 this.liHeight(true); 1498 this.setStyle(); 1499 this.setWidth(); 1500 if (this.$lis) this.$searchbox.trigger('propertychange'); 1501 1502 this.$element.trigger('refreshed.bs.select'); 1503 }, 1504 1505 hide: function () { 1506 this.$newElement.hide(); 1507 }, 1508 1509 show: function () { 1510 this.$newElement.show(); 1511 }, 1512 1513 remove: function () { 1514 this.$newElement.remove(); 1515 this.$element.remove(); 1516 } 1517 }; 1518 1519 // SELECTPICKER PLUGIN DEFINITION 1520 // ============================== 1521 function Plugin(option, event) { 1522 // get the args of the outer function.. 1523 var args = arguments; 1524 // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them
1525 // to get lost/corrupted in android 2.3 and IE9 #715 #775 1526 var _option = option, 1527 _event = event; 1528 [].shift.apply(args); 1529 1530 var value; 1531 var chain = this.each(function () { 1532 var $this = $(this); 1533 if ($this.is('select')) { 1534 var data = $this.data('selectpicker'), 1535 options = typeof _option == 'object' && _option; 1536 1537 if (!data) { 1538 var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, $this.data(), options); 1539 $this.data('selectpicker', (data = new Selectpicker(this, config, _event))); 1540 } else if (options) { 1541 for (var i in options) { 1542 if (options.hasOwnProperty(i)) { 1543 data.options[i] = options[i]; 1544 } 1545 } 1546 } 1547 1548 if (typeof _option == 'string') { 1549 if (data[_option] instanceof Function) { 1550 value = data[_option].apply(data, args); 1551 } else { 1552 value = data.options[_option]; 1553 } 1554 } 1555 } 1556 }); 1557 1558 if (typeof value !== 'undefined') { 1559 //noinspection JSUnusedAssignment 1560 return value; 1561 } else { 1562 return chain; 1563 } 1564 } 1565 1566 var old = $.fn.selectpicker; 1567 $.fn.selectpicker = Plugin; 1568 $.fn.selectpicker.Constructor = Selectpicker; 1569 1570 // SELECTPICKER NO CONFLICT 1571 // ======================== 1572 $.fn.selectpicker.noConflict = function () { 1573 $.fn.selectpicker = old; 1574 return this; 1575 }; 1576 1577 $(document) 1578 .data('keycount', 0) 1579 .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="menu"], .bs-searchbox input', Selectpicker.prototype.keydown) 1580 .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="menu"], .bs-searchbox input', function (e) { 1581 e.stopPropagation(); 1582 }); 1583 1584 // SELECTPICKER DATA-API 1585 // ===================== 1586 $(window).on('load.bs.select.data-api', function () { 1587 $('.selectpicker').each(function () { 1588 var $selectpicker = $(this); 1589 Plugin.call($selectpicker, $selectpicker.data()); 1590 }) 1591 }); 1592})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.