bootstrap-select-rails 1.6.3 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);