1(function ($) { 2 'use strict'; 3 4 // Case insensitive search 5 $.expr[':'].icontains = function (obj, index, meta) { 6 return icontains($(obj).text(), meta[3]); 7 }; 8 9 // Case and accent insensitive search 10 $.expr[':'].aicontains = function (obj, index, meta) { 11 return icontains($(obj).data('normalizedText') || $(obj).text(), meta[3]); 12 }; 13 14 /** 15 * Actual implementation of the case insensitive search. 16 * @access private 17 * @param {String} haystack 18 * @param {String} needle 19 * @returns {boolean} 20 */ 21 function icontains(haystack, needle) { 22 return haystack.toUpperCase().indexOf(needle.toUpperCase()) > -1; 23 } 24 25 /** 26 * Remove all diatrics from the given text. 27 * @access private 28 * @param {String} text 29 * @returns {String} 30 */ 31 function normalizeToBase(text) { 32 var rExps = [ 33 {re: /[\xC0-\xC6]/g, ch: "A"}, 34 {re: /[\xE0-\xE6]/g, ch: "a"}, 35 {re: /[\xC8-\xCB]/g, ch: "E"}, 36 {re: /[\xE8-\xEB]/g, ch: "e"}, 37 {re: /[\xCC-\xCF]/g, ch: "I"}, 38 {re: /[\xEC-\xEF]/g, ch: "i"}, 39 {re: /[\xD2-\xD6]/g, ch: "O"}, 40 {re: /[\xF2-\xF6]/g, ch: "o"}, 41 {re: /[\xD9-\xDC]/g, ch: "U"}, 42 {re: /[\xF9-\xFC]/g, ch: "u"}, 43 {re: /[\xC7-\xE7]/g, ch: "c"}, 44 {re: /[\xD1]/g, ch: "N"}, 45 {re: /[\xF1]/g, ch: "n"} 46 ]; 47 $.each(rExps, function () { 48 text = text.replace(this.re, this.ch); 49 }); 50 return text; 51 } 52 53 54 function htmlEscape(html) { 55 var escapeMap = { 56 '&': '&', 57 '<': '<', 58 '>': '>', 59 '"': '"', 60 "'": ''', 61 '`': '`' 62 }; 63 var source = '(?:' + Object.keys(escapeMap).join('|') + ')', 64 testRegexp = new RegExp(source), 65 replaceRegexp = new RegExp(source, 'g'), 66 string = html == null ? '' : '' + html; 67 return testRegexp.test(string) ? string.replace(replaceRegexp, function (match) { 68 return escapeMap[match]; 69 }) : string; 70 } 71 72 var Selectpicker = function (element, options, e) { 73 if (e) { 74 e.stopPropagation(); 75 e.preventDefault(); 76 } 77 78 this.$element = $(element); 79 this.$newElement = null; 80 this.$button = null; 81 this.$menu = null; 82 this.$lis = null; 83 this.options = options; 84 85 // 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 86 // data-attribute) 87 if (this.options.title === null) { 88 this.options.title = this.$element.attr('title'); 89 } 90 91 //Expose public methods 92 this.val = Selectpicker.prototype.val; 93 this.render = Selectpicker.prototype.render; 94 this.refresh = Selectpicker.prototype.refresh; 95 this.setStyle = Selectpicker.prototype.setStyle; 96 this.selectAll = Selectpicker.prototype.selectAll; 97 this.deselectAll = Selectpicker.prototype.deselectAll; 98 this.destroy = Selectpicker.prototype.remove; 99 this.remove = Selectpicker.prototype.remove; 100 this.show = Selectpicker.prototype.show; 101 this.hide = Selectpicker.prototype.hide; 102 103 this.init(); 104 }; 105 106 Selectpicker.VERSION = '1.6.3'; 107 108 // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. 109 Selectpicker.DEFAULTS = { 110 noneSelectedText: 'Nothing selected', 111 noneResultsText: 'No results match', 112 countSelectedText: function (numSelected, numTotal) { 113 return (numSelected == 1) ? "{0} item selected" : "{0} items selected"; 114 }, 115 maxOptionsText: function (numAll, numGroup) { 116 var arr = []; 117 118 arr[0] = (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)'; 119 arr[1] = (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)'; 120 121 return arr; 122 }, 123 selectAllText: 'Select All', 124 deselectAllText: 'Deselect All', 125 multipleSeparator: ', ', 126 style: 'btn-default', 127 size: 'auto', 128 title: null, 129 selectedTextFormat: 'values', 130 width: false, 131 container: false, 132 hideDisabled: false, 133 showSubtext: false, 134 showIcon: true, 135 showContent: true, 136 dropupAuto: true, 137 header: false, 138 liveSearch: false, 139 actionsBox: false, 140 iconBase: 'glyphicon', 141 tickIcon: 'glyphicon-ok', 142 maxOptions: false, 143 mobile: false, 144 selectOnTab: false, 145 dropdownAlignRight: false,
146 searchAccentInsensitive: false 147 }; 148 149 Selectpicker.prototype = { 150 151 constructor: Selectpicker, 152 153 init: function () { 154 var that = this, 155 id = this.$element.attr('id'); 156 157 this.$element.hide(); 158 this.multiple = this.$element.prop('multiple'); 159 this.autofocus = this.$element.prop('autofocus'); 160 this.$newElement = this.createView(); 161 this.$element.after(this.$newElement); 162 this.$menu = this.$newElement.find('> .dropdown-menu'); 163 this.$button = this.$newElement.find('> button'); 164 this.$searchbox = this.$newElement.find('input'); 165 166 if (this.options.dropdownAlignRight) 167 this.$menu.addClass('dropdown-menu-right'); 168 169 if (typeof id !== 'undefined') { 170 this.$button.attr('data-id', id); 171 $('label[for="' + id + '"]').click(function (e) { 172 e.preventDefault(); 173 that.$button.focus(); 174 }); 175 } 176 177 this.checkDisabled(); 178 this.clickListener(); 179 if (this.options.liveSearch) this.liveSearchListener(); 180 this.render(); 181 this.liHeight(); 182 this.setStyle(); 183 this.setWidth(); 184 if (this.options.container) this.selectPosition(); 185 this.$menu.data('this', this); 186 this.$newElement.data('this', this); 187 if (this.options.mobile) this.mobile(); 188 }, 189 190 createDropdown: function () { 191 // Options 192 // If we are multiple, then add the show-tick class by default 193 var multiple = this.multiple ? ' show-tick' : '', 194 inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '', 195 autofocus = this.autofocus ? ' autofocus' : '', 196 btnSize = this.$element.parents().hasClass('form-group-lg') ? ' btn-lg' : (this.$element.parents().hasClass('form-group-sm') ? ' btn-sm' : ''); 197 // Elements 198 var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">×</button>' + this.options.header + '</div>' : ''; 199 var searchbox = this.options.liveSearch ? '<div class="bs-searchbox"><input type="text" class="input-block-level form-control" autocomplete="off" /></div>' : ''; 200 var actionsbox = this.options.actionsBox ? '<div class="bs-actionsbox">' + 201 '<div class="btn-group btn-block">' + 202 '<button class="actions-btn bs-select-all btn btn-sm btn-default">' + 203 this.options.selectAllText + 204 '</button>' + 205 '<button class="actions-btn bs-deselect-all btn btn-sm btn-default">' + 206 this.options.deselectAllText + 207 '</button>' + 208 '</div>' + 209 '</div>' : ''; 210 var drop = 211 '<div class="btn-group bootstrap-select' + multiple + inputGroup + '">' + 212 '<button type="button" class="btn dropdown-toggle selectpicker' + btnSize + '" data-toggle="dropdown"' + autofocus + '>' + 213 '<span class="filter-option pull-left"></span> ' + 214 '<span class="caret"></span>' + 215 '</button>' + 216 '<div class="dropdown-menu open">' + 217 header + 218 searchbox + 219 actionsbox + 220 '<ul class="dropdown-menu inner selectpicker" role="menu">' + 221 '</ul>' + 222 '</div>' + 223 '</div>'; 224 225 return $(drop); 226 }, 227 228 createView: function () { 229 var $drop = this.createDropdown(); 230 var $li = this.createLi(); 231 $drop.find('ul').append($li); 232 return $drop; 233 }, 234 235 reloadLi: function () { 236 //Remove all children. 237 this.destroyLi(); 238 //Re build 239 var $li = this.createLi(); 240 this.$menu.find('ul').append($li); 241 }, 242 243 destroyLi: function () { 244 this.$menu.find('li').remove(); 245 }, 246 247 createLi: function () { 248 var that = this, 249 _li = [], 250 optID = 0; 251 252 // Helper functions 253 /** 254 * @param content 255 * @param [index] 256 * @param [classes] 257 * @returns {string} 258 */ 259 var generateLI = function (content, index, classes) { 260 return '<li' + 261 (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + 262 (typeof index !== 'undefined' | null === index ? ' data-original-index="' + index + '"' : '') + 263 '>' + content + '</li>'; 264 }; 265 266 /** 267 * @param text 268 * @param [classes] 269 * @param [inline] 270 * @param [optgroup] 271 * @returns {string} 272 */ 273 var generateA = function (text, classes, inline, optgroup) { 274 var normText = normalizeToBase(htmlEscape(text)); 275 return '<a tabindex="0"' + 276 (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + 277 (typeof inline !== 'undefined' ? ' style="' + inline + '"' : '') + 278 (typeof optgroup !== 'undefined' ? 'data-optgroup="' + optgroup + '"' : '') + 279 ' data-normalized-text="' + normText + '"' + 280 '>
280' + text + 281 '<span class="' + that.options.iconBase + ' ' + that.options.tickIcon + ' check-mark"></span>' + 282 '</a>'; 283 }; 284 285 this.$element.find('option').each(function () { 286 var $this = $(this); 287 288 // Get the class and text for the option 289 var optionClass = $this.attr('class') || '', 290 inline = $this.attr('style'), 291 text = $this.data('content') ? $this.data('content') : $this.html(), 292 subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="muted text-muted">' + $this.data('subtext') + '</small>' : '', 293 icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '', 294 isDisabled = $this.is(':disabled') || $this.parent().is(':disabled'), 295 index = $this[0].index; 296 if (icon !== '' && isDisabled) { 297 icon = '<span>' + icon + '</span>'; 298 } 299 300 if (!$this.data('content')) { 301 // Prepend any icon and append any subtext to the main text. 302 text = icon + '<span class="text">' + text + subtext + '</span>'; 303 } 304 305 if (that.options.hideDisabled && isDisabled) { 306 return; 307 } 308 309 if ($this.parent().is('optgroup') && $this.data('divider') !== true) { 310 if ($this.index() === 0) { // Is it the first option of the optgroup? 311 optID += 1; 312 313 // Get the opt group label 314 var label = $this.parent().attr('label'); 315 var labelSubtext = typeof $this.parent().data('subtext') !== 'undefined' ? '<small class="muted text-muted">' + $this.parent().data('subtext') + '</small>' : ''; 316 var labelIcon = $this.parent().data('icon') ? '<span class="' + that.options.iconBase + ' ' + $this.parent().data('icon') + '"></span> ' : ''; 317 label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>'; 318 319 if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown? 320 _li.push(generateLI('', null, 'divider')); 321 } 322 323 _li.push(generateLI(label, null, 'dropdown-header')); 324 } 325 326 _li.push(generateLI(generateA(text, 'opt ' + optionClass, inline, optID), index)); 327 } else if ($this.data('divider') === true) { 328 _li.push(generateLI('', index, 'divider')); 329 } else if ($this.data('hidden') === true) { 330 _li.push(generateLI(generateA(text, optionClass, inline), index, 'hide is-hidden')); 331 } else { 332 _li.push(generateLI(generateA(text, optionClass, inline), index)); 333 } 334 }); 335 336 //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 337 if (!this.multiple && this.$element.find('option:selected').length === 0 && !this.options.title) { 338 this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected'); 339 } 340 341 return $(_li.join('')); 342 }, 343 344 findLis: function () { 345 if (this.$lis == null) this.$lis = this.$menu.find('li'); 346 return this.$lis; 347 }, 348 349 /** 350 * @param [updateLi] defaults to true 351 */ 352 render: function (updateLi) { 353 var that = this; 354 355 //Update the LI to match the SELECT 356 if (updateLi !== false) { 357 this.$element.find('option').each(function (index) { 358 that.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled')); 359 that.setSelected(index, $(this).is(':selected')); 360 }); 361 } 362 363 this.tabIndex(); 364 var notDisabled = this.options.hideDisabled ? ':not([disabled])' : ''; 365 var selectedItems = this.$element.find('option:selected' + notDisabled).map(function () { 366 var $this = $(this); 367 var icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : ''; 368 var subtext; 369 if (that.options.showSubtext && $this.attr('data-subtext') && !that.multiple) { 370 subtext = ' <small class="muted text-muted">' + $this.data('subtext') + '</small>'; 371 } else { 372 subtext = ''; 373 } 374 if ($this.data('content') && that.options.showContent) { 375 return $this.data('content'); 376 } else if (typeof $this.attr('title') !== 'undefined') { 377 return $this.attr('title'); 378 } else { 379 return icon + $this.html() + subtext; 380 } 381 }).toArray(); 382 383 //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled 384 //Convert all the values into a comma delimited string 385 var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator); 386 387 //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc.. 388 if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) { 389 var max = this.options.selectedTextFormat.split('>'); 390 if ((max.length > 1 && selectedItems.length > max[1]) || (max.length == 1 && selectedItems.length >= 2)) { 391 notDisabled = this.options.hideDisabled ? ', [disabled]' : ''; 392 var totalCount = this.$element.find('option').not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length, 393 tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText; 394 title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString()); 395 } 396 } 397 398 this.options.title = this.$element.attr('title'); 399 400 if (this.options.selectedTextFormat == 'static') { 401 title = this.options.title; 402 } 403 404 //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text 405 if (!title) { 406 title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText; 407 } 408 409 this.$button.attr('title', htmlEscape(title)); 410 this.$newElement.find('.filter-option').html(title); 411 }, 412 413 /** 414 * @param [style] 415 * @param [status] 416 */ 417 setStyle: function (style, status) { 418 if (this.$element.attr('class')) { 419 this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|validate\[.*\]/gi, '')); 420 } 421 422 var buttonClass = style ? style : this.options.style; 423 424 if (status == 'add') { 425 this.$button.addClass(buttonClass); 426 } else if (status == 'remove') { 427 this.$button.removeClass(buttonClass); 428 } else { 429 this.$button.removeClass(this.options.style); 430 this.$button.addClass(buttonClass); 431 } 432 }, 433 434 liHeight: function () { 435 if (this.options.size === false) return; 436 437 var $selectClone = this.$menu.parent().clone().find('> .dropdown-toggle').prop('autofocus', false).end().appendTo('body'), 438 $menuClone = $selectClone.addClass('open').find('> .dropdown-menu'), 439 liHeight = $menuClone.find('li').not('.divider').not('.dropdown-header').filter(':visible').children('a').outerHeight(), 440 headerHeight = this.options.header ? $menuClone.find('.popover-title').outerHeight() : 0, 441 searchHeight = this.options.liveSearch ? $menuClone.find('.bs-searchbox').outerHeight() : 0, 442 actionsHeight = this.options.actionsBox ? $menuClone.find('.bs-actionsbox').outerHeight() : 0; 443 444 $selectClone.remove(); 445 446 this.$newElement 447 .data('liHeight', liHeight) 448 .data('headerHeight', headerHeight) 449 .data('searchHeight', searchHeight) 450 .data('actionsHeight', actionsHeight); 451 }, 452 453 setSize: function () { 454 this.findLis(); 455 var that = this, 456 menu = this.$menu, 457 menuInner = menu.find('.inner'), 458 selectHeight = this.$newElement.outerHeight(), 459 liHeight = this.$newElement.data('liHeight'), 460 headerHeight = this.$newElement.data('headerHeight'), 461 searchHeight = this.$newElement.data('searchHeight'), 462 actionsHeight = this.$newElement.data('actionsHeight'), 463 divHeight = this.$lis.filter('.divider').outerHeight(true), 464 menuPadding = parseInt(menu.css('padding-top')) + 465 parseInt(menu.css('padding-bottom')) + 466 parseInt(menu.css('border-top-width')) + 467 parseInt(menu.css('border-bottom-width')), 468 notDisabled = this.options.hideDisabled ? ', .disabled' : '', 469 $window = $(window), 470 menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2, 471 menuHeight, 472 selectOffsetTop, 473 selectOffsetBot, 474 posVert = function () { 475 // JQuery defines a scrollTop function, but in pure JS it's a property 476 //noinspection JSValidateTypes 477 selectOffsetTop = that.$newElement.offset().top - $window.scrollTop(); 478 selectOffsetBot = $window.height() - selectOffsetTop - selectHeight; 479 }; 480 posVert(); 481 if (this.options.header) menu.css('padding-top', 0); 482 483 if (this.options.size == 'auto') { 484 var getSize = function () { 485 var minHeight, 486 lisVis = that.$lis.not('.hide'); 487 488 posVert(); 489 menuHeight = selectOffsetBot - menuExtras; 490 491 if (that.options.dropupAuto) { 492 that.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && ((menuHeight - menuExtras) < menu.height())); 493 } 494 if (that.$newElement.hasClass('dropup')) { 495 menuHeight = selectOffsetTop - menuExtras; 496 } 497 498 if ((lisVis.length + lisVis.filter('.dropdown-header').length) > 3) { 499 minHeight = liHeight * 3 + menuExtras - 2; 500 } else { 501 minHeight = 0; 502 } 503 504 menu.css({ 505 'max-height': menuHeight + 'px', 506 'overflow': 'hidden', 507 'min-height': minHeight + headerHeight + searchHeight + actionsHeight + 'px' 508 }); 509 menuInner.css({ 510 'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - menuPadding + 'px', 511 'overflow-y': 'auto', 512 'min-height': Math.max(minHeight - menuPadding, 0) + 'px' 513 }); 514 }; 515 getSize(); 516 this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize); 517 $(window).off('resize.getSize').on('resize.getSize', getSize); 518 $(window).off('scroll.getSize').on('scroll.getSize', getSize); 519 } else if (this.options.size && this.options.size != 'auto' && menu.find('li' + notDisabled).length > this.options.size) { 520 var optIndex = this.$lis.not('.divider' + notDisabled).find(' > *').slice(0, this.options.size).last().parent().index(); 521 var divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length; 522 menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding; 523 if (that.options.dropupAuto) { 524 //noinspection JSUnusedAssignment 525 this.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && (menuHeight < menu.height())); 526 } 527 menu.css({'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + 'px', 'overflow': 'hidden'}); 528 menuInner.css({'max-height': menuHeight - menuPadding + 'px', 'overflow-y': 'auto'}); 529 } 530 }, 531 532 setWidth: function () { 533 if (this.options.width == 'auto') { 534 this.$menu.css('min-width', '0'); 535 536 // Get correct width if element hidden 537 var selectClone = this.$newElement.clone().appendTo('body'); 538 var ulWidth = selectClone.find('> .dropdown-menu').css('width'); 539 var btnWidth = selectClone.css('width', 'auto').find('> button').css('width'); 540 selectClone.remove(); 541 542 // Set width to whatever's larger, button title or longest option 543 this.$newElement.css('width', Math.max(parseInt(ulWidth), parseInt(btnWidth)) + 'px'); 544 } else if (this.options.width == 'fit') { 545 // Remove inline min-width so width can be changed from 'auto' 546 this.$menu.css('min-width', ''); 547 this.$newElement.css('width', '').addClass('fit-width'); 548 } else if (this.options.width) { 549 // Remove inline min-width so width can be changed from 'auto' 550 this.$menu.css('min-width', ''); 551 this.$newElement.css('width', this.options.width); 552 } else { 553 // Remove inline min-width/width so width can be changed 554 this.$menu.css('min-width', ''); 555 this.$newElement.css('width', ''); 556 } 557 // Remove fit-width class if width is changed programmatically 558 if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { 559 this.$newElement.removeClass('fit-width'); 560 } 561 }, 562 563 selectPosition: function () { 564 var that = this, 565 drop = '<div />', 566 $drop = $(drop), 567 pos, 568 actualHeight, 569 getPlacement = function ($element) { 570 $drop.addClass($element.attr('class').replace(/form-control/gi, '')).toggleClass('dropup', $element.hasClass('dropup')); 571 pos = $element.offset(); 572 actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight; 573 $drop.css({ 574 'top': pos.top + actualHeight, 575 'left': pos.left, 576 'width': $element[0].offsetWidth, 577 'position': 'absolute' 578 }); 579 }; 580 this.$newElement.on('click', function () { 581 if (that.isDisabled()) { 582 return; 583 } 584 getPlacement($(this)); 585 $drop.appendTo(that.options.container); 586 $drop.toggleClass('open', !$(this).hasClass('open')); 587 $drop.append(that.$menu); 588 }); 589 $(window).resize(function () { 590 getPlacement(that.$newElement); 591 }); 592 $(window).on('scroll', function () { 593 getPlacement(that.$newElement); 594 }); 595 $('html').on('click', function (e) { 596 if ($(e.target).closest(that.$newElement).length < 1) { 597 $drop.removeClass('open'); 598 } 599 }); 600 }, 601 602 setSelected: function (index, selected) { 603 this.findLis(); 604 this.$lis.filter('[data-original-index="' + index + '"]').toggleClass('selected', selected); 605 }, 606 607 setDisabled: function (index, disabled) { 608 this.findLis(); 609 if (disabled) { 610 this.$lis.filter('[data-original-index="' + index + '"]').addClass('disabled').find('a').attr('href', '#').attr('tabindex', -1); 611 } else { 612 this.$lis.filter('[data-original-index="' + index + '"]').removeClass('disabled').find('a').removeAttr('href').attr('tabindex', 0); 613 } 614 }, 615 616 isDisabled: function () { 617 return this.$element.is(':disabled'); 618 }, 619 620 checkDisabled: function () { 621 var that = this; 622 623 if (this.isDisabled()) { 624 this.$button.addClass('disabled').attr('tabindex', -1); 625 } else { 626 if (this.$button.hasClass('disabled')) { 627 this.$button.removeClass('disabled'); 628 } 629 630 if (this.$button.attr('tabindex') == -1) { 631 if (!this.$element.data('tabindex')) this.$button.removeAttr('tabindex'); 632 } 633 } 634 635 this.$button.click(function () { 636 return !that.isDisabled(); 637 }); 638 }, 639 640 tabIndex: function () { 641 if (this.$element.is('[tabindex]')) { 642 this.$element.data('tabindex', this.$element.attr('tabindex')); 643 this.$button.attr('tabindex', this.$element.data('tabindex')); 644 } 645 }, 646 647 clickListener: function () { 648 var that = this; 649 650 this.$newElement.on('touchstart.dropdown', '.dropdown-menu', function (e) { 651 e.stopPropagation(); 652 }); 653 654 this.$newElement.on('click', function () { 655 that.setSize(); 656 if (!that.options.liveSearch && !that.multiple) { 657 setTimeout(function () { 658 that.$menu.find('.selected a').focus(); 659 }, 10); 660 } 661 }); 662 663 this.$menu.on('click', 'li a', function (e) { 664 var $this = $(this), 665 clickedIndex = $this.parent().data('originalIndex'),
666 prevValue = that.$element.val(), 667 prevIndex = that.$element.prop('selectedIndex'); 668 669 // Don't close on multi choice menu 670 if (that.multiple) { 671 e.stopPropagation(); 672 } 673 674 e.preventDefault(); 675 676 //Don't run if we have been disabled 677 if (!that.isDisabled() && !$this.parent().hasClass('disabled')) { 678 var $options = that.$element.find('option'), 679 $option = $options.eq(clickedIndex), 680 state = $option.prop('selected'), 681 $optgroup = $option.parent('optgroup'), 682 maxOptions = that.options.maxOptions, 683 maxOptionsGrp = $optgroup.data('maxOptions') || false; 684 685 if (!that.multiple) { // Deselect all others if not multi select box 686 $options.prop('selected', false); 687 $option.prop('selected', true); 688 that.$menu.find('.selected').removeClass('selected'); 689 that.setSelected(clickedIndex, true); 690 } else { // Toggle the one we have chosen if we are multi select. 691 $option.prop('selected', !state); 692 that.setSelected(clickedIndex, !state); 693 $this.blur(); 694 695 if ((maxOptions !== false) || (maxOptionsGrp !== false)) { 696 var maxReached = maxOptions < $options.filter(':selected').length, 697 maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; 698 699 if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { 700 if (maxOptions && maxOptions == 1) { 701 $options.prop('selected', false); 702 $option.prop('selected', true); 703 that.$menu.find('.selected').removeClass('selected'); 704 that.setSelected(clickedIndex, true); 705 } else if (maxOptionsGrp && maxOptionsGrp == 1) { 706 $optgroup.find('option:selected').prop('selected', false); 707 $option.prop('selected', true); 708 var optgroupID = $this.data('optgroup'); 709 710 that.$menu.find('.selected').has('a[data-optgroup="' + optgroupID + '"]').removeClass('selected'); 711 712 that.setSelected(clickedIndex, true); 713 } else { 714 var maxOptionsArr = (typeof that.options.maxOptionsText === 'function') ? 715 that.options.maxOptionsText(maxOptions, maxOptionsGrp) : that.options.maxOptionsText, 716 maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), 717 maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), 718 $notify = $('<div class="notify"></div>'); 719 // If {var} is set in array, replace it 720 /** @deprecated */ 721 if (maxOptionsArr[2]) { 722 maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); 723 maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); 724 } 725 726 $option.prop('selected', false); 727 728 that.$menu.append($notify); 729 730 if (maxOptions && maxReached) { 731 $notify.append($('<div>' + maxTxt + '</div>')); 732 that.$element.trigger('maxReached.bs.select'); 733 } 734 735 if (maxOptionsGrp && maxReachedGrp) { 736 $notify.append($('<div>' + maxTxtGrp + '</div>')); 737 that.$element.trigger('maxReachedGrp.bs.select'); 738 } 739 740 setTimeout(function () { 741 that.setSelected(clickedIndex, false); 742 }, 10); 743 744 $notify.delay(750).fadeOut(300, function () { 745 $(this).remove(); 746 }); 747 } 748 } 749 } 750 } 751 752 if (!that.multiple) { 753 that.$button.focus(); 754 } else if (that.options.liveSearch) { 755 that.$searchbox.focus(); 756 } 757 758 // Trigger select 'change' 759 if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) { 760 that.$element.change(); 761 } 762 } 763 }); 764 765 this.$menu.on('click', 'li.disabled a, .popover-title, .popover-title :not(.close)', function (e) { 766 if (e.target == this) { 767 e.preventDefault(); 768 e.stopPropagation(); 769 if (!that.options.liveSearch) { 770 that.$button.focus(); 771 } else { 772 that.$searchbox.focus(); 773 } 774 } 775 }); 776 777 this.$menu.on('click', 'li.divider, li.dropdown-header', function (e) { 778 e.preventDefault(); 779 e.stopPropagation(); 780 if (!that.options.liveSearch) { 781 that.$button.focus(); 782 } else { 783 that.$searchbox.focus(); 784 } 785 }); 786 787 this.$menu.on('click', '.popover-title .close', function () { 788 that.$button.focus(); 789 }); 790 791 this.$searchbox.on('click', function (e) { 792 e.stopPropagation(); 793 }); 794 795 796 this.$menu.on('click', '.actions-btn', function (e) { 797 if (that.options.liveSearch) { 798 that.$searchbox.focus(); 799 } else { 800 that.$button.focus(); 801 } 802 803 e.preventDefault(); 804 e.stopPropagation(); 805 806 if ($(this).is('.bs-select-all')) { 807 that.selectAll(); 808 } else { 809 that.deselectAll(); 810 } 811 that.$element.change(); 812 }); 813 814 this.$element.change(function () { 815 that.render(false); 816 }); 817 }, 818 819 liveSearchListener: function () { 820 var that = this, 821 no_results = $('<li class="no-results"></li>'); 822 823 this.$newElement.on('click.dropdown.data-api touchstart.dropdown.data-api', function () { 824 that.$menu.find('.active').removeClass('active'); 825 if (!!that.$searchbox.val()) { 826 that.$searchbox.val(''); 827 that.$lis.not('.is-hidden').removeClass('hide'); 828 if (!!no_results.parent().length) no_results.remove(); 829 } 830 if (!that.multiple) that.$menu.find('.selected').addClass('active'); 831 setTimeout(function () { 832 that.$searchbox.focus(); 833 }, 10); 834 }); 835 836 this.$searchbox.on('click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api', function (e) { 837 e.stopPropagation(); 838 }); 839 840 this.$searchbox.on('input propertychange', function () { 841 if (that.$searchbox.val()) { 842
843 if (that.options.searchAccentInsensitive) { 844 that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':aicontains(' + normalizeToBase(that.$searchbox.val()) + ')').parent().addClass('hide'); 845 } else { 846 that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':icontains(' + that.$searchbox.val() + ')').parent().addClass('hide'); 847 } 848 849 if (!that.$menu.find('li').filter(':visible:not(.no-results)').length) { 850 if (!!no_results.parent().length) no_results.remove(); 851 no_results.html(that.options.noneResultsText + ' "' + htmlEscape(that.$searchbox.val()) + '"').show(); 852 that.$menu.find('li').last().after(no_results); 853 } else if (!!no_results.parent().length) { 854 no_results.remove(); 855 } 856 857 } else { 858 that.$lis.not('.is-hidden').removeClass('hide'); 859 if (!!no_results.parent().length) no_results.remove(); 860 } 861 862 that.$menu.find('li.active').removeClass('active'); 863 that.$menu.find('li').filter(':visible:not(.divider)').eq(0).addClass('active').find('a').focus(); 864 $(this).focus(); 865 }); 866 }, 867 868 val: function (value) { 869 if (typeof value !== 'undefined') { 870 this.$element.val(value); 871 this.render(); 872 873 return this.$element; 874 } else { 875 return this.$element.val(); 876 } 877 }, 878 879 selectAll: function () { 880 this.findLis(); 881 this.$lis.not('.divider').not('.disabled').not('.selected').filter(':visible').find('a').click(); 882 }, 883 884 deselectAll: function () { 885 this.findLis(); 886 this.$lis.not('.divider').not('.disabled').filter('.selected').filter(':visible').find('a').click(); 887 }, 888 889 keydown: function (e) { 890 var $this = $(this), 891 $parent = ($this.is('input')) ? $this.parent().parent() : $this.parent(), 892 $items, 893 that = $parent.data('this'), 894 index, 895 next, 896 first, 897 last, 898 prev, 899 nextPrev, 900 prevIndex, 901 isActive, 902 keyCodeMap = { 903 32: ' ', 904 48: '0', 905 49: '1', 906 50: '2', 907 51: '3', 908 52: '4', 909 53: '5', 910 54: '6', 911 55: '7', 912 56: '8', 913 57: '9', 914 59: ';', 915 65: 'a', 916 66: 'b', 917 67: 'c', 918 68: 'd', 919 69: 'e', 920 70: 'f', 921 71: 'g', 922 72: 'h', 923 73: 'i', 924 74: 'j', 925 75: 'k', 926 76: 'l', 927 77: 'm', 928 78: 'n', 929 79: 'o', 930 80: 'p', 931 81: 'q', 932 82: 'r', 933 83: 's', 934 84: 't', 935 85: 'u', 936 86: 'v', 937 87: 'w', 938 88: 'x', 939 89: 'y', 940 90: 'z', 941 96: '0', 942 97: '1', 943 98: '2', 944 99: '3', 945 100: '4', 946 101: '5', 947 102: '6', 948 103: '7', 949 104: '8', 950 105: '9' 951 }; 952 953 if (that.options.liveSearch) $parent = $this.parent().parent(); 954 955 if (that.options.container) $parent = that.$menu; 956 957 $items = $('[role=menu] li a', $parent); 958 959 isActive = that.$menu.parent().hasClass('open'); 960 961 if (!isActive && /([0-9]|[A-z])/.test(String.fromCharCode(e.keyCode))) { 962 if (!that.options.container) { 963 that.setSize(); 964 that.$menu.parent().addClass('open'); 965 isActive = true; 966 } else { 967 that.$newElement.trigger('click'); 968 } 969 that.$searchbox.focus(); 970 } 971 972 if (that.options.liveSearch) { 973 if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && that.$menu.find('.active').length === 0) { 974 e.preventDefault(); 975 that.$menu.parent().removeClass('open'); 976 that.$button.focus(); 977 } 978 $items = $('[role=menu] li:not(.divider):not(.dropdown-header):visible', $parent); 979 if (!$this.val() && !/(38|40)/.test(e.keyCode.toString(10))) { 980 if ($items.filter('.active').length === 0) {
981 if (that.options.searchAccentInsensitive) { 982 $items = that.$newElement.find('li').filter(':aicontains(' + normalizeToBase(keyCodeMap[e.keyCode]) + ')'); 983 } else { 984 $items = that.$newElement.find('li').filter(':icontains(' + keyCodeMap[e.keyCode] + ')'); 985 } 986 } 987 } 988 } 989 990 if (!$items.length) return; 991 992 if (/(38|40)/.test(e.keyCode.toString(10))) { 993 index = $items.index($items.filter(':focus')); 994 first = $items.parent(':not(.disabled):visible').first().index(); 995 last = $items.parent(':not(.disabled):visible').last().index(); 996 next = $items.eq(index).parent().nextAll(':not(.disabled):visible').eq(0).index(); 997 prev = $items.eq(index).parent().prevAll(':not(.disabled):visible').eq(0).index(); 998 nextPrev = $items.eq(next).parent().prevAll(':not(.disabled):visible').eq(0).index(); 999 1000 if (that.options.liveSearch) { 1001 $items.each(function (i) { 1002 if ($(this).is(':not(.disabled)')) { 1003 $(this).data('index', i); 1004 } 1005 }); 1006 index = $items.index($items.filter('.active')); 1007 first = $items.filter(':not(.disabled):visible').first().data('index'); 1008 last = $items.filter(':not(.disabled):visible').last().data('index'); 1009 next = $items.eq(index).nextAll(':not(.disabled):visible').eq(0).data('index'); 1010 prev = $items.eq(index).prevAll(':not(.disabled):visible').eq(0).data('index'); 1011 nextPrev = $items.eq(next).prevAll(':not(.disabled):visible').eq(0).data('index'); 1012 } 1013 1014 prevIndex = $this.data('prevIndex'); 1015 1016 if (e.keyCode == 38) { 1017 if (that.options.liveSearch) index -= 1; 1018 if (index != nextPrev && index > prev) index = prev; 1019 if (index < first) index = first; 1020 if (index == prevIndex) index = last; 1021 } 1022 1023 if (e.keyCode == 40) { 1024 if (that.options.liveSearch) index += 1; 1025 if (index == -1) index = 0; 1026 if (index != nextPrev && index < next) index = next; 1027 if (index > last) index = last; 1028 if (index == prevIndex) index = first; 1029 } 1030 1031 $this.data('prevIndex', index); 1032 1033 if (!that.options.liveSearch) { 1034 $items.eq(index).focus(); 1035 } else { 1036 e.preventDefault(); 1037 if (!$this.is('.dropdown-toggle')) { 1038 $items.removeClass('active'); 1039 $items.eq(index).addClass('active').find('a').focus(); 1040 $this.focus(); 1041 } 1042 } 1043 1044 } else if (!$this.is('input')) { 1045 var keyIndex = [], 1046 count, 1047 prevKey; 1048 1049 $items.each(function () { 1050 if ($(this).parent().is(':not(.disabled)')) { 1051 if ($.trim($(this).text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) { 1052 keyIndex.push($(this).parent().index()); 1053 } 1054 } 1055 }); 1056 1057 count = $(document).data('keycount'); 1058 count++; 1059 $(document).data('keycount', count); 1060 1061 prevKey = $.trim($(':focus').text().toLowerCase()).substring(0, 1); 1062 1063 if (prevKey != keyCodeMap[e.keyCode]) { 1064 count = 1; 1065 $(document).data('keycount', count); 1066 } else if (count >= keyIndex.length) { 1067 $(document).data('keycount', 0); 1068 if (count > keyIndex.length) count = 1; 1069 } 1070 1071 $items.eq(keyIndex[count - 1]).focus(); 1072 } 1073 1074 // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. 1075 if ((/(13|32)/.test(e.keyCode.toString(10)) || (/(^9$)/.test(e.keyCode.toString(10)) && that.options.selectOnTab)) && isActive) { 1076 if (!/(32)/.test(e.keyCode.toString(10))) e.preventDefault(); 1077 if (!that.options.liveSearch) { 1078 $(':focus').click(); 1079 } else if (!/(32)/.test(e.keyCode.toString(10))) { 1080 that.$menu.find('.active a').click(); 1081 $this.focus(); 1082 } 1083 $(document).data('keycount', 0); 1084 } 1085 1086 if ((/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode.toString(10)) && !isActive)) { 1087 that.$menu.parent().removeClass('open'); 1088 that.$button.focus(); 1089 } 1090 }, 1091 1092 mobile: function () { 1093 this.$element.addClass('mobile-device').appendTo(this.$newElement); 1094 if (this.options.container) this.$menu.hide(); 1095 }, 1096 1097 refresh: function () { 1098 this.$lis = null; 1099 this.reloadLi(); 1100 this.render(); 1101 this.setWidth(); 1102 this.setStyle(); 1103 this.checkDisabled(); 1104 this.liHeight(); 1105 }, 1106 1107 update: function () { 1108 this.reloadLi(); 1109 this.setWidth(); 1110 this.setStyle(); 1111 this.checkDisabled(); 1112 this.liHeight(); 1113 }, 1114 1115 hide: function () { 1116 this.$newElement.hide(); 1117 }, 1118 1119 show: function () { 1120 this.$newElement.show(); 1121 }, 1122 1123 remove: function () { 1124 this.$newElement.remove(); 1125 this.$element.remove(); 1126 } 1127 }; 1128 1129 // SELECTPICKER PLUGIN DEFINITION 1130 // ============================== 1131 function Plugin(option, event) { 1132 // get the args of the outer function.. 1133 var args = arguments; 1134 // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them
1135 // to get lost 1136 //noinspection JSDuplicatedDeclaration 1137 var _option = option, 1138 option = args[0], 1139 event = args[1]; 1140 [].shift.apply(args); 1141 1142 // This fixes a bug in the js implementation on android 2.3 #715 1143 if (typeof option == 'undefined') { 1144 option = _option; 1145 } 1146 1147 var value; 1148 var chain = this.each(function () { 1149 var $this = $(this); 1150 if ($this.is('select')) { 1151 var data = $this.data('selectpicker'), 1152 options = typeof option == 'object' && option; 1153 1154 if (!data) { 1155 var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, $this.data(), options); 1156 $this.data('selectpicker', (data = new Selectpicker(this, config, event))); 1157 } else if (options) { 1158 for (var i in options) { 1159 if (options.hasOwnProperty(i)) { 1160 data.options[i] = options[i]; 1161 } 1162 } 1163 } 1164 1165 if (typeof option == 'string') { 1166 if (data[option] instanceof Function) { 1167 value = data[option].apply(data, args); 1168 } else { 1169 value = data.options[option]; 1170 } 1171 } 1172 } 1173 }); 1174 1175 if (typeof value !== 'undefined') { 1176 //noinspection JSUnusedAssignment 1177 return value; 1178 } else { 1179 return chain; 1180 } 1181 } 1182 1183 var old = $.fn.selectpicker; 1184 $.fn.selectpicker = Plugin; 1185 $.fn.selectpicker.Constructor = Selectpicker; 1186 1187 // SELECTPICKER NO CONFLICT 1188 // ======================== 1189 $.fn.selectpicker.noConflict = function () { 1190 $.fn.selectpicker = old; 1191 return this; 1192 }; 1193 1194 $(document) 1195 .data('keycount', 0) 1196 .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', Selectpicker.prototype.keydown) 1197 .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', function (e) { 1198 e.stopPropagation(); 1199 }); 1200 1201 // SELECTPICKER DATA-API 1202 // ===================== 1203 $(window).on('load.bs.select.data-api', function () { 1204 $('.selectpicker').each(function () { 1205 var $selectpicker = $(this); 1206 Plugin.call($selectpicker, $selectpicker.data()); 1207 }) 1208 }); 1209})(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.