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