vendor/assets/javascripts/bootstrap-select.js in bootstrap-select-wrapper-rails-1.5.7 vs vendor/assets/javascripts/bootstrap-select.js in bootstrap-select-wrapper-rails-1.6.3

- old
+ new

@@ -1,972 +1,1215 @@ -/*! - * bootstrap-select v1.5.4 - * http://silviomoreto.github.io/bootstrap-select/ - * - * Copyright 2013 bootstrap-select - * Licensed under the MIT license - */ - -!function($) { - - 'use strict'; - - $.expr[':'].icontains = function(obj, index, meta) { - return $(obj).text().toUpperCase().indexOf(meta[3].toUpperCase()) >= 0; - }; - - var Selectpicker = function(element, options, e) { - if (e) { - e.stopPropagation(); - e.preventDefault(); - } - this.$element = $(element); - this.$newElement = null; - this.$button = null; - this.$menu = null; - this.$lis = null; - - //Merge defaults, options and data-attributes to make our options - this.options = $.extend({}, $.fn.selectpicker.defaults, this.$element.data(), typeof options == 'object' && options); - - //If we have no title yet, check the attribute 'title' (this is missed by jq as its not a data-attribute - if (this.options.title === null) { - this.options.title = this.$element.attr('title'); - } - - //Expose public methods - this.val = Selectpicker.prototype.val; - this.render = Selectpicker.prototype.render; - this.refresh = Selectpicker.prototype.refresh; - this.setStyle = Selectpicker.prototype.setStyle; - this.selectAll = Selectpicker.prototype.selectAll; - this.deselectAll = Selectpicker.prototype.deselectAll; - this.init(); - }; - - Selectpicker.prototype = { - - constructor: Selectpicker, - - init: function() { - var that = this, - id = this.$element.attr('id'); - - this.$element.hide(); - this.multiple = this.$element.prop('multiple'); - this.autofocus = this.$element.prop('autofocus'); - this.$newElement = this.createView(); - this.$element.after(this.$newElement); - this.$menu = this.$newElement.find('> .dropdown-menu'); - this.$button = this.$newElement.find('> button'); - this.$searchbox = this.$newElement.find('input'); - - if (id !== undefined) { - this.$button.attr('data-id', id); - $('label[for="' + id + '"]').click(function(e) { - e.preventDefault(); - that.$button.focus(); - }); - } - - this.checkDisabled(); - this.clickListener(); - if (this.options.liveSearch) this.liveSearchListener(); - this.render(); - this.liHeight(); - this.setStyle(); - this.setWidth(); - if (this.options.container) this.selectPosition(); - this.$menu.data('this', this); - this.$newElement.data('this', this); - }, - - createDropdown: function() { - //If we are multiple, then add the show-tick class by default - var multiple = this.multiple ? ' show-tick' : ''; - var inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : ''; - var autofocus = this.autofocus ? ' autofocus' : ''; - var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>' + this.options.header + '</div>' : ''; - var searchbox = this.options.liveSearch ? '<div class="bootstrap-select-searchbox"><input type="text" class="input-block-level form-control" autocomplete="off" /></div>' : ''; - var actionsbox = this.options.actionsBox ? '<div class="bs-actionsbox">' + - '<div class="btn-group btn-block">' + - '<button class="actions-btn bs-select-all btn btn-sm btn-default">' + - 'Select All' + - '</button>' + - '<button class="actions-btn bs-deselect-all btn btn-sm btn-default">' + - 'Deselect All' + - '</button>' + - '</div>' + - '</div>' : ''; - var drop = - '<div class="btn-group bootstrap-select' + multiple + inputGroup + '">' + - '<button type="button" class="btn dropdown-toggle selectpicker" data-toggle="dropdown"'+ autofocus +'>' + - '<span class="filter-option pull-left"></span>&nbsp;' + - '<span class="caret"></span>' + - '</button>' + - '<div class="dropdown-menu open">' + - header + - searchbox + - actionsbox + - '<ul class="dropdown-menu inner selectpicker" role="menu">' + - '</ul>' + - '</div>' + - '</div>'; - - return $(drop); - }, - - createView: function() { - var $drop = this.createDropdown(); - var $li = this.createLi(); - $drop.find('ul').append($li); - return $drop; - }, - - reloadLi: function() { - //Remove all children. - this.destroyLi(); - //Re build - var $li = this.createLi(); - this.$menu.find('ul').append( $li ); - }, - - destroyLi: function() { - this.$menu.find('li').remove(); - }, - - createLi: function() { - var that = this, - _liA = [], - _liHtml = ''; - - this.$element.find('option').each(function() { - var $this = $(this); - - //Get the class and text for the option - var optionClass = $this.attr('class') || ''; - var inline = $this.attr('style') || ''; - var text = $this.data('content') ? $this.data('content') : $this.html(); - var subtext = $this.data('subtext') !== undefined ? '<small class="muted text-muted">' + $this.data('subtext') + '</small>' : ''; - var icon = $this.data('icon') !== undefined ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : ''; - if (icon !== '' && ($this.is(':disabled') || $this.parent().is(':disabled'))) { - icon = '<span>'+icon+'</span>'; - } - - if (!$this.data('content')) { - //Prepend any icon and append any subtext to the main text. - text = icon + '<span class="text">' + text + subtext + '</span>'; - } - - if (that.options.hideDisabled && ($this.is(':disabled') || $this.parent().is(':disabled'))) { - _liA.push('<a style="min-height: 0; padding: 0"></a>'); - } else if ($this.parent().is('optgroup') && $this.data('divider') !== true) { - if ($this.index() === 0) { - //Get the opt group label - var label = $this.parent().attr('label'); - var labelSubtext = $this.parent().data('subtext') !== undefined ? '<small class="muted text-muted">'+$this.parent().data('subtext')+'</small>' : ''; - var labelIcon = $this.parent().data('icon') ? '<i class="'+$this.parent().data('icon')+'"></i> ' : ''; - label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>'; - - if ($this[0].index !== 0) { - _liA.push( - '<div class="div-contain"><div class="divider"></div></div>'+ - '<dt>'+label+'</dt>'+ - that.createA(text, 'opt ' + optionClass, inline ) - ); - } else { - _liA.push( - '<dt>'+label+'</dt>'+ - that.createA(text, 'opt ' + optionClass, inline )); - } - } else { - _liA.push(that.createA(text, 'opt ' + optionClass, inline )); - } - } else if ($this.data('divider') === true) { - _liA.push('<div class="div-contain"><div class="divider"></div></div>'); - } else if ($(this).data('hidden') === true) { - _liA.push('<a></a>'); - } else { - _liA.push(that.createA(text, optionClass, inline )); - } - }); - - $.each(_liA, function(i, item) { - var hide = item === '<a></a>' ? 'class="hide is-hidden"' : ''; - _liHtml += '<li rel="' + i + '"' + hide + '>' + item + '</li>'; - }); - - //If we are not multiple, and we dont have a selected item, and we dont have a title, select the first element so something is set in the button - if (!this.multiple && this.$element.find('option:selected').length===0 && !this.options.title) { - this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected'); - } - - return $(_liHtml); - }, - - createA: function(text, classes, inline) { - return '<a tabindex="0" class="'+classes+'" style="'+inline+'">' + - text + - '<i class="' + this.options.iconBase + ' ' + this.options.tickIcon + ' icon-ok check-mark"></i>' + - '</a>'; - }, - - render: function(updateLi) { - var that = this; - - //Update the LI to match the SELECT - if (updateLi !== false) { - this.$element.find('option').each(function(index) { - that.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled') ); - that.setSelected(index, $(this).is(':selected') ); - }); - } - - this.tabIndex(); - - var selectedItems = this.$element.find('option:selected').map(function() { - var $this = $(this); - var icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : ''; - var subtext; - if (that.options.showSubtext && $this.attr('data-subtext') && !that.multiple) { - subtext = ' <small class="muted text-muted">'+$this.data('subtext') +'</small>'; - } else { - subtext = ''; - } - if ($this.data('content') && that.options.showContent) { - return $this.data('content'); - } else if ($this.attr('title') !== undefined) { - return $this.attr('title'); - } else { - return icon + $this.html() + subtext; - } - }).toArray(); - - //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled - //Convert all the values into a comma delimited string - var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator); - - //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc.. - if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) { - var max = this.options.selectedTextFormat.split('>'); - var notDisabled = this.options.hideDisabled ? ':not([disabled])' : ''; - if ( (max.length>1 && selectedItems.length > max[1]) || (max.length==1 && selectedItems.length>=2)) { - title = this.options.countSelectedText.replace('{0}', selectedItems.length).replace('{1}', this.$element.find('option:not([data-divider="true"]):not([data-hidden="true"])'+notDisabled).length); - } - } - - this.options.title = this.$element.attr('title'); - - //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text - if (!title) { - title = this.options.title !== undefined ? this.options.title : this.options.noneSelectedText; - } - - this.$button.attr('title', $.trim(title)); - this.$newElement.find('.filter-option').html(title); - }, - - setStyle: function(style, status) { - if (this.$element.attr('class')) { - this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|validate\[.*\]/gi, '')); - } - - var buttonClass = style ? style : this.options.style; - - if (status == 'add') { - this.$button.addClass(buttonClass); - } else if (status == 'remove') { - this.$button.removeClass(buttonClass); - } else { - this.$button.removeClass(this.options.style); - this.$button.addClass(buttonClass); - } - }, - - liHeight: function() { - if (this.options.size === false) return; - - var $selectClone = this.$menu.parent().clone().find('> .dropdown-toggle').prop('autofocus', false).end().appendTo('body'), - $menuClone = $selectClone.addClass('open').find('> .dropdown-menu'), - liHeight = $menuClone.find('li > a').outerHeight(), - headerHeight = this.options.header ? $menuClone.find('.popover-title').outerHeight() : 0, - searchHeight = this.options.liveSearch ? $menuClone.find('.bootstrap-select-searchbox').outerHeight() : 0, - actionsHeight = this.options.actionsBox ? $menuClone.find('.bs-actionsbox').outerHeight() : 0; - - $selectClone.remove(); - - this.$newElement - .data('liHeight', liHeight) - .data('headerHeight', headerHeight) - .data('searchHeight', searchHeight) - .data('actionsHeight', actionsHeight); - }, - - setSize: function() { - var that = this, - menu = this.$menu, - menuInner = menu.find('.inner'), - selectHeight = this.$newElement.outerHeight(), - liHeight = this.$newElement.data('liHeight'), - headerHeight = this.$newElement.data('headerHeight'), - searchHeight = this.$newElement.data('searchHeight'), - actionsHeight = this.$newElement.data('actionsHeight'), - divHeight = menu.find('li .divider').outerHeight(true), - menuPadding = parseInt(menu.css('padding-top')) + - parseInt(menu.css('padding-bottom')) + - parseInt(menu.css('border-top-width')) + - parseInt(menu.css('border-bottom-width')), - notDisabled = this.options.hideDisabled ? ':not(.disabled)' : '', - $window = $(window), - menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2, - menuHeight, - selectOffsetTop, - selectOffsetBot, - posVert = function() { - selectOffsetTop = that.$newElement.offset().top - $window.scrollTop(); - selectOffsetBot = $window.height() - selectOffsetTop - selectHeight; - }; - posVert(); - if (this.options.header) menu.css('padding-top', 0); - - if (this.options.size == 'auto') { - var getSize = function() { - var minHeight, - lisVis = that.$lis.not('.hide'); - - posVert(); - menuHeight = selectOffsetBot - menuExtras; - - if (that.options.dropupAuto) { - that.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && ((menuHeight - menuExtras) < menu.height())); - } - if (that.$newElement.hasClass('dropup')) { - menuHeight = selectOffsetTop - menuExtras; - } - - if ((lisVis.length + lisVis.find('dt').length) > 3) { - minHeight = liHeight*3 + menuExtras - 2; - } else { - minHeight = 0; - } - - menu.css({'max-height' : menuHeight + 'px', 'overflow' : 'hidden', 'min-height' : minHeight + headerHeight + searchHeight + actionsHeight + 'px'}); - menuInner.css({'max-height' : menuHeight - headerHeight - searchHeight - actionsHeight - menuPadding + 'px', 'overflow-y' : 'auto', 'min-height' : Math.max(minHeight - menuPadding, 0) + 'px'}); - }; - getSize(); - this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize); - $(window).off('resize.getSize').on('resize.getSize', getSize); - $(window).off('scroll.getSize').on('scroll.getSize', getSize); - } else if (this.options.size && this.options.size != 'auto' && menu.find('li'+notDisabled).length > this.options.size) { - var optIndex = menu.find('li'+notDisabled+' > *').filter(':not(.div-contain)').slice(0,this.options.size).last().parent().index(); - var divLength = menu.find('li').slice(0,optIndex + 1).find('.div-contain').length; - menuHeight = liHeight*this.options.size + divLength*divHeight + menuPadding; - if (that.options.dropupAuto) { - this.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && (menuHeight < menu.height())); - } - menu.css({'max-height' : menuHeight + headerHeight + searchHeight + actionsHeight + 'px', 'overflow' : 'hidden'}); - menuInner.css({'max-height' : menuHeight - menuPadding + 'px', 'overflow-y' : 'auto'}); - } - }, - - setWidth: function() { - if (this.options.width == 'auto') { - this.$menu.css('min-width', '0'); - - // Get correct width if element hidden - var selectClone = this.$newElement.clone().appendTo('body'); - var ulWidth = selectClone.find('> .dropdown-menu').css('width'); - var btnWidth = selectClone.css('width', 'auto').find('> button').css('width'); - selectClone.remove(); - - // Set width to whatever's larger, button title or longest option - this.$newElement.css('width', Math.max(parseInt(ulWidth), parseInt(btnWidth)) + 'px'); - } else if (this.options.width == 'fit') { - // Remove inline min-width so width can be changed from 'auto' - this.$menu.css('min-width', ''); - this.$newElement.css('width', '').addClass('fit-width'); - } else if (this.options.width) { - // Remove inline min-width so width can be changed from 'auto' - this.$menu.css('min-width', ''); - this.$newElement.css('width', this.options.width); - } else { - // Remove inline min-width/width so width can be changed - this.$menu.css('min-width', ''); - this.$newElement.css('width', ''); - } - // Remove fit-width class if width is changed programmatically - if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { - this.$newElement.removeClass('fit-width'); - } - }, - - selectPosition: function() { - var that = this, - drop = '<div />', - $drop = $(drop), - pos, - actualHeight, - getPlacement = function($element) { - $drop.addClass($element.attr('class').replace(/form-control/gi, '')).toggleClass('dropup', $element.hasClass('dropup')); - pos = $element.offset(); - actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight; - $drop.css({'top' : pos.top + actualHeight, 'left' : pos.left, 'width' : $element[0].offsetWidth, 'position' : 'absolute'}); - }; - this.$newElement.on('click', function() { - if (that.isDisabled()) { - return; - } - getPlacement($(this)); - $drop.appendTo(that.options.container); - $drop.toggleClass('open', !$(this).hasClass('open')); - $drop.append(that.$menu); - }); - $(window).resize(function() { - getPlacement(that.$newElement); - }); - $(window).on('scroll', function() { - getPlacement(that.$newElement); - }); - $('html').on('click', function(e) { - if ($(e.target).closest(that.$newElement).length < 1) { - $drop.removeClass('open'); - } - }); - }, - - mobile: function() { - this.$element.addClass('mobile-device').appendTo(this.$newElement); - if (this.options.container) this.$menu.hide(); - }, - - refresh: function() { - this.$lis = null; - this.reloadLi(); - this.render(); - this.setWidth(); - this.setStyle(); - this.checkDisabled(); - this.liHeight(); - }, - - update: function() { - this.reloadLi(); - this.setWidth(); - this.setStyle(); - this.checkDisabled(); - this.liHeight(); - }, - - setSelected: function(index, selected) { - if (this.$lis == null) this.$lis = this.$menu.find('li'); - $(this.$lis[index]).toggleClass('selected', selected); - }, - - setDisabled: function(index, disabled) { - if (this.$lis == null) this.$lis = this.$menu.find('li'); - if (disabled) { - $(this.$lis[index]).addClass('disabled').find('a').attr('href', '#').attr('tabindex', -1); - } else { - $(this.$lis[index]).removeClass('disabled').find('a').removeAttr('href').attr('tabindex', 0); - } - }, - - isDisabled: function() { - return this.$element.is(':disabled'); - }, - - checkDisabled: function() { - var that = this; - - if (this.isDisabled()) { - this.$button.addClass('disabled').attr('tabindex', -1); - } else { - if (this.$button.hasClass('disabled')) { - this.$button.removeClass('disabled'); - } - - if (this.$button.attr('tabindex') == -1) { - if (!this.$element.data('tabindex')) this.$button.removeAttr('tabindex'); - } - } - - this.$button.click(function() { - return !that.isDisabled(); - }); - }, - - tabIndex: function() { - if (this.$element.is('[tabindex]')) { - this.$element.data('tabindex', this.$element.attr('tabindex')); - this.$button.attr('tabindex', this.$element.data('tabindex')); - } - }, - - clickListener: function() { - var that = this; - - $('body').on('touchstart.dropdown', '.dropdown-menu', function(e) { - e.stopPropagation(); - }); - - this.$newElement.on('click', function() { - that.setSize(); - if (!that.options.liveSearch && !that.multiple) { - setTimeout(function() { - that.$menu.find('.selected a').focus(); - }, 10); - } - }); - - this.$menu.on('click', 'li a', function(e) { - var clickedIndex = $(this).parent().index(), - prevValue = that.$element.val(), - prevIndex = that.$element.prop('selectedIndex'); - - //Dont close on multi choice menu - if (that.multiple) { - e.stopPropagation(); - } - - e.preventDefault(); - - //Dont run if we have been disabled - if (!that.isDisabled() && !$(this).parent().hasClass('disabled')) { - var $options = that.$element.find('option'), - $option = $options.eq(clickedIndex), - state = $option.prop('selected'), - $optgroup = $option.parent('optgroup'), - maxOptions = that.options.maxOptions, - maxOptionsGrp = $optgroup.data('maxOptions') || false; - - //Deselect all others if not multi select box - if (!that.multiple) { - $options.prop('selected', false); - $option.prop('selected', true); - that.$menu.find('.selected').removeClass('selected'); - that.setSelected(clickedIndex, true); - } - //Else toggle the one we have chosen if we are multi select. - else { - $option.prop('selected', !state); - that.setSelected(clickedIndex, !state); - - if ((maxOptions !== false) || (maxOptionsGrp !== false)) { - var maxReached = maxOptions < $options.filter(':selected').length, - maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length, - maxOptionsArr = that.options.maxOptionsText, - maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), - maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), - $notify = $('<div class="notify"></div>'); - - if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { - // If {var} is set in array, replace it - if (maxOptionsArr[2]) { - maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); - maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); - } - - $option.prop('selected', false); - - that.$menu.append($notify); - - if (maxOptions && maxReached) { - $notify.append($('<div>' + maxTxt + '</div>')); - that.$element.trigger('maxReached.bs.select'); - } - - if (maxOptionsGrp && maxReachedGrp) { - $notify.append($('<div>' + maxTxtGrp + '</div>')); - that.$element.trigger('maxReachedGrp.bs.select'); - } - - setTimeout(function() { - that.setSelected(clickedIndex, false); - }, 10); - - $notify.delay(750).fadeOut(300, function() { $(this).remove(); }); - } - } - } - - if (!that.multiple) { - that.$button.focus(); - } else if (that.options.liveSearch) { - that.$searchbox.focus(); - } - - // Trigger select 'change' - if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) { - that.$element.change(); - } - } - }); - - this.$menu.on('click', 'li.disabled a, li dt, li .div-contain, .popover-title, .popover-title :not(.close)', function(e) { - if (e.target == this) { - e.preventDefault(); - e.stopPropagation(); - if (!that.options.liveSearch) { - that.$button.focus(); - } else { - that.$searchbox.focus(); - } - } - }); - - this.$menu.on('click', '.popover-title .close', function() { - that.$button.focus(); - }); - - this.$searchbox.on('click', function(e) { - e.stopPropagation(); - }); - - - this.$menu.on('click', '.actions-btn', function(e) { - if (that.options.liveSearch) { - that.$searchbox.focus(); - } else { - that.$button.focus(); - } - - e.preventDefault(); - e.stopPropagation(); - - if ($(this).is('.bs-select-all')) { - that.selectAll(); - } else { - that.deselectAll(); - } - that.$element.change(); - }); - - this.$element.change(function() { - that.render(false); - }); - }, - - liveSearchListener: function() { - var that = this, - no_results = $('<li class="no-results"></li>'); - - this.$newElement.on('click.dropdown.data-api', function() { - that.$menu.find('.active').removeClass('active'); - if (!!that.$searchbox.val()) { - that.$searchbox.val(''); - that.$lis.not('.is-hidden').removeClass('hide'); - if (!!no_results.parent().length) no_results.remove(); - } - if (!that.multiple) that.$menu.find('.selected').addClass('active'); - setTimeout(function() { - that.$searchbox.focus(); - }, 10); - }); - - this.$searchbox.on('input propertychange', function() { - if (that.$searchbox.val()) { - that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':icontains(' + that.$searchbox.val() + ')').parent().addClass('hide'); - - if (!that.$menu.find('li').filter(':visible:not(.no-results)').length) { - if (!!no_results.parent().length) no_results.remove(); - no_results.html(that.options.noneResultsText + ' "'+ that.$searchbox.val() + '"').show(); - that.$menu.find('li').last().after(no_results); - } else if (!!no_results.parent().length) { - no_results.remove(); - } - - } else { - that.$lis.not('.is-hidden').removeClass('hide'); - if (!!no_results.parent().length) no_results.remove(); - } - - that.$menu.find('li.active').removeClass('active'); - that.$menu.find('li').filter(':visible:not(.divider)').eq(0).addClass('active').find('a').focus(); - $(this).focus(); - }); - - this.$menu.on('mouseenter', 'a', function(e) { - that.$menu.find('.active').removeClass('active'); - $(e.currentTarget).parent().not('.disabled').addClass('active'); - }); - - this.$menu.on('mouseleave', 'a', function() { - that.$menu.find('.active').removeClass('active'); - }); - }, - - val: function(value) { - - if (value !== undefined) { - this.$element.val( value ); - - this.$element.change(); - return this.$element; - } else { - return this.$element.val(); - } - }, - - selectAll: function() { - if (this.$lis == null) this.$lis = this.$menu.find('li'); - this.$element.find('option:enabled').prop('selected', true); - $(this.$lis).filter(':not(.disabled)').addClass('selected'); - this.render(false); - }, - - deselectAll: function() { - if (this.$lis == null) this.$lis = this.$menu.find('li'); - this.$element.find('option:enabled').prop('selected', false); - $(this.$lis).filter(':not(.disabled)').removeClass('selected'); - this.render(false); - }, - - keydown: function(e) { - var $this, - $items, - $parent, - index, - next, - first, - last, - prev, - nextPrev, - that, - prevIndex, - isActive, - keyCodeMap = { - 32:' ', 48:'0', 49:'1', 50:'2', 51:'3', 52:'4', 53:'5', 54:'6', 55:'7', 56:'8', 57:'9', 59:';', - 65:'a', 66:'b', 67:'c', 68:'d', 69:'e', 70:'f', 71:'g', 72:'h', 73:'i', 74:'j', 75:'k', 76:'l', - 77:'m', 78:'n', 79:'o', 80:'p', 81:'q', 82:'r', 83:'s', 84:'t', 85:'u', 86:'v', 87:'w', 88:'x', - 89:'y', 90:'z', 96:'0', 97:'1', 98:'2', 99:'3', 100:'4', 101:'5', 102:'6', 103:'7', 104:'8', 105:'9' - }; - - $this = $(this); - - $parent = $this.parent(); - - if ($this.is('input')) $parent = $this.parent().parent(); - - that = $parent.data('this'); - - if (that.options.liveSearch) $parent = $this.parent().parent(); - - if (that.options.container) $parent = that.$menu; - - $items = $('[role=menu] li:not(.divider) a', $parent); - - isActive = that.$menu.parent().hasClass('open'); - - if (!isActive && /([0-9]|[A-z])/.test(String.fromCharCode(e.keyCode))) { - if (!that.options.container) { - that.setSize(); - that.$menu.parent().addClass('open'); - isActive = that.$menu.parent().hasClass('open'); - } else { - that.$newElement.trigger('click'); - } - that.$searchbox.focus(); - } - - if (that.options.liveSearch) { - if (/(^9$|27)/.test(e.keyCode) && isActive && that.$menu.find('.active').length === 0) { - e.preventDefault(); - that.$menu.parent().removeClass('open'); - that.$button.focus(); - } - $items = $('[role=menu] li:not(.divider):visible', $parent); - if (!$this.val() && !/(38|40)/.test(e.keyCode)) { - if ($items.filter('.active').length === 0) { - $items = that.$newElement.find('li').filter(':icontains(' + keyCodeMap[e.keyCode] + ')'); - } - } - } - - if (!$items.length) return; - - if (/(38|40)/.test(e.keyCode)) { - - index = $items.index($items.filter(':focus')); - first = $items.parent(':not(.disabled):visible').first().index(); - last = $items.parent(':not(.disabled):visible').last().index(); - next = $items.eq(index).parent().nextAll(':not(.disabled):visible').eq(0).index(); - prev = $items.eq(index).parent().prevAll(':not(.disabled):visible').eq(0).index(); - nextPrev = $items.eq(next).parent().prevAll(':not(.disabled):visible').eq(0).index(); - - if (that.options.liveSearch) { - $items.each(function(i) { - if ($(this).is(':not(.disabled)')) { - $(this).data('index', i); - } - }); - index = $items.index($items.filter('.active')); - first = $items.filter(':not(.disabled):visible').first().data('index'); - last = $items.filter(':not(.disabled):visible').last().data('index'); - next = $items.eq(index).nextAll(':not(.disabled):visible').eq(0).data('index'); - prev = $items.eq(index).prevAll(':not(.disabled):visible').eq(0).data('index'); - nextPrev = $items.eq(next).prevAll(':not(.disabled):visible').eq(0).data('index'); - } - - prevIndex = $this.data('prevIndex'); - - if (e.keyCode == 38) { - if (that.options.liveSearch) index -= 1; - if (index != nextPrev && index > prev) index = prev; - if (index < first) index = first; - if (index == prevIndex) index = last; - } - - if (e.keyCode == 40) { - if (that.options.liveSearch) index += 1; - if (index == -1) index = 0; - if (index != nextPrev && index < next) index = next; - if (index > last) index = last; - if (index == prevIndex) index = first; - } - - $this.data('prevIndex', index); - - if (!that.options.liveSearch) { - $items.eq(index).focus(); - } else { - e.preventDefault(); - if (!$this.is('.dropdown-toggle')) { - $items.removeClass('active'); - $items.eq(index).addClass('active').find('a').focus(); - $this.focus(); - } - } - - } else if (!$this.is('input')) { - - var keyIndex = [], - count, - prevKey; - - $items.each(function() { - if ($(this).parent().is(':not(.disabled)')) { - if ($.trim($(this).text().toLowerCase()).substring(0,1) == keyCodeMap[e.keyCode]) { - keyIndex.push($(this).parent().index()); - } - } - }); - - count = $(document).data('keycount'); - count++; - $(document).data('keycount',count); - - prevKey = $.trim($(':focus').text().toLowerCase()).substring(0,1); - - if (prevKey != keyCodeMap[e.keyCode]) { - count = 1; - $(document).data('keycount', count); - } else if (count >= keyIndex.length) { - $(document).data('keycount', 0); - if (count > keyIndex.length) count = 1; - } - - $items.eq(keyIndex[count - 1]).focus(); - } - - // Select focused option if "Enter", "Spacebar", "Tab" are pressed inside the menu. - if (/(13|32|^9$)/.test(e.keyCode) && isActive) { - if (!/(32)/.test(e.keyCode)) e.preventDefault(); - if (!that.options.liveSearch) { - $(':focus').click(); - } else if (!/(32)/.test(e.keyCode)) { - that.$menu.find('.active a').click(); - $this.focus(); - } - $(document).data('keycount',0); - } - - if ((/(^9$|27)/.test(e.keyCode) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode) && !isActive)) { - that.$menu.parent().removeClass('open'); - that.$button.focus(); - } - - }, - - hide: function() { - this.$newElement.hide(); - }, - - show: function() { - this.$newElement.show(); - }, - - destroy: function() { - this.$newElement.remove(); - this.$element.remove(); - } - }; - - $.fn.selectpicker = function(option, event) { - //get the args of the outer function.. - var args = arguments; - var value; - var chain = this.each(function() { - if ($(this).is('select')) { - var $this = $(this), - data = $this.data('selectpicker'), - options = typeof option == 'object' && option; - - if (!data) { - $this.data('selectpicker', (data = new Selectpicker(this, options, event))); - } else if (options) { - for(var i in options) { - data.options[i] = options[i]; - } - } - - if (typeof option == 'string') { - //Copy the value of option, as once we shift the arguments - //it also shifts the value of option. - var property = option; - if (data[property] instanceof Function) { - [].shift.apply(args); - value = data[property].apply(data, args); - } else { - value = data.options[property]; - } - } - } - }); - - if (value !== undefined) { - return value; - } else { - return chain; - } - }; - - $.fn.selectpicker.defaults = { - style: 'btn-default', - size: 'auto', - title: null, - selectedTextFormat : 'values', - noneSelectedText : 'Nothing selected', - noneResultsText : 'No results match', - countSelectedText: '{0} of {1} selected', - maxOptionsText: ['Limit reached ({n} {var} max)', 'Group limit reached ({n} {var} max)', ['items','item']], - width: false, - container: false, - hideDisabled: false, - showSubtext: false, - showIcon: true, - showContent: true, - dropupAuto: true, - header: false, - liveSearch: false, - actionsBox: false, - multipleSeparator: ', ', - iconBase: 'glyphicon', - tickIcon: 'glyphicon-ok', - maxOptions: false - }; - - $(document) - .data('keycount', 0) - .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bootstrap-select-searchbox input', Selectpicker.prototype.keydown) - .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bootstrap-select-searchbox input', function (e) { e.stopPropagation(); }); - -}(window.jQuery); +/*! + * Bootstrap-select v1.6.3 (http://silviomoreto.github.io/bootstrap-select/) + * + * Copyright 2013-2014 bootstrap-select + * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) + */ +(function ($) { + 'use strict'; + + // Case insensitive search + $.expr[':'].icontains = function (obj, index, meta) { + return icontains($(obj).text(), meta[3]); + }; + + // Case and accent insensitive search + $.expr[':'].aicontains = function (obj, index, meta) { + return icontains($(obj).data('normalizedText') || $(obj).text(), meta[3]); + }; + + /** + * Actual implementation of the case insensitive search. + * @access private + * @param {String} haystack + * @param {String} needle + * @returns {boolean} + */ + function icontains(haystack, needle) { + return haystack.toUpperCase().indexOf(needle.toUpperCase()) > -1; + } + + /** + * Remove all diatrics from the given text. + * @access private + * @param {String} text + * @returns {String} + */ + function normalizeToBase(text) { + var rExps = [ + {re: /[\xC0-\xC6]/g, ch: "A"}, + {re: /[\xE0-\xE6]/g, ch: "a"}, + {re: /[\xC8-\xCB]/g, ch: "E"}, + {re: /[\xE8-\xEB]/g, ch: "e"}, + {re: /[\xCC-\xCF]/g, ch: "I"}, + {re: /[\xEC-\xEF]/g, ch: "i"}, + {re: /[\xD2-\xD6]/g, ch: "O"}, + {re: /[\xF2-\xF6]/g, ch: "o"}, + {re: /[\xD9-\xDC]/g, ch: "U"}, + {re: /[\xF9-\xFC]/g, ch: "u"}, + {re: /[\xC7-\xE7]/g, ch: "c"}, + {re: /[\xD1]/g, ch: "N"}, + {re: /[\xF1]/g, ch: "n"} + ]; + $.each(rExps, function () { + text = text.replace(this.re, this.ch); + }); + return text; + } + + + function htmlEscape(html) { + var escapeMap = { + '&': '&amp;', + '<': '&lt;', + '>': '&gt;', + '"': '&quot;', + "'": '&#x27;', + '`': '&#x60;' + }; + var source = '(?:' + Object.keys(escapeMap).join('|') + ')', + testRegexp = new RegExp(source), + replaceRegexp = new RegExp(source, 'g'), + string = html == null ? '' : '' + html; + return testRegexp.test(string) ? string.replace(replaceRegexp, function (match) { + return escapeMap[match]; + }) : string; + } + + var Selectpicker = function (element, options, e) { + if (e) { + e.stopPropagation(); + e.preventDefault(); + } + + this.$element = $(element); + this.$newElement = null; + this.$button = null; + this.$menu = null; + this.$lis = null; + this.options = options; + + // 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 + // data-attribute) + if (this.options.title === null) { + this.options.title = this.$element.attr('title'); + } + + //Expose public methods + this.val = Selectpicker.prototype.val; + this.render = Selectpicker.prototype.render; + this.refresh = Selectpicker.prototype.refresh; + this.setStyle = Selectpicker.prototype.setStyle; + this.selectAll = Selectpicker.prototype.selectAll; + this.deselectAll = Selectpicker.prototype.deselectAll; + this.destroy = Selectpicker.prototype.remove; + this.remove = Selectpicker.prototype.remove; + this.show = Selectpicker.prototype.show; + this.hide = Selectpicker.prototype.hide; + + this.init(); + }; + + Selectpicker.VERSION = '1.6.3'; + + // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. + Selectpicker.DEFAULTS = { + noneSelectedText: 'Nothing selected', + noneResultsText: 'No results match', + countSelectedText: function (numSelected, numTotal) { + return (numSelected == 1) ? "{0} item selected" : "{0} items selected"; + }, + maxOptionsText: function (numAll, numGroup) { + var arr = []; + + arr[0] = (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)'; + arr[1] = (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)'; + + return arr; + }, + selectAllText: 'Select All', + deselectAllText: 'Deselect All', + multipleSeparator: ', ', + style: 'btn-default', + size: 'auto', + title: null, + selectedTextFormat: 'values', + width: false, + container: false, + hideDisabled: false, + showSubtext: false, + showIcon: true, + showContent: true, + dropupAuto: true, + header: false, + liveSearch: false, + actionsBox: false, + iconBase: 'glyphicon', + tickIcon: 'glyphicon-ok', + maxOptions: false, + mobile: false, + selectOnTab: false, + dropdownAlignRight: false, + searchAccentInsensitive: false + }; + + Selectpicker.prototype = { + + constructor: Selectpicker, + + init: function () { + var that = this, + id = this.$element.attr('id'); + + this.$element.hide(); + this.multiple = this.$element.prop('multiple'); + this.autofocus = this.$element.prop('autofocus'); + this.$newElement = this.createView(); + this.$element.after(this.$newElement); + this.$menu = this.$newElement.find('> .dropdown-menu'); + this.$button = this.$newElement.find('> button'); + this.$searchbox = this.$newElement.find('input'); + + if (this.options.dropdownAlignRight) + this.$menu.addClass('dropdown-menu-right'); + + if (typeof id !== 'undefined') { + this.$button.attr('data-id', id); + $('label[for="' + id + '"]').click(function (e) { + e.preventDefault(); + that.$button.focus(); + }); + } + + this.checkDisabled(); + this.clickListener(); + if (this.options.liveSearch) this.liveSearchListener(); + this.render(); + this.liHeight(); + this.setStyle(); + this.setWidth(); + if (this.options.container) this.selectPosition(); + this.$menu.data('this', this); + this.$newElement.data('this', this); + if (this.options.mobile) this.mobile(); + }, + + createDropdown: function () { + // Options + // If we are multiple, then add the show-tick class by default + var multiple = this.multiple ? ' show-tick' : '', + inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '', + autofocus = this.autofocus ? ' autofocus' : '', + btnSize = this.$element.parents().hasClass('form-group-lg') ? ' btn-lg' : (this.$element.parents().hasClass('form-group-sm') ? ' btn-sm' : ''); + // Elements + var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>' + this.options.header + '</div>' : ''; + var searchbox = this.options.liveSearch ? '<div class="bs-searchbox"><input type="text" class="input-block-level form-control" autocomplete="off" /></div>' : ''; + var actionsbox = this.options.actionsBox ? '<div class="bs-actionsbox">' + + '<div class="btn-group btn-block">' + + '<button class="actions-btn bs-select-all btn btn-sm btn-default">' + + this.options.selectAllText + + '</button>' + + '<button class="actions-btn bs-deselect-all btn btn-sm btn-default">' + + this.options.deselectAllText + + '</button>' + + '</div>' + + '</div>' : ''; + var drop = + '<div class="btn-group bootstrap-select' + multiple + inputGroup + '">' + + '<button type="button" class="btn dropdown-toggle selectpicker' + btnSize + '" data-toggle="dropdown"' + autofocus + '>' + + '<span class="filter-option pull-left"></span>&nbsp;' + + '<span class="caret"></span>' + + '</button>' + + '<div class="dropdown-menu open">' + + header + + searchbox + + actionsbox + + '<ul class="dropdown-menu inner selectpicker" role="menu">' + + '</ul>' + + '</div>' + + '</div>'; + + return $(drop); + }, + + createView: function () { + var $drop = this.createDropdown(); + var $li = this.createLi(); + $drop.find('ul').append($li); + return $drop; + }, + + reloadLi: function () { + //Remove all children. + this.destroyLi(); + //Re build + var $li = this.createLi(); + this.$menu.find('ul').append($li); + }, + + destroyLi: function () { + this.$menu.find('li').remove(); + }, + + createLi: function () { + var that = this, + _li = [], + optID = 0; + + // Helper functions + /** + * @param content + * @param [index] + * @param [classes] + * @returns {string} + */ + var generateLI = function (content, index, classes) { + return '<li' + + (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + + (typeof index !== 'undefined' | null === index ? ' data-original-index="' + index + '"' : '') + + '>' + content + '</li>'; + }; + + /** + * @param text + * @param [classes] + * @param [inline] + * @param [optgroup] + * @returns {string} + */ + var generateA = function (text, classes, inline, optgroup) { + var normText = normalizeToBase(htmlEscape(text)); + return '<a tabindex="0"' + + (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + + (typeof inline !== 'undefined' ? ' style="' + inline + '"' : '') + + (typeof optgroup !== 'undefined' ? 'data-optgroup="' + optgroup + '"' : '') + + ' data-normalized-text="' + normText + '"' + + '>' + text + + '<span class="' + that.options.iconBase + ' ' + that.options.tickIcon + ' check-mark"></span>' + + '</a>'; + }; + + this.$element.find('option').each(function () { + var $this = $(this); + + // Get the class and text for the option + var optionClass = $this.attr('class') || '', + inline = $this.attr('style'), + text = $this.data('content') ? $this.data('content') : $this.html(), + subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="muted text-muted">' + $this.data('subtext') + '</small>' : '', + icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '', + isDisabled = $this.is(':disabled') || $this.parent().is(':disabled'), + index = $this[0].index; + if (icon !== '' && isDisabled) { + icon = '<span>' + icon + '</span>'; + } + + if (!$this.data('content')) { + // Prepend any icon and append any subtext to the main text. + text = icon + '<span class="text">' + text + subtext + '</span>'; + } + + if (that.options.hideDisabled && isDisabled) { + return; + } + + if ($this.parent().is('optgroup') && $this.data('divider') !== true) { + if ($this.index() === 0) { // Is it the first option of the optgroup? + optID += 1; + + // Get the opt group label + var label = $this.parent().attr('label'); + var labelSubtext = typeof $this.parent().data('subtext') !== 'undefined' ? '<small class="muted text-muted">' + $this.parent().data('subtext') + '</small>' : ''; + var labelIcon = $this.parent().data('icon') ? '<span class="' + that.options.iconBase + ' ' + $this.parent().data('icon') + '"></span> ' : ''; + label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>'; + + if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown? + _li.push(generateLI('', null, 'divider')); + } + + _li.push(generateLI(label, null, 'dropdown-header')); + } + + _li.push(generateLI(generateA(text, 'opt ' + optionClass, inline, optID), index)); + } else if ($this.data('divider') === true) { + _li.push(generateLI('', index, 'divider')); + } else if ($this.data('hidden') === true) { + _li.push(generateLI(generateA(text, optionClass, inline), index, 'hide is-hidden')); + } else { + _li.push(generateLI(generateA(text, optionClass, inline), index)); + } + }); + + //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 + if (!this.multiple && this.$element.find('option:selected').length === 0 && !this.options.title) { + this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected'); + } + + return $(_li.join('')); + }, + + findLis: function () { + if (this.$lis == null) this.$lis = this.$menu.find('li'); + return this.$lis; + }, + + /** + * @param [updateLi] defaults to true + */ + render: function (updateLi) { + var that = this; + + //Update the LI to match the SELECT + if (updateLi !== false) { + this.$element.find('option').each(function (index) { + that.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled')); + that.setSelected(index, $(this).is(':selected')); + }); + } + + this.tabIndex(); + var notDisabled = this.options.hideDisabled ? ':not([disabled])' : ''; + var selectedItems = this.$element.find('option:selected' + notDisabled).map(function () { + var $this = $(this); + var icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : ''; + var subtext; + if (that.options.showSubtext && $this.attr('data-subtext') && !that.multiple) { + subtext = ' <small class="muted text-muted">' + $this.data('subtext') + '</small>'; + } else { + subtext = ''; + } + if ($this.data('content') && that.options.showContent) { + return $this.data('content'); + } else if (typeof $this.attr('title') !== 'undefined') { + return $this.attr('title'); + } else { + return icon + $this.html() + subtext; + } + }).toArray(); + + //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled + //Convert all the values into a comma delimited string + var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator); + + //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc.. + if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) { + var max = this.options.selectedTextFormat.split('>'); + if ((max.length > 1 && selectedItems.length > max[1]) || (max.length == 1 && selectedItems.length >= 2)) { + notDisabled = this.options.hideDisabled ? ', [disabled]' : ''; + var totalCount = this.$element.find('option').not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length, + tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText; + title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString()); + } + } + + this.options.title = this.$element.attr('title'); + + if (this.options.selectedTextFormat == 'static') { + title = this.options.title; + } + + //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text + if (!title) { + title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText; + } + + this.$button.attr('title', htmlEscape(title)); + this.$newElement.find('.filter-option').html(title); + }, + + /** + * @param [style] + * @param [status] + */ + setStyle: function (style, status) { + if (this.$element.attr('class')) { + this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|validate\[.*\]/gi, '')); + } + + var buttonClass = style ? style : this.options.style; + + if (status == 'add') { + this.$button.addClass(buttonClass); + } else if (status == 'remove') { + this.$button.removeClass(buttonClass); + } else { + this.$button.removeClass(this.options.style); + this.$button.addClass(buttonClass); + } + }, + + liHeight: function () { + if (this.options.size === false) return; + + var $selectClone = this.$menu.parent().clone().find('> .dropdown-toggle').prop('autofocus', false).end().appendTo('body'), + $menuClone = $selectClone.addClass('open').find('> .dropdown-menu'), + liHeight = $menuClone.find('li').not('.divider').not('.dropdown-header').filter(':visible').children('a').outerHeight(), + headerHeight = this.options.header ? $menuClone.find('.popover-title').outerHeight() : 0, + searchHeight = this.options.liveSearch ? $menuClone.find('.bs-searchbox').outerHeight() : 0, + actionsHeight = this.options.actionsBox ? $menuClone.find('.bs-actionsbox').outerHeight() : 0; + + $selectClone.remove(); + + this.$newElement + .data('liHeight', liHeight) + .data('headerHeight', headerHeight) + .data('searchHeight', searchHeight) + .data('actionsHeight', actionsHeight); + }, + + setSize: function () { + this.findLis(); + var that = this, + menu = this.$menu, + menuInner = menu.find('.inner'), + selectHeight = this.$newElement.outerHeight(), + liHeight = this.$newElement.data('liHeight'), + headerHeight = this.$newElement.data('headerHeight'), + searchHeight = this.$newElement.data('searchHeight'), + actionsHeight = this.$newElement.data('actionsHeight'), + divHeight = this.$lis.filter('.divider').outerHeight(true), + menuPadding = parseInt(menu.css('padding-top')) + + parseInt(menu.css('padding-bottom')) + + parseInt(menu.css('border-top-width')) + + parseInt(menu.css('border-bottom-width')), + notDisabled = this.options.hideDisabled ? ', .disabled' : '', + $window = $(window), + menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2, + menuHeight, + selectOffsetTop, + selectOffsetBot, + posVert = function () { + // JQuery defines a scrollTop function, but in pure JS it's a property + //noinspection JSValidateTypes + selectOffsetTop = that.$newElement.offset().top - $window.scrollTop(); + selectOffsetBot = $window.height() - selectOffsetTop - selectHeight; + }; + posVert(); + if (this.options.header) menu.css('padding-top', 0); + + if (this.options.size == 'auto') { + var getSize = function () { + var minHeight, + lisVis = that.$lis.not('.hide'); + + posVert(); + menuHeight = selectOffsetBot - menuExtras; + + if (that.options.dropupAuto) { + that.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && ((menuHeight - menuExtras) < menu.height())); + } + if (that.$newElement.hasClass('dropup')) { + menuHeight = selectOffsetTop - menuExtras; + } + + if ((lisVis.length + lisVis.filter('.dropdown-header').length) > 3) { + minHeight = liHeight * 3 + menuExtras - 2; + } else { + minHeight = 0; + } + + menu.css({ + 'max-height': menuHeight + 'px', + 'overflow': 'hidden', + 'min-height': minHeight + headerHeight + searchHeight + actionsHeight + 'px' + }); + menuInner.css({ + 'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - menuPadding + 'px', + 'overflow-y': 'auto', + 'min-height': Math.max(minHeight - menuPadding, 0) + 'px' + }); + }; + getSize(); + this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize); + $(window).off('resize.getSize').on('resize.getSize', getSize); + $(window).off('scroll.getSize').on('scroll.getSize', getSize); + } else if (this.options.size && this.options.size != 'auto' && menu.find('li' + notDisabled).length > this.options.size) { + var optIndex = this.$lis.not('.divider' + notDisabled).find(' > *').slice(0, this.options.size).last().parent().index(); + var divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length; + menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding; + if (that.options.dropupAuto) { + //noinspection JSUnusedAssignment + this.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && (menuHeight < menu.height())); + } + menu.css({'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + 'px', 'overflow': 'hidden'}); + menuInner.css({'max-height': menuHeight - menuPadding + 'px', 'overflow-y': 'auto'}); + } + }, + + setWidth: function () { + if (this.options.width == 'auto') { + this.$menu.css('min-width', '0'); + + // Get correct width if element hidden + var selectClone = this.$newElement.clone().appendTo('body'); + var ulWidth = selectClone.find('> .dropdown-menu').css('width'); + var btnWidth = selectClone.css('width', 'auto').find('> button').css('width'); + selectClone.remove(); + + // Set width to whatever's larger, button title or longest option + this.$newElement.css('width', Math.max(parseInt(ulWidth), parseInt(btnWidth)) + 'px'); + } else if (this.options.width == 'fit') { + // Remove inline min-width so width can be changed from 'auto' + this.$menu.css('min-width', ''); + this.$newElement.css('width', '').addClass('fit-width'); + } else if (this.options.width) { + // Remove inline min-width so width can be changed from 'auto' + this.$menu.css('min-width', ''); + this.$newElement.css('width', this.options.width); + } else { + // Remove inline min-width/width so width can be changed + this.$menu.css('min-width', ''); + this.$newElement.css('width', ''); + } + // Remove fit-width class if width is changed programmatically + if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { + this.$newElement.removeClass('fit-width'); + } + }, + + selectPosition: function () { + var that = this, + drop = '<div />', + $drop = $(drop), + pos, + actualHeight, + getPlacement = function ($element) { + $drop.addClass($element.attr('class').replace(/form-control/gi, '')).toggleClass('dropup', $element.hasClass('dropup')); + pos = $element.offset(); + actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight; + $drop.css({ + 'top': pos.top + actualHeight, + 'left': pos.left, + 'width': $element[0].offsetWidth, + 'position': 'absolute' + }); + }; + this.$newElement.on('click', function () { + if (that.isDisabled()) { + return; + } + getPlacement($(this)); + $drop.appendTo(that.options.container); + $drop.toggleClass('open', !$(this).hasClass('open')); + $drop.append(that.$menu); + }); + $(window).resize(function () { + getPlacement(that.$newElement); + }); + $(window).on('scroll', function () { + getPlacement(that.$newElement); + }); + $('html').on('click', function (e) { + if ($(e.target).closest(that.$newElement).length < 1) { + $drop.removeClass('open'); + } + }); + }, + + setSelected: function (index, selected) { + this.findLis(); + this.$lis.filter('[data-original-index="' + index + '"]').toggleClass('selected', selected); + }, + + setDisabled: function (index, disabled) { + this.findLis(); + if (disabled) { + this.$lis.filter('[data-original-index="' + index + '"]').addClass('disabled').find('a').attr('href', '#').attr('tabindex', -1); + } else { + this.$lis.filter('[data-original-index="' + index + '"]').removeClass('disabled').find('a').removeAttr('href').attr('tabindex', 0); + } + }, + + isDisabled: function () { + return this.$element.is(':disabled'); + }, + + checkDisabled: function () { + var that = this; + + if (this.isDisabled()) { + this.$button.addClass('disabled').attr('tabindex', -1); + } else { + if (this.$button.hasClass('disabled')) { + this.$button.removeClass('disabled'); + } + + if (this.$button.attr('tabindex') == -1) { + if (!this.$element.data('tabindex')) this.$button.removeAttr('tabindex'); + } + } + + this.$button.click(function () { + return !that.isDisabled(); + }); + }, + + tabIndex: function () { + if (this.$element.is('[tabindex]')) { + this.$element.data('tabindex', this.$element.attr('tabindex')); + this.$button.attr('tabindex', this.$element.data('tabindex')); + } + }, + + clickListener: function () { + var that = this; + + this.$newElement.on('touchstart.dropdown', '.dropdown-menu', function (e) { + e.stopPropagation(); + }); + + this.$newElement.on('click', function () { + that.setSize(); + if (!that.options.liveSearch && !that.multiple) { + setTimeout(function () { + that.$menu.find('.selected a').focus(); + }, 10); + } + }); + + this.$menu.on('click', 'li a', function (e) { + var $this = $(this), + clickedIndex = $this.parent().data('originalIndex'), + prevValue = that.$element.val(), + prevIndex = that.$element.prop('selectedIndex'); + + // Don't close on multi choice menu + if (that.multiple) { + e.stopPropagation(); + } + + e.preventDefault(); + + //Don't run if we have been disabled + if (!that.isDisabled() && !$this.parent().hasClass('disabled')) { + var $options = that.$element.find('option'), + $option = $options.eq(clickedIndex), + state = $option.prop('selected'), + $optgroup = $option.parent('optgroup'), + maxOptions = that.options.maxOptions, + maxOptionsGrp = $optgroup.data('maxOptions') || false; + + if (!that.multiple) { // Deselect all others if not multi select box + $options.prop('selected', false); + $option.prop('selected', true); + that.$menu.find('.selected').removeClass('selected'); + that.setSelected(clickedIndex, true); + } else { // Toggle the one we have chosen if we are multi select. + $option.prop('selected', !state); + that.setSelected(clickedIndex, !state); + $this.blur(); + + if ((maxOptions !== false) || (maxOptionsGrp !== false)) { + var maxReached = maxOptions < $options.filter(':selected').length, + maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; + + if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { + if (maxOptions && maxOptions == 1) { + $options.prop('selected', false); + $option.prop('selected', true); + that.$menu.find('.selected').removeClass('selected'); + that.setSelected(clickedIndex, true); + } else if (maxOptionsGrp && maxOptionsGrp == 1) { + $optgroup.find('option:selected').prop('selected', false); + $option.prop('selected', true); + var optgroupID = $this.data('optgroup'); + + that.$menu.find('.selected').has('a[data-optgroup="' + optgroupID + '"]').removeClass('selected'); + + that.setSelected(clickedIndex, true); + } else { + var maxOptionsArr = (typeof that.options.maxOptionsText === 'function') ? + that.options.maxOptionsText(maxOptions, maxOptionsGrp) : that.options.maxOptionsText, + maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), + maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), + $notify = $('<div class="notify"></div>'); + // If {var} is set in array, replace it + /** @deprecated */ + if (maxOptionsArr[2]) { + maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); + maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); + } + + $option.prop('selected', false); + + that.$menu.append($notify); + + if (maxOptions && maxReached) { + $notify.append($('<div>' + maxTxt + '</div>')); + that.$element.trigger('maxReached.bs.select'); + } + + if (maxOptionsGrp && maxReachedGrp) { + $notify.append($('<div>' + maxTxtGrp + '</div>')); + that.$element.trigger('maxReachedGrp.bs.select'); + } + + setTimeout(function () { + that.setSelected(clickedIndex, false); + }, 10); + + $notify.delay(750).fadeOut(300, function () { + $(this).remove(); + }); + } + } + } + } + + if (!that.multiple) { + that.$button.focus(); + } else if (that.options.liveSearch) { + that.$searchbox.focus(); + } + + // Trigger select 'change' + if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) { + that.$element.change(); + } + } + }); + + this.$menu.on('click', 'li.disabled a, .popover-title, .popover-title :not(.close)', function (e) { + if (e.target == this) { + e.preventDefault(); + e.stopPropagation(); + if (!that.options.liveSearch) { + that.$button.focus(); + } else { + that.$searchbox.focus(); + } + } + }); + + this.$menu.on('click', 'li.divider, li.dropdown-header', function (e) { + e.preventDefault(); + e.stopPropagation(); + if (!that.options.liveSearch) { + that.$button.focus(); + } else { + that.$searchbox.focus(); + } + }); + + this.$menu.on('click', '.popover-title .close', function () { + that.$button.focus(); + }); + + this.$searchbox.on('click', function (e) { + e.stopPropagation(); + }); + + + this.$menu.on('click', '.actions-btn', function (e) { + if (that.options.liveSearch) { + that.$searchbox.focus(); + } else { + that.$button.focus(); + } + + e.preventDefault(); + e.stopPropagation(); + + if ($(this).is('.bs-select-all')) { + that.selectAll(); + } else { + that.deselectAll(); + } + that.$element.change(); + }); + + this.$element.change(function () { + that.render(false); + }); + }, + + liveSearchListener: function () { + var that = this, + no_results = $('<li class="no-results"></li>'); + + this.$newElement.on('click.dropdown.data-api touchstart.dropdown.data-api', function () { + that.$menu.find('.active').removeClass('active'); + if (!!that.$searchbox.val()) { + that.$searchbox.val(''); + that.$lis.not('.is-hidden').removeClass('hide'); + if (!!no_results.parent().length) no_results.remove(); + } + if (!that.multiple) that.$menu.find('.selected').addClass('active'); + setTimeout(function () { + that.$searchbox.focus(); + }, 10); + }); + + this.$searchbox.on('click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api', function (e) { + e.stopPropagation(); + }); + + this.$searchbox.on('input propertychange', function () { + if (that.$searchbox.val()) { + + if (that.options.searchAccentInsensitive) { + that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':aicontains(' + normalizeToBase(that.$searchbox.val()) + ')').parent().addClass('hide'); + } else { + that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':icontains(' + that.$searchbox.val() + ')').parent().addClass('hide'); + } + + if (!that.$menu.find('li').filter(':visible:not(.no-results)').length) { + if (!!no_results.parent().length) no_results.remove(); + no_results.html(that.options.noneResultsText + ' "' + htmlEscape(that.$searchbox.val()) + '"').show(); + that.$menu.find('li').last().after(no_results); + } else if (!!no_results.parent().length) { + no_results.remove(); + } + + } else { + that.$lis.not('.is-hidden').removeClass('hide'); + if (!!no_results.parent().length) no_results.remove(); + } + + that.$menu.find('li.active').removeClass('active'); + that.$menu.find('li').filter(':visible:not(.divider)').eq(0).addClass('active').find('a').focus(); + $(this).focus(); + }); + }, + + val: function (value) { + if (typeof value !== 'undefined') { + this.$element.val(value); + this.render(); + + return this.$element; + } else { + return this.$element.val(); + } + }, + + selectAll: function () { + this.findLis(); + this.$lis.not('.divider').not('.disabled').not('.selected').filter(':visible').find('a').click(); + }, + + deselectAll: function () { + this.findLis(); + this.$lis.not('.divider').not('.disabled').filter('.selected').filter(':visible').find('a').click(); + }, + + keydown: function (e) { + var $this = $(this), + $parent = ($this.is('input')) ? $this.parent().parent() : $this.parent(), + $items, + that = $parent.data('this'), + index, + next, + first, + last, + prev, + nextPrev, + prevIndex, + isActive, + keyCodeMap = { + 32: ' ', + 48: '0', + 49: '1', + 50: '2', + 51: '3', + 52: '4', + 53: '5', + 54: '6', + 55: '7', + 56: '8', + 57: '9', + 59: ';', + 65: 'a', + 66: 'b', + 67: 'c', + 68: 'd', + 69: 'e', + 70: 'f', + 71: 'g', + 72: 'h', + 73: 'i', + 74: 'j', + 75: 'k', + 76: 'l', + 77: 'm', + 78: 'n', + 79: 'o', + 80: 'p', + 81: 'q', + 82: 'r', + 83: 's', + 84: 't', + 85: 'u', + 86: 'v', + 87: 'w', + 88: 'x', + 89: 'y', + 90: 'z', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9' + }; + + if (that.options.liveSearch) $parent = $this.parent().parent(); + + if (that.options.container) $parent = that.$menu; + + $items = $('[role=menu] li a', $parent); + + isActive = that.$menu.parent().hasClass('open'); + + if (!isActive && /([0-9]|[A-z])/.test(String.fromCharCode(e.keyCode))) { + if (!that.options.container) { + that.setSize(); + that.$menu.parent().addClass('open'); + isActive = true; + } else { + that.$newElement.trigger('click'); + } + that.$searchbox.focus(); + } + + if (that.options.liveSearch) { + if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && that.$menu.find('.active').length === 0) { + e.preventDefault(); + that.$menu.parent().removeClass('open'); + that.$button.focus(); + } + $items = $('[role=menu] li:not(.divider):not(.dropdown-header):visible', $parent); + if (!$this.val() && !/(38|40)/.test(e.keyCode.toString(10))) { + if ($items.filter('.active').length === 0) { + if (that.options.searchAccentInsensitive) { + $items = that.$newElement.find('li').filter(':aicontains(' + normalizeToBase(keyCodeMap[e.keyCode]) + ')'); + } else { + $items = that.$newElement.find('li').filter(':icontains(' + keyCodeMap[e.keyCode] + ')'); + } + } + } + } + + if (!$items.length) return; + + if (/(38|40)/.test(e.keyCode.toString(10))) { + index = $items.index($items.filter(':focus')); + first = $items.parent(':not(.disabled):visible').first().index(); + last = $items.parent(':not(.disabled):visible').last().index(); + next = $items.eq(index).parent().nextAll(':not(.disabled):visible').eq(0).index(); + prev = $items.eq(index).parent().prevAll(':not(.disabled):visible').eq(0).index(); + nextPrev = $items.eq(next).parent().prevAll(':not(.disabled):visible').eq(0).index(); + + if (that.options.liveSearch) { + $items.each(function (i) { + if ($(this).is(':not(.disabled)')) { + $(this).data('index', i); + } + }); + index = $items.index($items.filter('.active')); + first = $items.filter(':not(.disabled):visible').first().data('index'); + last = $items.filter(':not(.disabled):visible').last().data('index'); + next = $items.eq(index).nextAll(':not(.disabled):visible').eq(0).data('index'); + prev = $items.eq(index).prevAll(':not(.disabled):visible').eq(0).data('index'); + nextPrev = $items.eq(next).prevAll(':not(.disabled):visible').eq(0).data('index'); + } + + prevIndex = $this.data('prevIndex'); + + if (e.keyCode == 38) { + if (that.options.liveSearch) index -= 1; + if (index != nextPrev && index > prev) index = prev; + if (index < first) index = first; + if (index == prevIndex) index = last; + } + + if (e.keyCode == 40) { + if (that.options.liveSearch) index += 1; + if (index == -1) index = 0; + if (index != nextPrev && index < next) index = next; + if (index > last) index = last; + if (index == prevIndex) index = first; + } + + $this.data('prevIndex', index); + + if (!that.options.liveSearch) { + $items.eq(index).focus(); + } else { + e.preventDefault(); + if (!$this.is('.dropdown-toggle')) { + $items.removeClass('active'); + $items.eq(index).addClass('active').find('a').focus(); + $this.focus(); + } + } + + } else if (!$this.is('input')) { + var keyIndex = [], + count, + prevKey; + + $items.each(function () { + if ($(this).parent().is(':not(.disabled)')) { + if ($.trim($(this).text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) { + keyIndex.push($(this).parent().index()); + } + } + }); + + count = $(document).data('keycount'); + count++; + $(document).data('keycount', count); + + prevKey = $.trim($(':focus').text().toLowerCase()).substring(0, 1); + + if (prevKey != keyCodeMap[e.keyCode]) { + count = 1; + $(document).data('keycount', count); + } else if (count >= keyIndex.length) { + $(document).data('keycount', 0); + if (count > keyIndex.length) count = 1; + } + + $items.eq(keyIndex[count - 1]).focus(); + } + + // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. + if ((/(13|32)/.test(e.keyCode.toString(10)) || (/(^9$)/.test(e.keyCode.toString(10)) && that.options.selectOnTab)) && isActive) { + if (!/(32)/.test(e.keyCode.toString(10))) e.preventDefault(); + if (!that.options.liveSearch) { + $(':focus').click(); + } else if (!/(32)/.test(e.keyCode.toString(10))) { + that.$menu.find('.active a').click(); + $this.focus(); + } + $(document).data('keycount', 0); + } + + if ((/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode.toString(10)) && !isActive)) { + that.$menu.parent().removeClass('open'); + that.$button.focus(); + } + }, + + mobile: function () { + this.$element.addClass('mobile-device').appendTo(this.$newElement); + if (this.options.container) this.$menu.hide(); + }, + + refresh: function () { + this.$lis = null; + this.reloadLi(); + this.render(); + this.setWidth(); + this.setStyle(); + this.checkDisabled(); + this.liHeight(); + }, + + update: function () { + this.reloadLi(); + this.setWidth(); + this.setStyle(); + this.checkDisabled(); + this.liHeight(); + }, + + hide: function () { + this.$newElement.hide(); + }, + + show: function () { + this.$newElement.show(); + }, + + remove: function () { + this.$newElement.remove(); + this.$element.remove(); + } + }; + + // SELECTPICKER PLUGIN DEFINITION + // ============================== + function Plugin(option, event) { + // get the args of the outer function.. + var args = arguments; + // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them + // to get lost + //noinspection JSDuplicatedDeclaration + var _option = option, + option = args[0], + event = args[1]; + [].shift.apply(args); + + // This fixes a bug in the js implementation on android 2.3 #715 + if (typeof option == 'undefined') { + option = _option; + } + + var value; + var chain = this.each(function () { + var $this = $(this); + if ($this.is('select')) { + var data = $this.data('selectpicker'), + options = typeof option == 'object' && option; + + if (!data) { + var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, $this.data(), options); + $this.data('selectpicker', (data = new Selectpicker(this, config, event))); + } else if (options) { + for (var i in options) { + if (options.hasOwnProperty(i)) { + data.options[i] = options[i]; + } + } + } + + if (typeof option == 'string') { + if (data[option] instanceof Function) { + value = data[option].apply(data, args); + } else { + value = data.options[option]; + } + } + } + }); + + if (typeof value !== 'undefined') { + //noinspection JSUnusedAssignment + return value; + } else { + return chain; + } + } + + var old = $.fn.selectpicker; + $.fn.selectpicker = Plugin; + $.fn.selectpicker.Constructor = Selectpicker; + + // SELECTPICKER NO CONFLICT + // ======================== + $.fn.selectpicker.noConflict = function () { + $.fn.selectpicker = old; + return this; + }; + + $(document) + .data('keycount', 0) + .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', Selectpicker.prototype.keydown) + .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', function (e) { + e.stopPropagation(); + }); + + // SELECTPICKER DATA-API + // ===================== + $(window).on('load.bs.select.data-api', function () { + $('.selectpicker').each(function () { + var $selectpicker = $(this); + Plugin.call($selectpicker, $selectpicker.data()); + }) + }); +})(jQuery);