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