UNPKG

tropical-components

Version:
1,077 lines (951 loc) 89.8 kB
/*! * Bootstrap-select v1.12.2 (http://silviomoreto.github.io/bootstrap-select) * * Copyright 2013-2017 bootstrap-select * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) */ /* Creative Tim Modifications Line: 379-380 - we changed glyphicons with material-icons. Line: 593 - we put tickIcon inside of span tag. */ (function(root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module unless amdModuleId is set define(["jquery"], function(a0) { return (factory(a0)); }); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(require("jquery")); } else { factory(root["jQuery"]); } }(this, function(jQuery) { (function($) { 'use strict'; //<editor-fold desc="Shims"> if (!String.prototype.includes) { (function() { 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` var toString = {}.toString; var defineProperty = (function() { // IE 8 only supports `Object.defineProperty` on DOM elements try { var object = {}; var $defineProperty = Object.defineProperty; var result = $defineProperty(object, object, object) && $defineProperty; } catch (error) {} return result; }()); var indexOf = ''.indexOf; var includes = function(search) { if (this == null) { throw new TypeError(); } var string = String(this); if (search && toString.call(search) == '[object RegExp]') { throw new TypeError(); } var stringLength = string.length; var searchString = String(search); var searchLength = searchString.length; var position = arguments.length > 1 ? arguments[1] : undefined; // `ToInteger` var pos = position ? Number(position) : 0; if (pos != pos) { // better `isNaN` pos = 0; } var start = Math.min(Math.max(pos, 0), stringLength); // Avoid the `indexOf` call if no match is possible if (searchLength + start > stringLength) { return false; } return indexOf.call(string, searchString, pos) != -1; }; if (defineProperty) { defineProperty(String.prototype, 'includes', { 'value': includes, 'configurable': true, 'writable': true }); } else { String.prototype.includes = includes; } }()); } if (!String.prototype.startsWith) { (function() { 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` var defineProperty = (function() { // IE 8 only supports `Object.defineProperty` on DOM elements try { var object = {}; var $defineProperty = Object.defineProperty; var result = $defineProperty(object, object, object) && $defineProperty; } catch (error) {} return result; }()); var toString = {}.toString; var startsWith = function(search) { if (this == null) { throw new TypeError(); } var string = String(this); if (search && toString.call(search) == '[object RegExp]') { throw new TypeError(); } var stringLength = string.length; var searchString = String(search); var searchLength = searchString.length; var position = arguments.length > 1 ? arguments[1] : undefined; // `ToInteger` var pos = position ? Number(position) : 0; if (pos != pos) { // better `isNaN` pos = 0; } var start = Math.min(Math.max(pos, 0), stringLength); // Avoid the `indexOf` call if no match is possible if (searchLength + start > stringLength) { return false; } var index = -1; while (++index < searchLength) { if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { return false; } } return true; }; if (defineProperty) { defineProperty(String.prototype, 'startsWith', { 'value': startsWith, 'configurable': true, 'writable': true }); } else { String.prototype.startsWith = startsWith; } }()); } if (!Object.keys) { Object.keys = function( o, // object k, // key r // result array ) { // initialize object and result r = []; // iterate over object keys for (k in o) // fill result array with non-prototypical keys r.hasOwnProperty.call(o, k) && r.push(k); // return result return r; }; } // set data-selected on select element if the value has been programmatically selected // prior to initialization of bootstrap-select // * consider removing or replacing an alternative method * var valHooks = { useDefault: false, _set: $.valHooks.select.set }; $.valHooks.select.set = function(elem, value) { if (value && !valHooks.useDefault) $(elem).data('selected', true); return valHooks._set.apply(this, arguments); }; var changed_arguments = null; $.fn.triggerNative = function(eventName) { var el = this[0], event; if (el.dispatchEvent) { // for modern browsers & IE9+ if (typeof Event === 'function') { // For modern browsers event = new Event(eventName, { bubbles: true }); } else { // For IE since it doesn't support Event constructor event = document.createEvent('Event'); event.initEvent(eventName, true, false); } el.dispatchEvent(event); } else if (el.fireEvent) { // for IE8 event = document.createEventObject(); event.eventType = eventName; el.fireEvent('on' + eventName, event); } else { // fall back to jQuery.trigger this.trigger(eventName); } }; //</editor-fold> // Case insensitive contains search $.expr.pseudos.icontains = function(obj, index, meta) { var $obj = $(obj); var haystack = ($obj.data('tokens') || $obj.text()).toString().toUpperCase(); return haystack.includes(meta[3].toUpperCase()); }; // Case insensitive begins search $.expr.pseudos.ibegins = function(obj, index, meta) { var $obj = $(obj); var haystack = ($obj.data('tokens') || $obj.text()).toString().toUpperCase(); return haystack.startsWith(meta[3].toUpperCase()); }; // Case and accent insensitive contains search $.expr.pseudos.aicontains = function(obj, index, meta) { var $obj = $(obj); var haystack = ($obj.data('tokens') || $obj.data('normalizedText') || $obj.text()).toString().toUpperCase(); return haystack.includes(meta[3].toUpperCase()); }; // Case and accent insensitive begins search $.expr.pseudos.aibegins = function(obj, index, meta) { var $obj = $(obj); var haystack = ($obj.data('tokens') || $obj.data('normalizedText') || $obj.text()).toString().toUpperCase(); return haystack.startsWith(meta[3].toUpperCase()); }; /** * 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 ? text.replace(this.re, this.ch) : ''; }); return text; } // List of HTML entities for escaping. var escapeMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#x27;', '`': '&#x60;' }; var unescapeMap = { '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&#x27;': "'", '&#x60;': '`' }; // Functions for escaping and unescaping strings to/from HTML interpolation. var createEscaper = function(map) { var escaper = function(match) { return map[match]; }; // Regexes for identifying a key that needs to be escaped. var source = '(?:' + Object.keys(map).join('|') + ')'; var testRegexp = RegExp(source); var replaceRegexp = RegExp(source, 'g'); return function(string) { string = string == null ? '' : '' + string; return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; }; }; var htmlEscape = createEscaper(escapeMap); var htmlUnescape = createEscaper(unescapeMap); var Selectpicker = function(element, options) { // bootstrap-select has been initialized - revert valHooks.select.set back to its original function if (!valHooks.useDefault) { $.valHooks.select.set = valHooks._set; valHooks.useDefault = true; } 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'); } // Format window padding var winPad = this.options.windowPadding; if (typeof winPad === 'number') { this.options.windowPadding = [winPad, winPad, winPad, winPad]; } //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.destroy; this.remove = Selectpicker.prototype.remove; this.show = Selectpicker.prototype.show; this.hide = Selectpicker.prototype.hide; this.init(); }; Selectpicker.VERSION = '1.12.2'; // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. Selectpicker.DEFAULTS = { noneSelectedText: 'Nothing selected', noneResultsText: 'No results matched {0}', countSelectedText: function(numSelected, numTotal) { return (numSelected == 1) ? "{0} item selected" : "{0} items selected"; }, maxOptionsText: function(numAll, numGroup) { return [ (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)', (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)' ]; }, selectAllText: 'Select All', deselectAllText: 'Deselect All', doneButton: false, doneButtonText: 'Close', multipleSeparator: ', ', styleBase: 'btn', 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, liveSearchPlaceholder: null, liveSearchNormalize: false, liveSearchStyle: 'contains', actionsBox: false, iconBase: 'material-icons', tickIcon: 'done', showTick: false, template: { caret: '<span class="caret"></span>' }, maxOptions: false, mobile: false, selectOnTab: false, dropdownAlignRight: false, windowPadding: 0 }; Selectpicker.prototype = { constructor: Selectpicker, init: function() { var that = this, id = this.$element.attr('id'); this.$element.addClass('bs-select-hidden'); // store originalIndex (key) and newIndex (value) in this.liObj for fast accessibility // allows us to do this.$lis.eq(that.liObj[index]) instead of this.$lis.filter('[data-original-index="' + index + '"]') this.liObj = {}; this.multiple = this.$element.prop('multiple'); this.autofocus = this.$element.prop('autofocus'); this.$newElement = this.createView(); this.$element .after(this.$newElement) .appendTo(this.$newElement); this.$button = this.$newElement.children('button'); this.$menu = this.$newElement.children('.dropdown-menu'); this.$menuInner = this.$menu.children('.inner'); this.$searchbox = this.$menu.find('input'); this.$element.removeClass('bs-select-hidden'); if (this.options.dropdownAlignRight === true) 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.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(); this.$newElement.on({ 'hide.bs.dropdown': function(e) { that.$menuInner.attr('aria-expanded', false); that.$element.trigger('hide.bs.select', e); }, 'hidden.bs.dropdown': function(e) { that.$element.trigger('hidden.bs.select', e); }, 'show.bs.dropdown': function(e) { that.$menuInner.attr('aria-expanded', true); that.$element.trigger('show.bs.select', e); }, 'shown.bs.dropdown': function(e) { that.$element.trigger('shown.bs.select', e); } }); if (that.$element[0].hasAttribute('required')) { this.$element.on('invalid', function() { that.$button .addClass('bs-invalid') .focus(); that.$element.on({ 'focus.bs.select': function() { that.$button.focus(); that.$element.off('focus.bs.select'); }, 'shown.bs.select': function() { that.$element .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened .off('shown.bs.select'); }, 'rendered.bs.select': function() { // if select is no longer invalid, remove the bs-invalid class if (this.validity.valid) that.$button.removeClass('bs-invalid'); that.$element.off('rendered.bs.select'); } }); }); } setTimeout(function() { that.$element.trigger('loaded.bs.select'); }); }, createDropdown: function() { // Options // If we are multiple or showTick option is set, then add the show-tick class var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '', inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '', autofocus = this.autofocus ? ' autofocus' : ''; // 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="form-control" autocomplete="off"' + (null === this.options.liveSearchPlaceholder ? '' : ' placeholder="' + htmlEscape(this.options.liveSearchPlaceholder) + '"') + ' role="textbox" aria-label="Search">' + '</div>' : ''; var actionsbox = this.multiple && this.options.actionsBox ? '<div class="bs-actionsbox">' + '<div class="btn-group btn-group-sm btn-block">' + '<button type="button" class="actions-btn bs-select-all btn btn-default">' + this.options.selectAllText + '</button>' + '<button type="button" class="actions-btn bs-deselect-all btn btn-default">' + this.options.deselectAllText + '</button>' + '</div>' + '</div>' : ''; var donebutton = this.multiple && this.options.doneButton ? '<div class="bs-donebutton">' + '<div class="btn-group btn-block">' + '<button type="button" class="btn btn-sm btn-default">' + this.options.doneButtonText + '</button>' + '</div>' + '</div>' : ''; var drop = '<div class="btn-group bootstrap-select' + showTick + inputGroup + '">' + '<button type="button" class="' + this.options.styleBase + ' dropdown-toggle" data-toggle="dropdown"' + autofocus + ' role="button">' + '<span class="filter-option pull-left"></span>&nbsp;' + '<span class="bs-caret">' + this.options.template.caret + '</span>' + '</button>' + '<div class="dropdown-menu open" role="combobox">' + header + searchbox + actionsbox + '<ul class="dropdown-menu inner" role="listbox" aria-expanded="false">' + '</ul>' + donebutton + '</div>' + '</div>'; return $(drop); }, createView: function() { var $drop = this.createDropdown(), li = this.createLi(); $drop.find('ul')[0].innerHTML = li; return $drop; }, reloadLi: function() { // rebuild var li = this.createLi(); this.$menuInner[0].innerHTML = li; }, createLi: function() { var that = this, _li = [], optID = 0, titleOption = document.createElement('option'), liIndex = -1; // increment liIndex whenever a new <li> element is created to ensure liObj is correct // Helper functions /** * @param content * @param [index] * @param [classes] * @param [optgroup] * @returns {string} */ var generateLI = function(content, index, classes, optgroup) { return '<li' + ((typeof classes !== 'undefined' & '' !== classes) ? ' class="' + classes + '"' : '') + ((typeof index !== 'undefined' & null !== index) ? ' data-original-index="' + index + '"' : '') + ((typeof optgroup !== 'undefined' & null !== optgroup) ? 'data-optgroup="' + optgroup + '"' : '') + '>' + content + '</li>'; }; /** * @param text * @param [classes] * @param [inline] * @param [tokens] * @returns {string} */ var generateA = function(text, classes, inline, tokens) { return '<a tabindex="0"' + (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') + (inline ? ' style="' + inline + '"' : '') + (that.options.liveSearchNormalize ? ' data-normalized-text="' + normalizeToBase(htmlEscape($(text).html())) + '"' : '') + (typeof tokens !== 'undefined' || tokens !== null ? ' data-tokens="' + tokens + '"' : '') + ' role="option">' + text + '<span class="' + that.options.iconBase + ' check-mark"> ' + that.options.tickIcon + ' </span>' + '</a>'; }; if (this.options.title && !this.multiple) { // this option doesn't create a new <li> element, but does add a new option, so liIndex is decreased // since liObj is recalculated on every refresh, liIndex needs to be decreased even if the titleOption is already appended liIndex--; if (!this.$element.find('.bs-title-option').length) { // Use native JS to prepend option (faster) var element = this.$element[0]; titleOption.className = 'bs-title-option'; titleOption.innerHTML = this.options.title; titleOption.value = ''; element.insertBefore(titleOption, element.firstChild); // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option. // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs, // if so, the select will have the data-selected attribute var $opt = $(element.options[element.selectedIndex]); if ($opt.attr('selected') === undefined && this.$element.data('selected') === undefined) { titleOption.selected = true; } } } this.$element.find('option').each(function(index) { var $this = $(this); liIndex++; if ($this.hasClass('bs-title-option')) return; // Get the class and text for the option var optionClass = this.className || '', inline = this.style.cssText, text = $this.data('content') ? $this.data('content') : $this.html(), tokens = $this.data('tokens') ? $this.data('tokens') : null, subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="text-muted">' + $this.data('subtext') + '</small>' : '', icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '', $parent = $this.parent(), isOptgroup = $parent[0].tagName === 'OPTGROUP', isOptgroupDisabled = isOptgroup && $parent[0].disabled, isDisabled = this.disabled || isOptgroupDisabled; if (icon !== '' && isDisabled) { icon = '<span>' + icon + '</span>'; } if (that.options.hideDisabled && (isDisabled && !isOptgroup || isOptgroupDisabled)) { liIndex--; return; } if (!$this.data('content')) { // Prepend any icon and append any subtext to the main text. text = icon + '<span class="text">' + text + subtext + '</span>'; } if (isOptgroup && $this.data('divider') !== true) { if (that.options.hideDisabled && isDisabled) { if ($parent.data('allOptionsDisabled') === undefined) { var $options = $parent.children(); $parent.data('allOptionsDisabled', $options.filter(':disabled').length === $options.length); } if ($parent.data('allOptionsDisabled')) { liIndex--; return; } } var optGroupClass = ' ' + $parent[0].className || ''; if ($this.index() === 0) { // Is it the first option of the optgroup? optID += 1; // Get the opt group label var label = $parent[0].label, labelSubtext = typeof $parent.data('subtext') !== 'undefined' ? '<small class="text-muted">' + $parent.data('subtext') + '</small>' : '', labelIcon = $parent.data('icon') ? '<span class="' + that.options.iconBase + ' ' + $parent.data('icon') + '"></span> ' : ''; label = labelIcon + '<span class="text">' + htmlEscape(label) + labelSubtext + '</span>'; if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown? liIndex++; _li.push(generateLI('', null, 'divider', optID + 'div')); } liIndex++; _li.push(generateLI(label, null, 'dropdown-header' + optGroupClass, optID)); } if (that.options.hideDisabled && isDisabled) { liIndex--; return; } _li.push(generateLI(generateA(text, 'opt ' + optionClass + optGroupClass, inline, tokens), index, '', optID)); } else if ($this.data('divider') === true) { _li.push(generateLI('', index, 'divider')); } else if ($this.data('hidden') === true) { _li.push(generateLI(generateA(text, optionClass, inline, tokens), index, 'hidden is-hidden')); } else { var showDivider = this.previousElementSibling && this.previousElementSibling.tagName === 'OPTGROUP'; // if previous element is not an optgroup and hideDisabled is true if (!showDivider && that.options.hideDisabled) { // get previous elements var $prev = $(this).prevAll(); for (var i = 0; i < $prev.length; i++) { // find the first element in the previous elements that is an optgroup if ($prev[i].tagName === 'OPTGROUP') { var optGroupDistance = 0; // loop through the options in between the current option and the optgroup // and check if they are hidden or disabled for (var d = 0; d < i; d++) { var prevOption = $prev[d]; if (prevOption.disabled || $(prevOption).data('hidden') === true) optGroupDistance++; } // if all of the options between the current option and the optgroup are hidden or disabled, show the divider if (optGroupDistance === i) showDivider = true; break; } } } if (showDivider) { liIndex++; _li.push(generateLI('', null, 'divider', optID + 'div')); } _li.push(generateLI(generateA(text, optionClass, inline, tokens), index)); } that.liObj[index] = liIndex; }); //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, notDisabled; //Update the LI to match the SELECT if (updateLi !== false) { this.$element.find('option').each(function(index) { var $lis = that.findLis().eq(that.liObj[index]); that.setDisabled(index, this.disabled || this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled, $lis); that.setSelected(index, this.selected, $lis); }); } this.togglePlaceholder(); this.tabIndex(); var selectedItems = this.$element.find('option').map(function() { if (this.selected) { if (that.options.hideDisabled && (this.disabled || this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled)) return; var $this = $(this), icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : '', subtext; if (that.options.showSubtext && $this.data('subtext') && !that.multiple) { subtext = ' <small class="text-muted">' + $this.data('subtext') + '</small>'; } else { subtext = ''; } if (typeof $this.attr('title') !== 'undefined') { return $this.attr('title'); } else if ($this.data('content') && that.options.showContent) { return $this.data('content').toString(); } 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()); } } if (this.options.title == undefined) { 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; } //strip all HTML tags and trim the result, then unescape any escaped tags this.$button.attr('title', htmlUnescape($.trim(title.replace(/<[^>]*>?/g, '')))); this.$button.children('.filter-option').html(title); this.$element.trigger('rendered.bs.select'); }, /** * @param [style] * @param [status] */ setStyle: function(style, status) { if (this.$element.attr('class')) { this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|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(refresh) { if (!refresh && (this.options.size === false || this.sizeInfo)) return; var newElement = document.createElement('div'), menu = document.createElement('div'), menuInner = document.createElement('ul'), divider = document.createElement('li'), li = document.createElement('li'), a = document.createElement('a'), text = document.createElement('span'), header = this.options.header && this.$menu.find('.popover-title').length > 0 ? this.$menu.find('.popover-title')[0].cloneNode(true) : null, search = this.options.liveSearch ? document.createElement('div') : null, actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null, doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null; text.className = 'text'; newElement.className = this.$menu[0].parentNode.className + ' open'; menu.className = 'dropdown-menu open'; menuInner.className = 'dropdown-menu inner'; divider.className = 'divider'; text.appendChild(document.createTextNode('Inner text')); a.appendChild(text); li.appendChild(a); menuInner.appendChild(li); menuInner.appendChild(divider); if (header) menu.appendChild(header); if (search) { var input = document.createElement('input'); search.className = 'bs-searchbox'; input.className = 'form-control'; search.appendChild(input); menu.appendChild(search); } if (actions) menu.appendChild(actions); menu.appendChild(menuInner); if (doneButton) menu.appendChild(doneButton); newElement.appendChild(menu); document.body.appendChild(newElement); var liHeight = a.offsetHeight, headerHeight = header ? header.offsetHeight : 0, searchHeight = search ? search.offsetHeight : 0, actionsHeight = actions ? actions.offsetHeight : 0, doneButtonHeight = doneButton ? doneButton.offsetHeight : 0, dividerHeight = $(divider).outerHeight(true), // fall back to jQuery if getComputedStyle is not supported menuStyle = typeof getComputedStyle === 'function' ? getComputedStyle(menu) : false, $menu = menuStyle ? null : $(menu), menuPadding = { vert: parseInt(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) + parseInt(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) + parseInt(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) + parseInt(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')), horiz: parseInt(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) + parseInt(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) + parseInt(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) + parseInt(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth')) }, menuExtras = { vert: menuPadding.vert + parseInt(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) + parseInt(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2, horiz: menuPadding.horiz + parseInt(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) + parseInt(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2 } document.body.removeChild(newElement); this.sizeInfo = { liHeight: liHeight, headerHeight: headerHeight, searchHeight: searchHeight, actionsHeight: actionsHeight, doneButtonHeight: doneButtonHeight, dividerHeight: dividerHeight, menuPadding: menuPadding, menuExtras: menuExtras }; }, setSize: function() { this.findLis(); this.liHeight(); if (this.options.header) this.$menu.css('padding-top', 0); if (this.options.size === false) return; var that = this, $menu = this.$menu, $menuInner = this.$menuInner, $window = $(window), selectHeight = this.$newElement[0].offsetHeight, selectWidth = this.$newElement[0].offsetWidth, liHeight = this.sizeInfo['liHeight'], headerHeight = this.sizeInfo['headerHeight'], searchHeight = this.sizeInfo['searchHeight'], actionsHeight = this.sizeInfo['actionsHeight'], doneButtonHeight = this.sizeInfo['doneButtonHeight'], divHeight = this.sizeInfo['dividerHeight'], menuPadding = this.sizeInfo['menuPadding'], menuExtras = this.sizeInfo['menuExtras'], notDisabled = this.options.hideDisabled ? '.disabled' : '', menuHeight, menuWidth, getHeight, getWidth, selectOffsetTop, selectOffsetBot, selectOffsetLeft, selectOffsetRight, getPos = function() { var pos = that.$newElement.offset(), $container = $(that.options.container), containerPos; if (that.options.container && !$container.is('body')) { containerPos = $container.offset(); containerPos.top += parseInt($container.css('borderTopWidth')); containerPos.left += parseInt($container.css('borderLeftWidth')); } else { containerPos = { top: 0, left: 0 }; } var winPad = that.options.windowPadding; selectOffsetTop = pos.top - containerPos.top - $window.scrollTop(); selectOffsetBot = $window.height() - selectOffsetTop - selectHeight - containerPos.top - winPad[2]; selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft(); selectOffsetRight = $window.width() - selectOffsetLeft - selectWidth - containerPos.left - winPad[1]; selectOffsetTop -= winPad[0]; selectOffsetLeft -= winPad[3]; }; getPos(); if (this.options.size === 'auto') { var getSize = function() { var minHeight, hasClass = function(className, include) { return function(element) { if (include) { return (element.classList ? element.classList.contains(className) : $(element).hasClass(className)); } else { return !(element.classList ? element.classList.contains(className) : $(element).hasClass(className)); } }; }, lis = that.$menuInner[0].getElementsByTagName('li'), lisVisible = Array.prototype.filter ? Array.prototype.filter.call(lis, hasClass('hidden', false)) : that.$lis.not('.hidden'), optGroup = Array.prototype.filter ? Array.prototype.filter.call(lisVisible, hasClass('dropdown-header', true)) : lisVisible.filter('.dropdown-header'); getPos(); menuHeight = selectOffsetBot - menuExtras.vert; menuWidth = selectOffsetRight - menuExtras.horiz; if (that.options.container) { if (!$menu.data('height')) $menu.data('height', $menu.height()); getHeight = $menu.data('height'); if (!$menu.data('width')) $menu.data('width', $menu.width()); getWidth = $menu.data('width'); } else { getHeight = $menu.height(); getWidth = $menu.width(); } if (that.options.dropupAuto) { that.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras.vert) < getHeight); } if (that.$newElement.hasClass('dropup')) { menuHeight = selectOffsetTop - menuExtras.vert; } if (that.options.dropdownAlignRight === 'auto') { $menu.toggleClass('dropdown-menu-right', selectOffsetLeft > selectOffsetRight && (menuWidth - menuExtras.horiz) < (getWidth - selectWidth)); } if ((lisVisible.length + optGroup.length) > 3) { minHeight = liHeight * 3 + menuExtras.vert - 2; } else { minHeight = 0; } $menu.css({ 'max-height': menuHeight + 'px', 'overflow': 'hidden', 'min-height': minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px' }); $menuInner.