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