PageSourceSearch

https://hbalansing.com/js/bootstrap-select/bootstrap-select.js

js hbalansing.com collected 2026-10-07 19:41:42 UTC 69,902 bytes, 1,896 lines download raw bytes

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

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.