bootstrap-select_new.js 51 KB

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