bootstrap-select-rails 1.6.3 → 1.12.1

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