PageSourceSearch

https://sjcmaui.org/Site/javascript/plugins/bootstrap-selectpicker.js

js sjcmaui.org collected 2026-10-02 13:45:46 UTC 74,539 bytes, 1,961 lines download raw bytes

vendor: 4,233 bytes, lines 1-116
1/*!
2 * Bootstrap-select v1.12.4 (https://silviomoreto.github.io/bootstrap-select)
3 *
4 * Copyright 2013-2018 bootstrap-select
5 * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
6 */
7
8(function(root, factory) {
9  if (typeof define === 'function' && define.amd) {
10    // AMD. Register as an anonymous module unless amdModuleId is set
11    define(["jquery"], function(a0) {
12      return (factory(a0));
13    });
14  } else if (typeof module === 'object' && module.exports) {
15    // Node. Does not work with strict CommonJS, but
16    // only CommonJS-like environments that support module.exports,
17    // like Node.
18    module.exports = factory(require("jquery"));
19  } else {
20    factory(root["jQuery"]);
21  }
22}(this, function(jQuery) {
23
24  (function($) {
25    'use strict';
26
27    //<editor-fold desc="Shims">
28    if (!String.prototype.includes) {
29      (function() {
30        'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
31        var toString = {}.toString;
32        var defineProperty = (function() {
33          // IE 8 only supports `Object.defineProperty` on DOM elements
34          try {
35            var object = {};
36            var $defineProperty = Object.defineProperty;
37            var result = $defineProperty(object, object, object) && $defineProperty;
38          } catch (error) {}
39          return result;
40        }());
41        var indexOf = ''.indexOf;
42        var includes = function(search) {
43          if (this == null) {
44            throw new TypeError();
45          }
46          var string = String(this);
47          if (search && toString.call(search) == '[object RegExp]') {
48            throw new TypeError();
49          }
50          var stringLength = string.length;
51          var searchString = String(search);
52          var searchLength = searchString.length;
53          var position = arguments.length > 1 ? arguments[1] : undefined;
54          // `ToInteger`
55          var pos = position ? Number(position) : 0;
56          if (pos != pos) { // better `isNaN`
57            pos = 0;
58          }
59          var start = Math.min(Math.max(pos, 0), stringLength);
60          // Avoid the `indexOf` call if no match is possible
61          if (searchLength + start > stringLength) {
62            return false;
63          }
64          return indexOf.call(string, searchString, pos) != -1;
65        };
66        if (defineProperty) {
67          defineProperty(String.prototype, 'includes', {
68            'value': includes,
69            'configurable': true,
70            'writable': true
71          });
72        } else {
73          String.prototype.includes = includes;
74        }
75      }());
76    }
77
78    if (!String.prototype.startsWith) {
79      (function() {
80        'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
81        var defineProperty = (function() {
82          // IE 8 only supports `Object.defineProperty` on DOM elements
83          try {
84            var object = {};
85            var $defineProperty = Object.defineProperty;
86            var result = $defineProperty(object, object, object) && $defineProperty;
87          } catch (error) {}
88          return result;
89        }());
90        var toString = {}.toString;
91        var startsWith = function(search) {
92          if (this == null) {
93            throw new TypeError();
94          }
95          var string = String(this);
96          if (search && toString.call(search) == '[object RegExp]') {
97            throw new TypeError();
98          }
99          var stringLength = string.length;
100          var searchString = String(search);
101          var searchLength = searchString.length;
102          var position = arguments.length > 1 ? arguments[1] : undefined;
103          // `ToInteger`
104          var pos = position ? Number(position) : 0;
105          if (pos != pos) { // better `isNaN`
106            pos = 0;
107          }
108          var start = Math.min(Math.max(pos, 0), stringLength);
109          // Avoid the `indexOf` call if no match is possible
110          if (searchLength + start > stringLength) {
111            return false;
112          }
113          var index = -1;
114          while (++index < searchLength) {
115            if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) {
116              return false;
117            }
118          }
119          return true;
120        };
121        if (defineProperty) {
122          defineProperty(String.prototype, 'startsWith', {
123            'value': startsWith,
124            'configurable': true,
125            'writable': true
126          });
127        } else {
128          String.prototype.startsWith = startsWith;
129        }
130      }());
131    }
132
133    if (!Object.keys) {
134      Object.keys = function(
135        o, // object
136        k, // key
137        r // result array
138      ) {
139        // initialize object and result
140        r = [];
141        // iterate over object keys
142        for (k in o)
143          // fill result array with non-prototypical keys
144          r.hasOwnProperty.call(o, k) && r.push(k);
145        // return result
146        return r;
147      };
148    }
149
150    // set data-selected on select element if the value has been programmatically selected
151    // prior to initialization of bootstrap-select
152    // * consider removing or replacing an alternative method *
153    var valHooks = {
154      useDefault: false,
155      _set: $.valHooks.select.set
156    };
157
158    $.valHooks.select.set = function(elem, value) {
159      if (value && !valHooks.useDefault) $(elem).data('selected', true);
160
161      return valHooks._set.apply(this, arguments);
162    };
163
164    var changed_arguments = null;
165
166    var EventIsSupported = (function() {
167      try {
168        new Event('change');
169        return true;
170      } catch (e) {
171        return false;
172      }
173    })();
174
175    $.fn.triggerNative = function(eventName) {
176      var el = this[0],
177        event;
178
179      if (el.dispatchEvent) { // for modern browsers & IE9+
180        if (EventIsSupported) {
181          // For modern browsers
182          event = new Event(eventName, {
183            bubbles: true
184          });
185        } else {
186          // For IE since it doesn't support Event constructor
187          event = document.createEvent('Event');
188          event.initEvent(eventName, true, false);
189        }
190
191        el.dispatchEvent(event);
192      } else if (el.fireEvent) { // for IE8
193        event = document.createEventObject();
194        event.eventType = eventName;
195        el.fireEvent('on' + eventName, event);
196      } else {
197        // fall back to jQuery.trigger
198        this.trigger(eventName);
199      }
200    };
201    //</editor-fold>
202
203    // Case insensitive contains search
204    $.expr.pseudos.icontains = function(obj, index, meta) {
205      var $obj = $(obj).find('a');
206      var haystack = ($obj.data('tokens') || $obj.text()).toString().toUpperCase();
207      return haystack.includes(meta[3].toUpperCase());
208    };
209
210    // Case insensitive begins search
211    $.expr.pseudos.ibegins = function(obj, index, meta) {
212      var $obj = $(obj).find('a');
213      var haystack = ($obj.data('tokens') || $obj.text()).toString().toUpperCase();
214      return haystack.startsWith(meta[3].toUpperCase());
215    };
216
217    // Case and accent insensitive contains search
218    $.expr.pseudos.aicontains = function(obj, index, meta) {
219      var $obj = $(obj).find('a');
220      var haystack = ($obj.data('tokens') || $obj.data('normalizedText') || $obj.text()).toString().toUpperCase();
221      return haystack.includes(meta[3].toUpperCase());
222    };
223
224    // Case and accent insensitive begins search
225    $.expr.pseudos.aibegins = function(obj, index, meta) {
226      var $obj = $(obj).find('a');
227      var haystack = ($obj.data('tokens') || $obj.data('normalizedText') || $obj.text()).toString().toUpperCase();
228      return haystack.startsWith(meta[3].toUpperCase());
229    };
230
231    /**
232     * Remove all diatrics from the given text.
233     * @access private
234     * @param {String} text
235     * @returns {String}
236     */
237    function normalizeToBase(text) {
238      var rExps = [{
239          re: /[\xC0-\xC6]/g,
240          ch: "A"
241        },
242        {
243          re: /[\xE0-\xE6]/g,
244          ch: "a"
245        },
246        {
247          re: /[\xC8-\xCB]/g,
248          ch: "E"
249        },
250        {
251          re: /[\xE8-\xEB]/g,
252          ch: "e"
253        },
254        {
255          re: /[\xCC-\xCF]/g,
256          ch: "I"
257        },
258        {
259          re: /[\xEC-\xEF]/g,
260          ch: "i"
261        },
262        {
263          re: /[\xD2-\xD6]/g,
264          ch: "O"
265        },
266        {
267          re: /[\xF2-\xF6]/g,
268          ch: "o"
269        },
270        {
271          re: /[\xD9-\xDC]/g,
272          ch: "U"
273        },
274        {
275          re: /[\xF9-\xFC]/g,
276          ch: "u"
277        },
278        {
279          re: /[\xC7-\xE7]/g,
280          ch: "c"
281        },
282        {
283          re: /[\xD1]/g,
284          ch: "N"
285        },
286        {
287          re: /[\xF1]/g,
288          ch: "n"
289        }
290      ];
291      $.each(rExps, function() {
292        text = text ? text.replace(this.re, this.ch) : '';
293      });
294      return text;
295    }
296
297
298    // List of HTML entities for escaping.
299    var escapeMap = {
300      '&': '&amp;',
301      '<': '&lt;',
302      '>': '&gt;',
303      '"': '&quot;',
304      "'": '&#x27;',
305      '`': '&#x60;'
306    };
307
308    var unescapeMap = {
309      '&amp;': '&',
310      '&lt;': '<',
311      '&gt;': '>',
312      '&quot;': '"',
313      '&#x27;': "'",
314      '&#x60;': '`'
315    };
316
317    // Functions for escaping and unescaping strings to/from HTML interpolation.
318    var createEscaper = function(map) {
319      var escaper = function(match) {
320        return map[match];
321      };
322      // Regexes for identifying a key that needs to be escaped.
323      var source = '(?:' + Object.keys(map).join('|') + ')';
324      var testRegexp = RegExp(source);
325      var replaceRegexp = RegExp(source, 'g');
326      return function(string) {
327        string = string == null ? '' : '' + string;
328        return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
329      };
330    };
331
332    var htmlEscape = createEscaper(escapeMap);
333    var htmlUnescape = createEscaper(unescapeMap);
334
335    var Selectpicker = function(element, options) {
336      // bootstrap-select has been initialized - revert valHooks.select.set back to its original function
337      if (!valHooks.useDefault) {
338        $.valHooks.select.set = valHooks._set;
339        valHooks.useDefault = true;
340      }
341
342      this.$element = $(element);
343      this.$newElement = null;
344      this.$button = null;
345      this.$menu = null;
346      this.$lis = null;
347      this.options = options;
348
349      // 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
350      // data-attribute)
351      if (this.options.title === null) {
352        this.options.title = this.$element.attr('title');
353      }
354
355      // Format window padding
356      var winPad = this.options.windowPadding;
357      if (typeof winPad === 'number') {
358        this.options.windowPadding = [winPad, winPad, winPad, winPad];
359      }
360
361      //Expose public methods
362      this.val = Selectpicker.prototype.val;
363      this.render = Selectpicker.prototype.render;
364      this.refresh = Selectpicker.prototype.refresh;
365      this.setStyle = Selectpicker.prototype.setStyle;
366      this.selectAll = Selectpicker.prototype.selectAll;
367      this.deselectAll = Selectpicker.prototype.deselectAll;
368      this.destroy = Selectpicker.prototype.destroy;
369      this.remove = Selectpicker.prototype.remove;
370      this.show = Selectpicker.prototype.show;
371      this.hide = Selectpicker.prototype.hide;
372
373      this.init();
374    };
375
376    Selectpicker.VERSION = '1.12.4';
377
378    // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both.
379    Selectpicker.DEFAULTS = {
380      noneSelectedText: 'Nothing selected',
381      noneResultsText: 'No results matched {0}',
382      addNewText: '<strong>Add new:</strong> {0}',
383      countSelectedText: function(numSelected, numTotal) {
384        return (numSelected == 1) ? "{0} item selected" : "{0} items selected";
385      },
386      maxOptionsText: function(numAll, numGroup) {
387        return [
388          (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)',
389          (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)'
390        ];
391      },
392      selectAllText: 'Select All',
393      deselectAllText: 'Deselect All',
394      doneButton: false,
395      doneButtonText: 'Close',
396      multipleSeparator: ', ',
397      styleBase: 'btn',
398      style: 'btn-default',
399      size: 'auto',
400      title: null,
401      selectedTextFormat: 'values',
402      width: false,
403      container: false,
404      hideDisabled: false,
405      showSubtext: false,
406      showIcon: true,
407      showContent: true,
408      dropupAuto: true,
409      header: false,
410      liveSearch: false,
411      liveSearchPlaceholder: null,
412      liveSearchNormalize: false,
413      liveSearchStyle: 'contains',
414      addResults: false,
415      actionsBox: false,
416      iconBase: 'material-icons',
417      tickIcon: 'done',
418      showTick: false,
419      template: {
420        caret: '<span class="caret"></span>'
421      },
422      maxOptions: false,
423      mobile: false,
424      selectOnTab: false,
425      dropdownAlignRight: false,
426      windowPadding: 0
427    };
428
429    Selectpicker.prototype = {
430
431      constructor: Selectpicker,
432
433      init: function() {
434        var that = this,
435          id = this.$element.attr('id');
436
437        // remember that we are doing the init
438        this.options.initInProcess = true;
439        if (this.options.width === 'auto' && this.options.lazyLoadLiElements === true) {
440          // creates a bit of a problem for us. we would need to render the LIs to figure out their width
441          // that is what lazyLoading is all about avoiding.  these options are incompatible
442          // tell somebody and opt out of lazy load
443          console.log('Selectpicker option lazyLoadLiElements=true is incompatible with option width="auto".  Option \'lazyLoadLiElements\' has been reset to false, however this may cause serious performance degradation.');
444          this.options.lazyLoadLiElements = false;
445        }
446
447        this.$element.addClass('bs-select-hidden');
448
449        // store originalIndex (key) and newIndex (value) in this.liObj for fast accessibility
450        // allows us to do this.$lis.eq(that.liObj[index]) instead of this.$lis.filter('[data-original-index="' + index + '"]')
451        this.liObj = {};
452        this.multiple = this.$element.prop('multiple');
453        this.autofocus = this.$element.prop('autofocus');
454        this.$newElement = this.createView();
455        this.$element
456          .after(this.$newElement)
457          .appendTo(this.$newElement);
458        this.$button = this.$newElement.children('button');
459        this.$menu = this.$newElement.children('.dropdown-menu');
460        this.$menuInner = this.$menu.children('.inner');
461        this.$searchbox = this.$menu.find('input');
462
463        this.$element.removeClass('bs-select-hidden');
464
465        if (this.options.dropdownAlignRight === true) this.$menu.addClass('dropdown-menu-right');
466
467        if (typeof id !== 'undefined') {
468          this.$button.attr('data-id', id);
469          $('label[for="' + id + '"]').click(function(e) {
470            e.preventDefault();
471            that.$button.focus();
472          });
473        }
474
475        this.checkDisabled();
476        this.clickListener();
477
478        // we lazy loaded this select picker, so we need to make sure
479        // that we finish the rendering the first time someone actually activates
480        // the menu
481        if (this.options.lazyLoadLiElements === true) {
482          this.$button.one('click.dropdown.data-api', function(e) {
483            // render the menu
484            that.$lis = null;
485            that.liObj = {};
486            that.reloadLi();
487            that.render();
488            that.checkDisabled();
489            that.liHeight(true);
490            that.setStyle();
491            that.setWidth();
492          });
493        }
494
495        if (this.options.liveSearch) this.liveSearchListener();
496        this.render();
497        this.setStyle();
498        this.setWidth();
499        if (this.options.container) this.selectPosition();
500        this.$menu.data('this', this);
501        this.$newElement.data('this', this);
502        if (this.options.mobile) this.mobile();
503
504        this.$newElement.on({
505          'hide.bs.dropdown': function(e) {
506            that.$menuInner.attr('aria-expanded', false);
507            that.$element.trigger('hide.bs.select', e);
508          },
509          'hidden.bs.dropdown': function(e) {
510            that.$element.trigger('hidden.bs.select', e);
511          },
512          'show.bs.dropdown': function(e) {
513            that.$menuInner.attr('aria-expanded', true);
514            that.$element.trigger('show.bs.select', e);
515          },
516          'shown.bs.dropdown': function(e) {
517            that.$element.trigger('shown.bs.select', e);
518          }
519        });
520
521        if (that.$element[0].hasAttribute('required')) {
522          this.$element.on('invalid', function() {
523            that.$button.addClass('bs-invalid');
524
525            that.$element.on({
526              'focus.bs.select': function() {
527                that.$button.focus();
528                that.$element.off('focus.bs.select');
529              },
530              'shown.bs.select': function() {
531                that.$element
532                  .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened
533                  .off('shown.bs.select');
534              },
535              'rendered.bs.select': function() {
536                // if select is no longer invalid, remove the bs-invalid class
537                if (this.validity.valid) that.$button.removeClass('bs-invalid');
538                that.$element.off('rendered.bs.select');
539              }
540            });
541
542            that.$button.on('blur.bs.select', function() {
543              that.$element.focus().blur();
544              that.$button.off('blur.bs.select');
545            });
546          });
547        }
548
549        setTimeout(function() {
550          that.$element.trigger('loaded.bs.select');
551        });
552
553        this.options.initInProcess = false;
554      },
555
556      createDropdown: function() {
557        // Options
558        // If we are multiple or showTick option is set, then add the show-tick class
559        var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '',
560          inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '',
561          autofocus = this.autofocus ? ' autofocus' : '';
562        // Elements
563        var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>' + this.options.header + '</div>' : '';
564        var searchbox = this.options.liveSearch ?
565          '<div class="bs-searchbox">' +
566          '<input type="text" class="form-control" autocomplete="off"' +
567          (null === this.options.liveSearchPlaceholder ? '' : ' placeholder="' + htmlEscape(this.options.liveSearchPlaceholder) + '"') + ' role="textbox" aria-label="Search">' +
568          '</div>' :
569          '';
570        var actionsbox = this.multiple && this.options.actionsBox ?
571          '<div class="bs-actionsbox">' +
572          '<div class="btn-group btn-group-sm btn-block">' +
573          '<button type="button" class="actions-btn bs-select-all btn btn-default">' +
574          this.options.selectAllText +
575          '</button>' +
576          '<button type="button" class="actions-btn bs-deselect-all btn btn-default">' +
577          this.options.deselectAllText +
578          '</button>' +
579          '</div>' +
580          '</div>' :
581          '';
582        var donebutton = this.multiple && this.options.doneButton ?
583          '<div class="bs-donebutton">' +
584          '<div class="btn-group btn-block">' +
585          '<button type="button" class="btn btn-sm btn-default">' +
586          this.options.doneButtonText +
587          '</button>' +
588          '</div>' +
589          '</div>' :
590          '';
591        var drop =
592          '<div class="btn-group bootstrap-select' + showTick + inputGroup + '">' +
593          '<button type="button" class="' + this.options.styleBase + ' dropdown-toggle" data-toggle="dropdown"' + autofocus + ' role="button" aria-labelledby="' + this.$element.attr("aria-labelledby") + '">' +
594          '<span class="filter-option pull-left"></span>&nbsp;' +
595          '<span class="bs-caret">' +
596          this.options.template.caret +
597          '</span>' +
598          '</button>' +
599          '<div class="dropdown-menu open" role="combobox">' +
600          header +
601          searchbox +
602          actionsbox +
603          '<ul class="dropdown-menu inner" role="listbox" aria-expanded="false">' +
604          '</ul>' +
605          donebutton +
606          '</div>' +
607          '</div>';
608
609        return $(drop);
610      },
611
612      createView: function() {
613        var $drop = this.createDropdown(),
614          li = this.createLi();
615
616        $drop.find('ul')[0].innerHTML = li;
617        return $drop;
618      },
619
620      reloadLi: function() {
621        // rebuild
622        var li = this.createLi();
623        this.$menuInner[0].innerHTML = li;
624      },
625
626      createLi: function() {
627        var that = this,
628          _li = [],
629          optID = 0,
630          titleOption = document.createElement('option'),
631          liIndex = -1; // increment liIndex whenever a new <li> element is created to ensure liObj is correct
632
633        // Helper functions
634        /**
635         * @param content
636         * @param [index]
637         * @param [classes]
638         * @param [optgroup]
639         * @returns {string}
640         */
641        var generateLI = function(content, index, classes, optgroup) {
642          return '<li' +
643            ((typeof classes !== 'undefined' && '' !== classes) ? ' class="' + classes + '"' : '') +
644            ((typeof index !== 'undefined' && null !== index) ? ' data-original-index="' + index + '"' : '') +
645            ((typeof optgroup !== 'undefined' && null !== optgroup) ? 'data-optgroup="' + optgroup + '"' : '') +
646            '>' + content + '</li>';
647        };
648
649        /**
650         * @param text
651         * @param [classes]
652         * @param [inline]
653         * @param [tokens]
654         * @returns {string}
655         */
656        var generateA = function(text, classes, inline, tokens) {
657          return '<a tabindex="0"' +
658            (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') +
659            (inline ? ' style="' + inline + '"' : '') +
660            (that.options.liveSearchNormalize ? ' data-normalized-text="' + normalizeToBase(htmlEscape($(text).html())) + '"' : '') +
661            (typeof tokens !== 'undefined' || tokens !== null ? ' data-tokens="' + tokens + '"' : '') +
662            ' role="option">
662' + text +
663            '<span class="' + that.options.iconBase + '  check-mark"> ' + that.options.tickIcon + ' </span>' +
664            '</a>';
665        };
666
667        if (this.options.title && !this.multiple) {
668          // this option doesn't create a new <li> element, but does add a new option, so liIndex is decreased
669          // since liObj is recalculated on every refresh, liIndex needs to be decreased even if the titleOption is already appended
670          liIndex--;
671
672          if (!this.$element.find('.bs-title-option').length) {
673            // Use native JS to prepend option (faster)
674            var element = this.$element[0];
675            titleOption.className = 'bs-title-option';
676            titleOption.innerHTML = this.options.title;
677            titleOption.value = '';
678            element.insertBefore(titleOption, element.firstChild);
679            // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option.
680            // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs,
681            // if so, the select will have the data-selected attribute
682            var $opt = $(element.options[element.selectedIndex]);
683            if ($opt.attr('selected') === undefined && this.$element.data('selected') === undefined) {
684              titleOption.selected = true;
685            }
686          }
687        }
688
689        if (!this.options.initInProcess || this.options.lazyLoadLiElements !== true) { // skip LI creation when lazy loading
690
691          var $selectOptions = this.$element.find('option');
692
693          $selectOptions.each(function(index) {
694            var $this = $(this);
695
696            liIndex++;
697
698            if ($this.hasClass('bs-title-option')) return;
699
700            // Get the class and text for the option
701            var optionClass = this.className || '',
702              inline = htmlEscape(this.style.cssText),
703              text = $this.data('content') ? $this.data('content') : $this.html(),
704              tokens = $this.data('tokens') ? $this.data('tokens') : null,
705              subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="text-muted">' + $this.data('subtext') + '</small>' : '',
706              icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '',
707              $parent = $this.parent(),
708              isOptgroup = $parent[0].tagName === 'OPTGROUP',
709              isOptgroupDisabled = isOptgroup && $parent[0].disabled,
710              isDisabled = this.disabled || isOptgroupDisabled,
711              prevHiddenIndex;
712
713            if (icon !== '' && isDisabled) {
714              icon = '<span>' + icon + '</span>';
715            }
716
717            if (that.options.hideDisabled && (isDisabled && !isOptgroup || isOptgroupDisabled)) {
718              // set prevHiddenIndex - the index of the first hidden option in a group of hidden options
719              // used to determine whether or not a divider should be placed after an optgroup if there are
720              // hidden options between the optgroup and the first visible option
721              prevHiddenIndex = $this.data('prevHiddenIndex');
722              $this.next().data('prevHiddenIndex', (prevHiddenIndex !== undefined ? prevHiddenIndex : index));
723
724              liIndex--;
725              return;
726            }
727
728            if (!$this.data('content')) {
729              // Prepend any icon and append any subtext to the main text.
730              text = icon + '<span class="text">' + text + subtext + '</span>';
731            }
732
733            if (isOptgroup && $this.data('divider') !== true) {
734              if (that.options.hideDisabled && isDisabled) {
735                if ($parent.data('allOptionsDisabled') === undefined) {
736                  var $options = $parent.children();
737                  $parent.data('allOptionsDisabled', $options.filter(':disabled').length === $options.length);
738                }
739
740                if ($parent.data('allOptionsDisabled')) {
741                  liIndex--;
742                  return;
743                }
744              }
745
746              var optGroupClass = ' ' + $parent[0].className || '';
747
748              if ($this.index() === 0) { // Is it the first option of the optgroup?
749                optID += 1;
750
751                // Get the opt group label
752                var label = $parent[0].label,
753                  labelSubtext = typeof $parent.data('subtext') !== 'undefined' ? '<small class="text-muted">' + $parent.data('subtext') + '</small>' : '',
754                  labelIcon = $parent.data('icon') ? '<span class="' + that.options.iconBase + ' ' + $parent.data('icon') + '"></span> ' : '';
755
756                label = labelIcon + '<span class="text">' + htmlEscape(label) + labelSubtext + '</span>';
757
758                if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown?
759                  liIndex++;
760                  _li.push(generateLI('', null, 'divider', optID + 'div'));
761                }
762                liIndex++;
763                _li.push(generateLI(label, null, 'dropdown-header' + optGroupClass, optID));
764              }
765
766              if (that.options.hideDisabled && isDisabled) {
767                liIndex--;
768                return;
769              }
770
771              _li.push(generateLI(generateA(text, 'opt ' + optionClass + optGroupClass, inline, tokens), index, '', optID));
772            } else if ($this.data('divider') === true) {
773              _li.push(generateLI('', index, 'divider'));
774            } else if ($this.data('hidden') === true) {
775              // set prevHiddenIndex - the index of the first hidden option in a group of hidden options
776              // used to determine whether or not a divider should be placed after an optgroup if there are
777              // hidden options between the optgroup and the first visible option
778              prevHiddenIndex = $this.data('prevHiddenIndex');
779              $this.next().data('prevHiddenIndex', (prevHiddenIndex !== undefined ? prevHiddenIndex : index));
780
781              _li.push(generateLI(generateA(text, optionClass, inline, tokens), index, 'hidden is-hidden'));
782            } else {
783              var showDivider = this.previousElementSibling && this.previousElementSibling.tagName === 'OPTGROUP';
784
785              // if previous element is not an optgroup and hideDisabled is true
786              if (!showDivider && that.options.hideDisabled) {
787                prevHiddenIndex = $this.data('prevHiddenIndex');
788
789                if (prevHiddenIndex !== undefined) {
790                  // select the element **before** the first hidden element in the group
791                  var prevHidden = $selectOptions.eq(prevHiddenIndex)[0].previousElementSibling;
792
793                  if (prevHidden && prevHidden.tagName === 'OPTGROUP' && !prevHidden.disabled) {
794                    showDivider = true;
795                  }
796                }
797              }
798
799              if (showDivider) {
800                liIndex++;
801                _li.push(generateLI('', null, 'divider', optID + 'div'));
802              }
803              _li.push(generateLI(generateA(text, optionClass, inline, tokens), index));
804            }
805
806            that.liObj[index] = liIndex;
807          });
808
809        } // end if for skipping li creation when lazy loading
810
811        //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
812        if (!this.multiple && this.$element.find('option:selected').length === 0 && !this.options.title) {
813          this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected');
814        }
815
816        return _li.join('');
817      },
818
819      findLis: function() {
820        if (this.$lis == null) this.$lis = this.$menu.find('li');
821        return this.$lis;
822      },
823
824      /**
825       * @param [updateLi] defaults to true
826       */
827      render: function(updateLi) {
828        var that = this,
829          notDisabled,
830          $selectOptions = this.$element.find('option');
831
832        //Update the LI to match the SELECT
833        if (updateLi !== false && (!this.options.initInProcess || this.options.lazyLoadLiElements !== true)) { // skip this if we are lazy loading
834          $selectOptions.each(function(index) {
835            var $lis = that.findLis().eq(that.liObj[index]);
836
837            that.setDisabled(index, this.disabled || this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled, $lis);
838            that.setSelected(index, this.selected, $lis);
839          });
840        }
841
842        this.togglePlaceholder();
843
844        this.tabIndex();
845
846        var createOptionText = function(opt) {
847          if (that.options.hideDisabled && (opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) return;
848
849          var $this = $(opt),
850            icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : '',
851            subtext;
852
853          if (that.options.showSubtext && $this.data('subtext') && !that.multiple) {
854            subtext = ' <small class="text-muted">' + $this.data('subtext') + '</small>';
855          } else {
856            subtext = '';
857          }
858          if (typeof $this.attr('title') !== 'undefined') {
859            return $this.attr('title');
860          } else if ($this.data('content') && that.options.showContent) {
861            return $this.data('content').toString();
862          } else {
863            return icon + $this.html() + subtext;
864          }
865        };
866
867        var selectedItems = (this.multiple || this.options.singleSelectPerfTweak !== true ? $selectOptions.map(function() {
868            if (this.selected) {
869              return createOptionText(this);
870            }
871          }).toArray() :
872          // only single select - do not need to iterate every single option
873          [createOptionText(this.$element[0][this.$element[0].selectedIndex])]
874        );
875
876        //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled
877        //Convert all the values into a comma delimited string
878        var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator);
879
880        //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..
881        if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) {
882          var max = this.options.selectedTextFormat.split('>');
883          if ((max.length > 1 && selectedItems.length > max[1]) || (max.length == 1 && selectedItems.length >= 2)) {
884            notDisabled = this.options.hideDisabled ? ', [disabled]' : '';
885            var totalCount = $selectOptions.not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length,
886              tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText;
887            title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString());
888          }
889        }
890
891        if (this.options.title == undefined) {
892          this.options.title = this.$element.attr('title');
893        }
894
895        if (this.options.selectedTextFormat == 'static') {
896          title = this.options.title;
897        }
898
899        //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text
900        if (!title) {
901          title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText;
902        }
903
904        //strip all HTML tags and trim the result, then unescape any escaped tags
905        this.$button.attr('title', htmlUnescape($.trim(title.replace(/<[^>]*>?/g, ''))));
906        this.$button.children('.filter-option').html(title);
907
908        if (!this.options.initInProcess || this.options.lazyLoadLiElements !== true) { // this is very expensive on IE and we haven;t really rendered the select if we are lazy loading, so hold off on this event
909          this.$element.trigger('rendered.bs.select');
910        };
911      },
912
913      /**
914       * @param [style]
915       * @param [status]
916       */
917      setStyle: function(style, status) {
918        if (this.$element.attr('class')) {
919          this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, ''));
920        }
921
922        var buttonClass = style ? style : this.options.style;
923
924        if (status == 'add') {
925          this.$button.addClass(buttonClass);
926        } else if (status == 'remove') {
927          this.$button.removeClass(buttonClass);
928        } else {
929          this.$button.removeClass(this.options.style);
930          this.$button.addClass(buttonClass);
931        }
932      },
933
934      liHeight: function(refresh) {
935        if (!refresh && (this.options.size === false || this.sizeInfo)) return;
936
937        var newElement = document.createElement('div'),
938          menu = document.createElement('div'),
939          menuInner = document.createElement('ul'),
940          divider = document.createElement('li'),
941          li = document.createElement('li'),
942          a = document.createElement('a'),
943          text = document.createElement('span'),
944          header = this.options.header && this.$menu.find('.popover-title').length > 0 ? this.$menu.find('.popover-title')[0].cloneNode(true) : null,
945          search = this.options.liveSearch ? document.createElement('div') : null,
946          actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null,
947          doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null;
948
949        text.className = 'text';
950        newElement.className = this.$menu[0].parentNode.className + ' open';
951        menu.className = 'dropdown-menu open';
952        menuInner.className = 'dropdown-menu inner';
953        divider.className = 'divider';
954
955        text.appendChild(document.createTextNode('Inner text'));
956        a.appendChild(text);
957        li.appendChild(a);
958        menuInner.appendChild(li);
959        menuInner.appendChild(divider);
960        if (header) menu.appendChild(header);
961        if (search) {
962          var input = document.createElement('input');
963          search.className = 'bs-searchbox';
964          input.className = 'form-control';
965          search.appendChild(input);
966          menu.appendChild(search);
967        }
968        if (actions) menu.appendChild(actions);
969        menu.appendChild(menuInner);
970        if (doneButton) menu.appendChild(doneButton);
971        newElement.appendChild(menu);
972
973        document.body.appendChild(newElement);
974
975        var liHeight = a.offsetHeight,
976          headerHeight = header ? header.offsetHeight : 0,
977          searchHeight = search ? search.offsetHeight : 0,
978          actionsHeight = actions ? actions.offsetHeight : 0,
979          doneButtonHeight = doneButton ? doneButton.offsetHeight : 0,
980          dividerHeight = $(divider).outerHeight(true),
981          // fall back to jQuery if getComputedStyle is not supported
982          menuStyle = typeof getComputedStyle === 'function' ? getComputedStyle(menu) : false,
983          $menu = menuStyle ? null : $(menu),
984          menuPadding = {
985            vert: parseInt(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) +
986              parseInt(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) +
987              parseInt(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) +
988              parseInt(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')),
989            horiz: parseInt(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) +
990              parseInt(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) +
991              parseInt(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) +
992              parseInt(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth'))
993          },
994          menuExtras = {
995            vert: menuPadding.vert +
996              parseInt(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) +
997              parseInt(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2,
998            horiz: menuPadding.horiz +
999              parseInt(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) +
1000              parseInt(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2
1001          }
1002
1003        document.body.removeChild(newElement);
1004
1005        this.sizeInfo = {
1006          liHeight: liHeight,
1007          headerHeight: headerHeight,
1008          searchHeight: searchHeight,
1009          actionsHeight: actionsHeight,
1010          doneButtonHeight: doneButtonHeight,
1011          dividerHeight: dividerHeight,
1012          menuPadding: menuPadding,
1013          menuExtras: menuExtras
1014        };
1015      },
1016
1017      setSize: function() {
1018        this.findLis();
1019        this.liHeight();
1020
1021        if (this.options.header) this.$menu.css('padding-top', 0);
1022        if (this.options.size === false) return;
1023
1024        var that = this,
1025          $menu = this.$menu,
1026          $menuInner = this.$menuInner,
1027          $window = $(window),
1028          selectHeight = this.$newElement[0].offsetHeight,
1029          selectWidth = this.$newElement[0].offsetWidth,
1030          liHeight = this.sizeInfo['liHeight'],
1031          headerHeight = this.sizeInfo['headerHeight'],
1032          searchHeight = this.sizeInfo['searchHeight'],
1033          actionsHeight = this.sizeInfo['actionsHeight'],
1034          doneButtonHeight = this.sizeInfo['doneButtonHeight'],
1035          divHeight = this.sizeInfo['dividerHeight'],
1036          menuPadding = this.sizeInfo['menuPadding'],
1037          menuExtras = this.sizeInfo['menuExtras'],
1038          notDisabled = this.options.hideDisabled ? '.disabled' : '',
1039          menuHeight,
1040          menuWidth,
1041          getHeight,
1042          getWidth,
1043          selectOffsetTop,
1044          selectOffsetBot,
1045          selectOffsetLeft,
1046          selectOffsetRight,
1047          getPos = function() {
1048            var pos = that.$newElement.offset(),
1049              $container = $(that.options.container),
1050              containerPos;
1051
1052            if (that.options.container && !$container.is('body')) {
1053              containerPos = $container.offset();
1054              containerPos.top += parseInt($container.css('borderTopWidth'));
1055              containerPos.left += parseInt($container.css('borderLeftWidth'));
1056            } else {
1057              containerPos = {
1058                top: 0,
1059                left: 0
1060              };
1061            }
1062
1063            var winPad = that.options.windowPadding;
1064            selectOffsetTop = pos.top - containerPos.top - $window.scrollTop();
1065            selectOffsetBot = $window.height() - selectOffsetTop - selectHeight - containerPos.top - winPad[2];
1066            selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft();
1067            selectOffsetRight = $window.width() - selectOffsetLeft - selectWidth - containerPos.left - winPad[1];
1068            selectOffsetTop -= winPad[0];
1069            selectOffsetLeft -= winPad[3];
1070          };
1071
1072        getPos();
1073
1074        if (this.options.size === 'auto') {
1075          var getSize = function() {
1076            var minHeight,
1077              hasClass = function(className, include) {
1078                return function(element) {
1079                  if (include) {
1080                    return (element.classList ? element.classList.contains(className) : $(element).hasClass(className));
1081                  } else {
1082                    return !(element.classList ? element.classList.contains(className) : $(element).hasClass(className));
1083                  }
1084                };
1085              },
1086              lis = that.$menuInner[0].getElementsByTagName('li'),
1087              lisVisible = Array.prototype.filter ? Array.prototype.filter.call(lis, hasClass('hidden', false)) : that.$lis.not('.hidden'),
1088              optGroup = Array.prototype.filter ? Array.prototype.filter.call(lisVisible, hasClass('dropdown-header', true)) : lisVisible.filter('.dropdown-header');
1089
1090            getPos();
1091            menuHeight = selectOffsetBot - menuExtras.vert;
1092            menuWidth = selectOffsetRight - menuExtras.horiz;
1093
1094            if (that.options.container) {
1095              if (!$menu.data('height')) $menu.data('height', $menu.height());
1096              getHeight = $menu.data('height');
1097
1098              if (!$menu.data('width')) $menu.data('width', $menu.width());
1099              getWidth = $menu.data('width');
1100            } else {
1101              getHeight = $menu.height();
1102              getWidth = $menu.width();
1103            }
1104
1105            if (that.options.dropupAuto) {
1106              that.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras.vert) < getHeight);
1107            }
1108
1109            if (that.$newElement.hasClass('dropup')) {
1110              menuHeight = selectOffsetTop - menuExtras.vert;
1111            }
1112
1113            if (that.options.dropdownAlignRight === 'auto') {
1114              $menu.toggleClass('dropdown-menu-right', selectOffsetLeft > selectOffsetRight && (menuWidth - menuExtras.horiz) < (getWidth - selectWidth));
1115            }
1116
1117            if ((lisVisible.length + optGroup.length) > 3) {
1118              minHeight = liHeight * 3 + menuExtras.vert - 2;
1119            } else {
1120              minHeight = 0;
1121            }
1122
1123            $menu.css({
1124              'max-height': menuHeight + 'px',
1125              'overflow': 'hidden',
1126              'min-height': minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px'
1127            });
1128            $menuInner.css({
1129              'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert + 'px',
1130              'overflow-y': 'auto',
1131              'min-height': Math.max(minHeight - menuPadding.vert, 0) + 'px'
1132            });
1133          };
1134          getSize();
1135          this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize);
1136          $window.off('resize.getSize scroll.getSize').on('resize.getSize scroll.getSize', getSize);
1137        } else if (this.options.size && this.options.size != 'auto' && this.$lis.not(notDisabled).length > this.options.size) {
1138          var optIndex = this.$lis.not('.divider').not(notDisabled).children().slice(0, this.options.size).last().parent().index(),
1139            divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length;
1140          menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert;
1141
1142          if (that.options.container) {
1143            if (!$menu.data('height')) $menu.data('height', $menu.height());
1144            getHeight = $menu.data('height');
1145          } else {
1146            getHeight = $menu.height();
1147          }
1148
1149          if (that.options.dropupAuto) {
1150            //noinspection JSUnusedAssignment
1151            this.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras.vert) < getHeight);
1152          }
1153          $menu.css({
1154            'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px',
1155            'overflow': 'hidden',
1156            'min-height': ''
1157          });
1158          $menuInner.css({
1159            'max-height': menuHeight - menuPadding.vert + 'px',
1160            'overflow-y': 'auto',
1161            'min-height': ''
1162          });
1163        }
1164      },
1165
1166      setWidth: function() {
1167        if (this.options.width === 'auto') {
1168          this.$menu.css('min-width', '0');
1169
1170          // Get correct width if element is hidden
1171          var $selectClone = this.$menu.parent().clone().appendTo('body'),
1172            $selectClone2 = this.options.container ? this.$newElement.clone().appendTo('body') : $selectClone,
1173            ulWidth = $selectClone.children('.dropdown-menu').outerWidth(),
1174            btnWidth = $selectClone2.css('width', 'auto').children('button').outerWidth();
1175
1176          $selectClone.remove();
1177          $selectClone2.remove();
1178
1179          // Set width to whatever's larger, button title or longest option
1180          this.$newElement.css('width', Math.max(ulWidth, btnWidth) + 'px');
1181        } else if (this.options.width === 'fit') {
1182          // Remove inline min-width so width can be changed from 'auto'
1183          this.$menu.css('min-width', '');
1184          this.$newElement.css('width', '').addClass('fit-width');
1185        } else if (this.options.width) {
1186          // Remove inline min-width so width can be changed from 'auto'
1187          this.$menu.css('min-width', '');
1188          this.$newElement.css('width', this.options.width);
1189        } else {
1190          // Remove inline min-width/width so width can be changed
1191          this.$menu.css('min-width', '');
1192          this.$newElement.css('width', '');
1193        }
1194        // Remove fit-width class if width is changed programmatically
1195        if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') {
1196          this.$newElement.removeClass('fit-width');
1197        }
1198      },
1199
1200      selectPosition: function() {
1201        this.$bsContainer = $('<div class="bs-container" />');
1202
1203        var that = this,
1204          $container = $(this.options.container),
1205          pos,
1206          containerPos,
1207          actualHeight,
1208          getPlacement = function($element) {
1209            that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass('dropup', $element.hasClass('dropup'));
1210            pos = $element.offset();
1211
1212            if (!$container.is('body')) {
1213              containerPos = $container.offset();
1214              containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop();
1215              containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft();
1216            } else {
1217              containerPos = {
1218                top: 0,
1219                left: 0
1220              };
1221            }
1222
1223            actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight;
1224
1225            that.$bsContainer.css({
1226              'top': pos.top - containerPos.top + actualHeight,
1227              'left': pos.left - containerPos.left,
1228              'width': $element[0].offsetWidth
1229            });
1230          };
1231
1232        this.$button.on('click', function() {
1233          var $this = $(this);
1234
1235          if (that.isDisabled()) {
1236            return;
1237          }
1238
1239          getPlacement(that.$newElement);
1240
1241          that.$bsContainer
1242            .appendTo(that.options.container)
1243            .toggleClass('open', !$this.hasClass('open'))
1244            .append(that.$menu);
1245        });
1246
1247        $(window).on('resize scroll', function() {
1248          getPlacement(that.$newElement);
1249        });
1250
1251        this.$element.on('hide.bs.select', function() {
1252          that.$menu.data('height', that.$menu.height());
1253          that.$bsContainer.detach();
1254        });
1255      },
1256
1257      /**
1258       * @param {number} index - the index of the option that is being changed
1259       * @param {boolean} selected - true if the option is being selected, false if being deselected
1260       * @param {JQuery} $lis - the 'li' element that is being modified
1261       */
1262      setSelected: function(index, selected, $lis) {
1263        if (!$lis) {
1264          this.togglePlaceholder(); // check if setSelected is being called by changing the value of the select
1265          $lis = this.findLis().eq(this.liObj[index]);
1266        }
1267
1268        $lis.toggleClass('selected', selected).find('a').attr('aria-selected', selected);
1269      },
1270
1271      /**
1272       * @param {number} index - the index of the option that is being disabled
1273       * @param {boolean} disabled - true if the option is being disabled, false if being enabled
1274       * @param {JQuery} $lis - the 'li' element that is being modified
1275       */
1276      setDisabled: function(index, disabled, $lis) {
1277        if (!$lis) {
1278          $lis = this.findLis().eq(this.liObj[index]);
1279        }
1280
1281        if (disabled) {
1282          $lis.addClass('disabled').children('a').attr('href', '#').attr('tabindex', -1).attr('aria-disabled', true);
1283        } else {
1284          $lis.removeClass('disabled').children('a').removeAttr('href').attr('tabindex', 0).attr('aria-disabled', false);
1285        }
1286      },
1287
1288      isDisabled: function() {
1289        return this.$element[0].disabled;
1290      },
1291
1292      checkDisabled: function() {
1293        var that = this;
1294
1295        if (this.isDisabled()) {
1296          this.$newElement.addClass('disabled');
1297          this.$button.addClass('disabled').attr('tabindex', -1).attr('aria-disabled', true);
1298        } else {
1299          if (this.$button.hasClass('disabled')) {
1300            this.$newElement.removeClass('disabled');
1301            this.$button.removeClass('disabled').attr('aria-disabled', false);
1302          }
1303
1304          if (this.$button.attr('tabindex') == -1 && !this.$element.data('tabindex')) {
1305            this.$button.removeAttr('tabindex');
1306          }
1307        }
1308
1309        this.$button.click(function() {
1310          return !that.isDisabled();
1311        });
1312      },
1313
1314      togglePlaceholder: function() {
1315        var value = this.$element.val();
1316        this.$button.toggleClass('bs-placeholder', value === null || value === '' || (value.constructor === Array && value.length === 0));
1317      },
1318
1319      tabIndex: function() {
1320        if (this.$element.data('tabindex') !== this.$element.attr('tabindex') &&
1321          (this.$element.attr('tabindex') !== -98 && this.$element.attr('tabindex') !== '-98')) {
1322          this.$element.data('tabindex', this.$element.attr('tabindex'));
1323          this.$button.attr('tabindex', this.$element.data('tabindex'));
1324        }
1325
1326        this.$element.attr('tabindex', -98);
1327      },
1328
1329      clickListener: function() {
1330        var that = this,
1331          $document = $(document);
1332
1333        $document.data('spaceSelect', false);
1334
1335        this.$button.on('keyup', function(e) {
1336          if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) {
1337            e.preventDefault();
1338            $document.data('spaceSelect', false);
1339          }
1340        });
1341
1342        //TODO: this should not happen before the list is initialised in case
1343        //lazy loading option is used.
1344        this.$button.on('click', function() {
1345          that.setSize();
1346        });
1347
1348        this.$element.on('shown.bs.select', function() {
1349          if (!that.options.liveSearch && !that.multiple) {
1350            that.$menuInner.find('.selected a').focus();
1351          } else if (!that.multiple) {
1352            var selectedIndex = that.liObj[that.$element[0].selectedIndex];
1353
1354            if (typeof selectedIndex !== 'number' || that.options.size === false) return;
1355
1356            // scroll to selected option
1357            var offset = that.$lis.eq(selectedIndex)[0].offsetTop - that.$menuInner[0].offsetTop;
1358            offset = offset - that.$menuInner[0].offsetHeight / 2 + that.sizeInfo.liHeight / 2;
1359            that.$menuInner[0].scrollTop = offset;
1360          }
1361        });
1362
1363        this.$menuInner.on('click', 'li a', function(e) {
1364          var $this = $(this),
1365            clickedIndex = $this.parent().data('originalIndex'),
1366            prevValue = that.$element.val(),
1367            prevIndex = that.$element.prop('selectedIndex'),
1368            triggerChange = true;
1369
1370          // Don't close on multi choice menu
1371          if (that.multiple && that.options.maxOptions !== 1) {
1372            e.stopPropagation();
1373          }
1374
1375          e.preventDefault();
1376
1377          //Don't run if we have been disabled
1378          if (!that.isDisabled() && !$this.parent().hasClass('disabled')) {
1379            var $options = that.$element.find('option'),
1380              $option = $options.eq(clickedIndex),
1381              state = $option.prop('selected'),
1382              $optgroup = $option.parent('optgroup'),
1383              maxOptions = that.options.maxOptions,
1384              maxOptionsGrp = $optgroup.data('maxOptions') || false;
1385
1386            if (!that.multiple) { // Deselect all others if not multi select box
1387              $options.prop('selected', false);
1388              $option.prop('selected', true);
1389              that.$menuInner.find('.selected').removeClass('selected').find('a').attr('aria-selected', false);
1390              that.setSelected(clickedIndex, true);
1391            } else { // Toggle the one we have chosen if we are multi select.
1392              $option.prop('selected', !state);
1393              that.setSelected(clickedIndex, !state);
1394              $this.blur();
1395
1396              if (maxOptions !== false || maxOptionsGrp !== false) {
1397                var maxReached = maxOptions < $options.filter(':selected').length,
1398                  maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length;
1399
1400                if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) {
1401                  if (maxOptions && maxOptions == 1) {
1402                    $options.prop('selected', false);
1403                    $option.prop('selected', true);
1404                    that.$menuInner.find('.selected').removeClass('selected');
1405                    that.setSelected(clickedIndex, true);
1406                  } else if (maxOptionsGrp && maxOptionsGrp == 1) {
1407                    $optgroup.find('option:selected').prop('selected', false);
1408                    $option.prop('selected', true);
1409                    var optgroupID = $this.parent().data('optgroup');
1410                    that.$menuInner.find('[data-optgroup="' + optgroupID + '"]').removeClass('selected');
1411                    that.setSelected(clickedIndex, true);
1412                  } else {
1413                    var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText,
1414                      maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText,
1415                      maxTxt = maxOptionsArr[0].replace('{n}', maxOptions),
1416                      maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp),
1417                      $notify = $('<div class="notify"></div>');
1418                    // If {var} is set in array, replace it
1419                    /** @deprecated */
1420                    if (maxOptionsArr[2]) {
1421                      maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]);
1422                      maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]);
1423                    }
1424
1425                    $option.prop('selected', false);
1426
1427                    that.$menu.append($notify);
1428
1429                    if (maxOptions && maxReached) {
1430                      $notify.append($('<div>' + maxTxt + '</div>'));
1431                      triggerChange = false;
1432                      that.$element.trigger('maxReached.bs.select');
1433                    }
1434
1435                    if (maxOptionsGrp && maxReachedGrp) {
1436                      $notify.append($('<div>' + maxTxtGrp + '</div>'));
1437                      triggerChange = false;
1438                      that.$element.trigger('maxReachedGrp.bs.select');
1439                    }
1440
1441                    setTimeout(function() {
1442                      that.setSelected(clickedIndex, false);
1443                    }, 10);
1444
1445                    $notify.delay(750).fadeOut(300, function() {
1446                      $(this).remove();
1447                    });
1448                  }
1449                }
1450              }
1451            }
1452
1453            if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) {
1454              that.$button.focus();
1455            } else if (that.options.liveSearch) {
1456              that.$searchbox.focus();
1457            }
1458
1459            // Trigger select 'change'
1460            if (triggerChange) {
1461              if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) {
1462                // $option.prop('selected') is current option state (selected/unselected). state is previous option state.
1463                changed_arguments = [clickedIndex, $option.prop('selected'), state];
1464                that.$element
1465                  .triggerNative('change');
1466              }
1467            }
1468          }
1469        });
1470
1471        this.$menu.on('click', 'li.disabled a, .popover-title, .popover-title :not(.close)', function(e) {
1472          if (e.currentTarget == this) {
1473            e.preventDefault();
1474            e.stopPropagation();
1475            if (that.options.liveSearch && !$(e.target).hasClass('close')) {
1476              that.$searchbox.focus();
1477            } else {
1478              that.$button.focus();
1479            }
1480          }
1481        });
1482
1483        this.$menuInner.on('click', '.divider, .dropdown-header', function(e) {
1484          e.preventDefault();
1485          e.stopPropagation();
1486          if (that.options.liveSearch) {
1487            that.$searchbox.focus();
1488          } else {
1489            that.$button.focus();
1490          }
1491        });
1492
1493        this.$menu.on('click', '.popover-title .close', function() {
1494          that.$button.click();
1495        });
1496
1497        this.$searchbox.on('click', function(e) {
1498          e.stopPropagation();
1499        });
1500
1501        this.$menu.on('click', '.actions-btn', function(e) {
1502          if (that.options.liveSearch) {
1503            that.$searchbox.focus();
1504          } else {
1505            that.$button.focus();
1506          }
1507
1508          e.preventDefault();
1509          e.stopPropagation();
1510
1511          if ($(this).hasClass('bs-select-all')) {
1512            that.selectAll();
1513          } else {
1514            that.deselectAll();
1515          }
1516        });
1517
1518        this.$element.change(function() {
1519          that.render(false);
1520          that.$element.trigger('changed.bs.select', changed_arguments);
1521          changed_arguments = null;
1522        });
1523      },
1524
1525      liveSearchListener: function() {
1526        var that = this,
1527          $no_results = $('<li class="no-results"></li>');
1528
1529        this.$button.on('click.dropdown.data-api', function() {
1530          that.$menuInner.find('.active').removeClass('active');
1531          if (!!that.$searchbox.val()) {
1532            that.$searchbox.val('');
1533            that.$lis.not('.is-hidden').removeClass('hidden');
1534            if (!!$no_results.parent().length) $no_results.remove();
1535          }
1536          if (!that.multiple) that.$menuInner.find('.selected').addClass('active');
1537          setTimeout(function() {
1538            that.$searchbox.focus();
1539          }, 10);
1540        });
1541
1542        this.$searchbox.on('click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api', function(e) {
1543          e.stopPropagation();
1544        });
1545
1546        this.$searchbox.on('input propertychange', function() {
1547          that.$lis.not('.is-hidden').removeClass('hidden');
1548          that.$lis.filter('.active').removeClass('active');
1549          $no_results.remove();
1550
1551          if (that.$searchbox.val()) {
1552            var $searchBase = that.$lis.not('.is-hidden, .divider, .dropdown-header'),
1553              $hideItems;
1554            if (that.options.liveSearchNormalize) {
1555              $hideItems = $searchBase.not(':a' + that._searchStyle() + '("' + normalizeToBase(that.$searchbox.val()) + '")');
1556            } else {
1557              $hideItems = $searchBase.not(':' + that._searchStyle() + '("' + that.$searchbox.val() + '")');
1558            }
1559
1560            if ($hideItems.length === $searchBase.length) {
1561              $no_results.html(that.options.noneResultsText.replace('{0}', '"' + htmlEscape(that.$searchbox.val()) + '"'));
1562              that.$menuInner.append($no_results);
1563              that.$lis.addClass('hidden');
1564            } else {
1565              $hideItems.addClass('hidden');
1566
1567              var $lisVisible = that.$lis.not('.hidden'),
1568                $foundDiv;
1569
1570              // hide divider if first or last visible, or if followed by another divider
1571              $lisVisible.each(function(index) {
1572                var $this = $(this);
1573
1574                if ($this.hasClass('divider')) {
1575                  if ($foundDiv === undefined) {
1576                    $this.addClass('hidden');
1577                  } else {
1578                    if ($foundDiv) $foundDiv.addClass('hidden');
1579                    $foundDiv = $this;
1580                  }
1581                } else if ($this.hasClass('dropdown-header') && $lisVisible.eq(index + 1).data('optgroup') !== $this.data('optgroup')) {
1582                  $this.addClass('hidden');
1583                } else {
1584                  $foundDiv = null;
1585                }
1586              });
1587              if ($foundDiv) $foundDiv.addClass('hidden');
1588
1589              $searchBase.not('.hidden').first().addClass('active');
1590              that.$menuInner.scrollTop(0);
1591            }
1592          }
1593        });
1594      },
1595
1596      _searchStyle: function() {
1597        var styles = {
1598          begins: 'ibegins',
1599          startsWith: 'ibegins'
1600        };
1601
1602        return styles[this.options.liveSearchStyle] || 'icontains';
1603      },
1604
1605      val: function(value) {
1606        if (typeof value !== 'undefined') {
1607          this.$element.val(value);
1608          this.render();
1609
1610          return this.$element;
1611        } else {
1612          return this.$element.val();
1613        }
1614      },
1615
1616      changeAll: function(status) {
1617        if (!this.multiple) return;
1618        if (typeof status === 'undefined') status = true;
1619
1620        this.findLis();
1621
1622        var $options = this.$element.find('option'),
1623          $lisVisible = this.$lis.not('.divider, .dropdown-header, .disabled, .hidden'),
1624          lisVisLen = $lisVisible.length,
1625          selectedOptions = [];
1626
1627        if (status) {
1628          if ($lisVisible.filter('.selected').length === $lisVisible.length) return;
1629        } else {
1630          if ($lisVisible.filter('.selected').length === 0) return;
1631        }
1632
1633        $lisVisible.toggleClass('selected', status);
1634
1635        for (var i = 0; i < lisVisLen; i++) {
1636          var origIndex = $lisVisible[i].getAttribute('data-original-index');
1637          selectedOptions[selectedOptions.length] = $options.eq(origIndex)[0];
1638        }
1639
1640        $(selectedOptions).prop('selected', status);
1641
1642        this.render(false);
1643
1644        this.togglePlaceholder();
1645
1646        this.$element
1647          .triggerNative('change');
1648      },
1649
1650      selectAll: function() {
1651        return this.changeAll(true);
1652      },
1653
1654      deselectAll: function() {
1655        return this.changeAll(false);
1656      },
1657
1658      toggle: function(e) {
1659        e = e || window.event;
1660
1661        if (e) e.stopPropagation();
1662
1663        this.$button.trigger('click');
1664      },
1665
1666      keydown: function(e) {
1667        var $this = $(this),
1668          $parent = $this.is('input') ? $this.parent().parent().parent() : $this.parent(),
1669          $items,
1670          that = $parent.data('this'),
1671          index,
1672          prevIndex,
1673          isActive,
1674          selector = ':not(.disabled, .hidden, .dropdown-header, .divider)',
1675          keyCodeMap = {
1676            32: ' ',
1677            48: '0',
1678            49: '1',
1679            50: '2',
1680            51: '3',
1681            52: '4',
1682            53: '5',
1683            54: '6',
1684            55: '7',
1685            56: '8',
1686            57: '9',
1687            59: ';',
1688            65: 'a',
1689            66: 'b',
1690            67: 'c',
1691            68: 'd',
1692            69: 'e',
1693            70: 'f',
1694            71: 'g',
1695            72: 'h',
1696            73: 'i',
1697            74: 'j',
1698            75: 'k',
1699            76: 'l',
1700            77: 'm',
1701            78: 'n',
1702            79: 'o',
1703            80: 'p',
1704            81: 'q',
1705            82: 'r',
1706            83: 's',
1707            84: 't',
1708            85: 'u',
1709            86: 'v',
1710            87: 'w',
1711            88: 'x',
1712            89: 'y',
1713            90: 'z',
1714            96: '0',
1715            97: '1',
1716            98: '2',
1717            99: '3',
1718            100: '4',
1719            101: '5',
1720            102: '6',
1721            103: '7',
1722            104: '8',
1723            105: '9'
1724          };
1725
1726
1727        isActive = that.$newElement.hasClass('show');
1728
1729        if (!isActive && (e.keyCode >= 48 && e.keyCode <= 57 || e.keyCode >= 96 && e.keyCode <= 105 || e.keyCode >= 65 && e.keyCode <= 90)) {
1730          if (!that.options.container) {
1731            that.setSize();
1732            that.$menu.parent().addClass('open');
1733            isActive = true;
1734          } else {
1735            that.$button.trigger('click');
1736          }
1737          that.$searchbox.focus();
1738          return;
1739        }
1740
1741        if (that.options.liveSearch) {
1742          if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive) {
1743            e.preventDefault();
1744            e.stopPropagation();
1745            that.$menuInner.click();
1746            that.$button.focus();
1747          }
1748        }
1749
1750        if (/(38|40)/.test(e.keyCode.toString(10))) {
1751          $items = that.$lis.filter(selector);
1752          if (!$items.length) return;
1753
1754          if (!that.options.liveSearch) {
1755            index = $items.index($items.find('a').filter(':focus').parent());
1756          } else {
1757            index = $items.index($items.filter('.active'));
1758          }
1759
1760          prevIndex = that.$menuInner.data('prevIndex');
1761
1762          if (e.keyCode == 38) {
1763            if ((that.options.liveSearch || index == prevIndex) && index != -1) index--;
1764            if (index < 0) index += $items.length;
1765          } else if (e.keyCode == 40) {
1766            if (that.options.liveSearch || index == prevIndex) index++;
1767            index = index % $items.length;
1768          }
1769
1770          that.$menuInner.data('prevIndex', index);
1771
1772          if (!that.options.liveSearch) {
1773            $items.eq(index).children('a').focus();
1774          } else {
1775            e.preventDefault();
1776            if (!$this.hasClass('dropdown-toggle')) {
1777              $items.removeClass('active').eq(index).addClass('active').children('a').focus();
1778              $this.focus();
1779            }
1780          }
1781
1782        } else if (!$this.is('input')) {
1783          var keyIndex = [],
1784            count,
1785            prevKey;
1786
1787          $items = that.$lis.filter(selector);
1788          $items.each(function(i) {
1789            if ($.trim($(this).children('a').text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) {
1790              keyIndex.push(i);
1791            }
1792          });
1793
1794          count = $(document).data('keycount');
1795          count++;
1796          $(document).data('keycount', count);
1797
1798          prevKey = $.trim($(':focus').text().toLowerCase()).substring(0, 1);
1799
1800          if (prevKey != keyCodeMap[e.keyCode]) {
1801            count = 1;
1802            $(document).data('keycount', count);
1803          } else if (count >= keyIndex.length) {
1804            $(document).data('keycount', 0);
1805            if (count > keyIndex.length) count = 1;
1806          }
1807
1808          $items.eq(keyIndex[count - 1]).children('a').focus();
1809        }
1810
1811        // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu.
1812        if ((/(13|32)/.test(e.keyCode.toString(10)) || (/(^9$)/.test(e.keyCode.toString(10)) && that.options.selectOnTab)) && isActive) {
1813          if (!/(32)/.test(e.keyCode.toString(10))) e.preventDefault();
1814          if (!that.options.liveSearch) {
1815            var elem = $(':focus');
1816            elem.click();
1817            // Bring back focus for multiselects
1818            elem.focus();
1819            // Prevent screen from scrolling if the user hit the spacebar
1820            e.preventDefault();
1821            // Fixes spacebar selection of dropdown items in FF & IE
1822            $(document).data('spaceSelect', true);
1823          } else if (!/(32)/.test(e.keyCode.toString(10))) {
1824            that.$menuInner.find('.active a').click();
1825            $this.focus();
1826          }
1827          $(document).data('keycount', 0);
1828        }
1829
1830        if ((/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode.toString(10)) && !isActive)) {
1831          that.$menu.parent().removeClass('open');
1832          if (that.options.container) that.$newElement.removeClass('open');
1833          that.$button.focus();
1834        }
1835      },
1836
1837      mobile: function() {
1838        this.$element.addClass('mobile-device');
1839      },
1840
1841      refresh: function() {
1842        this.$lis = null;
1843        this.liObj = {};
1844        this.reloadLi();
1845        this.render();
1846        this.checkDisabled();
1847        this.liHeight(true);
1848        this.setStyle();
1849        this.setWidth();
1850        if (this.$lis) this.$searchbox.trigger('propertychange');
1851
1852        this.$element.trigger('refreshed.bs.select');
1853      },
1854
1855      hide: function() {
1856        this.$newElement.hide();
1857      },
1858
1859      show: function() {
1860        this.$newElement.show();
1861      },
1862
1863      remove: function() {
1864        this.$newElement.remove();
1865        this.$element.remove();
1866      },
1867
1868      destroy: function() {
1869        this.$newElement.before(this.$element).remove();
1870
1871        if (this.$bsContainer) {
1872          this.$bsContainer.remove();
1873        } else {
1874          this.$menu.remove();
1875        }
1876
1877        this.$element
1878          .off('.bs.select')
1879          .removeData('selectpicker')
1880          .removeClass('bs-select-hidden selectpicker');
1881      }
1882    };
1883
1884    // SELECTPICKER PLUGIN DEFINITION
1885    // ==============================
1886    function Plugin(option) {
1887      // get the args of the outer function..
1888      var args = arguments;
1889      // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them
1890      // to get lost/corrupted in android 2.3 and IE9 #715 #775
1891      var _option = option;
1892
1893      [].shift.apply(args);
1894
1895      var value;
1896      var chain = this.each(function() {
1897        var $this = $(this);
1898        if ($this.is('select')) {
1899          var data = $this.data('selectpicker'),
1900            options = typeof _option == 'object' && _option;
1901
1902          if (!data) {
1903            var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, $this.data(), options);
1904            config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), $this.data().template, options.template);
1905            $this.data('selectpicker', (data = new Selectpicker(this, config)));
1906          } else if (options) {
1907            for (var i in options) {
1908              if (options.hasOwnProperty(i)) {
1909                data.options[i] = options[i];
1910              }
1911            }
1912          }
1913
1914          if (typeof _option == 'string') {
1915            if (data[_option] instanceof Function) {
1916              value = data[_option].apply(data, args);
1917            } else {
1918              value = data.options[_option];
1919            }
1920          }
1921        }
1922      });
1923
1924      if (typeof value !== 'undefined') {
1925        //noinspection JSUnusedAssignment
1926        return value;
1927      } else {
1928        return chain;
1929      }
1930    }
1931
1932    var old = $.fn.selectpicker;
1933    $.fn.selectpicker = Plugin;
1934    $.fn.selectpicker.Constructor = Selectpicker;
1935
1936    // SELECTPICKER NO CONFLICT
1937    // ========================
1938    $.fn.selectpicker.noConflict = function() {
1939      $.fn.selectpicker = old;
1940      return this;
1941    };
1942
1943    $(document)
1944      .data('keycount', 0)
1945      .on('keydown.bs.select', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input', Selectpicker.prototype.keydown)
1946      .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input', function(e) {
1947        e.stopPropagation();
1948      });
1949
1950    // SELECTPICKER DATA-API
1951    // =====================
1952    $(window).on('load.bs.select.data-api', function() {
1953      $('.selectpicker').each(function() {
1954        var $selectpicker = $(this);
1955        Plugin.call($selectpicker, $selectpicker.data());
1956      })
1957    });
1958  })(jQuery);
1959
1960
1961}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.