PageSourceSearch

https://seaboats.net/public/js/plugins/bootstrap-select.js?45032187

js seaboats.net collected 2026-09-24 22:49:18 UTC 58,680 bytes, 1,592 lines download raw bytes

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

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