7d4b79f48d20f3f0f96e9579f251d946d7d84f80
[motion.git] / public / bower_components / bootstrap-select / bootstrap-select.js
1 /*!\r
2  * Bootstrap-select v1.7.5 (http://silviomoreto.github.io/bootstrap-select)\r
3  *\r
4  * Copyright 2013-2015 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.remove;\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.7.5';\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     template: {\r
331       caret: '<span class="caret"></span>'\r
332     },\r
333     maxOptions: false,\r
334     mobile: false,\r
335     selectOnTab: false,\r
336     dropdownAlignRight: false\r
337   };\r
338 \r
339   Selectpicker.prototype = {\r
340 \r
341     constructor: Selectpicker,\r
342 \r
343     init: function () {\r
344       var that = this,\r
345           id = this.$element.attr('id');\r
346 \r
347       this.$element.addClass('bs-select-hidden');\r
348       // store originalIndex (key) and newIndex (value) in this.liObj for fast accessibility\r
349       // allows us to do this.$lis.eq(that.liObj[index]) instead of this.$lis.filter('[data-original-index="' + index + '"]')\r
350       this.liObj = {};\r
351       this.multiple = this.$element.prop('multiple');\r
352       this.autofocus = this.$element.prop('autofocus');\r
353       this.$newElement = this.createView();\r
354       this.$element.after(this.$newElement);\r
355       this.$button = this.$newElement.children('button');\r
356       this.$menu = this.$newElement.children('.dropdown-menu');\r
357       this.$menuInner = this.$menu.children('.inner');\r
358       this.$searchbox = this.$menu.find('input');\r
359 \r
360       if (this.options.dropdownAlignRight)\r
361         this.$menu.addClass('dropdown-menu-right');\r
362 \r
363       if (typeof id !== 'undefined') {\r
364         this.$button.attr('data-id', id);\r
365         $('label[for="' + id + '"]').click(function (e) {\r
366           e.preventDefault();\r
367           that.$button.focus();\r
368         });\r
369       }\r
370 \r
371       this.checkDisabled();\r
372       this.clickListener();\r
373       if (this.options.liveSearch) this.liveSearchListener();\r
374       this.render();\r
375       this.setStyle();\r
376       this.setWidth();\r
377       if (this.options.container) this.selectPosition();\r
378       this.$menu.data('this', this);\r
379       this.$newElement.data('this', this);\r
380       if (this.options.mobile) this.mobile();\r
381 \r
382       this.$newElement.on({\r
383         'hide.bs.dropdown': function (e) {\r
384           that.$element.trigger('hide.bs.select', e);\r
385         },\r
386         'hidden.bs.dropdown': function (e) {\r
387           that.$element.trigger('hidden.bs.select', e);\r
388         },\r
389         'show.bs.dropdown': function (e) {\r
390           that.$element.trigger('show.bs.select', e);\r
391         },\r
392         'shown.bs.dropdown': function (e) {\r
393           that.$element.trigger('shown.bs.select', e);\r
394         }\r
395       });\r
396 \r
397       setTimeout(function () {\r
398         that.$element.trigger('loaded.bs.select');\r
399       });\r
400     },\r
401 \r
402     createDropdown: function () {\r
403       // Options\r
404       // If we are multiple, then add the show-tick class by default\r
405       var multiple = this.multiple ? ' show-tick' : '',\r
406           inputGroup = this.$element.parent().hasClass('input-group') ? ' input-group-btn' : '',\r
407           autofocus = this.autofocus ? ' autofocus' : '';\r
408       // Elements\r
409       var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>' + this.options.header + '</div>' : '';\r
410       var searchbox = this.options.liveSearch ?\r
411       '<div class="bs-searchbox">' +\r
412       '<input type="text" class="form-control" autocomplete="off"' +\r
413       (null === this.options.liveSearchPlaceholder ? '' : ' placeholder="' + htmlEscape(this.options.liveSearchPlaceholder) + '"') + '>' +\r
414       '</div>'\r
415           : '';\r
416       var actionsbox = this.multiple && this.options.actionsBox ?\r
417       '<div class="bs-actionsbox">' +\r
418       '<div class="btn-group btn-group-sm btn-block">' +\r
419       '<button type="button" class="actions-btn bs-select-all btn btn-default">' +\r
420       this.options.selectAllText +\r
421       '</button>' +\r
422       '<button type="button" class="actions-btn bs-deselect-all btn btn-default">' +\r
423       this.options.deselectAllText +\r
424       '</button>' +\r
425       '</div>' +\r
426       '</div>'\r
427           : '';\r
428       var donebutton = this.multiple && this.options.doneButton ?\r
429       '<div class="bs-donebutton">' +\r
430       '<div class="btn-group btn-block">' +\r
431       '<button type="button" class="btn btn-sm btn-default">' +\r
432       this.options.doneButtonText +\r
433       '</button>' +\r
434       '</div>' +\r
435       '</div>'\r
436           : '';\r
437       var drop =\r
438           '<div class="btn-group bootstrap-select' + multiple + inputGroup + '">' +\r
439           '<button type="button" class="' + this.options.styleBase + ' dropdown-toggle" data-toggle="dropdown"' + autofocus + '>' +\r
440           '<span class="filter-option pull-left"></span>&nbsp;' +\r
441           '<span class="bs-caret">' +\r
442           this.options.template.caret +\r
443           '</span>' +\r
444           '</button>' +\r
445           '<div class="dropdown-menu open">' +\r
446           header +\r
447           searchbox +\r
448           actionsbox +\r
449           '<ul class="dropdown-menu inner" role="menu">' +\r
450           '</ul>' +\r
451           donebutton +\r
452           '</div>' +\r
453           '</div>';\r
454 \r
455       return $(drop);\r
456     },\r
457 \r
458     createView: function () {\r
459       var $drop = this.createDropdown(),\r
460           li = this.createLi();\r
461 \r
462       $drop.find('ul')[0].innerHTML = li;\r
463       return $drop;\r
464     },\r
465 \r
466     reloadLi: function () {\r
467       //Remove all children.\r
468       this.destroyLi();\r
469       //Re build\r
470       var li = this.createLi();\r
471       this.$menuInner[0].innerHTML = li;\r
472     },\r
473 \r
474     destroyLi: function () {\r
475       this.$menu.find('li').remove();\r
476     },\r
477 \r
478     createLi: function () {\r
479       var that = this,\r
480           _li = [],\r
481           optID = 0,\r
482           titleOption = document.createElement('option'),\r
483           liIndex = -1; // increment liIndex whenever a new <li> element is created to ensure liObj is correct\r
484 \r
485       // Helper functions\r
486       /**\r
487        * @param content\r
488        * @param [index]\r
489        * @param [classes]\r
490        * @param [optgroup]\r
491        * @returns {string}\r
492        */\r
493       var generateLI = function (content, index, classes, optgroup) {\r
494         return '<li' +\r
495             ((typeof classes !== 'undefined' & '' !== classes) ? ' class="' + classes + '"' : '') +\r
496             ((typeof index !== 'undefined' & null !== index) ? ' data-original-index="' + index + '"' : '') +\r
497             ((typeof optgroup !== 'undefined' & null !== optgroup) ? 'data-optgroup="' + optgroup + '"' : '') +\r
498             '>' + content + '</li>';\r
499       };\r
500 \r
501       /**\r
502        * @param text\r
503        * @param [classes]\r
504        * @param [inline]\r
505        * @param [tokens]\r
506        * @returns {string}\r
507        */\r
508       var generateA = function (text, classes, inline, tokens) {\r
509         return '<a tabindex="0"' +\r
510             (typeof classes !== 'undefined' ? ' class="' + classes + '"' : '') +\r
511             (typeof inline !== 'undefined' ? ' style="' + inline + '"' : '') +\r
512             (that.options.liveSearchNormalize ? ' data-normalized-text="' + normalizeToBase(htmlEscape(text)) + '"' : '') +\r
513             (typeof tokens !== 'undefined' || tokens !== null ? ' data-tokens="' + tokens + '"' : '') +\r
514             '>' + text +\r
515             '<span class="' + that.options.iconBase + ' ' + that.options.tickIcon + ' check-mark"></span>' +\r
516             '</a>';\r
517       };\r
518 \r
519       if (this.options.title && !this.multiple) {\r
520         // this option doesn't create a new <li> element, but does add a new option, so liIndex is decreased\r
521         // since liObj is recalculated on every refresh, liIndex needs to be decreased even if the titleOption is already appended\r
522         liIndex--;\r
523 \r
524         if (!this.$element.find('.bs-title-option').length) {\r
525           // Use native JS to prepend option (faster)\r
526           var element = this.$element[0];\r
527           titleOption.className = 'bs-title-option';\r
528           titleOption.appendChild(document.createTextNode(this.options.title));\r
529           titleOption.value = '';\r
530           element.insertBefore(titleOption, element.firstChild);\r
531           // Check if selected attribute is already set on an option. If not, select the titleOption option.\r
532           if ($(element.options[element.selectedIndex]).attr('selected') === undefined) titleOption.selected = true;\r
533         }\r
534       }\r
535 \r
536       this.$element.find('option').each(function (index) {\r
537         var $this = $(this);\r
538 \r
539         liIndex++;\r
540 \r
541         if ($this.hasClass('bs-title-option')) return;\r
542 \r
543         // Get the class and text for the option\r
544         var optionClass = this.className || '',\r
545             inline = this.style.cssText,\r
546             text = $this.data('content') ? $this.data('content') : $this.html(),\r
547             tokens = $this.data('tokens') ? $this.data('tokens') : null,\r
548             subtext = typeof $this.data('subtext') !== 'undefined' ? '<small class="text-muted">' + $this.data('subtext') + '</small>' : '',\r
549             icon = typeof $this.data('icon') !== 'undefined' ? '<span class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></span> ' : '',\r
550             isDisabled = this.disabled || (this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled);\r
551 \r
552         if (icon !== '' && isDisabled) {\r
553           icon = '<span>' + icon + '</span>';\r
554         }\r
555 \r
556         if (that.options.hideDisabled && isDisabled) {\r
557           liIndex--;\r
558           return;\r
559         }\r
560 \r
561         if (!$this.data('content')) {\r
562           // Prepend any icon and append any subtext to the main text.\r
563           text = icon + '<span class="text">' + text + subtext + '</span>';\r
564         }\r
565 \r
566         if (this.parentNode.tagName === 'OPTGROUP' && $this.data('divider') !== true) {\r
567           var optGroupClass = ' ' + this.parentNode.className || '';\r
568 \r
569           if ($this.index() === 0) { // Is it the first option of the optgroup?\r
570             optID += 1;\r
571 \r
572             // Get the opt group label\r
573             var label = this.parentNode.label,\r
574                 labelSubtext = typeof $this.parent().data('subtext') !== 'undefined' ? '<small class="text-muted">' + $this.parent().data('subtext') + '</small>' : '',\r
575                 labelIcon = $this.parent().data('icon') ? '<span class="' + that.options.iconBase + ' ' + $this.parent().data('icon') + '"></span> ' : '';\r
576 \r
577             label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>';\r
578 \r
579             if (index !== 0 && _li.length > 0) { // Is it NOT the first option of the select && are there elements in the dropdown?\r
580               liIndex++;\r
581               _li.push(generateLI('', null, 'divider', optID + 'div'));\r
582             }\r
583             liIndex++;\r
584             _li.push(generateLI(label, null, 'dropdown-header' + optGroupClass, optID));\r
585           }\r
586           _li.push(generateLI(generateA(text, 'opt ' + optionClass + optGroupClass, inline, tokens), index, '', optID));\r
587         } else if ($this.data('divider') === true) {\r
588           _li.push(generateLI('', index, 'divider'));\r
589         } else if ($this.data('hidden') === true) {\r
590           _li.push(generateLI(generateA(text, optionClass, inline, tokens), index, 'hidden is-hidden'));\r
591         } else {\r
592           if (this.previousElementSibling && this.previousElementSibling.tagName === 'OPTGROUP') {\r
593             liIndex++;\r
594             _li.push(generateLI('', null, 'divider', optID + 'div'));\r
595           }\r
596           _li.push(generateLI(generateA(text, optionClass, inline, tokens), index));\r
597         }\r
598 \r
599         that.liObj[index] = liIndex;\r
600       });\r
601 \r
602       //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
603       if (!this.multiple && this.$element.find('option:selected').length === 0 && !this.options.title) {\r
604         this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected');\r
605       }\r
606 \r
607       return _li.join('');\r
608     },\r
609 \r
610     findLis: function () {\r
611       if (this.$lis == null) this.$lis = this.$menu.find('li');\r
612       return this.$lis;\r
613     },\r
614 \r
615     /**\r
616      * @param [updateLi] defaults to true\r
617      */\r
618     render: function (updateLi) {\r
619       var that = this,\r
620           notDisabled;\r
621 \r
622       //Update the LI to match the SELECT\r
623       if (updateLi !== false) {\r
624         this.$element.find('option').each(function (index) {\r
625           var $lis = that.findLis().eq(that.liObj[index]);\r
626 \r
627           that.setDisabled(index, this.disabled || this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled, $lis);\r
628           that.setSelected(index, this.selected, $lis);\r
629         });\r
630       }\r
631 \r
632       this.tabIndex();\r
633 \r
634       var selectedItems = this.$element.find('option').map(function () {\r
635         if (this.selected) {\r
636           if (that.options.hideDisabled && (this.disabled || this.parentNode.tagName === 'OPTGROUP' && this.parentNode.disabled)) return;\r
637 \r
638           var $this = $(this),\r
639               icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : '',\r
640               subtext;\r
641 \r
642           if (that.options.showSubtext && $this.data('subtext') && !that.multiple) {\r
643             subtext = ' <small class="text-muted">' + $this.data('subtext') + '</small>';\r
644           } else {\r
645             subtext = '';\r
646           }\r
647           if (typeof $this.attr('title') !== 'undefined') {\r
648             return $this.attr('title');\r
649           } else if ($this.data('content') && that.options.showContent) {\r
650             return $this.data('content');\r
651           } else {\r
652             return icon + $this.html() + subtext;\r
653           }\r
654         }\r
655       }).toArray();\r
656 \r
657       //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled\r
658       //Convert all the values into a comma delimited string\r
659       var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator);\r
660 \r
661       //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..\r
662       if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) {\r
663         var max = this.options.selectedTextFormat.split('>');\r
664         if ((max.length > 1 && selectedItems.length > max[1]) || (max.length == 1 && selectedItems.length >= 2)) {\r
665           notDisabled = this.options.hideDisabled ? ', [disabled]' : '';\r
666           var totalCount = this.$element.find('option').not('[data-divider="true"], [data-hidden="true"]' + notDisabled).length,\r
667               tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedItems.length, totalCount) : this.options.countSelectedText;\r
668           title = tr8nText.replace('{0}', selectedItems.length.toString()).replace('{1}', totalCount.toString());\r
669         }\r
670       }\r
671 \r
672       if (this.options.title == undefined) {\r
673         this.options.title = this.$element.attr('title');\r
674       }\r
675 \r
676       if (this.options.selectedTextFormat == 'static') {\r
677         title = this.options.title;\r
678       }\r
679 \r
680       //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text\r
681       if (!title) {\r
682         title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText;\r
683       }\r
684 \r
685       //strip all html-tags and trim the result\r
686       this.$button.attr('title', $.trim(title.replace(/<[^>]*>?/g, '')));\r
687       this.$button.children('.filter-option').html(title);\r
688 \r
689       this.$element.trigger('rendered.bs.select');\r
690     },\r
691 \r
692     /**\r
693      * @param [style]\r
694      * @param [status]\r
695      */\r
696     setStyle: function (style, status) {\r
697       if (this.$element.attr('class')) {\r
698         this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, ''));\r
699       }\r
700 \r
701       var buttonClass = style ? style : this.options.style;\r
702 \r
703       if (status == 'add') {\r
704         this.$button.addClass(buttonClass);\r
705       } else if (status == 'remove') {\r
706         this.$button.removeClass(buttonClass);\r
707       } else {\r
708         this.$button.removeClass(this.options.style);\r
709         this.$button.addClass(buttonClass);\r
710       }\r
711     },\r
712 \r
713     liHeight: function (refresh) {\r
714       if (!refresh && (this.options.size === false || this.sizeInfo)) return;\r
715 \r
716       var newElement = document.createElement('div'),\r
717           menu = document.createElement('div'),\r
718           menuInner = document.createElement('ul'),\r
719           divider = document.createElement('li'),\r
720           li = document.createElement('li'),\r
721           a = document.createElement('a'),\r
722           text = document.createElement('span'),\r
723           header = this.options.header ? this.$menu.find('.popover-title')[0].cloneNode(true) : null,\r
724           search = this.options.liveSearch ? document.createElement('div') : null,\r
725           actions = this.options.actionsBox && this.multiple ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null,\r
726           doneButton = this.options.doneButton && this.multiple ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null;\r
727 \r
728       text.className = 'text';\r
729       newElement.className = this.$menu[0].parentNode.className + ' open';\r
730       menu.className = 'dropdown-menu open';\r
731       menuInner.className = 'dropdown-menu inner';\r
732       divider.className = 'divider';\r
733 \r
734       text.appendChild(document.createTextNode('Inner text'));\r
735       a.appendChild(text);\r
736       li.appendChild(a);\r
737       menuInner.appendChild(li);\r
738       menuInner.appendChild(divider);\r
739       if (header) menu.appendChild(header);\r
740       if (search) {\r
741         // create a span instead of input as creating an input element is slower\r
742         var input = document.createElement('span');\r
743         search.className = 'bs-searchbox';\r
744         input.className = 'form-control';\r
745         search.appendChild(input);\r
746         menu.appendChild(search);\r
747       }\r
748       if (actions) menu.appendChild(actions);\r
749       menu.appendChild(menuInner);\r
750       if (doneButton) menu.appendChild(doneButton);\r
751       newElement.appendChild(menu);\r
752 \r
753       document.body.appendChild(newElement);\r
754 \r
755       var liHeight = a.offsetHeight,\r
756           headerHeight = header ? header.offsetHeight : 0,\r
757           searchHeight = search ? search.offsetHeight : 0,\r
758           actionsHeight = actions ? actions.offsetHeight : 0,\r
759           doneButtonHeight = doneButton ? doneButton.offsetHeight : 0,\r
760           dividerHeight = $(divider).outerHeight(true),\r
761           // fall back to jQuery if getComputedStyle is not supported\r
762           menuStyle = typeof getComputedStyle === 'function' ? getComputedStyle(menu) : false,\r
763           $menu = menuStyle ? null : $(menu),\r
764           menuPadding = parseInt(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) +\r
765                         parseInt(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) +\r
766                         parseInt(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) +\r
767                         parseInt(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')),\r
768           menuExtras =  menuPadding +\r
769                         parseInt(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) +\r
770                         parseInt(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2;\r
771 \r
772       document.body.removeChild(newElement);\r
773 \r
774       this.sizeInfo = {\r
775         liHeight: liHeight,\r
776         headerHeight: headerHeight,\r
777         searchHeight: searchHeight,\r
778         actionsHeight: actionsHeight,\r
779         doneButtonHeight: doneButtonHeight,\r
780         dividerHeight: dividerHeight,\r
781         menuPadding: menuPadding,\r
782         menuExtras: menuExtras\r
783       };\r
784     },\r
785 \r
786     setSize: function () {\r
787       this.findLis();\r
788       this.liHeight();\r
789 \r
790       if (this.options.header) this.$menu.css('padding-top', 0);\r
791       if (this.options.size === false) return;\r
792 \r
793       var that = this,\r
794           $menu = this.$menu,\r
795           $menuInner = this.$menuInner,\r
796           $window = $(window),\r
797           selectHeight = this.$newElement[0].offsetHeight,\r
798           liHeight = this.sizeInfo['liHeight'],\r
799           headerHeight = this.sizeInfo['headerHeight'],\r
800           searchHeight = this.sizeInfo['searchHeight'],\r
801           actionsHeight = this.sizeInfo['actionsHeight'],\r
802           doneButtonHeight = this.sizeInfo['doneButtonHeight'],\r
803           divHeight = this.sizeInfo['dividerHeight'],\r
804           menuPadding = this.sizeInfo['menuPadding'],\r
805           menuExtras = this.sizeInfo['menuExtras'],\r
806           notDisabled = this.options.hideDisabled ? '.disabled' : '',\r
807           menuHeight,\r
808           getHeight,\r
809           selectOffsetTop,\r
810           selectOffsetBot,\r
811           posVert = function () {\r
812             selectOffsetTop = that.$newElement.offset().top - $window.scrollTop();\r
813             selectOffsetBot = $window.height() - selectOffsetTop - selectHeight;\r
814           };\r
815 \r
816       posVert();\r
817 \r
818       if (this.options.size === 'auto') {\r
819         var getSize = function () {\r
820           var minHeight,\r
821               hasClass = function (className, include) {\r
822                 return function (element) {\r
823                     if (include) {\r
824                         return (element.classList ? element.classList.contains(className) : $(element).hasClass(className));\r
825                     } else {\r
826                         return !(element.classList ? element.classList.contains(className) : $(element).hasClass(className));\r
827                     }\r
828                 };\r
829               },\r
830               lis = that.$menuInner[0].getElementsByTagName('li'),\r
831               lisVisible = Array.prototype.filter ? Array.prototype.filter.call(lis, hasClass('hidden', false)) : that.$lis.not('.hidden'),\r
832               optGroup = Array.prototype.filter ? Array.prototype.filter.call(lisVisible, hasClass('dropdown-header', true)) : lisVisible.filter('.dropdown-header');\r
833 \r
834           posVert();\r
835           menuHeight = selectOffsetBot - menuExtras;\r
836 \r
837           if (that.options.container) {\r
838             if (!$menu.data('height')) $menu.data('height', $menu.height());\r
839             getHeight = $menu.data('height');\r
840           } else {\r
841             getHeight = $menu.height();\r
842           }\r
843 \r
844           if (that.options.dropupAuto) {\r
845             that.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras) < getHeight);\r
846           }\r
847           if (that.$newElement.hasClass('dropup')) {\r
848             menuHeight = selectOffsetTop - menuExtras;\r
849           }\r
850 \r
851           if ((lisVisible.length + optGroup.length) > 3) {\r
852             minHeight = liHeight * 3 + menuExtras - 2;\r
853           } else {\r
854             minHeight = 0;\r
855           }\r
856 \r
857           $menu.css({\r
858             'max-height': menuHeight + 'px',\r
859             'overflow': 'hidden',\r
860             'min-height': minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px'\r
861           });\r
862           $menuInner.css({\r
863             'max-height': menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding + 'px',\r
864             'overflow-y': 'auto',\r
865             'min-height': Math.max(minHeight - menuPadding, 0) + 'px'\r
866           });\r
867         };\r
868         getSize();\r
869         this.$searchbox.off('input.getSize propertychange.getSize').on('input.getSize propertychange.getSize', getSize);\r
870         $window.off('resize.getSize scroll.getSize').on('resize.getSize scroll.getSize', getSize);\r
871       } else if (this.options.size && this.options.size != 'auto' && this.$lis.not(notDisabled).length > this.options.size) {\r
872         var optIndex = this.$lis.not('.divider').not(notDisabled).children().slice(0, this.options.size).last().parent().index(),\r
873             divLength = this.$lis.slice(0, optIndex + 1).filter('.divider').length;\r
874         menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding;\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           //noinspection JSUnusedAssignment\r
885           this.$newElement.toggleClass('dropup', selectOffsetTop > selectOffsetBot && (menuHeight - menuExtras) < getHeight);\r
886         }\r
887         $menu.css({\r
888           'max-height': menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight + 'px',\r
889           'overflow': 'hidden',\r
890           'min-height': ''\r
891         });\r
892         $menuInner.css({\r
893           'max-height': menuHeight - menuPadding + 'px',\r
894           'overflow-y': 'auto',\r
895           'min-height': ''\r
896         });\r
897       }\r
898     },\r
899 \r
900     setWidth: function () {\r
901       if (this.options.width === 'auto') {\r
902         this.$menu.css('min-width', '0');\r
903 \r
904         // Get correct width if element is hidden\r
905         var $selectClone = this.$menu.parent().clone().appendTo('body'),\r
906             $selectClone2 = this.options.container ? this.$newElement.clone().appendTo('body') : $selectClone,\r
907             ulWidth = $selectClone.children('.dropdown-menu').outerWidth(),\r
908             btnWidth = $selectClone2.css('width', 'auto').children('button').outerWidth();\r
909 \r
910         $selectClone.remove();\r
911         $selectClone2.remove();\r
912 \r
913         // Set width to whatever's larger, button title or longest option\r
914         this.$newElement.css('width', Math.max(ulWidth, btnWidth) + 'px');\r
915       } else if (this.options.width === 'fit') {\r
916         // Remove inline min-width so width can be changed from 'auto'\r
917         this.$menu.css('min-width', '');\r
918         this.$newElement.css('width', '').addClass('fit-width');\r
919       } else if (this.options.width) {\r
920         // Remove inline min-width so width can be changed from 'auto'\r
921         this.$menu.css('min-width', '');\r
922         this.$newElement.css('width', this.options.width);\r
923       } else {\r
924         // Remove inline min-width/width so width can be changed\r
925         this.$menu.css('min-width', '');\r
926         this.$newElement.css('width', '');\r
927       }\r
928       // Remove fit-width class if width is changed programmatically\r
929       if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') {\r
930         this.$newElement.removeClass('fit-width');\r
931       }\r
932     },\r
933 \r
934     selectPosition: function () {\r
935       var that = this,\r
936           $drop = $('<div class="bs-container" />'),\r
937           pos,\r
938           actualHeight,\r
939           getPlacement = function ($element) {\r
940             $drop.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass('dropup', $element.hasClass('dropup'));\r
941             pos = $element.offset();\r
942             actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight;\r
943             $drop.css({\r
944               'top': pos.top + actualHeight,\r
945               'left': pos.left,\r
946               'width': $element[0].offsetWidth\r
947             });\r
948           };\r
949 \r
950       this.$newElement.on('click', function () {\r
951         if (that.isDisabled()) {\r
952           return;\r
953         }\r
954         getPlacement($(this));\r
955         $drop.appendTo(that.options.container);\r
956         $drop.toggleClass('open', !$(this).hasClass('open'));\r
957         $drop.append(that.$menu);\r
958       });\r
959 \r
960       $(window).on('resize scroll', function () {\r
961         getPlacement(that.$newElement);\r
962       });\r
963 \r
964       this.$element.on('hide.bs.select', function () {\r
965         that.$menu.data('height', that.$menu.height());\r
966         $drop.detach();\r
967       });\r
968     },\r
969 \r
970     setSelected: function (index, selected, $lis) {\r
971       if (!$lis) {\r
972         $lis = this.findLis().eq(this.liObj[index]);\r
973       }\r
974 \r
975       $lis.toggleClass('selected', selected);\r
976     },\r
977 \r
978     setDisabled: function (index, disabled, $lis) {\r
979       if (!$lis) {\r
980         $lis = this.findLis().eq(this.liObj[index]);\r
981       }\r
982 \r
983       if (disabled) {\r
984         $lis.addClass('disabled').children('a').attr('href', '#').attr('tabindex', -1);\r
985       } else {\r
986         $lis.removeClass('disabled').children('a').removeAttr('href').attr('tabindex', 0);\r
987       }\r
988     },\r
989 \r
990     isDisabled: function () {\r
991       return this.$element[0].disabled;\r
992     },\r
993 \r
994     checkDisabled: function () {\r
995       var that = this;\r
996 \r
997       if (this.isDisabled()) {\r
998         this.$newElement.addClass('disabled');\r
999         this.$button.addClass('disabled').attr('tabindex', -1);\r
1000       } else {\r
1001         if (this.$button.hasClass('disabled')) {\r
1002           this.$newElement.removeClass('disabled');\r
1003           this.$button.removeClass('disabled');\r
1004         }\r
1005 \r
1006         if (this.$button.attr('tabindex') == -1 && !this.$element.data('tabindex')) {\r
1007           this.$button.removeAttr('tabindex');\r
1008         }\r
1009       }\r
1010 \r
1011       this.$button.click(function () {\r
1012         return !that.isDisabled();\r
1013       });\r
1014     },\r
1015 \r
1016     tabIndex: function () {\r
1017       if (this.$element.is('[tabindex]')) {\r
1018         this.$element.data('tabindex', this.$element.attr('tabindex'));\r
1019         this.$button.attr('tabindex', this.$element.data('tabindex'));\r
1020       }\r
1021     },\r
1022 \r
1023     clickListener: function () {\r
1024       var that = this,\r
1025           $document = $(document);\r
1026 \r
1027       this.$newElement.on('touchstart.dropdown', '.dropdown-menu', function (e) {\r
1028         e.stopPropagation();\r
1029       });\r
1030 \r
1031       $document.data('spaceSelect', false);\r
1032 \r
1033       this.$button.on('keyup', function (e) {\r
1034         if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) {\r
1035             e.preventDefault();\r
1036             $document.data('spaceSelect', false);\r
1037         }\r
1038       });\r
1039 \r
1040       this.$newElement.on('click', function () {\r
1041         that.setSize();\r
1042         that.$element.on('shown.bs.select', function () {\r
1043           if (!that.options.liveSearch && !that.multiple) {\r
1044             that.$menuInner.find('.selected a').focus();\r
1045           } else if (!that.multiple) {\r
1046             var selectedIndex = that.liObj[that.$element[0].selectedIndex];\r
1047 \r
1048             if (typeof selectedIndex !== 'number' || that.options.size === false) return;\r
1049 \r
1050             // scroll to selected option\r
1051             var offset = that.$lis.eq(selectedIndex)[0].offsetTop - that.$menuInner[0].offsetTop;\r
1052             offset = offset - that.$menuInner[0].offsetHeight/2 + that.sizeInfo.liHeight/2;\r
1053             that.$menuInner[0].scrollTop = offset;\r
1054           }\r
1055         });\r
1056       });\r
1057 \r
1058       this.$menuInner.on('click', 'li a', function (e) {\r
1059         var $this = $(this),\r
1060             clickedIndex = $this.parent().data('originalIndex'),\r
1061             prevValue = that.$element.val(),\r
1062             prevIndex = that.$element.prop('selectedIndex');\r
1063 \r
1064         // Don't close on multi choice menu\r
1065         if (that.multiple) {\r
1066           e.stopPropagation();\r
1067         }\r
1068 \r
1069         e.preventDefault();\r
1070 \r
1071         //Don't run if we have been disabled\r
1072         if (!that.isDisabled() && !$this.parent().hasClass('disabled')) {\r
1073           var $options = that.$element.find('option'),\r
1074               $option = $options.eq(clickedIndex),\r
1075               state = $option.prop('selected'),\r
1076               $optgroup = $option.parent('optgroup'),\r
1077               maxOptions = that.options.maxOptions,\r
1078               maxOptionsGrp = $optgroup.data('maxOptions') || false;\r
1079 \r
1080           if (!that.multiple) { // Deselect all others if not multi select box\r
1081             $options.prop('selected', false);\r
1082             $option.prop('selected', true);\r
1083             that.$menuInner.find('.selected').removeClass('selected');\r
1084             that.setSelected(clickedIndex, true);\r
1085           } else { // Toggle the one we have chosen if we are multi select.\r
1086             $option.prop('selected', !state);\r
1087             that.setSelected(clickedIndex, !state);\r
1088             $this.blur();\r
1089 \r
1090             if (maxOptions !== false || maxOptionsGrp !== false) {\r
1091               var maxReached = maxOptions < $options.filter(':selected').length,\r
1092                   maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length;\r
1093 \r
1094               if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) {\r
1095                 if (maxOptions && maxOptions == 1) {\r
1096                   $options.prop('selected', false);\r
1097                   $option.prop('selected', true);\r
1098                   that.$menuInner.find('.selected').removeClass('selected');\r
1099                   that.setSelected(clickedIndex, true);\r
1100                 } else if (maxOptionsGrp && maxOptionsGrp == 1) {\r
1101                   $optgroup.find('option:selected').prop('selected', false);\r
1102                   $option.prop('selected', true);\r
1103                   var optgroupID = $this.parent().data('optgroup');\r
1104                   that.$menuInner.find('[data-optgroup="' + optgroupID + '"]').removeClass('selected');\r
1105                   that.setSelected(clickedIndex, true);\r
1106                 } else {\r
1107                   var maxOptionsArr = (typeof that.options.maxOptionsText === 'function') ?\r
1108                           that.options.maxOptionsText(maxOptions, maxOptionsGrp) : that.options.maxOptionsText,\r
1109                       maxTxt = maxOptionsArr[0].replace('{n}', maxOptions),\r
1110                       maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp),\r
1111                       $notify = $('<div class="notify"></div>');\r
1112                   // If {var} is set in array, replace it\r
1113                   /** @deprecated */\r
1114                   if (maxOptionsArr[2]) {\r
1115                     maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]);\r
1116                     maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]);\r
1117                   }\r
1118 \r
1119                   $option.prop('selected', false);\r
1120 \r
1121                   that.$menu.append($notify);\r
1122 \r
1123                   if (maxOptions && maxReached) {\r
1124                     $notify.append($('<div>' + maxTxt + '</div>'));\r
1125                     that.$element.trigger('maxReached.bs.select');\r
1126                   }\r
1127 \r
1128                   if (maxOptionsGrp && maxReachedGrp) {\r
1129                     $notify.append($('<div>' + maxTxtGrp + '</div>'));\r
1130                     that.$element.trigger('maxReachedGrp.bs.select');\r
1131                   }\r
1132 \r
1133                   setTimeout(function () {\r
1134                     that.setSelected(clickedIndex, false);\r
1135                   }, 10);\r
1136 \r
1137                   $notify.delay(750).fadeOut(300, function () {\r
1138                     $(this).remove();\r
1139                   });\r
1140                 }\r
1141               }\r
1142             }\r
1143           }\r
1144 \r
1145           if (!that.multiple) {\r
1146             that.$button.focus();\r
1147           } else if (that.options.liveSearch) {\r
1148             that.$searchbox.focus();\r
1149           }\r
1150 \r
1151           // Trigger select 'change'\r
1152           if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) {\r
1153             that.$element.triggerNative('change');\r
1154             // $option.prop('selected') is current option state (selected/unselected). state is previous option state.\r
1155             that.$element.trigger('changed.bs.select', [clickedIndex, $option.prop('selected'), state]);\r
1156           }\r
1157         }\r
1158       });\r
1159 \r
1160       this.$menu.on('click', 'li.disabled a, .popover-title, .popover-title :not(.close)', function (e) {\r
1161         if (e.currentTarget == this) {\r
1162           e.preventDefault();\r
1163           e.stopPropagation();\r
1164           if (that.options.liveSearch && !$(e.target).hasClass('close')) {\r
1165             that.$searchbox.focus();\r
1166           } else {\r
1167             that.$button.focus();\r
1168           }\r
1169         }\r
1170       });\r
1171 \r
1172       this.$menuInner.on('click', '.divider, .dropdown-header', function (e) {\r
1173         e.preventDefault();\r
1174         e.stopPropagation();\r
1175         if (that.options.liveSearch) {\r
1176           that.$searchbox.focus();\r
1177         } else {\r
1178           that.$button.focus();\r
1179         }\r
1180       });\r
1181 \r
1182       this.$menu.on('click', '.popover-title .close', function () {\r
1183         that.$button.click();\r
1184       });\r
1185 \r
1186       this.$searchbox.on('click', function (e) {\r
1187         e.stopPropagation();\r
1188       });\r
1189 \r
1190       this.$menu.on('click', '.actions-btn', function (e) {\r
1191         if (that.options.liveSearch) {\r
1192           that.$searchbox.focus();\r
1193         } else {\r
1194           that.$button.focus();\r
1195         }\r
1196 \r
1197         e.preventDefault();\r
1198         e.stopPropagation();\r
1199 \r
1200         if ($(this).hasClass('bs-select-all')) {\r
1201           that.selectAll();\r
1202         } else {\r
1203           that.deselectAll();\r
1204         }\r
1205         that.$element.triggerNative('change');\r
1206       });\r
1207 \r
1208       this.$element.change(function () {\r
1209         that.render(false);\r
1210       });\r
1211     },\r
1212 \r
1213     liveSearchListener: function () {\r
1214       var that = this,\r
1215           $no_results = $('<li class="no-results"></li>');\r
1216 \r
1217       this.$newElement.on('click.dropdown.data-api touchstart.dropdown.data-api', function () {\r
1218         that.$menuInner.find('.active').removeClass('active');\r
1219         if (!!that.$searchbox.val()) {\r
1220           that.$searchbox.val('');\r
1221           that.$lis.not('.is-hidden').removeClass('hidden');\r
1222           if (!!$no_results.parent().length) $no_results.remove();\r
1223         }\r
1224         if (!that.multiple) that.$menuInner.find('.selected').addClass('active');\r
1225         setTimeout(function () {\r
1226           that.$searchbox.focus();\r
1227         }, 10);\r
1228       });\r
1229 \r
1230       this.$searchbox.on('click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api', function (e) {\r
1231         e.stopPropagation();\r
1232       });\r
1233 \r
1234       this.$searchbox.on('input propertychange', function () {\r
1235         if (that.$searchbox.val()) {\r
1236           var $searchBase = that.$lis.not('.is-hidden').removeClass('hidden').children('a');\r
1237           if (that.options.liveSearchNormalize) {\r
1238             $searchBase = $searchBase.not(':a' + that._searchStyle() + '("' + normalizeToBase(that.$searchbox.val()) + '")');\r
1239           } else {\r
1240             $searchBase = $searchBase.not(':' + that._searchStyle() + '("' + that.$searchbox.val() + '")');\r
1241           }\r
1242           $searchBase.parent().addClass('hidden');\r
1243 \r
1244           that.$lis.filter('.dropdown-header').each(function () {\r
1245             var $this = $(this),\r
1246                 optgroup = $this.data('optgroup');\r
1247 \r
1248             if (that.$lis.filter('[data-optgroup=' + optgroup + ']').not($this).not('.hidden').length === 0) {\r
1249               $this.addClass('hidden');\r
1250               that.$lis.filter('[data-optgroup=' + optgroup + 'div]').addClass('hidden');\r
1251             }\r
1252           });\r
1253 \r
1254           var $lisVisible = that.$lis.not('.hidden');\r
1255 \r
1256           // hide divider if first or last visible, or if followed by another divider\r
1257           $lisVisible.each(function (index) {\r
1258             var $this = $(this);\r
1259 \r
1260             if ($this.hasClass('divider') && (\r
1261               $this.index() === $lisVisible.first().index() ||\r
1262               $this.index() === $lisVisible.last().index() ||\r
1263               $lisVisible.eq(index + 1).hasClass('divider'))) {\r
1264               $this.addClass('hidden');\r
1265             }\r
1266           });\r
1267 \r
1268           if (!that.$lis.not('.hidden, .no-results').length) {\r
1269             if (!!$no_results.parent().length) {\r
1270               $no_results.remove();\r
1271             }\r
1272             $no_results.html(that.options.noneResultsText.replace('{0}', '"' + htmlEscape(that.$searchbox.val()) + '"')).show();\r
1273             that.$menuInner.append($no_results);\r
1274           } else if (!!$no_results.parent().length) {\r
1275             $no_results.remove();\r
1276           }\r
1277         } else {\r
1278           that.$lis.not('.is-hidden').removeClass('hidden');\r
1279           if (!!$no_results.parent().length) {\r
1280             $no_results.remove();\r
1281           }\r
1282         }\r
1283 \r
1284         that.$lis.filter('.active').removeClass('active');\r
1285         if (that.$searchbox.val()) that.$lis.not('.hidden, .divider, .dropdown-header').eq(0).addClass('active').children('a').focus();\r
1286         $(this).focus();\r
1287       });\r
1288     },\r
1289 \r
1290     _searchStyle: function () {\r
1291       var styles = {\r
1292         begins: 'ibegins',\r
1293         startsWith: 'ibegins'\r
1294       };\r
1295 \r
1296       return styles[this.options.liveSearchStyle] || 'icontains';\r
1297     },\r
1298 \r
1299     val: function (value) {\r
1300       if (typeof value !== 'undefined') {\r
1301         this.$element.val(value);\r
1302         this.render();\r
1303 \r
1304         return this.$element;\r
1305       } else {\r
1306         return this.$element.val();\r
1307       }\r
1308     },\r
1309 \r
1310     changeAll: function (status) {\r
1311       if (typeof status === 'undefined') status = true;\r
1312 \r
1313       this.findLis();\r
1314 \r
1315       var $options = this.$element.find('option'),\r
1316           $lisVisible = this.$lis.not('.divider, .dropdown-header, .disabled, .hidden').toggleClass('selected', status),\r
1317           lisVisLen = $lisVisible.length,\r
1318           selectedOptions = [];\r
1319 \r
1320       for (var i = 0; i < lisVisLen; i++) {\r
1321         var origIndex = $lisVisible[i].getAttribute('data-original-index');\r
1322         selectedOptions[selectedOptions.length] = $options.eq(origIndex)[0];\r
1323       }\r
1324 \r
1325       $(selectedOptions).prop('selected', status);\r
1326 \r
1327       this.render(false);\r
1328     },\r
1329 \r
1330     selectAll: function () {\r
1331       return this.changeAll(true);\r
1332     },\r
1333 \r
1334     deselectAll: function () {\r
1335       return this.changeAll(false);\r
1336     },\r
1337 \r
1338     keydown: function (e) {\r
1339       var $this = $(this),\r
1340           $parent = $this.is('input') ? $this.parent().parent() : $this.parent(),\r
1341           $items,\r
1342           that = $parent.data('this'),\r
1343           index,\r
1344           next,\r
1345           first,\r
1346           last,\r
1347           prev,\r
1348           nextPrev,\r
1349           prevIndex,\r
1350           isActive,\r
1351           selector = ':not(.disabled, .hidden, .dropdown-header, .divider)',\r
1352           keyCodeMap = {\r
1353             32: ' ',\r
1354             48: '0',\r
1355             49: '1',\r
1356             50: '2',\r
1357             51: '3',\r
1358             52: '4',\r
1359             53: '5',\r
1360             54: '6',\r
1361             55: '7',\r
1362             56: '8',\r
1363             57: '9',\r
1364             59: ';',\r
1365             65: 'a',\r
1366             66: 'b',\r
1367             67: 'c',\r
1368             68: 'd',\r
1369             69: 'e',\r
1370             70: 'f',\r
1371             71: 'g',\r
1372             72: 'h',\r
1373             73: 'i',\r
1374             74: 'j',\r
1375             75: 'k',\r
1376             76: 'l',\r
1377             77: 'm',\r
1378             78: 'n',\r
1379             79: 'o',\r
1380             80: 'p',\r
1381             81: 'q',\r
1382             82: 'r',\r
1383             83: 's',\r
1384             84: 't',\r
1385             85: 'u',\r
1386             86: 'v',\r
1387             87: 'w',\r
1388             88: 'x',\r
1389             89: 'y',\r
1390             90: 'z',\r
1391             96: '0',\r
1392             97: '1',\r
1393             98: '2',\r
1394             99: '3',\r
1395             100: '4',\r
1396             101: '5',\r
1397             102: '6',\r
1398             103: '7',\r
1399             104: '8',\r
1400             105: '9'\r
1401           };\r
1402 \r
1403       if (that.options.liveSearch) $parent = $this.parent().parent();\r
1404 \r
1405       if (that.options.container) $parent = that.$menu;\r
1406 \r
1407       $items = $('[role=menu] li', $parent);\r
1408 \r
1409       isActive = that.$menu.parent().hasClass('open');\r
1410 \r
1411       if (!isActive && (e.keyCode >= 48 && e.keyCode <= 57 || e.keyCode >= 96 && e.keyCode <= 105 || e.keyCode >= 65 && e.keyCode <= 90)) {\r
1412         if (!that.options.container) {\r
1413           that.setSize();\r
1414           that.$menu.parent().addClass('open');\r
1415           isActive = true;\r
1416         } else {\r
1417           that.$newElement.trigger('click');\r
1418         }\r
1419         that.$searchbox.focus();\r
1420       }\r
1421 \r
1422       if (that.options.liveSearch) {\r
1423         if (/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && that.$menu.find('.active').length === 0) {\r
1424           e.preventDefault();\r
1425           that.$menu.parent().removeClass('open');\r
1426           if (that.options.container) that.$newElement.removeClass('open');\r
1427           that.$button.focus();\r
1428         }\r
1429         // $items contains li elements when liveSearch is enabled\r
1430         $items = $('[role=menu] li' + selector, $parent);\r
1431         if (!$this.val() && !/(38|40)/.test(e.keyCode.toString(10))) {\r
1432           if ($items.filter('.active').length === 0) {\r
1433             $items = that.$menuInner.find('li');\r
1434             if (that.options.liveSearchNormalize) {\r
1435               $items = $items.filter(':a' + that._searchStyle() + '(' + normalizeToBase(keyCodeMap[e.keyCode]) + ')');\r
1436             } else {\r
1437               $items = $items.filter(':' + that._searchStyle() + '(' + keyCodeMap[e.keyCode] + ')');\r
1438             }\r
1439           }\r
1440         }\r
1441       }\r
1442 \r
1443       if (!$items.length) return;\r
1444 \r
1445       if (/(38|40)/.test(e.keyCode.toString(10))) {\r
1446         index = $items.index($items.find('a').filter(':focus').parent());\r
1447         first = $items.filter(selector).first().index();\r
1448         last = $items.filter(selector).last().index();\r
1449         next = $items.eq(index).nextAll(selector).eq(0).index();\r
1450         prev = $items.eq(index).prevAll(selector).eq(0).index();\r
1451         nextPrev = $items.eq(next).prevAll(selector).eq(0).index();\r
1452 \r
1453         if (that.options.liveSearch) {\r
1454           $items.each(function (i) {\r
1455             if (!$(this).hasClass('disabled')) {\r
1456               $(this).data('index', i);\r
1457             }\r
1458           });\r
1459           index = $items.index($items.filter('.active'));\r
1460           first = $items.first().data('index');\r
1461           last = $items.last().data('index');\r
1462           next = $items.eq(index).nextAll().eq(0).data('index');\r
1463           prev = $items.eq(index).prevAll().eq(0).data('index');\r
1464           nextPrev = $items.eq(next).prevAll().eq(0).data('index');\r
1465         }\r
1466 \r
1467         prevIndex = $this.data('prevIndex');\r
1468 \r
1469         if (e.keyCode == 38) {\r
1470           if (that.options.liveSearch) index--;\r
1471           if (index != nextPrev && index > prev) index = prev;\r
1472           if (index < first) index = first;\r
1473           if (index == prevIndex) index = last;\r
1474         } else if (e.keyCode == 40) {\r
1475           if (that.options.liveSearch) index++;\r
1476           if (index == -1) index = 0;\r
1477           if (index != nextPrev && index < next) index = next;\r
1478           if (index > last) index = last;\r
1479           if (index == prevIndex) index = first;\r
1480         }\r
1481 \r
1482         $this.data('prevIndex', index);\r
1483 \r
1484         if (!that.options.liveSearch) {\r
1485           $items.eq(index).children('a').focus();\r
1486         } else {\r
1487           e.preventDefault();\r
1488           if (!$this.hasClass('dropdown-toggle')) {\r
1489             $items.removeClass('active').eq(index).addClass('active').children('a').focus();\r
1490             $this.focus();\r
1491           }\r
1492         }\r
1493 \r
1494       } else if (!$this.is('input')) {\r
1495         var keyIndex = [],\r
1496             count,\r
1497             prevKey;\r
1498 \r
1499         $items.each(function () {\r
1500           if (!$(this).hasClass('disabled')) {\r
1501             if ($.trim($(this).children('a').text().toLowerCase()).substring(0, 1) == keyCodeMap[e.keyCode]) {\r
1502               keyIndex.push($(this).index());\r
1503             }\r
1504           }\r
1505         });\r
1506 \r
1507         count = $(document).data('keycount');\r
1508         count++;\r
1509         $(document).data('keycount', count);\r
1510 \r
1511         prevKey = $.trim($(':focus').text().toLowerCase()).substring(0, 1);\r
1512 \r
1513         if (prevKey != keyCodeMap[e.keyCode]) {\r
1514           count = 1;\r
1515           $(document).data('keycount', count);\r
1516         } else if (count >= keyIndex.length) {\r
1517           $(document).data('keycount', 0);\r
1518           if (count > keyIndex.length) count = 1;\r
1519         }\r
1520 \r
1521         $items.eq(keyIndex[count - 1]).children('a').focus();\r
1522       }\r
1523 \r
1524       // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu.\r
1525       if ((/(13|32)/.test(e.keyCode.toString(10)) || (/(^9$)/.test(e.keyCode.toString(10)) && that.options.selectOnTab)) && isActive) {\r
1526         if (!/(32)/.test(e.keyCode.toString(10))) e.preventDefault();\r
1527         if (!that.options.liveSearch) {\r
1528           var elem = $(':focus');\r
1529           elem.click();\r
1530           // Bring back focus for multiselects\r
1531           elem.focus();\r
1532           // Prevent screen from scrolling if the user hit the spacebar\r
1533           e.preventDefault();\r
1534           // Fixes spacebar selection of dropdown items in FF & IE\r
1535           $(document).data('spaceSelect', true);\r
1536         } else if (!/(32)/.test(e.keyCode.toString(10))) {\r
1537           that.$menuInner.find('.active a').click();\r
1538           $this.focus();\r
1539         }\r
1540         $(document).data('keycount', 0);\r
1541       }\r
1542 \r
1543       if ((/(^9$|27)/.test(e.keyCode.toString(10)) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode.toString(10)) && !isActive)) {\r
1544         that.$menu.parent().removeClass('open');\r
1545         if (that.options.container) that.$newElement.removeClass('open');\r
1546         that.$button.focus();\r
1547       }\r
1548     },\r
1549 \r
1550     mobile: function () {\r
1551       this.$element.addClass('mobile-device').appendTo(this.$newElement);\r
1552       if (this.options.container) this.$menu.hide();\r
1553     },\r
1554 \r
1555     refresh: function () {\r
1556       this.$lis = null;\r
1557       this.liObj = {};\r
1558       this.reloadLi();\r
1559       this.render();\r
1560       this.checkDisabled();\r
1561       this.liHeight(true);\r
1562       this.setStyle();\r
1563       this.setWidth();\r
1564       if (this.$lis) this.$searchbox.trigger('propertychange');\r
1565 \r
1566       this.$element.trigger('refreshed.bs.select');\r
1567     },\r
1568 \r
1569     hide: function () {\r
1570       this.$newElement.hide();\r
1571     },\r
1572 \r
1573     show: function () {\r
1574       this.$newElement.show();\r
1575     },\r
1576 \r
1577     remove: function () {\r
1578       this.$newElement.remove();\r
1579       this.$element.remove();\r
1580     }\r
1581   };\r
1582 \r
1583   // SELECTPICKER PLUGIN DEFINITION\r
1584   // ==============================\r
1585   function Plugin(option, event) {\r
1586     // get the args of the outer function..\r
1587     var args = arguments;\r
1588     // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them\r
1589     // to get lost/corrupted in android 2.3 and IE9 #715 #775\r
1590     var _option = option,\r
1591         _event = event;\r
1592     [].shift.apply(args);\r
1593 \r
1594     var value;\r
1595     var chain = this.each(function () {\r
1596       var $this = $(this);\r
1597       if ($this.is('select')) {\r
1598         var data = $this.data('selectpicker'),\r
1599             options = typeof _option == 'object' && _option;\r
1600 \r
1601         if (!data) {\r
1602           var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, $this.data(), options);\r
1603           config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), $this.data().template, options.template);\r
1604           $this.data('selectpicker', (data = new Selectpicker(this, config, _event)));\r
1605         } else if (options) {\r
1606           for (var i in options) {\r
1607             if (options.hasOwnProperty(i)) {\r
1608               data.options[i] = options[i];\r
1609             }\r
1610           }\r
1611         }\r
1612 \r
1613         if (typeof _option == 'string') {\r
1614           if (data[_option] instanceof Function) {\r
1615             value = data[_option].apply(data, args);\r
1616           } else {\r
1617             value = data.options[_option];\r
1618           }\r
1619         }\r
1620       }\r
1621     });\r
1622 \r
1623     if (typeof value !== 'undefined') {\r
1624       //noinspection JSUnusedAssignment\r
1625       return value;\r
1626     } else {\r
1627       return chain;\r
1628     }\r
1629   }\r
1630 \r
1631   var old = $.fn.selectpicker;\r
1632   $.fn.selectpicker = Plugin;\r
1633   $.fn.selectpicker.Constructor = Selectpicker;\r
1634 \r
1635   // SELECTPICKER NO CONFLICT\r
1636   // ========================\r
1637   $.fn.selectpicker.noConflict = function () {\r
1638     $.fn.selectpicker = old;\r
1639     return this;\r
1640   };\r
1641 \r
1642   $(document)\r
1643       .data('keycount', 0)\r
1644       .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="menu"], .bs-searchbox input', Selectpicker.prototype.keydown)\r
1645       .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="menu"], .bs-searchbox input', function (e) {\r
1646         e.stopPropagation();\r
1647       });\r
1648 \r
1649   // SELECTPICKER DATA-API\r
1650   // =====================\r
1651   $(window).on('load.bs.select.data-api', function () {\r
1652     $('.selectpicker').each(function () {\r
1653       var $selectpicker = $(this);\r
1654       Plugin.call($selectpicker, $selectpicker.data());\r
1655     })\r
1656   });\r
1657 })(jQuery);\r
1658
1659
1660 }));