bootstrap-select.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. /*!
  2. * bootstrap-select v1.4.3
  3. * http://silviomoreto.github.io/bootstrap-select/
  4. *
  5. * Copyright 2013 bootstrap-select
  6. * Licensed under the MIT license
  7. */
  8. !function($) {
  9. 'use strict';
  10. $.expr[':'].icontains = function(obj, index, meta) {
  11. return $(obj).text().toUpperCase().indexOf(meta[3].toUpperCase()) >= 0;
  12. };
  13. var Selectpicker = function(element, options, e) {
  14. if (e) {
  15. e.stopPropagation();
  16. e.preventDefault();
  17. }
  18. this.$element = $(element);
  19. this.$newElement = null;
  20. this.$button = null;
  21. this.$menu = null;
  22. this.$lis = null;
  23. //Merge defaults, options and data-attributes to make our options
  24. this.options = $.extend({}, $.fn.selectpicker.defaults, this.$element.data(), typeof options == 'object' && options);
  25. //If we have no title yet, check the attribute 'title' (this is missed by jq as its not a data-attribute
  26. if (this.options.title === null) {
  27. this.options.title = this.$element.attr('title');
  28. }
  29. //Expose public methods
  30. this.val = Selectpicker.prototype.val;
  31. this.render = Selectpicker.prototype.render;
  32. this.refresh = Selectpicker.prototype.refresh;
  33. this.setStyle = Selectpicker.prototype.setStyle;
  34. this.selectAll = Selectpicker.prototype.selectAll;
  35. this.deselectAll = Selectpicker.prototype.deselectAll;
  36. this.init();
  37. };
  38. Selectpicker.prototype = {
  39. constructor: Selectpicker,
  40. init: function() {
  41. var that = this,
  42. id = this.$element.attr('id');
  43. this.$element.hide();
  44. this.multiple = this.$element.prop('multiple');
  45. this.autofocus = this.$element.prop('autofocus');
  46. this.$newElement = this.createView();
  47. this.$element.after(this.$newElement);
  48. this.$menu = this.$newElement.find('> .dropdown-menu');
  49. this.$button = this.$newElement.find('> button');
  50. this.$searchbox = this.$newElement.find('input');
  51. if (id !== undefined) {
  52. this.$button.attr('data-id', id);
  53. $('label[for="' + id + '"]').click(function(e) {
  54. e.preventDefault();
  55. that.$button.focus();
  56. });
  57. }
  58. this.checkDisabled();
  59. this.clickListener();
  60. if (this.options.liveSearch) this.liveSearchListener();
  61. this.render();
  62. this.liHeight();
  63. this.setStyle();
  64. this.setWidth();
  65. if (this.options.container) this.selectPosition();
  66. this.$menu.data('this', this);
  67. this.$newElement.data('this', this);
  68. },
  69. createDropdown: function() {
  70. //If we are multiple, then add the show-tick class by default
  71. var multiple = this.multiple ? ' show-tick' : '';
  72. var autofocus = this.autofocus ? ' autofocus' : '';
  73. var header = this.options.header ? '<div class="popover-title"><button type="button" class="close" aria-hidden="true">&times;</button>' + this.options.header + '</div>' : '';
  74. var searchbox = this.options.liveSearch ? '<div class="bootstrap-select-searchbox"><input type="text" class="input-block-level form-control" /></div>' : '';
  75. var drop =
  76. '<div class="btn-group bootstrap-select' + multiple + '">' +
  77. '<button type="button" class="btn dropdown-toggle selectpicker" data-toggle="dropdown"'+ autofocus +'>' +
  78. '<span class="filter-option pull-left"></span>&nbsp;' +
  79. '<span class="caret"></span>' +
  80. '</button>' +
  81. '<div class="dropdown-menu open">' +
  82. header +
  83. searchbox +
  84. '<ul class="dropdown-menu inner selectpicker" role="menu">' +
  85. '</ul>' +
  86. '</div>' +
  87. '</div>';
  88. return $(drop);
  89. },
  90. createView: function() {
  91. var $drop = this.createDropdown();
  92. var $li = this.createLi();
  93. $drop.find('ul').append($li);
  94. return $drop;
  95. },
  96. reloadLi: function() {
  97. //Remove all children.
  98. this.destroyLi();
  99. //Re build
  100. var $li = this.createLi();
  101. this.$menu.find('ul').append( $li );
  102. },
  103. destroyLi: function() {
  104. this.$menu.find('li').remove();
  105. },
  106. createLi: function() {
  107. var that = this,
  108. _liA = [],
  109. _liHtml = '';
  110. this.$element.find('option').each(function() {
  111. var $this = $(this);
  112. //Get the class and text for the option
  113. var optionClass = $this.attr('class') || '';
  114. var inline = $this.attr('style') || '';
  115. var text = $this.data('content') ? $this.data('content') : $this.html();
  116. var subtext = $this.data('subtext') !== undefined ? '<small class="muted text-muted">' + $this.data('subtext') + '</small>' : '';
  117. var icon = $this.data('icon') !== undefined ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : '';
  118. if (icon !== '' && ($this.is(':disabled') || $this.parent().is(':disabled'))) {
  119. icon = '<span>'+icon+'</span>';
  120. }
  121. if (!$this.data('content')) {
  122. //Prepend any icon and append any subtext to the main text.
  123. text = icon + '<span class="text">' + text + subtext + '</span>';
  124. }
  125. if (that.options.hideDisabled && ($this.is(':disabled') || $this.parent().is(':disabled'))) {
  126. _liA.push('<a style="min-height: 0; padding: 0"></a>');
  127. } else if ($this.parent().is('optgroup') && $this.data('divider') !== true) {
  128. if ($this.index() === 0) {
  129. //Get the opt group label
  130. var label = $this.parent().attr('label');
  131. var labelSubtext = $this.parent().data('subtext') !== undefined ? '<small class="muted text-muted">'+$this.parent().data('subtext')+'</small>' : '';
  132. var labelIcon = $this.parent().data('icon') ? '<i class="'+$this.parent().data('icon')+'"></i> ' : '';
  133. label = labelIcon + '<span class="text">' + label + labelSubtext + '</span>';
  134. if ($this[0].index !== 0) {
  135. _liA.push(
  136. '<div class="div-contain"><div class="divider"></div></div>'+
  137. '<dt>'+label+'</dt>'+
  138. that.createA(text, 'opt ' + optionClass, inline )
  139. );
  140. } else {
  141. _liA.push(
  142. '<dt>'+label+'</dt>'+
  143. that.createA(text, 'opt ' + optionClass, inline ));
  144. }
  145. } else {
  146. _liA.push(that.createA(text, 'opt ' + optionClass, inline ));
  147. }
  148. } else if ($this.data('divider') === true) {
  149. _liA.push('<div class="div-contain"><div class="divider"></div></div>');
  150. } else if ($(this).data('hidden') === true) {
  151. _liA.push('');
  152. } else {
  153. _liA.push(that.createA(text, optionClass, inline ));
  154. }
  155. });
  156. $.each(_liA, function(i, item) {
  157. _liHtml += '<li rel=' + i + '>' + item + '</li>';
  158. });
  159. //If we are not multiple, and we dont have a selected item, and we dont have a title, select the first element so something is set in the button
  160. if (!this.multiple && this.$element.find('option:selected').length===0 && !this.options.title) {
  161. this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected');
  162. }
  163. return $(_liHtml);
  164. },
  165. createA: function(text, classes, inline) {
  166. return '<a tabindex="0" class="'+classes+'" style="'+inline+'">' +
  167. text +
  168. '<i class="' + this.options.iconBase + ' ' + this.options.tickIcon + ' icon-ok check-mark"></i>' +
  169. '</a>';
  170. },
  171. render: function(updateLi) {
  172. var that = this;
  173. //Update the LI to match the SELECT
  174. if (updateLi !== false) {
  175. this.$element.find('option').each(function(index) {
  176. that.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled') );
  177. that.setSelected(index, $(this).is(':selected') );
  178. });
  179. }
  180. this.tabIndex();
  181. var selectedItems = this.$element.find('option:selected').map(function() {
  182. var $this = $(this);
  183. var icon = $this.data('icon') && that.options.showIcon ? '<i class="' + that.options.iconBase + ' ' + $this.data('icon') + '"></i> ' : '';
  184. var subtext;
  185. if (that.options.showSubtext && $this.attr('data-subtext') && !that.multiple) {
  186. subtext = ' <small class="muted text-muted">'+$this.data('subtext') +'</small>';
  187. } else {
  188. subtext = '';
  189. }
  190. if ($this.data('content') && that.options.showContent) {
  191. return $this.data('content');
  192. } else if ($this.attr('title') !== undefined) {
  193. return $this.attr('title');
  194. } else {
  195. return icon + $this.html() + subtext;
  196. }
  197. }).toArray();
  198. //Fixes issue in IE10 occurring when no default option is selected and at least one option is disabled
  199. //Convert all the values into a comma delimited string
  200. var title = !this.multiple ? selectedItems[0] : selectedItems.join(this.options.multipleSeparator);
  201. //If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..
  202. if (this.multiple && this.options.selectedTextFormat.indexOf('count') > -1) {
  203. var max = this.options.selectedTextFormat.split('>');
  204. var notDisabled = this.options.hideDisabled ? ':not([disabled])' : '';
  205. if ( (max.length>1 && selectedItems.length > max[1]) || (max.length==1 && selectedItems.length>=2)) {
  206. title = this.options.countSelectedText.replace('{0}', selectedItems.length).replace('{1}', this.$element.find('option:not([data-divider="true"]):not([data-hidden="true"])'+notDisabled).length);
  207. }
  208. }
  209. //If we dont have a title, then use the default, or if nothing is set at all, use the not selected text
  210. if (!title) {
  211. title = this.options.title !== undefined ? this.options.title : this.options.noneSelectedText;
  212. }
  213. this.$button.attr('title', $.trim(title));
  214. this.$newElement.find('.filter-option').html(title);
  215. },
  216. setStyle: function(style, status) {
  217. if (this.$element.attr('class')) {
  218. this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device/gi, ''));
  219. }
  220. var buttonClass = style ? style : this.options.style;
  221. if (status == 'add') {
  222. this.$button.addClass(buttonClass);
  223. } else if (status == 'remove') {
  224. this.$button.removeClass(buttonClass);
  225. } else {
  226. this.$button.removeClass(this.options.style);
  227. this.$button.addClass(buttonClass);
  228. }
  229. },
  230. liHeight: function() {
  231. var $selectClone = this.$menu.parent().clone().find('> .dropdown-toggle').prop('autofocus', false).end().appendTo('body'),
  232. $menuClone = $selectClone.addClass('open').find('> .dropdown-menu'),
  233. liHeight = $menuClone.find('li > a').outerHeight(),
  234. headerHeight = this.options.header ? $menuClone.find('.popover-title').outerHeight() : 0,
  235. searchHeight = this.options.liveSearch ? $menuClone.find('.bootstrap-select-searchbox').outerHeight() : 0;
  236. $selectClone.remove();
  237. this.$newElement
  238. .data('liHeight', liHeight)
  239. .data('headerHeight', headerHeight)
  240. .data('searchHeight', searchHeight);
  241. },
  242. setSize: function() {
  243. var that = this,
  244. menu = this.$menu,
  245. menuInner = menu.find('.inner'),
  246. selectHeight = this.$newElement.outerHeight(),
  247. liHeight = this.$newElement.data('liHeight'),
  248. headerHeight = this.$newElement.data('headerHeight'),
  249. searchHeight = this.$newElement.data('searchHeight'),
  250. divHeight = menu.find('li .divider').outerHeight(true),
  251. menuPadding = parseInt(menu.css('padding-top')) +
  252. parseInt(menu.css('padding-bottom')) +
  253. parseInt(menu.css('border-top-width')) +
  254. parseInt(menu.css('border-bottom-width')),
  255. notDisabled = this.options.hideDisabled ? ':not(.disabled)' : '',
  256. $window = $(window),
  257. menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2,
  258. menuHeight,
  259. selectOffsetTop,
  260. selectOffsetBot,
  261. posVert = function() {
  262. selectOffsetTop = that.$newElement.offset().top - $window.scrollTop();
  263. selectOffsetBot = $window.height() - selectOffsetTop - selectHeight;
  264. };
  265. posVert();
  266. if (this.options.header) menu.css('padding-top', 0);
  267. if (this.options.size == 'auto') {
  268. var getSize = function() {
  269. var minHeight;
  270. posVert();
  271. menuHeight = selectOffsetBot - menuExtras;
  272. if (that.options.dropupAuto) {
  273. that.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && ((menuHeight - menuExtras) < menu.height()));
  274. }
  275. if (that.$newElement.hasClass('dropup')) {
  276. menuHeight = selectOffsetTop - menuExtras;
  277. }
  278. if ((menu.find('li').length + menu.find('dt').length) > 3) {
  279. minHeight = liHeight*3 + menuExtras - 2;
  280. } else {
  281. minHeight = 0;
  282. }
  283. menu.css({'max-height' : menuHeight + 'px', 'overflow' : 'hidden', 'min-height' : minHeight + 'px'});
  284. menuInner.css({'max-height' : menuHeight - headerHeight - searchHeight- menuPadding + 'px', 'overflow-y' : 'auto', 'min-height' : minHeight - menuPadding + 'px'});
  285. };
  286. getSize();
  287. $(window).resize(getSize);
  288. $(window).scroll(getSize);
  289. } else if (this.options.size && this.options.size != 'auto' && menu.find('li'+notDisabled).length > this.options.size) {
  290. var optIndex = menu.find('li'+notDisabled+' > *').filter(':not(.div-contain)').slice(0,this.options.size).last().parent().index();
  291. var divLength = menu.find('li').slice(0,optIndex + 1).find('.div-contain').length;
  292. menuHeight = liHeight*this.options.size + divLength*divHeight + menuPadding;
  293. if (that.options.dropupAuto) {
  294. this.$newElement.toggleClass('dropup', (selectOffsetTop > selectOffsetBot) && (menuHeight < menu.height()));
  295. }
  296. menu.css({'max-height' : menuHeight + headerHeight + searchHeight + 'px', 'overflow' : 'hidden'});
  297. menuInner.css({'max-height' : menuHeight - menuPadding + 'px', 'overflow-y' : 'auto'});
  298. }
  299. },
  300. setWidth: function() {
  301. if (this.options.width == 'auto') {
  302. this.$menu.css('min-width', '0');
  303. // Get correct width if element hidden
  304. var selectClone = this.$newElement.clone().appendTo('body');
  305. var ulWidth = selectClone.find('> .dropdown-menu').css('width');
  306. selectClone.remove();
  307. this.$newElement.css('width', ulWidth);
  308. } else if (this.options.width == 'fit') {
  309. // Remove inline min-width so width can be changed from 'auto'
  310. this.$menu.css('min-width', '');
  311. this.$newElement.css('width', '').addClass('fit-width');
  312. } else if (this.options.width) {
  313. // Remove inline min-width so width can be changed from 'auto'
  314. this.$menu.css('min-width', '');
  315. this.$newElement.css('width', this.options.width);
  316. } else {
  317. // Remove inline min-width/width so width can be changed
  318. this.$menu.css('min-width', '');
  319. this.$newElement.css('width', '');
  320. }
  321. // Remove fit-width class if width is changed programmatically
  322. if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') {
  323. this.$newElement.removeClass('fit-width');
  324. }
  325. },
  326. selectPosition: function() {
  327. var that = this,
  328. drop = '<div />',
  329. $drop = $(drop),
  330. pos,
  331. actualHeight,
  332. getPlacement = function($element) {
  333. $drop.addClass($element.attr('class')).toggleClass('dropup', $element.hasClass('dropup'));
  334. pos = $element.offset();
  335. actualHeight = $element.hasClass('dropup') ? 0 : $element[0].offsetHeight;
  336. $drop.css({'top' : pos.top + actualHeight, 'left' : pos.left, 'width' : $element[0].offsetWidth, 'position' : 'absolute'});
  337. };
  338. this.$newElement.on('click', function() {
  339. getPlacement($(this));
  340. $drop.appendTo(that.options.container);
  341. $drop.toggleClass('open', !$(this).hasClass('open'));
  342. $drop.append(that.$menu);
  343. });
  344. $(window).resize(function() {
  345. getPlacement(that.$newElement);
  346. });
  347. $(window).on('scroll', function() {
  348. getPlacement(that.$newElement);
  349. });
  350. $('html').on('click', function(e) {
  351. if ($(e.target).closest(that.$newElement).length < 1) {
  352. $drop.removeClass('open');
  353. }
  354. });
  355. },
  356. mobile: function() {
  357. this.$element.addClass('mobile-device').appendTo(this.$newElement);
  358. if (this.options.container) this.$menu.hide();
  359. },
  360. refresh: function() {
  361. this.$lis = null;
  362. this.reloadLi();
  363. this.render();
  364. this.setWidth();
  365. this.setStyle();
  366. this.checkDisabled();
  367. this.liHeight();
  368. },
  369. update: function() {
  370. this.reloadLi();
  371. this.setWidth();
  372. this.setStyle();
  373. this.checkDisabled();
  374. this.liHeight();
  375. },
  376. setSelected: function(index, selected) {
  377. if (this.$lis == null) this.$lis = this.$menu.find('li');
  378. $(this.$lis[index]).toggleClass('selected', selected);
  379. },
  380. setDisabled: function(index, disabled) {
  381. if (this.$lis == null) this.$lis = this.$menu.find('li');
  382. if (disabled) {
  383. $(this.$lis[index]).addClass('disabled').find('a').attr('href', '#').attr('tabindex', -1);
  384. } else {
  385. $(this.$lis[index]).removeClass('disabled').find('a').removeAttr('href').attr('tabindex', 0);
  386. }
  387. },
  388. isDisabled: function() {
  389. return this.$element.is(':disabled');
  390. },
  391. checkDisabled: function() {
  392. var that = this;
  393. if (this.isDisabled()) {
  394. this.$button.addClass('disabled').attr('tabindex', -1);
  395. } else {
  396. if (this.$button.hasClass('disabled')) {
  397. this.$button.removeClass('disabled');
  398. }
  399. if (this.$button.attr('tabindex') == -1) {
  400. if (!this.$element.data('tabindex')) this.$button.removeAttr('tabindex');
  401. }
  402. }
  403. this.$button.click(function() {
  404. return !that.isDisabled();
  405. });
  406. },
  407. tabIndex: function() {
  408. if (this.$element.is('[tabindex]')) {
  409. this.$element.data('tabindex', this.$element.attr('tabindex'));
  410. this.$button.attr('tabindex', this.$element.data('tabindex'));
  411. }
  412. },
  413. clickListener: function() {
  414. var that = this;
  415. $('body').on('touchstart.dropdown', '.dropdown-menu', function(e) {
  416. e.stopPropagation();
  417. });
  418. this.$newElement.on('click', function() {
  419. that.setSize();
  420. if (!that.options.liveSearch && !that.multiple) {
  421. setTimeout(function() {
  422. that.$menu.find('.selected a').focus();
  423. }, 10);
  424. }
  425. });
  426. this.$menu.on('click', 'li a', function(e) {
  427. var clickedIndex = $(this).parent().index(),
  428. prevValue = that.$element.val(),
  429. prevIndex = that.$element.prop('selectedIndex');
  430. //Dont close on multi choice menu
  431. if (that.multiple) {
  432. e.stopPropagation();
  433. }
  434. e.preventDefault();
  435. //Dont run if we have been disabled
  436. if (!that.isDisabled() && !$(this).parent().hasClass('disabled')) {
  437. var $options = that.$element.find('option'),
  438. $option = $options.eq(clickedIndex),
  439. state = $option.prop('selected');
  440. //Deselect all others if not multi select box
  441. if (!that.multiple) {
  442. $options.prop('selected', false);
  443. $option.prop('selected', true);
  444. that.$menu.find('.selected').removeClass('selected');
  445. that.setSelected(clickedIndex, true);
  446. }
  447. //Else toggle the one we have chosen if we are multi select.
  448. else {
  449. $option.prop('selected', !state);
  450. that.setSelected(clickedIndex, !state);
  451. }
  452. if (!that.multiple) {
  453. that.$button.focus();
  454. } else if (that.options.liveSearch) {
  455. that.$searchbox.focus();
  456. }
  457. // Trigger select 'change'
  458. if ((prevValue != that.$element.val() && that.multiple) || (prevIndex != that.$element.prop('selectedIndex') && !that.multiple)) {
  459. that.$element.change();
  460. }
  461. }
  462. });
  463. this.$menu.on('click', 'li.disabled a, li dt, li .div-contain, .popover-title, .popover-title :not(.close)', function(e) {
  464. if (e.target == this) {
  465. e.preventDefault();
  466. e.stopPropagation();
  467. if (!that.options.liveSearch) {
  468. that.$button.focus();
  469. } else {
  470. that.$searchbox.focus();
  471. }
  472. }
  473. });
  474. this.$menu.on('click', '.popover-title .close', function() {
  475. that.$button.focus();
  476. });
  477. this.$searchbox.on('click', function(e) {
  478. e.stopPropagation();
  479. });
  480. this.$element.change(function() {
  481. that.render(false);
  482. });
  483. },
  484. liveSearchListener: function() {
  485. var that = this,
  486. no_results = $('<li class="no-results"></li>');
  487. this.$newElement.on('click.dropdown.data-api', function() {
  488. that.$menu.find('.active').removeClass('active');
  489. if (!!that.$searchbox.val()) {
  490. that.$searchbox.val('');
  491. that.$menu.find('li').show();
  492. if (!!no_results.parent().length) no_results.remove();
  493. }
  494. if (!that.multiple) that.$menu.find('.selected').addClass('active');
  495. setTimeout(function() {
  496. that.$searchbox.focus();
  497. }, 10);
  498. });
  499. this.$searchbox.on('input propertychange', function() {
  500. if (that.$searchbox.val()) {
  501. that.$menu.find('li').show().not(':icontains(' + that.$searchbox.val() + ')').hide();
  502. if (!that.$menu.find('li').filter(':visible:not(.no-results)').length) {
  503. if (!!no_results.parent().length) no_results.remove();
  504. no_results.html(that.options.noneResultsText + ' "'+ that.$searchbox.val() + '"').show();
  505. that.$menu.find('li').last().after(no_results);
  506. } else if (!!no_results.parent().length) {
  507. no_results.remove();
  508. }
  509. } else {
  510. that.$menu.find('li').show();
  511. if (!!no_results.parent().length) no_results.remove();
  512. }
  513. that.$menu.find('li.active').removeClass('active');
  514. that.$menu.find('li').filter(':visible:not(.divider)').eq(0).addClass('active').find('a').focus();
  515. $(this).focus();
  516. });
  517. this.$menu.on('mouseenter', 'a', function(e) {
  518. that.$menu.find('.active').removeClass('active');
  519. $(e.currentTarget).parent().not('.disabled').addClass('active');
  520. });
  521. this.$menu.on('mouseleave', 'a', function() {
  522. that.$menu.find('.active').removeClass('active');
  523. });
  524. },
  525. val: function(value) {
  526. if (value !== undefined) {
  527. this.$element.val( value );
  528. this.$element.change();
  529. return this.$element;
  530. } else {
  531. return this.$element.val();
  532. }
  533. },
  534. selectAll: function() {
  535. this.$element.find('option').prop('selected', true).attr('selected', 'selected');
  536. this.render();
  537. },
  538. deselectAll: function() {
  539. this.$element.find('option').prop('selected', false).removeAttr('selected');
  540. this.render();
  541. },
  542. keydown: function(e) {
  543. var $this,
  544. $items,
  545. $parent,
  546. index,
  547. next,
  548. first,
  549. last,
  550. prev,
  551. nextPrev,
  552. that,
  553. prevIndex,
  554. isActive,
  555. keyCodeMap = {
  556. 32:' ', 48:'0', 49:'1', 50:'2', 51:'3', 52:'4', 53:'5', 54:'6', 55:'7', 56:'8', 57:'9', 59:';',
  557. 65:'a', 66:'b', 67:'c', 68:'d', 69:'e', 70:'f', 71:'g', 72:'h', 73:'i', 74:'j', 75:'k', 76:'l',
  558. 77:'m', 78:'n', 79:'o', 80:'p', 81:'q', 82:'r', 83:'s', 84:'t', 85:'u', 86:'v', 87:'w', 88:'x',
  559. 89:'y', 90:'z', 96:'0', 97:'1', 98:'2', 99:'3', 100:'4', 101:'5', 102:'6', 103:'7', 104:'8', 105:'9'
  560. };
  561. $this = $(this);
  562. $parent = $this.parent();
  563. if ($this.is('input')) $parent = $this.parent().parent();
  564. that = $parent.data('this');
  565. if (that.options.liveSearch) $parent = $this.parent().parent();
  566. if (that.options.container) $parent = that.$menu;
  567. $items = $('[role=menu] li:not(.divider) a', $parent);
  568. isActive = that.$menu.parent().hasClass('open');
  569. if (!isActive && /([0-9]|[A-z])/.test(String.fromCharCode(e.keyCode))) {
  570. that.setSize();
  571. that.$menu.parent().addClass('open');
  572. isActive = that.$menu.parent().hasClass('open');
  573. that.$searchbox.focus();
  574. }
  575. if (that.options.liveSearch) {
  576. if (/(^9$|27)/.test(e.keyCode) && isActive && that.$menu.find('.active').length === 0) {
  577. e.preventDefault();
  578. that.$menu.parent().removeClass('open');
  579. that.$button.focus();
  580. }
  581. $items = $('[role=menu] li:not(.divider):visible', $parent);
  582. if (!$this.val() && !/(38|40)/.test(e.keyCode)) {
  583. if ($items.filter('.active').length === 0) {
  584. $items = that.$newElement.find('li').filter(':icontains(' + keyCodeMap[e.keyCode] + ')');
  585. }
  586. }
  587. }
  588. if (!$items.length) return;
  589. if (/(38|40)/.test(e.keyCode)) {
  590. index = $items.index($items.filter(':focus'));
  591. first = $items.parent(':not(.disabled):visible').first().index();
  592. last = $items.parent(':not(.disabled):visible').last().index();
  593. next = $items.eq(index).parent().nextAll(':not(.disabled):visible').eq(0).index();
  594. prev = $items.eq(index).parent().prevAll(':not(.disabled):visible').eq(0).index();
  595. nextPrev = $items.eq(next).parent().prevAll(':not(.disabled):visible').eq(0).index();
  596. if (that.options.liveSearch) {
  597. $items.each(function(i) {
  598. if ($(this).is(':not(.disabled)')) {
  599. $(this).data('index', i);
  600. }
  601. });
  602. index = $items.index($items.filter('.active'));
  603. first = $items.filter(':not(.disabled):visible').first().data('index');
  604. last = $items.filter(':not(.disabled):visible').last().data('index');
  605. next = $items.eq(index).nextAll(':not(.disabled):visible').eq(0).data('index');
  606. prev = $items.eq(index).prevAll(':not(.disabled):visible').eq(0).data('index');
  607. nextPrev = $items.eq(next).prevAll(':not(.disabled):visible').eq(0).data('index');
  608. }
  609. prevIndex = $this.data('prevIndex');
  610. if (e.keyCode == 38) {
  611. if (that.options.liveSearch) index -= 1;
  612. if (index != nextPrev && index > prev) index = prev;
  613. if (index < first) index = first;
  614. if (index == prevIndex) index = last;
  615. }
  616. if (e.keyCode == 40) {
  617. if (that.options.liveSearch) index += 1;
  618. if (index == -1) index = 0;
  619. if (index != nextPrev && index < next) index = next;
  620. if (index > last) index = last;
  621. if (index == prevIndex) index = first;
  622. }
  623. $this.data('prevIndex', index);
  624. if (!that.options.liveSearch) {
  625. $items.eq(index).focus();
  626. } else {
  627. e.preventDefault();
  628. if (!$this.is('.dropdown-toggle')) {
  629. $items.removeClass('active');
  630. $items.eq(index).addClass('active').find('a').focus();
  631. $this.focus();
  632. }
  633. }
  634. } else if (!$this.is('input')) {
  635. var keyIndex = [],
  636. count,
  637. prevKey;
  638. $items.each(function() {
  639. if ($(this).parent().is(':not(.disabled)')) {
  640. if ($.trim($(this).text().toLowerCase()).substring(0,1) == keyCodeMap[e.keyCode]) {
  641. keyIndex.push($(this).parent().index());
  642. }
  643. }
  644. });
  645. count = $(document).data('keycount');
  646. count++;
  647. $(document).data('keycount',count);
  648. prevKey = $.trim($(':focus').text().toLowerCase()).substring(0,1);
  649. if (prevKey != keyCodeMap[e.keyCode]) {
  650. count = 1;
  651. $(document).data('keycount', count);
  652. } else if (count >= keyIndex.length) {
  653. $(document).data('keycount', 0);
  654. if (count > keyIndex.length) count = 1;
  655. }
  656. $items.eq(keyIndex[count - 1]).focus();
  657. }
  658. // Select focused option if "Enter", "Spacebar", "Tab" are pressed inside the menu.
  659. if (/(13|32|^9$)/.test(e.keyCode) && isActive) {
  660. if (!/(32)/.test(e.keyCode)) e.preventDefault();
  661. if (!that.options.liveSearch) {
  662. $(':focus').click();
  663. } else if (!/(32)/.test(e.keyCode)) {
  664. that.$menu.find('.active a').click();
  665. $this.focus();
  666. }
  667. $(document).data('keycount',0);
  668. }
  669. if ((/(^9$|27)/.test(e.keyCode) && isActive && (that.multiple || that.options.liveSearch)) || (/(27)/.test(e.keyCode) && !isActive)) {
  670. that.$menu.parent().removeClass('open');
  671. that.$button.focus();
  672. }
  673. },
  674. hide: function() {
  675. this.$newElement.hide();
  676. },
  677. show: function() {
  678. this.$newElement.show();
  679. },
  680. destroy: function() {
  681. this.$newElement.remove();
  682. this.$element.remove();
  683. }
  684. };
  685. $.fn.selectpicker = function(option, event) {
  686. //get the args of the outer function..
  687. var args = arguments;
  688. var value;
  689. var chain = this.each(function() {
  690. if ($(this).is('select')) {
  691. var $this = $(this),
  692. data = $this.data('selectpicker'),
  693. options = typeof option == 'object' && option;
  694. if (!data) {
  695. $this.data('selectpicker', (data = new Selectpicker(this, options, event)));
  696. } else if (options) {
  697. for(var i in options) {
  698. data.options[i] = options[i];
  699. }
  700. }
  701. if (typeof option == 'string') {
  702. //Copy the value of option, as once we shift the arguments
  703. //it also shifts the value of option.
  704. var property = option;
  705. if (data[property] instanceof Function) {
  706. [].shift.apply(args);
  707. value = data[property].apply(data, args);
  708. } else {
  709. value = data.options[property];
  710. }
  711. }
  712. }
  713. });
  714. if (value !== undefined) {
  715. return value;
  716. } else {
  717. return chain;
  718. }
  719. };
  720. $.fn.selectpicker.defaults = {
  721. style: 'btn-default',
  722. size: 'auto',
  723. title: null,
  724. selectedTextFormat : 'values',
  725. noneSelectedText : 'Nothing selected',
  726. noneResultsText : 'No results match',
  727. countSelectedText: '{0} of {1} selected',
  728. width: false,
  729. container: false,
  730. hideDisabled: false,
  731. showSubtext: false,
  732. showIcon: true,
  733. showContent: true,
  734. dropupAuto: true,
  735. header: false,
  736. liveSearch: false,
  737. multipleSeparator: ', ',
  738. iconBase: 'glyphicon',
  739. tickIcon: 'glyphicon-ok'
  740. };
  741. $(document)
  742. .data('keycount', 0)
  743. .on('keydown', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bootstrap-select-searchbox input', Selectpicker.prototype.keydown)
  744. .on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bootstrap-select-searchbox input', function (e) { e.stopPropagation(); });
  745. }(window.jQuery);