comparison query_builder/bootstrap-select/dist/js/bootstrap-select.js @ 26:22be4ea663a7

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