bootstrap-select-rails 1.3.0.1 → 1.6.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e50e69dd81e893f3a23930cf8f29969c3aac337b
4
+ data.tar.gz: 8f6c90b9fce9fa026a72d6aaff1a6d9d0ea1d965
5
+ SHA512:
6
+ metadata.gz: a088e190043a5d63c4ef3908d6ab4d1423f2b1fca32dfff366c31eb011b927e6d6409e3e5b114202c5503198488684193b77cba3135e88d9438dba872855c463
7
+ data.tar.gz: 433f79a27a1bb4f214c2f3fbc7d9bc17ce04eea6e319bf75d627e963dca720d2084165cb6dec25b827d9c7e0196386000c96f3e53c688b85d594b702eb212004
data/README.md CHANGED
@@ -6,6 +6,12 @@ add to application.js and application.css something like
6
6
 
7
7
  //= require bootstrap-select
8
8
 
9
+ Also, you must require at least the *alert* and *dropdown* bootstrap components.
10
+ For example, if using
11
+ [bootstrap-sass](https://github.com/twbs/bootstrap-sass):
12
+
13
+ //= require bootstrap/alert
14
+ //= require bootstrap/dropdown
9
15
 
10
16
  ## Installation
11
17
 
@@ -1,7 +1,7 @@
1
1
  module Bootstrap
2
2
  module Select
3
3
  module Rails
4
- VERSION = "1.3.0.1"
4
+ VERSION = "1.6.2"
5
5
  end
6
6
  end
7
7
  end
@@ -1,557 +1,1145 @@
1
- !function($) {
2
- var Selectpicker = function(element, options, e) {
3
- if (e ) {
1
+ /*!
2
+ * Bootstrap-select v1.6.2 (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
+ (function ($) {
8
+ 'use strict';
9
+
10
+ // Case insensitive search
11
+ $.expr[':'].icontains = function (obj, index, meta) {
12
+ return icontains($(obj).text(), meta[3]);
13
+ };
14
+
15
+ // Case and accent insensitive search
16
+ $.expr[':'].aicontains = function (obj, index, meta) {
17
+ return icontains($(obj).data('normalizedText') || $(obj).text(), meta[3]);
18
+ };
19
+
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
+ }
30
+
31
+ /**
32
+ * Remove all diatrics from the given text.
33
+ * @access private
34
+ * @param {String} text
35
+ * @returns {String}
36
+ */
37
+ function normalizeToBase(text) {
38
+ var rExps = [
39
+ {re: /[\xC0-\xC6]/g, ch: "A"},
40
+ {re: /[\xE0-\xE6]/g, ch: "a"},
41
+ {re: /[\xC8-\xCB]/g, ch: "E"},
42
+ {re: /[\xE8-\xEB]/g, ch: "e"},
43
+ {re: /[\xCC-\xCF]/g, ch: "I"},
44
+ {re: /[\xEC-\xEF]/g, ch: "i"},
45
+ {re: /[\xD2-\xD6]/g, ch: "O"},
46
+ {re: /[\xF2-\xF6]/g, ch: "o"},
47
+ {re: /[\xD9-\xDC]/g, ch: "U"},
48
+ {re: /[\xF9-\xFC]/g, ch: "u"},
49
+ {re: /[\xC7-\xE7]/g, ch: "c"},
50
+ {re: /[\xD1]/g, ch: "N"},
51
+ {re: /[\xF1]/g, ch: "n"}
52
+ ];
53
+ $.each(rExps, function () {
54
+ text = text.replace(this.re, this.ch);
55
+ });
56
+ return text;
57
+ }
58
+
59
+ var Selectpicker = function (element, options, e) {
60
+ if (e) {
4
61
  e.stopPropagation();
5
62
  e.preventDefault();
6
63
  }
64
+
7
65
  this.$element = $(element);
8
66
  this.$newElement = null;
9
- this.button = null;
10
-
11
- //Merge defaults, options and data-attributes to make our options
12
- this.options = $.extend({}, $.fn.selectpicker.defaults, this.$element.data(), typeof options == 'object' && options);
13
-
14
- //If we have no title yet, check the attribute 'title' (this is missed by jq as its not a data-attribute
15
- if(this.options.title==null)
67
+ this.$button = null;
68
+ this.$menu = null;
69
+ this.$lis = null;
70
+ this.options = options;
71
+
72
+ // If we have no title yet, try to pull it from the html title attribute (jQuery doesnt' pick it up as it's not a
73
+ // data-attribute)
74
+ if (this.options.title === null) {
16
75
  this.options.title = this.$element.attr('title');
17
-
76
+ }
77
+
18
78
  //Expose public methods
19
79
  this.val = Selectpicker.prototype.val;
20
80
  this.render = Selectpicker.prototype.render;
21
81
  this.refresh = Selectpicker.prototype.refresh;
82
+ this.setStyle = Selectpicker.prototype.setStyle;
22
83
  this.selectAll = Selectpicker.prototype.selectAll;
23
84
  this.deselectAll = Selectpicker.prototype.deselectAll;
85
+ this.destroy = Selectpicker.prototype.remove;
86
+ this.remove = Selectpicker.prototype.remove;
87
+ this.show = Selectpicker.prototype.show;
88
+ this.hide = Selectpicker.prototype.hide;
89
+
24
90
  this.init();
25
91
  };
26
92
 
93
+ Selectpicker.VERSION = '1.6.2';
94
+
95
+ // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both.
96
+ Selectpicker.DEFAULTS = {
97
+ noneSelectedText: 'Nothing selected',
98
+ noneResultsText: 'No results match',
99
+ countSelectedText: function (numSelected, numTotal) {
100
+ return (numSelected == 1) ? "{0} item selected" : "{0} items selected";
101
+ },
102
+ maxOptionsText: function (numAll, numGroup) {
103
+ var arr = [];
104
+
105
+ arr[0] = (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)';
106
+ arr[1] = (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)';
107
+
108
+ return arr;
109
+ },
110
+ selectAllText: 'Select All',
111
+ deselectAllText: 'Deselect All',
112
+ multipleSeparator: ', ',
113
+ style: 'btn-default',
114
+ size: 'auto',
115
+ title: null,
116
+ selectedTextFormat: 'values',
117
+ width: false,
118
+ container: false,
119
+ hideDisabled: false,
120
+ showSubtext: false,
121
+ showIcon: true,
122
+ showContent: true,
123
+ dropupAuto: true,
124
+ header: false,
125
+ liveSearch: false,
126
+ actionsBox: false,
127
+ iconBase: 'glyphicon',
128
+ tickIcon: 'glyphicon-ok',
129
+ maxOptions: false,
130
+ mobile: false,
131
+ selectOnTab: false,
132
+ dropdownAlignRight: false,
133
+ searchAccentInsensitive: false
134
+ };
135
+
27
136
  Selectpicker.prototype = {
28
137
 
29
138
  constructor: Selectpicker,
30
139
 
31
- init: function (e) {
32
- if (!this.options.container) {
33
- this.$element.hide();
34
- } else {
35
- this.$element.css('visibility','hidden');
36
- };
37
- this.multiple = this.$element.prop('multiple');
38
- var classList = this.$element.attr('class') !== undefined ? this.$element.attr('class').split(/\s+/) : '';
39
- var id = this.$element.attr('id');
40
- this.$element.after( this.createView() );
41
- this.$newElement = this.$element.next('.bootstrap-select');
42
- if (this.options.container) {
43
- this.selectPosition();
44
- }
45
- this.button = this.$newElement.find('> button');
46
- if (id !== undefined) {
47
- var _this = this;
48
- this.button.attr('data-id', id);
49
- $('label[for="' + id + '"]').click(function(){
50
- _this.$newElement.find('button[data-id='+id+']').focus();
51
- })
52
- }
53
- for (var i = 0; i < classList.length; i++) {
54
- if(classList[i] != 'selectpicker') {
55
- this.$newElement.addClass(classList[i]);
56
- }
57
- }
58
- //If we are multiple, then add the show-tick class by default
59
- if(this.multiple) {
60
- this.$newElement.addClass('show-tick');
140
+ init: function () {
141
+ var that = this,
142
+ id = this.$element.attr('id');
143
+
144
+ this.$element.hide();
145
+ this.multiple = this.$element.prop('multiple');
146
+ this.autofocus = this.$element.prop('autofocus');
147
+ this.$newElement = this.createView();
148
+ this.$element.after(this.$newElement);
149
+ this.$menu = this.$newElement.find('> .dropdown-menu');
150
+ this.$button = this.$newElement.find('> button');
151
+ this.$searchbox = this.$newElement.find('input');
152
+
153
+ if (this.options.dropdownAlignRight)
154
+ this.$menu.addClass('dropdown-menu-right');
155
+
156
+ if (typeof id !== 'undefined') {
157
+ this.$button.attr('data-id', id);
158
+ $('label[for="' + id + '"]').click(function (e) {
159
+ e.preventDefault();
160
+ that.$button.focus();
161
+ });
61
162
  }
62
- this.button.addClass(this.options.style);
163
+
63
164
  this.checkDisabled();
64
- this.checkTabIndex();
65
165
  this.clickListener();
66
-
166
+ if (this.options.liveSearch) this.liveSearchListener();
67
167
  this.render();
68
- this.setSize();
168
+ this.liHeight();
169
+ this.setStyle();
170
+ this.setWidth();
171
+ if (this.options.container) this.selectPosition();
172
+ this.$menu.data('this', this);
173
+ this.$newElement.data('this', this);
174
+ if (this.options.mobile) this.mobile();
69
175
  },
70
176
 
71
- createDropdown: function() {
177
+ createDropdown: function () {
178
+ // Options
179
+ // If we are multiple, then add the show-tick class by default
180
+ var multiple = this.multiple ? ' show-tick' : '',
181
+ inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '',
182
+ autofocus = this.autofocus ? ' autofocus' : '',
183
+ btnSize = this.$element.parents().hasClass('form-group-lg') ? ' btn-lg' : (this.$element.parents().hasClass('form-group-sm') ? ' btn-sm' : '');
184
+ // Elements
185
+ var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>' + this.options.header + '</div>' : '';
186
+ var searchbox = this.options.liveSearch ? '<div class="bs-searchbox"><input type="text" class="input-block-level form-control" autocomplete="off" /></div>' : '';
187
+ var actionsbox = this.options.actionsBox ? '<div class="bs-actionsbox">' +
188
+ '<div class="btn-group btn-block">' +
189
+ '<button class="actions-btn bs-select-all btn btn-sm btn-default">' +
190
+ this.options.selectAllText +
191
+ '</button>' +
192
+ '<button class="actions-btn bs-deselect-all btn btn-sm btn-default">' +
193
+ this.options.deselectAllText +
194
+ '</button>' +
195
+ '</div>' +
196
+ '</div>' : '';
72
197
  var drop =
73
- "<div class='btn-group bootstrap-select'>" +
74
- "<button type='button' class='btn dropdown-toggle' data-toggle='dropdown'>" +
75
- "<span class='filter-option pull-left'></span>&nbsp;" +
76
- "<span class='caret'></span>" +
77
- "</button>" +
78
- "<ul class='dropdown-menu' role='menu'>" +
79
- "</ul>" +
80
- "</div>";
198
+ '<div class="btn-group bootstrap-select' + multiple + inputGroup + '">' +
199
+ '<button type="button" class="btn dropdown-toggle selectpicker' + btnSize + '" data-toggle="dropdown"' + autofocus + '>' +
200
+ '<span class="filter-option pull-left"></span>&nbsp;' +
201
+ '<span class="caret"></span>' +
202
+ '</button>' +
203
+ '<div class="dropdown-menu open">' +
204
+ header +
205
+ searchbox +
206
+ actionsbox +
207
+ '<ul class="dropdown-menu inner selectpicker" role="menu">' +
208
+ '</ul>' +
209
+ '</div>' +
210
+ '</div>';
81
211
 
82
212
  return $(drop);
83
213
  },
84
214
 
85
-
86
- createView: function() {
215
+ createView: function () {
87
216
  var $drop = this.createDropdown();
88
217
  var $li = this.createLi();
89
218
  $drop.find('ul').append($li);
90
219
  return $drop;
91
220
  },
92
-
93
- reloadLi: function() {
221
+
222
+ reloadLi: function () {
94
223
  //Remove all children.
95
224
  this.destroyLi();
96
225
  //Re build
97
- $li = this.createLi();
98
- this.$newElement.find('ul').append( $li );
226
+ var $li = this.createLi();
227
+ this.$menu.find('ul').append($li);
99
228
  },
100
-
101
- destroyLi:function() {
102
- this.$newElement.find('li').remove();
229
+
230
+ destroyLi: function () {
231
+ this.$menu.find('li').remove();
103
232
  },
104
233
 
105
- createLi: function() {
234
+ createLi: function () {
235
+ var that = this,
236
+ _li = [],
237
+ optID = 0;
106
238
 
107
- var _this = this;
108
- var _li = [];
109
- var _liA = [];
110
- var _liHtml = '';
111
-
112
- this.$element.find('option').each(function(){
113
- _li.push($(this).text());
114
- });
239
+ // Helper functions
240
+ /**
241
+ * @param content
242
+ * @param [index]
243
+ * @param [classes]
244
+ * @returns {string}
245
+ */
246
+ var generateLI = function (content, index, classes) {
247
+ return '<li' +
248
+ (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') +
249
+ (typeof index !== 'undefined' | null === index ? ' data-original-index="' + index + '"' : '') +
250
+ '>' + content + '</li>';
251
+ };
252
+
253
+ /**
254
+ * @param text
255
+ * @param [classes]
256
+ * @param [inline]
257
+ * @param [optgroup]
258
+ * @returns {string}
259
+ */
260
+ var generateA = function (text, classes, inline, optgroup) {
261
+ var normText = normalizeToBase($.trim($("<div/>").html(text).text()).replace(/\s\s+/g, ' '));
262
+ return '<a tabindex="0"' +
263
+ (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') +
264
+ (typeof inline !== 'undefined' ? ' style="' + inline + '"' : '') +
265
+ (typeof optgroup !== 'undefined' ? 'data-optgroup="' + optgroup + '"' : '') +
266
+ ' data-normalized-text="' + normText + '"' +
267
+ '>' + text +
268
+ '<span class="' + that.options.iconBase + ' ' + that.options.tickIcon + ' icon-ok check-mark"></span>' +
269
+ '</a>';
270
+ };
115
271
 
116
- this.$element.find('option').each(function(index) {
272
+ this.$element.find('option').each(function () {
117
273
  var $this = $(this);
118
274
 
119
- //Get the class and text for the option
120
- var optionClass = $this.attr("class") !== undefined ? $this.attr("class") : '';
121
- var text = $this.text();
122
- var subtext = $this.data('subtext') !== undefined ? '<small class="muted">'+$this.data('subtext')+'</small>' : '';
123
- var icon = $this.data('icon') !== undefined ? '<i class="'+$this.data('icon')+'"></i> ' : '';
124
- if ($this.is(':disabled') || $this.parent().is(':disabled')) {
125
- icon = '<span>'+icon+'</span>';
126
- }
127
-
128
- //Prepend any icon and append any subtext to the main text.
129
- text = icon + '<span class="text">' + text + subtext + '</span>';
130
-
131
- if (_this.options.hideDisabled && ($this.is(':disabled') || $this.parent().is(':disabled'))) {
132
- _liA.push('<a style="min-height: 0; padding: 0"></a>');
133
- } else if ($this.parent().is('optgroup') && $this.data('divider') != true) {
134
- if ($this.index() == 0) {
135
- //Get the opt group label
275
+ // Get the class and text for the option
276
+ var optionClass = $this.attr('class') || '',
277
+ inline = $this.attr('style'),
278
+ text = $this.data('content') ? $this.data('content') : $this.html(),
279
+ subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="muted text-muted">' + $this.data('subtext') + '</small>' : '',
280
+ icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '',
281
+ isDisabled = $this.is(':disabled') || $this.parent().is(':disabled'),
282
+ index = $this[0].index;
283
+ if (icon !== '' && isDisabled) {
284
+ icon = '<span>' + icon + '</span>';
285
+ }
286
+
287
+ if (!$this.data('content')) {
288
+ // Prepend any icon and append any subtext to the main text.
289
+ text = icon + '<span class="text">' + text + subtext + '</span>';
290
+ }
291
+
292
+ if (that.options.hideDisabled && isDisabled) {
293
+ return;
294
+ }
295
+
296
+ if ($this.parent().is('optgroup') && $this.data('divider') !== true) {
297
+ if ($this.index() === 0) { // Is it the first option of the optgroup?
298
+ optID += 1;
299
+
300
+ // Get the opt group label
136
301
  var label = $this.parent().attr('label');
137
- var labelSubtext = $this.parent().data('subtext') !== undefined ? '<small class="muted">'+$this.parent().data('subtext')+'</small>' : '';
138
- var labelIcon = $this.parent().data('icon') ? '<i class="'+$this.parent().data('icon')+'"></i> ' : '';
302
+ var labelSubtext = typeof $this.parent().data('subtext') !== 'undefined' ? '<small class="muted text-muted">' + $this.parent().data('subtext') + '</small>' : '';
303
+ var labelIcon = $this.parent().data('icon') ? '<span class="' + that.options.iconBase + ' ' + $this.parent().data('icon') + '"></span> ' : '';
139
304
  label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>';
140
-
141
- if ($this[0].index != 0) {
142
- _liA.push(
143
- '<div class="div-contain"><div class="divider"></div></div>'+
144
- '<dt>'+label+'</dt>'+
145
- _this.createA(text, "opt " + optionClass )
146
- );
147
- } else {
148
- _liA.push(
149
- '<dt>'+label+'</dt>'+
150
- _this.createA(text, "opt " + optionClass ));
305
+
306
+ if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown?
307
+ _li.push(generateLI('', null, 'divider'));
151
308
  }
152
- } else {
153
- _liA.push( _this.createA(text, "opt " + optionClass ) );
309
+
310
+ _li.push(generateLI(label, null, 'dropdown-header'));
154
311
  }
155
- } else if ($this.data('divider') == true) {
156
- _liA.push('<div class="div-contain"><div class="divider"></div></div>');
157
- } else if ($(this).data('hidden') == true) {
158
- _liA.push('');
312
+
313
+ _li.push(generateLI(generateA(text, 'opt ' + optionClass, inline, optID), index));
314
+ } else if ($this.data('divider') === true) {
315
+ _li.push(generateLI('', index, 'divider'));
316
+ } else if ($this.data('hidden') === true) {
317
+ _li.push(generateLI(generateA(text, optionClass, inline), index, 'hide is-hidden'));
159
318
  } else {
160
- _liA.push( _this.createA(text, optionClass ) );
319
+ _li.push(generateLI(generateA(text, optionClass, inline), index));
161
320
  }
162
321
  });
163
-
164
- if (_li.length > 0) {
165
- for (var i = 0; i < _li.length; i++) {
166
- var $option = this.$element.find('option').eq(i);
167
- _liHtml += "<li rel=" + i + ">" + _liA[i] + "</li>";
168
- }
169
- }
170
-
171
- //If we are not multiple, and we dont have a selected item, and we dont have a title, select the first element so something is set in the button
172
- if(!this.multiple && this.$element.find('option:selected').length==0 && !_this.options.title) {
322
+
323
+ //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
324
+ if (!this.multiple && this.$element.find('option:selected').length === 0 && !this.options.title) {
173
325
  this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected');
174
326
  }
175
-
176
- return $(_liHtml);
327
+
328
+ return $(_li.join(''));
177
329
  },
178
-
179
- createA:function(text, classes) {
180
- return '<a tabindex="0" class="'+classes+'">' +
181
- text +
182
- '<i class="icon-ok check-mark"></i>' +
183
- '</a>';
330
+
331
+ findLis: function () {
332
+ if (this.$lis == null) this.$lis = this.$menu.find('li');
333
+ return this.$lis;
184
334
  },
185
-
186
- render:function() {
187
- var _this = this;
335
+
336
+ /**
337
+ * @param [updateLi] defaults to true
338
+ */
339
+ render: function (updateLi) {
340
+ var that = this;
188
341
 
189
342
  //Update the LI to match the SELECT
190
- this.$element.find('option').each(function(index) {
191
- _this.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled') );
192
- _this.setSelected(index, $(this).is(':selected') );
193
- });
194
-
195
- var selectedItems = this.$element.find('option:selected').map(function(index,value) {
196
- if($(this).attr('title')!=undefined) {
197
- return $(this).attr('title');
343
+ if (updateLi !== false) {
344
+ this.$element.find('option').each(function (index) {
345
+ that.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled'));
346
+ that.setSelected(index, $(this).is(':selected'));
347
+ });
348
+ }
349
+
350
+ this.tabIndex();
351
+ var notDisabled = this.options.hideDisabled ? ':not([disabled])' : '';
352
+ var selectedItems = this.$element.find('option:selected' + notDisabled).map(function () {
353
+ var $this = $(this);
354
+ var icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : '';
355
+ var subtext;
356
+ if (that.options.showSubtext && $this.attr('data-subtext') && !that.multiple) {
357
+ subtext = ' <small class="muted text-muted">' + $this.data('subtext') + '</small>';
198
358
  } else {
199
- return $(this).text();
359
+ subtext = '';
360
+ }
361
+ if ($this.data('content') && that.options.showContent) {
362
+ return $this.data('content');
363
+ } else if (typeof $this.attr('title') !== 'undefined') {
364
+ return $this.attr('title');
365
+ } else {
366
+ return icon + $this.html() + subtext;
200
367
  }
201
368
  }).toArray();
202
-
369
+
203
370
  //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled
204
- //Convert all the values into a comma delimited string
205
- var title = !this.multiple ? selectedItems[0] : selectedItems.join(", ");
206
-
207
- //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..
208
- if(_this.multiple && _this.options.selectedTextFormat.indexOf('count') > -1) {
209
- var max = _this.options.selectedTextFormat.split(">");
210
- if( (max.length>1 && selectedItems.length > max[1]) || (max.length==1 && selectedItems.length>=2)) {
211
- title = selectedItems.length +' of ' + this.$element.find('option').length + ' selected';
212
- }
213
- }
214
-
371
+ //Convert all the values into a comma delimited string
372
+ var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator);
373
+
374
+ //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..
375
+ if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) {
376
+ var max = this.options.selectedTextFormat.split('>');
377
+ if ((max.length > 1 && selectedItems.length > max[1]) || (max.length == 1 && selectedItems.length >= 2)) {
378
+ notDisabled = this.options.hideDisabled ? ', [disabled]' : '';
379
+ var totalCount = this.$element.find('option').not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length,
380
+ tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText;
381
+ title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString());
382
+ }
383
+ }
384
+
385
+ this.options.title = this.$element.attr('title');
386
+
387
+ if (this.options.selectedTextFormat == 'static') {
388
+ title = this.options.title;
389
+ }
390
+
215
391
  //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text
216
- if(!title) {
217
- title = _this.options.title != undefined ? _this.options.title : _this.options.noneSelectedText;
392
+ if (!title) {
393
+ title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText;
394
+ }
395
+
396
+ this.$button.attr('title', $.trim($("<div/>").html(title).text()).replace(/\s\s+/g, ' '));
397
+ this.$newElement.find('.filter-option').html(title);
398
+ },
399
+
400
+ /**
401
+ * @param [style]
402
+ * @param [status]
403
+ */
404
+ setStyle: function (style, status) {
405
+ if (this.$element.attr('class')) {
406
+ this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|validate\[.*\]/gi, ''));
407
+ }
408
+
409
+ var buttonClass = style ? style : this.options.style;
410
+
411
+ if (status == 'add') {
412
+ this.$button.addClass(buttonClass);
413
+ } else if (status == 'remove') {
414
+ this.$button.removeClass(buttonClass);
415
+ } else {
416
+ this.$button.removeClass(this.options.style);
417
+ this.$button.addClass(buttonClass);
218
418
  }
219
-
220
- _this.$newElement.find('.filter-option').html( title );
221
419
  },
222
-
223
- setSize:function() {
224
- var _this = this;
225
- var menu = this.$newElement.find('.dropdown-menu');
226
- var menuA = menu.find('li > a');
227
- var liHeight = this.$newElement.addClass('open').find('.dropdown-menu li > a').outerHeight();
228
- this.$newElement.removeClass('open');
229
- var divHeight = menu.find('li .divider').outerHeight(true);
230
- var selectOffset_top = this.$newElement.offset().top;
231
- var selectHeight = this.$newElement.outerHeight();
232
- var menuPadding = parseInt(menu.css('padding-top')) + parseInt(menu.css('padding-bottom')) + parseInt(menu.css('border-top-width')) + parseInt(menu.css('border-bottom-width'));
233
- var notDisabled = this.options.hideDisabled ? ':not(.disabled)' : '';
420
+
421
+ liHeight: function () {
422
+ if (this.options.size === false) return;
423
+
424
+ var $selectClone = this.$menu.parent().clone().find('> .dropdown-toggle').prop('autofocus', false).end().appendTo('body'),
425
+ $menuClone = $selectClone.addClass('open').find('> .dropdown-menu'),
426
+ liHeight = $menuClone.find('li').not('.divider').not('.dropdown-header').filter(':visible').children('a').outerHeight(),
427
+ headerHeight = this.options.header ? $menuClone.find('.popover-title').outerHeight() : 0,
428
+ searchHeight = this.options.liveSearch ? $menuClone.find('.bs-searchbox').outerHeight() : 0,
429
+ actionsHeight = this.options.actionsBox ? $menuClone.find('.bs-actionsbox').outerHeight() : 0;
430
+
431
+ $selectClone.remove();
432
+
433
+ this.$newElement
434
+ .data('liHeight', liHeight)
435
+ .data('headerHeight', headerHeight)
436
+ .data('searchHeight', searchHeight)
437
+ .data('actionsHeight', actionsHeight);
438
+ },
439
+
440
+ setSize: function () {
441
+ this.findLis();
442
+ var that = this,
443
+ menu = this.$menu,
444
+ menuInner = menu.find('.inner'),
445
+ selectHeight = this.$newElement.outerHeight(),
446
+ liHeight = this.$newElement.data('liHeight'),
447
+ headerHeight = this.$newElement.data('headerHeight'),
448
+ searchHeight = this.$newElement.data('searchHeight'),
449
+ actionsHeight = this.$newElement.data('actionsHeight'),
450
+ divHeight = this.$lis.filter('.divider').outerHeight(true),
451
+ menuPadding = parseInt(menu.css('padding-top')) +
452
+ parseInt(menu.css('padding-bottom')) +
453
+ parseInt(menu.css('border-top-width')) +
454
+ parseInt(menu.css('border-bottom-width')),
455
+ notDisabled = this.options.hideDisabled ? ', .disabled' : '',
456
+ $window = $(window),
457
+ menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2,
458
+ menuHeight,
459
+ selectOffsetTop,
460
+ selectOffsetBot,
461
+ posVert = function () {
462
+ // JQuery defines a scrollTop function, but in pure JS it's a property
463
+ //noinspection JSValidateTypes
464
+ selectOffsetTop = that.$newElement.offset().top - $window.scrollTop();
465
+ selectOffsetBot = $window.height() - selectOffsetTop - selectHeight;
466
+ };
467
+ posVert();
468
+ if (this.options.header) menu.css('padding-top', 0);
469
+
234
470
  if (this.options.size == 'auto') {
235
- function getSize() {
236
- var selectOffset_top_scroll = selectOffset_top - $(window).scrollTop();
237
- var windowHeight = window.innerHeight;
238
- var menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2;
239
- var selectOffset_bot = windowHeight - selectOffset_top_scroll - selectHeight - menuExtras;
240
- menuHeight = selectOffset_bot;
241
- if (_this.$newElement.hasClass('dropup')) {
242
- menuHeight = selectOffset_top_scroll - menuExtras;
471
+ var getSize = function () {
472
+ var minHeight,
473
+ lisVis = that.$lis.not('.hide');
474
+
475
+ posVert();
476
+ menuHeight = selectOffsetBot - menuExtras;
477
+
478
+ if (that.options.dropupAuto) {
479
+ that.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && ((menuHeight - menuExtras) < menu.height()));
480
+ }
481
+ if (that.$newElement.hasClass('dropup')) {
482
+ menuHeight = selectOffsetTop - menuExtras;
243
483
  }
244
- if ((menu.find('li').length + menu.find('dt').length) > 3) {
245
- minHeight = liHeight*3 + menuExtras - 2;
484
+
485
+ if ((lisVis.length + lisVis.filter('.dropdown-header').length) > 3) {
486
+ minHeight = liHeight * 3 + menuExtras - 2;
246
487
  } else {
247
488
  minHeight = 0;
248
489
  }
249
- menu.css({'max-height' : menuHeight + 'px', 'overflow-y' : 'auto', 'min-height' : minHeight + 'px'});
250
- }
490
+
491
+ menu.css({'max-height': menuHeight + 'px', 'overflow': 'hidden', 'min-height': minHeight + headerHeight + searchHeight + actionsHeight + 'px'});
492
+ menuInner.css({'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - menuPadding + 'px', 'overflow-y': 'auto', 'min-height': Math.max(minHeight - menuPadding, 0) + 'px'});
493
+ };
251
494
  getSize();
252
- $(window).resize(getSize);
253
- $(window).scroll(getSize);
254
- } else if (this.options.size && this.options.size != 'auto' && menu.find('li'+notDisabled).length > this.options.size) {
255
- var optIndex = menu.find("li"+notDisabled+" > *").filter(':not(.div-contain)').slice(0,this.options.size).last().parent().index();
256
- var divLength = menu.find("li").slice(0,optIndex + 1).find('.div-contain').length;
257
- menuHeight = liHeight*this.options.size + divLength*divHeight + menuPadding;
258
- menu.css({'max-height' : menuHeight + 'px', 'overflow-y' : 'auto'});
495
+ this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize);
496
+ $(window).off('resize.getSize').on('resize.getSize', getSize);
497
+ $(window).off('scroll.getSize').on('scroll.getSize', getSize);
498
+ } else if (this.options.size && this.options.size != 'auto' && menu.find('li' + notDisabled).length > this.options.size) {
499
+ var optIndex = this.$lis.not('.divider' + notDisabled).find(' > *').slice(0, this.options.size).last().parent().index();
500
+ var divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length;
501
+ menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding;
502
+ if (that.options.dropupAuto) {
503
+ //noinspection JSUnusedAssignment
504
+ this.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && (menuHeight < menu.height()));
505
+ }
506
+ menu.css({'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + 'px', 'overflow': 'hidden'});
507
+ menuInner.css({'max-height': menuHeight - menuPadding + 'px', 'overflow-y': 'auto'});
508
+ }
509
+ },
510
+
511
+ setWidth: function () {
512
+ if (this.options.width == 'auto') {
513
+ this.$menu.css('min-width', '0');
514
+
515
+ // Get correct width if element hidden
516
+ var selectClone = this.$newElement.clone().appendTo('body');
517
+ var ulWidth = selectClone.find('> .dropdown-menu').css('width');
518
+ var btnWidth = selectClone.css('width', 'auto').find('> button').css('width');
519
+ selectClone.remove();
520
+
521
+ // Set width to whatever's larger, button title or longest option
522
+ this.$newElement.css('width', Math.max(parseInt(ulWidth), parseInt(btnWidth)) + 'px');
523
+ } else if (this.options.width == 'fit') {
524
+ // Remove inline min-width so width can be changed from 'auto'
525
+ this.$menu.css('min-width', '');
526
+ this.$newElement.css('width', '').addClass('fit-width');
527
+ } else if (this.options.width) {
528
+ // Remove inline min-width so width can be changed from 'auto'
529
+ this.$menu.css('min-width', '');
530
+ this.$newElement.css('width', this.options.width);
531
+ } else {
532
+ // Remove inline min-width/width so width can be changed
533
+ this.$menu.css('min-width', '');
534
+ this.$newElement.css('width', '');
259
535
  }
536
+ // Remove fit-width class if width is changed programmatically
537
+ if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') {
538
+ this.$newElement.removeClass('fit-width');
539
+ }
540
+ },
260
541
 
261
- //Set width of select
262
- if (this.options.width == 'auto') {
263
- this.$newElement.find('.dropdown-menu').css('min-width','0');
264
- var ulWidth = this.$newElement.find('.dropdown-menu').css('width');
265
- this.$newElement.css('width',ulWidth);
266
- if (this.options.container) {
267
- this.$element.css('width',ulWidth);
542
+ selectPosition: function () {
543
+ var that = this,
544
+ drop = '<div />',
545
+ $drop = $(drop),
546
+ pos,
547
+ actualHeight,
548
+ getPlacement = function ($element) {
549
+ $drop.addClass($element.attr('class').replace(/form-control/gi, '')).toggleClass('dropup', $element.hasClass('dropup'));
550
+ pos = $element.offset();
551
+ actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight;
552
+ $drop.css({'top': pos.top + actualHeight, 'left': pos.left, 'width': $element[0].offsetWidth, 'position': 'absolute'});
553
+ };
554
+ this.$newElement.on('click', function () {
555
+ if (that.isDisabled()) {
556
+ return;
268
557
  }
269
- } else if (this.options.width && this.options.width != 'auto') {
270
- this.$newElement.css('width',this.options.width);
271
- if (this.options.container) {
272
- this.$element.css('width',this.options.width);
558
+ getPlacement($(this));
559
+ $drop.appendTo(that.options.container);
560
+ $drop.toggleClass('open', !$(this).hasClass('open'));
561
+ $drop.append(that.$menu);
562
+ });
563
+ $(window).resize(function () {
564
+ getPlacement(that.$newElement);
565
+ });
566
+ $(window).on('scroll', function () {
567
+ getPlacement(that.$newElement);
568
+ });
569
+ $('html').on('click', function (e) {
570
+ if ($(e.target).closest(that.$newElement).length < 1) {
571
+ $drop.removeClass('open');
273
572
  }
274
- }
573
+ });
275
574
  },
276
575
 
277
- selectPosition:function() {
278
- var selectElementTop = this.$element.offset().top;
279
- var selectElementLeft = this.$element.offset().left;
280
- this.$newElement.appendTo(this.options.container);
281
- this.$newElement.css({'position':'absolute', 'top':selectElementTop+'px', 'left':selectElementLeft+'px'});
576
+ setSelected: function (index, selected) {
577
+ this.findLis();
578
+ this.$lis.filter('[data-original-index="' + index + '"]').toggleClass('selected', selected);
282
579
  },
283
580
 
284
- refresh:function() {
285
- this.reloadLi();
286
- this.render();
287
- this.setSize();
288
- this.checkDisabled();
289
- if (this.options.container) {
290
- this.selectPosition();
291
- }
292
- },
293
-
294
- setSelected:function(index, selected) {
295
- if(selected) {
296
- this.$newElement.find('li').eq(index).addClass('selected');
297
- } else {
298
- this.$newElement.find('li').eq(index).removeClass('selected');
299
- }
300
- },
301
-
302
- setDisabled:function(index, disabled) {
303
- if(disabled) {
304
- this.$newElement.find('li').eq(index).addClass('disabled').find('a').attr('href','#').attr('tabindex',-1);
581
+ setDisabled: function (index, disabled) {
582
+ this.findLis();
583
+ if (disabled) {
584
+ this.$lis.filter('[data-original-index="' + index + '"]').addClass('disabled').find('a').attr('href', '#').attr('tabindex', -1);
305
585
  } else {
306
- this.$newElement.find('li').eq(index).removeClass('disabled').find('a').removeAttr('href').attr('tabindex',0);
586
+ this.$lis.filter('[data-original-index="' + index + '"]').removeClass('disabled').find('a').removeAttr('href').attr('tabindex', 0);
307
587
  }
308
588
  },
309
589
 
310
- isDisabled: function() {
311
- return this.$element.is(':disabled') || this.$element.attr('readonly');
590
+ isDisabled: function () {
591
+ return this.$element.is(':disabled');
312
592
  },
313
-
314
- checkDisabled: function() {
593
+
594
+ checkDisabled: function () {
595
+ var that = this;
596
+
315
597
  if (this.isDisabled()) {
316
- this.button.addClass('disabled');
317
- this.button.click(function(e) {
318
- e.preventDefault();
319
- });
320
- this.button.attr('tabindex','-1');
321
- } else if (!this.isDisabled() && this.button.hasClass('disabled')) {
322
- this.button.removeClass('disabled');
323
- this.button.click(function() {
324
- return true;
325
- });
326
- this.button.removeAttr('tabindex');
598
+ this.$button.addClass('disabled').attr('tabindex', -1);
599
+ } else {
600
+ if (this.$button.hasClass('disabled')) {
601
+ this.$button.removeClass('disabled');
602
+ }
603
+
604
+ if (this.$button.attr('tabindex') == -1) {
605
+ if (!this.$element.data('tabindex')) this.$button.removeAttr('tabindex');
606
+ }
327
607
  }
608
+
609
+ this.$button.click(function () {
610
+ return !that.isDisabled();
611
+ });
328
612
  },
329
-
330
- checkTabIndex: function() {
613
+
614
+ tabIndex: function () {
331
615
  if (this.$element.is('[tabindex]')) {
332
- var tabindex = this.$element.attr("tabindex");
333
- this.button.attr('tabindex', tabindex);
616
+ this.$element.data('tabindex', this.$element.attr('tabindex'));
617
+ this.$button.attr('tabindex', this.$element.data('tabindex'));
334
618
  }
335
619
  },
336
-
337
- clickListener: function() {
338
- var _this = this;
339
-
340
- $('body').on('touchstart.dropdown', '.dropdown-menu', function (e) { e.stopPropagation(); });
341
-
342
- this.$newElement.on('click', 'li a', function(e){
343
- var clickedIndex = $(this).parent().index(),
344
- $this = $(this).parent(),
345
- $select = $this.parents('.bootstrap-select'),
346
- prevValue = _this.$element.val();
347
-
348
- //Dont close on multi choice menu
349
- if(_this.multiple) {
620
+
621
+ clickListener: function () {
622
+ var that = this;
623
+
624
+ this.$newElement.on('touchstart.dropdown', '.dropdown-menu', function (e) {
625
+ e.stopPropagation();
626
+ });
627
+
628
+ this.$newElement.on('click', function () {
629
+ that.setSize();
630
+ if (!that.options.liveSearch && !that.multiple) {
631
+ setTimeout(function () {
632
+ that.$menu.find('.selected a').focus();
633
+ }, 10);
634
+ }
635
+ });
636
+
637
+ this.$menu.on('click', 'li a', function (e) {
638
+ var $this = $(this),
639
+ clickedIndex = $this.parent().data('originalIndex'),
640
+ prevValue = that.$element.val(),
641
+ prevIndex = that.$element.prop('selectedIndex');
642
+
643
+ // Don't close on multi choice menu
644
+ if (that.multiple) {
350
645
  e.stopPropagation();
351
646
  }
352
-
647
+
353
648
  e.preventDefault();
354
-
355
- //Dont run if we have been disabled
356
- if (_this.$element.not(':disabled') && !$(this).parent().hasClass('disabled')){
357
- //Deselect all others if not multi select box
358
- if (!_this.multiple) {
359
- _this.$element.find('option').removeAttr('selected');
360
- _this.$element.find('option').eq(clickedIndex).prop('selected', true).attr('selected', 'selected');
361
- }
362
- //Else toggle the one we have chosen if we are multi select.
363
- else {
364
- var selected = _this.$element.find('option').eq(clickedIndex).prop('selected');
365
-
366
- if(selected) {
367
- _this.$element.find('option').eq(clickedIndex).removeAttr('selected');
368
- } else {
369
- _this.$element.find('option').eq(clickedIndex).prop('selected', true).attr('selected', 'selected');
649
+
650
+ //Don't run if we have been disabled
651
+ if (!that.isDisabled() && !$this.parent().hasClass('disabled')) {
652
+ var $options = that.$element.find('option'),
653
+ $option = $options.eq(clickedIndex),
654
+ state = $option.prop('selected'),
655
+ $optgroup = $option.parent('optgroup'),
656
+ maxOptions = that.options.maxOptions,
657
+ maxOptionsGrp = $optgroup.data('maxOptions') || false;
658
+
659
+ if (!that.multiple) { // Deselect all others if not multi select box
660
+ $options.prop('selected', false);
661
+ $option.prop('selected', true);
662
+ that.$menu.find('.selected').removeClass('selected');
663
+ that.setSelected(clickedIndex, true);
664
+ } else { // Toggle the one we have chosen if we are multi select.
665
+ $option.prop('selected', !state);
666
+ that.setSelected(clickedIndex, !state);
667
+ $this.blur();
668
+
669
+ if ((maxOptions !== false) || (maxOptionsGrp !== false)) {
670
+ var maxReached = maxOptions < $options.filter(':selected').length,
671
+ maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length;
672
+
673
+ if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) {
674
+ if (maxOptions && maxOptions == 1) {
675
+ $options.prop('selected', false);
676
+ $option.prop('selected', true);
677
+ that.$menu.find('.selected').removeClass('selected');
678
+ that.setSelected(clickedIndex, true);
679
+ } else if (maxOptionsGrp && maxOptionsGrp == 1) {
680
+ $optgroup.find('option:selected').prop('selected', false);
681
+ $option.prop('selected', true);
682
+ var optgroupID = $this.data('optgroup');
683
+
684
+ that.$menu.find('.selected').has('a[data-optgroup="' + optgroupID + '"]').removeClass('selected');
685
+
686
+ that.setSelected(clickedIndex, true);
687
+ } else {
688
+ var maxOptionsArr = (typeof that.options.maxOptionsText === 'function') ?
689
+ that.options.maxOptionsText(maxOptions, maxOptionsGrp) : that.options.maxOptionsText,
690
+ maxTxt = maxOptionsArr[0].replace('{n}', maxOptions),
691
+ maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp),
692
+ $notify = $('<div class="notify"></div>');
693
+ // If {var} is set in array, replace it
694
+ /** @deprecated */
695
+ if (maxOptionsArr[2]) {
696
+ maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]);
697
+ maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]);
698
+ }
699
+
700
+ $option.prop('selected', false);
701
+
702
+ that.$menu.append($notify);
703
+
704
+ if (maxOptions && maxReached) {
705
+ $notify.append($('<div>' + maxTxt + '</div>'));
706
+ that.$element.trigger('maxReached.bs.select');
707
+ }
708
+
709
+ if (maxOptionsGrp && maxReachedGrp) {
710
+ $notify.append($('<div>' + maxTxtGrp + '</div>'));
711
+ that.$element.trigger('maxReachedGrp.bs.select');
712
+ }
713
+
714
+ setTimeout(function () {
715
+ that.setSelected(clickedIndex, false);
716
+ }, 10);
717
+
718
+ $notify.delay(750).fadeOut(300, function () {
719
+ $(this).remove();
720
+ });
721
+ }
722
+ }
370
723
  }
371
724
  }
372
-
373
-
374
- $select.find('.filter-option').html($this.text());
375
- $select.find('button').focus();
725
+
726
+ if (!that.multiple) {
727
+ that.$button.focus();
728
+ } else if (that.options.liveSearch) {
729
+ that.$searchbox.focus();
730
+ }
376
731
 
377
732
  // Trigger select 'change'
378
- if (prevValue != _this.$element.val()) {
379
- _this.$element.trigger('change');
733
+ if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) {
734
+ that.$element.change();
380
735
  }
736
+ }
737
+ });
381
738
 
382
- _this.render();
739
+ this.$menu.on('click', 'li.disabled a, .popover-title, .popover-title :not(.close)', function (e) {
740
+ if (e.target == this) {
741
+ e.preventDefault();
742
+ e.stopPropagation();
743
+ if (!that.options.liveSearch) {
744
+ that.$button.focus();
745
+ } else {
746
+ that.$searchbox.focus();
747
+ }
383
748
  }
749
+ });
384
750
 
751
+ this.$menu.on('click', 'li.divider, li.dropdown-header', function (e) {
752
+ e.preventDefault();
753
+ e.stopPropagation();
754
+ if (!that.options.liveSearch) {
755
+ that.$button.focus();
756
+ } else {
757
+ that.$searchbox.focus();
758
+ }
759
+ });
760
+
761
+ this.$menu.on('click', '.popover-title .close', function () {
762
+ that.$button.focus();
763
+ });
764
+
765
+ this.$searchbox.on('click', function (e) {
766
+ e.stopPropagation();
385
767
  });
386
-
387
- this.$newElement.on('click', 'li.disabled a, li dt, li .div-contain', function(e) {
768
+
769
+
770
+ this.$menu.on('click', '.actions-btn', function (e) {
771
+ if (that.options.liveSearch) {
772
+ that.$searchbox.focus();
773
+ } else {
774
+ that.$button.focus();
775
+ }
776
+
388
777
  e.preventDefault();
389
778
  e.stopPropagation();
390
- $select = $(this).parent().parents('.bootstrap-select');
391
- $select.find('button').focus();
779
+
780
+ if ($(this).is('.bs-select-all')) {
781
+ that.selectAll();
782
+ } else {
783
+ that.deselectAll();
784
+ }
785
+ that.$element.change();
392
786
  });
393
787
 
394
- this.$element.on('change', function(e) {
395
- _this.render();
788
+ this.$element.change(function () {
789
+ that.render(false);
396
790
  });
397
791
  },
398
-
399
- val:function(value) {
400
-
401
- if(value!=undefined) {
402
- this.$element.val( value );
403
-
404
- this.$element.trigger('change');
792
+
793
+ liveSearchListener: function () {
794
+ var that = this,
795
+ no_results = $('<li class="no-results"></li>');
796
+
797
+ this.$newElement.on('click.dropdown.data-api', function () {
798
+ that.$menu.find('.active').removeClass('active');
799
+ if (!!that.$searchbox.val()) {
800
+ that.$searchbox.val('');
801
+ that.$lis.not('.is-hidden').removeClass('hide');
802
+ if (!!no_results.parent().length) no_results.remove();
803
+ }
804
+ if (!that.multiple) that.$menu.find('.selected').addClass('active');
805
+ setTimeout(function () {
806
+ that.$searchbox.focus();
807
+ }, 10);
808
+ });
809
+
810
+ this.$searchbox.on('input propertychange', function () {
811
+ if (that.$searchbox.val()) {
812
+
813
+ if (that.options.searchAccentInsensitive) {
814
+ that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':aicontains(' + normalizeToBase(that.$searchbox.val()) + ')').parent().addClass('hide');
815
+ } else {
816
+ that.$lis.not('.is-hidden').removeClass('hide').find('a').not(':icontains(' + that.$searchbox.val() + ')').parent().addClass('hide');
817
+ }
818
+
819
+ if (!that.$menu.find('li').filter(':visible:not(.no-results)').length) {
820
+ if (!!no_results.parent().length) no_results.remove();
821
+ no_results.html(that.options.noneResultsText + ' "' + that.$searchbox.val() + '"').show();
822
+ that.$menu.find('li').last().after(no_results);
823
+ } else if (!!no_results.parent().length) {
824
+ no_results.remove();
825
+ }
826
+
827
+ } else {
828
+ that.$lis.not('.is-hidden').removeClass('hide');
829
+ if (!!no_results.parent().length) no_results.remove();
830
+ }
831
+
832
+ that.$menu.find('li.active').removeClass('active');
833
+ that.$menu.find('li').filter(':visible:not(.divider)').eq(0).addClass('active').find('a').focus();
834
+ $(this).focus();
835
+ });
836
+
837
+ this.$menu.on('mouseenter', 'a', function (e) {
838
+ that.$menu.find('.active').removeClass('active');
839
+ $(e.currentTarget).parent().not('.disabled').addClass('active');
840
+ });
841
+
842
+ this.$menu.on('mouseleave', 'a', function () {
843
+ that.$menu.find('.active').removeClass('active');
844
+ });
845
+ },
846
+
847
+ val: function (value) {
848
+ if (typeof value !== 'undefined') {
849
+ this.$element.val(value);
850
+ this.render();
851
+
405
852
  return this.$element;
406
853
  } else {
407
854
  return this.$element.val();
408
855
  }
409
856
  },
410
-
411
- selectAll:function() {
412
- this.$element.find('option').prop('selected', true).attr('selected', 'selected');
413
- this.render();
857
+
858
+ selectAll: function () {
859
+ this.findLis();
860
+ this.$lis.not('.divider').not('.disabled').not('.selected').filter(':visible').find('a').click();
414
861
  },
415
-
416
- deselectAll:function() {
417
- this.$element.find('option').prop('selected', false).removeAttr('selected');
418
- this.render();
862
+
863
+ deselectAll: function () {
864
+ this.findLis();
865
+ this.$lis.not('.divider').not('.disabled').filter('.selected').filter(':visible').find('a').click();
419
866
  },
420
867
 
421
868
  keydown: function (e) {
422
- var $this,
423
- $items,
424
- $parent,
869
+ var $this,
870
+ $items,
871
+ $parent,
425
872
  index,
426
873
  next,
427
874
  first,
428
875
  last,
429
876
  prev,
430
- nextPrev
431
-
877
+ nextPrev,
878
+ that,
879
+ prevIndex,
880
+ isActive,
881
+ keyCodeMap = {
882
+ 32: ' ', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 56: '8', 57: '9', 59: ';',
883
+ 65: 'a', 66: 'b', 67: 'c', 68: 'd', 69: 'e', 70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j', 75: 'k', 76: 'l',
884
+ 77: 'm', 78: 'n', 79: 'o', 80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't', 85: 'u', 86: 'v', 87: 'w', 88: 'x',
885
+ 89: 'y', 90: 'z', 96: '0', 97: '1', 98: '2', 99: '3', 100: '4', 101: '5', 102: '6', 103: '7', 104: '8', 105: '9'
886
+ };
887
+
432
888
  $this = $(this);
433
-
889
+
434
890
  $parent = $this.parent();
435
-
436
- $items = $('[role=menu] li:not(.divider):visible a', $parent);
437
-
891
+
892
+ if ($this.is('input')) $parent = $this.parent().parent();
893
+
894
+ that = $parent.data('this');
895
+
896
+ if (that.options.liveSearch) $parent = $this.parent().parent();
897
+
898
+ if (that.options.container) $parent = that.$menu;
899
+
900
+ $items = $('[role=menu] li a', $parent);
901
+
902
+ isActive = that.$menu.parent().hasClass('open');
903
+
904
+ if (!isActive && /([0-9]|[A-z])/.test(String.fromCharCode(e.keyCode))) {
905
+ if (!that.options.container) {
906
+ that.setSize();
907
+ that.$menu.parent().addClass('open');
908
+ isActive = true;
909
+ } else {
910
+ that.$newElement.trigger('click');
911
+ }
912
+ that.$searchbox.focus();
913
+ }
914
+
915
+ if (that.options.liveSearch) {
916
+ if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && that.$menu.find('.active').length === 0) {
917
+ e.preventDefault();
918
+ that.$menu.parent().removeClass('open');
919
+ that.$button.focus();
920
+ }
921
+ $items = $('[role=menu] li:not(.divider):not(.dropdown-header):visible', $parent);
922
+ if (!$this.val() && !/(38|40)/.test(e.keyCode.toString(10))) {
923
+ if ($items.filter('.active').length === 0) {
924
+ if (that.options.searchAccentInsensitive) {
925
+ $items = that.$newElement.find('li').filter(':aicontains(' + normalizeToBase(keyCodeMap[e.keyCode]) + ')');
926
+ } else {
927
+ $items = that.$newElement.find('li').filter(':icontains(' + keyCodeMap[e.keyCode] + ')');
928
+ }
929
+ }
930
+ }
931
+ }
932
+
438
933
  if (!$items.length) return;
439
-
440
- if (/(38|40)/.test(e.keyCode)) {
441
934
 
935
+ if (/(38|40)/.test(e.keyCode.toString(10))) {
442
936
  index = $items.index($items.filter(':focus'));
443
-
444
- first = $items.parent(':not(.disabled)').first().index();
445
- last = $items.parent(':not(.disabled)').last().index();
446
- next = $items.eq(index).parent().nextAll(':not(.disabled)').eq(0).index();
447
- prev = $items.eq(index).parent().prevAll(':not(.disabled)').eq(0).index();
448
- nextPrev = $items.eq(next).parent().prevAll(':not(.disabled)').eq(0).index();
937
+ first = $items.parent(':not(.disabled):visible').first().index();
938
+ last = $items.parent(':not(.disabled):visible').last().index();
939
+ next = $items.eq(index).parent().nextAll(':not(.disabled):visible').eq(0).index();
940
+ prev = $items.eq(index).parent().prevAll(':not(.disabled):visible').eq(0).index();
941
+ nextPrev = $items.eq(next).parent().prevAll(':not(.disabled):visible').eq(0).index();
942
+
943
+ if (that.options.liveSearch) {
944
+ $items.each(function (i) {
945
+ if ($(this).is(':not(.disabled)')) {
946
+ $(this).data('index', i);
947
+ }
948
+ });
949
+ index = $items.index($items.filter('.active'));
950
+ first = $items.filter(':not(.disabled):visible').first().data('index');
951
+ last = $items.filter(':not(.disabled):visible').last().data('index');
952
+ next = $items.eq(index).nextAll(':not(.disabled):visible').eq(0).data('index');
953
+ prev = $items.eq(index).prevAll(':not(.disabled):visible').eq(0).data('index');
954
+ nextPrev = $items.eq(next).prevAll(':not(.disabled):visible').eq(0).data('index');
955
+ }
956
+
957
+ prevIndex = $this.data('prevIndex');
449
958
 
450
959
  if (e.keyCode == 38) {
960
+ if (that.options.liveSearch) index -= 1;
451
961
  if (index != nextPrev && index > prev) index = prev;
452
962
  if (index < first) index = first;
963
+ if (index == prevIndex) index = last;
453
964
  }
454
-
965
+
455
966
  if (e.keyCode == 40) {
967
+ if (that.options.liveSearch) index += 1;
968
+ if (index == -1) index = 0;
456
969
  if (index != nextPrev && index < next) index = next;
457
970
  if (index > last) index = last;
971
+ if (index == prevIndex) index = first;
458
972
  }
459
-
460
- $items.eq(index).focus()
461
- } else {
462
- var keyCodeMap = {
463
- 48:"0", 49:"1", 50:"2", 51:"3", 52:"4", 53:"5", 54:"6", 55:"7", 56:"8", 57:"9", 59:";",
464
- 65:"a", 66:"b", 67:"c", 68:"d", 69:"e", 70:"f", 71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l",
465
- 77:"m", 78:"n", 79:"o", 80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v", 87:"w", 88:"x", 89:"y", 90:"z",
466
- 96:"0", 97:"1", 98:"2", 99:"3", 100:"4", 101:"5", 102:"6", 103:"7", 104:"8", 105:"9"
973
+
974
+ $this.data('prevIndex', index);
975
+
976
+ if (!that.options.liveSearch) {
977
+ $items.eq(index).focus();
978
+ } else {
979
+ e.preventDefault();
980
+ if (!$this.is('.dropdown-toggle')) {
981
+ $items.removeClass('active');
982
+ $items.eq(index).addClass('active').find('a').focus();
983
+ $this.focus();
984
+ }
467
985
  }
468
-
469
- var keyIndex = [];
470
986
 
471
- $items.each(function() {
987
+ } else if (!$this.is('input')) {
988
+ var keyIndex = [],
989
+ count,
990
+ prevKey;
991
+
992
+ $items.each(function () {
472
993
  if ($(this).parent().is(':not(.disabled)')) {
473
- if ($.trim($(this).text().toLowerCase()).substring(0,1) == keyCodeMap[e.keyCode]) {
994
+ if ($.trim($(this).text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) {
474
995
  keyIndex.push($(this).parent().index());
475
- }
996
+ }
476
997
  }
477
998
  });
478
999
 
479
- var count = $(document).data('keycount');
1000
+ count = $(document).data('keycount');
480
1001
  count++;
481
- $(document).data('keycount',count);
482
-
483
- var prevKey = $.trim($(':focus').text().toLowerCase()).substring(0,1);
484
-
1002
+ $(document).data('keycount', count);
1003
+
1004
+ prevKey = $.trim($(':focus').text().toLowerCase()).substring(0, 1);
1005
+
485
1006
  if (prevKey != keyCodeMap[e.keyCode]) {
486
1007
  count = 1;
487
- $(document).data('keycount',count);
1008
+ $(document).data('keycount', count);
488
1009
  } else if (count >= keyIndex.length) {
489
- $(document).data('keycount',0);
1010
+ $(document).data('keycount', 0);
1011
+ if (count > keyIndex.length) count = 1;
490
1012
  }
491
-
1013
+
492
1014
  $items.eq(keyIndex[count - 1]).focus();
493
1015
  }
494
1016
 
495
- if (/(13)/.test(e.keyCode)) {
496
- $(':focus').click();
497
- $parent.addClass('open');
498
- $(document).data('keycount',0);
1017
+ // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu.
1018
+ if ((/(13|32)/.test(e.keyCode.toString(10)) || (/(^9$)/.test(e.keyCode.toString(10)) && that.options.selectOnTab)) && isActive) {
1019
+ if (!/(32)/.test(e.keyCode.toString(10))) e.preventDefault();
1020
+ if (!that.options.liveSearch) {
1021
+ $(':focus').click();
1022
+ } else if (!/(32)/.test(e.keyCode.toString(10))) {
1023
+ that.$menu.find('.active a').click();
1024
+ $this.focus();
1025
+ }
1026
+ $(document).data('keycount', 0);
1027
+ }
1028
+
1029
+ if ((/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode.toString(10)) && !isActive)) {
1030
+ that.$menu.parent().removeClass('open');
1031
+ that.$button.focus();
499
1032
  }
1033
+ },
1034
+
1035
+ mobile: function () {
1036
+ this.$element.addClass('mobile-device').appendTo(this.$newElement);
1037
+ if (this.options.container) this.$menu.hide();
1038
+ },
1039
+
1040
+ refresh: function () {
1041
+ this.$lis = null;
1042
+ this.reloadLi();
1043
+ this.render();
1044
+ this.setWidth();
1045
+ this.setStyle();
1046
+ this.checkDisabled();
1047
+ this.liHeight();
1048
+ },
1049
+
1050
+ update: function () {
1051
+ this.reloadLi();
1052
+ this.setWidth();
1053
+ this.setStyle();
1054
+ this.checkDisabled();
1055
+ this.liHeight();
1056
+ },
1057
+
1058
+ hide: function () {
1059
+ this.$newElement.hide();
1060
+ },
1061
+
1062
+ show: function () {
1063
+ this.$newElement.show();
1064
+ },
1065
+
1066
+ remove: function () {
1067
+ this.$newElement.remove();
1068
+ this.$element.remove();
500
1069
  }
501
1070
  };
502
1071
 
503
- $.fn.selectpicker = function(option, event) {
504
- //get the args of the outer function..
505
- var args = arguments;
506
- var value;
507
- var chain = this.each(function () {
508
- if ($(this).is('select')) {
509
- var $this = $(this),
510
- data = $this.data('selectpicker'),
1072
+ // SELECTPICKER PLUGIN DEFINITION
1073
+ // ==============================
1074
+ function Plugin(option, event) {
1075
+ // get the args of the outer function..
1076
+ var args = arguments;
1077
+ // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them
1078
+ // to get lost
1079
+ //noinspection JSDuplicatedDeclaration
1080
+ var option = args[0],
1081
+ event = args[1];
1082
+ [].shift.apply(args);
1083
+ var value;
1084
+ var chain = this.each(function () {
1085
+ var $this = $(this);
1086
+ if ($this.is('select')) {
1087
+ var data = $this.data('selectpicker'),
511
1088
  options = typeof option == 'object' && option;
512
-
1089
+
513
1090
  if (!data) {
514
- $this.data('selectpicker', (data = new Selectpicker(this, options, event)));
515
- } else if(options){
516
- for(var i in options) {
517
- data.options[i]=options[i];
1091
+ var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults, $this.data(), options);
1092
+ $this.data('selectpicker', (data = new Selectpicker(this, config, event)));
1093
+ } else if (options) {
1094
+ for (var i in options) {
1095
+ if (options.hasOwnProperty(i)) {
1096
+ data.options[i] = options[i];
1097
+ }
518
1098
  }
519
1099
  }
520
-
1100
+
521
1101
  if (typeof option == 'string') {
522
- //Copy the value of option, as once we shift the arguments
523
- //it also shifts the value of option.
524
- property = option;
525
- if(data[property] instanceof Function) {
526
- [].shift.apply(args);
527
- value = data[property].apply(data, args);
1102
+ if (data[option] instanceof Function) {
1103
+ value = data[option].apply(data, args);
528
1104
  } else {
529
- value = data.options[property];
1105
+ value = data.options[option];
530
1106
  }
531
1107
  }
532
1108
  }
533
1109
  });
534
-
535
- if(value!=undefined) {
1110
+
1111
+ if (typeof value !== 'undefined') {
1112
+ //noinspection JSUnusedAssignment
536
1113
  return value;
537
1114
  } else {
538
1115
  return chain;
539
- }
540
- };
541
-
542
- $.fn.selectpicker.defaults = {
543
- style: null,
544
- size: 'auto',
545
- title: null,
546
- selectedTextFormat : 'values',
547
- noneSelectedText : 'Nothing selected',
548
- width: null,
549
- container: false,
550
- hideDisabled: false
1116
+ }
551
1117
  }
552
1118
 
1119
+ var old = $.fn.selectpicker;
1120
+ $.fn.selectpicker = Plugin;
1121
+ $.fn.selectpicker.Constructor = Selectpicker;
1122
+
1123
+ // SELECTPICKER NO CONFLICT
1124
+ // ========================
1125
+ $.fn.selectpicker.noConflict = function () {
1126
+ $.fn.selectpicker = old;
1127
+ return this;
1128
+ };
1129
+
553
1130
  $(document)
554
- .data('keycount',0)
555
- .on('keydown', '[data-toggle=dropdown], [role=menu]' , Selectpicker.prototype.keydown)
1131
+ .data('keycount', 0)
1132
+ .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', Selectpicker.prototype.keydown)
1133
+ .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input', function (e) {
1134
+ e.stopPropagation();
1135
+ });
556
1136
 
557
- }(window.jQuery);
1137
+ // SELECTPICKER DATA-API
1138
+ // =====================
1139
+ $(window).on('load.bs.select.data-api', function () {
1140
+ $('.selectpicker').each(function () {
1141
+ var $selectpicker = $(this);
1142
+ Plugin.call($selectpicker, $selectpicker.data());
1143
+ })
1144
+ });
1145
+ })(jQuery);