jquery.multiselect.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. /**
  2. * Display a nice easy to use multiselect list
  3. * @Version: 2.3.0
  4. * @Author: Patrick Springstubbe
  5. * @Contact: @JediNobleclem
  6. * @Website: springstubbe.us
  7. * @Source: https://github.com/nobleclem/jQuery-MultiSelect
  8. * @Notes: If select list is hidden on page load use the jquery.actual plugin
  9. * to resolve issues with preselected items placeholder text
  10. * https://github.com/dreamerslab/jquery.actual
  11. *
  12. * Usage:
  13. * $('select[multiple]').multiselect();
  14. * $('select[multiple]').multiselect({ texts: { placeholder: 'Select options' } });
  15. * $('select[multiple]').multiselect('reload');
  16. * $('select[multiple]').multiselect( 'loadOptions', [{
  17. * name : 'Option Name 1',
  18. * value : 'option-value-1',
  19. * checked: false,
  20. * attributes : {
  21. * custom1: 'value1',
  22. * custom2: 'value2'
  23. * }
  24. * },{
  25. * name : 'Option Name 2',
  26. * value : 'option-value-2',
  27. * checked: false,
  28. * attributes : {
  29. * custom1: 'value1',
  30. * custom2: 'value2'
  31. * }
  32. * }]);
  33. *
  34. **/
  35. (function($){
  36. var defaults = {
  37. columns : 1, // how many columns should be use to show options
  38. search : false, // include option search box
  39. // search filter options
  40. searchOptions : {
  41. showOptGroups: false, // show option group titles if no options remaining
  42. onSearch : function( element ){} // fires on keyup before search on options happens
  43. },
  44. texts: {
  45. placeholder: 'Select options', // text to use in dummy input
  46. search: 'Search', // search input placeholder text
  47. selectedOptions: ' selected', // selected suffix text
  48. selectAll: 'Select all', // select all text
  49. deselectAll: 'Deselect all', // select all text
  50. noneSelected: 'None Selected' // None selected text
  51. },
  52. selectAll : false, // add select all option
  53. selectGroup : false, // select entire optgroup
  54. minHeight : 200, // minimum height of option overlay
  55. maxHeight : null, // maximum height of option overlay
  56. showCheckbox : true, // display the checkbox to the user
  57. jqActualOpts : {}, // options for jquery.actual
  58. optionAttributes: [], // attributes to copy to the checkbox from the option element
  59. // Callbacks
  60. onLoad : function( element ) {}, // fires at end of list initialization
  61. onOptionClick : function( element, option ){}, // fires when an option is clicked
  62. onControlClose: function( element ){}, // fires when the options list is closed
  63. // @NOTE: these are for future development
  64. maxWidth : null, // maximum width of option overlay (or selector)
  65. minSelect : false, // minimum number of items that can be selected
  66. maxSelect : false, // maximum number of items that can be selected
  67. };
  68. var msCounter = 1;
  69. // FOR LEGACY BROWSERS (talking to you IE8)
  70. if( typeof Array.prototype.map !== 'function' ) {
  71. Array.prototype.map = function( callback, thisArg ) {
  72. if( typeof thisArg === 'undefined' ) {
  73. thisArg = this;
  74. }
  75. return Array.isArray( thisArg ) ? $.map( thisArg, callback ) : [];
  76. };
  77. }
  78. if( typeof String.prototype.trim !== 'function' ) {
  79. String.prototype.trim = function() {
  80. return this.replace(/^\s+|\s+$/g, '');
  81. }
  82. }
  83. function MultiSelect( element, options )
  84. {
  85. this.element = element;
  86. this.options = $.extend( true, {}, defaults, options );
  87. /** BACKWARDS COMPATIBILITY **/
  88. if( 'placeholder' in this.options ) {
  89. this.options.texts.placeholder = this.options.placeholder;
  90. delete this.options.placeholder;
  91. }
  92. if( 'default' in this.options.searchOptions ) {
  93. this.options.texts.search = this.options.searchOptions['default'];
  94. delete this.options.searchOptions['default'];
  95. }
  96. /** END BACKWARDS COMPATIBILITY **/
  97. // load this instance
  98. this.load();
  99. }
  100. MultiSelect.prototype = {
  101. /* LOAD CUSTOM MULTISELECT DOM/ACTIONS */
  102. load: function() {
  103. var instance = this;
  104. // make sure this is a select list and not loaded
  105. if( (instance.element.nodeName != 'SELECT') || $(instance.element).hasClass('jqmsLoaded') ) {
  106. return true;
  107. }
  108. // sanity check so we don't double load on a select element
  109. $(instance.element).addClass('jqmsLoaded').data( 'plugin_multiselect-instance', instance );
  110. // add option container
  111. $(instance.element).after('<div class="ms-options-wrap"><button>None Selected</button><div class="ms-options"><ul></ul></div></div>');
  112. var placeholder = $(instance.element).next('.ms-options-wrap').find('> button:first-child');
  113. var optionsWrap = $(instance.element).next('.ms-options-wrap').find('> .ms-options');
  114. var optionsList = optionsWrap.find('> ul');
  115. var hasOptGroup = $(instance.element).find('optgroup').length ? true : false;
  116. // don't show checkbox (add class for css to hide checkboxes)
  117. if( !instance.options.showCheckbox ) {
  118. optionsWrap.addClass('hide-checkbox');
  119. }
  120. // determine maxWidth
  121. var maxWidth = null;
  122. if( typeof instance.options.width == 'number' ) {
  123. optionsWrap.parent().css( 'position', 'relative' );
  124. maxWidth = instance.options.width;
  125. }
  126. else if( typeof instance.options.width == 'string' ) {
  127. $( instance.options.width ).css( 'position', 'relative' );
  128. maxWidth = '100%';
  129. }
  130. else {
  131. optionsWrap.parent().css( 'position', 'relative' );
  132. }
  133. // cacl default maxHeight
  134. var maxHeight = ($(window).height() - optionsWrap.offset().top + $(window).scrollTop() - 20);
  135. // override with user defined maxHeight
  136. if( instance.options.maxHeight ) {
  137. maxHeight = instance.options.maxHeight;
  138. }
  139. // maxHeight cannot be less than options.minHeight
  140. maxHeight = maxHeight < instance.options.minHeight ? instance.options.minHeight : maxHeight;
  141. optionsWrap.css({
  142. maxWidth : maxWidth,
  143. minHeight: instance.options.minHeight,
  144. maxHeight: maxHeight,
  145. overflow : 'auto'
  146. }).hide();
  147. // isolate options scroll
  148. // @source: https://github.com/nobleclem/jQuery-IsolatedScroll
  149. optionsWrap.bind( 'touchmove mousewheel DOMMouseScroll', function ( e ) {
  150. if( ($(this).outerHeight() < $(this)[0].scrollHeight) ) {
  151. var e0 = e.originalEvent,
  152. delta = e0.wheelDelta || -e0.detail;
  153. if( ($(this).outerHeight() + $(this)[0].scrollTop) > $(this)[0].scrollHeight ) {
  154. e.preventDefault();
  155. this.scrollTop += ( delta < 0 ? 1 : -1 );
  156. }
  157. }
  158. });
  159. // hide options menus if click happens off of the list placeholder button
  160. $(document).off('click.ms-hideopts').on('click.ms-hideopts', function( event ){
  161. if( !$(event.target).closest('.ms-options-wrap').length ) {
  162. if( $('.ms-options-wrap > .ms-options:visible').length ) {
  163. $('.ms-options-wrap > .ms-options:visible').each(function(){
  164. $(this).hide();
  165. var thisInst = $(this).parent().prev('.jqmsLoaded').data('plugin_multiselect-instance');
  166. // USER CALLBACK
  167. if( typeof thisInst.options.onControlClose == 'function' ) {
  168. thisInst.options.onControlClose( thisInst.element );
  169. }
  170. });
  171. }
  172. }
  173. });
  174. // disable button action
  175. placeholder.bind('mousedown',function( event ){
  176. // ignore if its not a left click
  177. if( event.which != 1 ) {
  178. return true;
  179. }
  180. // hide other menus before showing this one
  181. $('.ms-options-wrap > .ms-options:visible').each(function(){
  182. if( $(this).parent().prev()[0] != optionsWrap.parent().prev()[0] ) {
  183. $(this).hide();
  184. }
  185. });
  186. // show/hide options
  187. optionsWrap.toggle();
  188. // recalculate height
  189. if( optionsWrap.is(':visible') ) {
  190. optionsWrap.css( 'maxHeight', '' );
  191. // cacl default maxHeight
  192. var maxHeight = ($(window).height() - optionsWrap.offset().top + $(window).scrollTop() - 20);
  193. // override with user defined maxHeight
  194. if( instance.options.maxHeight ) {
  195. maxHeight = instance.options.maxHeight;
  196. }
  197. // maxHeight cannot be less than options.minHeight
  198. maxHeight = maxHeight < instance.options.minHeight ? instance.options.minHeight : maxHeight;
  199. optionsWrap.css( 'maxHeight', maxHeight );
  200. }
  201. }).click(function( event ){ event.preventDefault(); });
  202. // add placeholder copy
  203. if( instance.options.texts.placeholder ) {
  204. placeholder.text( instance.options.texts.placeholder );
  205. }
  206. // add search box
  207. if( instance.options.search ) {
  208. optionsList.before('<div class="ms-search"><input type="text" value="" placeholder="'+ instance.options.texts.search +'" /></div>');
  209. var search = optionsWrap.find('.ms-search input');
  210. search.on('keyup', function(){
  211. // ignore keystrokes that don't make a difference
  212. if( $(this).data('lastsearch') == $(this).val() ) {
  213. return true;
  214. }
  215. $(this).data('lastsearch', $(this).val() );
  216. // USER CALLBACK
  217. if( typeof instance.options.searchOptions.onSearch == 'function' ) {
  218. instance.options.searchOptions.onSearch( instance.element );
  219. }
  220. // search non optgroup li's
  221. optionsList.find('li:not(.optgroup)').each(function(){
  222. var optText = $(this).text();
  223. // show option if string exists
  224. if( optText.toLowerCase().indexOf( search.val().toLowerCase() ) > -1 ) {
  225. $(this).show();
  226. }
  227. // don't hide selected items
  228. else if( !$(this).hasClass('selected') ) {
  229. $(this).hide();
  230. }
  231. // hide / show optgroups depending on if options within it are visible
  232. if( !instance.options.searchOptions.showOptGroups && $(this).closest('li.optgroup') ) {
  233. $(this).closest('li.optgroup').show();
  234. if( $(this).closest('li.optgroup').find('li:visible').length ) {
  235. $(this).closest('li.optgroup').show();
  236. }
  237. else {
  238. $(this).closest('li.optgroup').hide();
  239. }
  240. }
  241. });
  242. });
  243. }
  244. // handle select all option
  245. optionsWrap.on('click', '.ms-selectall', function( event ){
  246. event.preventDefault();
  247. if( $(this).hasClass('global') ) {
  248. // check if any selected if so then select them
  249. if( optionsList.find('li:not(.optgroup)').filter(':not(.selected)').filter(':visible').length ) {
  250. optionsList.find('li:not(.optgroup)').filter(':not(.selected)').filter(':visible').find('input[type="checkbox"]').trigger('click');
  251. $(this).html( instance.options.texts.deselectAll );
  252. }
  253. // deselect everything
  254. else {
  255. optionsList.find('li:not(.optgroup).selected:visible input[type="checkbox"]').trigger('click');
  256. $(this).html( instance.options.texts.selectAll );
  257. }
  258. }
  259. else if( $(this).closest('li').hasClass('optgroup') ) {
  260. var optgroup = $(this).closest('li.optgroup');
  261. // check if any selected if so then select them
  262. if( optgroup.find('li:not(.selected)').filter(':visible').length ) {
  263. optgroup.find('li:not(.selected):visible input[type="checkbox"]').trigger('click');
  264. }
  265. // deselect everything
  266. else {
  267. optgroup.find('li.selected:visible input[type="checkbox"]').trigger('click');
  268. }
  269. }
  270. });
  271. // add options to wrapper
  272. var options = [];
  273. $(instance.element).children().each(function(){
  274. if( this.nodeName == 'OPTGROUP' ) {
  275. var groupOptions = [];
  276. $(this).children('option').each(function(){
  277. var thisOptionAtts = {};
  278. for( var i = 0; i < instance.options.optionAttributes.length; i++ ) {
  279. var thisOptAttr = instance.options.optionAttributes[ i ];
  280. if( $(this).attr( thisOptAttr ) !== undefined ) {
  281. thisOptionAtts[ thisOptAttr ] = $(this).attr( thisOptAttr );
  282. }
  283. }
  284. groupOptions.push({
  285. name : $(this).text(),
  286. value : $(this).val(),
  287. checked: $(this).prop( 'selected' ),
  288. attributes: thisOptionAtts
  289. });
  290. });
  291. options.push({
  292. label : $(this).attr('label'),
  293. options: groupOptions
  294. });
  295. }
  296. else if( this.nodeName == 'OPTION' ) {
  297. var thisOptionAtts = {};
  298. for( var i = 0; i < instance.options.optionAttributes.length; i++ ) {
  299. var thisOptAttr = instance.options.optionAttributes[ i ];
  300. if( $(this).attr( thisOptAttr ) !== undefined ) {
  301. thisOptionAtts[ thisOptAttr ] = $(this).attr( thisOptAttr );
  302. }
  303. }
  304. options.push({
  305. name : $(this).text(),
  306. value : $(this).val(),
  307. checked : $(this).prop( 'selected' ),
  308. attributes: thisOptionAtts
  309. });
  310. }
  311. else {
  312. // bad option
  313. return true;
  314. }
  315. });
  316. instance.loadOptions( options, true, false );
  317. // BIND SELECT ACTION
  318. optionsWrap.on( 'click', 'input[type="checkbox"]', function(){
  319. $(this).closest( 'li' ).toggleClass( 'selected' );
  320. var select = optionsWrap.parent().prev();
  321. // toggle clicked option
  322. select.find('option[value="'+ $(this).val() +'"]').prop(
  323. 'selected', $(this).is(':checked')
  324. ).closest('select').trigger('change');
  325. // USER CALLBACK
  326. if( typeof instance.options.onOptionClick == 'function' ) {
  327. instance.options.onOptionClick(instance.element, this);
  328. }
  329. instance._updatePlaceholderText();
  330. });
  331. // BIND FOCUS EVENT
  332. optionsWrap.on('focusin', 'input[type="checkbox"]', function(){
  333. $(this).closest('label').addClass('focused');
  334. }).on('focusout', 'input[type="checkbox"]', function(){
  335. $(this).closest('label').removeClass('focused');
  336. });
  337. // USER CALLBACK
  338. if( typeof instance.options.onLoad === 'function' ) {
  339. instance.options.onLoad( instance.element );
  340. }
  341. // hide native select list
  342. $(instance.element).hide();
  343. },
  344. /* LOAD SELECT OPTIONS */
  345. loadOptions: function( options, overwrite, updateSelect ) {
  346. overwrite = (typeof overwrite == 'boolean') ? overwrite : true;
  347. updateSelect = (typeof updateSelect == 'boolean') ? updateSelect : true;
  348. var instance = this;
  349. var optionsList = $(instance.element).next('.ms-options-wrap').find('> .ms-options > ul');
  350. var optionsWrap = $(instance.element).next('.ms-options-wrap').find('> .ms-options');
  351. var select = optionsWrap.parent().prev();
  352. if( overwrite ) {
  353. optionsList.find('> li').remove();
  354. if( updateSelect ) {
  355. select.find('> *').remove();
  356. }
  357. }
  358. for( var key in options ) {
  359. // Prevent prototype methods injected into options from being iterated over.
  360. if( !options.hasOwnProperty( key ) ) {
  361. continue;
  362. }
  363. var thisOption = options[ key ];
  364. var container = $('<li></li>');
  365. var appendContainer = true;
  366. // OPTGROUP
  367. if( thisOption.hasOwnProperty('options') ) {
  368. optionsList.find('> li.optgroup > span.label').each(function(){
  369. if( $(this).text() == thisOption.label ) {
  370. container = $(this).closest('.optgroup');
  371. appendContainer = false;
  372. }
  373. });
  374. // prepare to append optgroup to select element
  375. if( updateSelect ) {
  376. if( select.find('optgroup[label="'+ thisOption.label +'"]').length ) {
  377. var optGroup = select.find('optgroup[label="'+ thisOption.label +'"]');
  378. }
  379. else {
  380. var optGroup = $('<optgroup label="'+ thisOption.label +'"></optgroup>');
  381. select.append( optGroup );
  382. }
  383. }
  384. // setup container
  385. if( appendContainer ) {
  386. container.addClass('optgroup');
  387. container.append('<span class="label">'+ thisOption.label +'</span>');
  388. container.find('> .label').css({
  389. clear: 'both'
  390. });
  391. // add select all link
  392. if( instance.options.selectGroup ) {
  393. container.append('<a href="#" class="ms-selectall">' + instance.options.texts.selectAll + '</a>')
  394. }
  395. container.append('<ul></ul>');
  396. }
  397. for( var gKey in thisOption.options ) {
  398. // Prevent prototype methods injected into options from
  399. // being iterated over.
  400. if( !thisOption.options.hasOwnProperty( gKey ) ) {
  401. continue;
  402. }
  403. var thisGOption = thisOption.options[ gKey ];
  404. var gContainer = $('<li></li>').addClass('ms-reflow');
  405. instance._addOption( gContainer, thisGOption );
  406. container.find('> ul').append( gContainer );
  407. // add option to optgroup in select element
  408. if( updateSelect ) {
  409. var selOption = $('<option value="'+ thisGOption.value +'">'+ thisGOption.name +'</option>');
  410. // add custom user attributes
  411. if( thisGOption.hasOwnProperty('attributes') && Object.keys( thisGOption.attributes ).length ) {
  412. //selOption.attr( thisGOption.attributes );
  413. }
  414. // mark option as selected
  415. if( thisGOption.checked ) {
  416. selOption.prop( 'selected', true );
  417. }
  418. optGroup.append( selOption );
  419. }
  420. }
  421. }
  422. // OPTION
  423. else if( thisOption.hasOwnProperty('value') ) {
  424. container.addClass('ms-reflow')
  425. // add option to ms dropdown
  426. instance._addOption( container, thisOption );
  427. if( updateSelect ) {
  428. var selOption = $('<option value="'+ thisOption.value +'">'+ thisOption.name +'</option>');
  429. // add custom user attributes
  430. if( thisOption.hasOwnProperty('attributes') && Object.keys( thisOption.attributes ).length ) {
  431. selOption.attr( thisOption.attributes );
  432. }
  433. // mark option as selected
  434. if( thisOption.checked ) {
  435. selOption.prop( 'selected', true );
  436. }
  437. select.append( selOption );
  438. }
  439. }
  440. if( appendContainer ) {
  441. optionsList.append( container );
  442. }
  443. }
  444. optionsList.find('.ms-reflow input[type="checkbox"]').each(function( idx ){
  445. if( $(this).css('display').match(/block$/) ) {
  446. var checkboxWidth = $(this).outerWidth();
  447. checkboxWidth = checkboxWidth ? checkboxWidth : 15;
  448. $(this).closest('label').css(
  449. 'padding-left',
  450. (parseInt( $(this).closest('label').css('padding-left') ) * 2) + checkboxWidth
  451. );
  452. $(this).closest('.ms-reflow').removeClass('ms-reflow');
  453. }
  454. });
  455. // add global select all options
  456. if( instance.options.selectAll ) {
  457. optionsList.parent().find('.ms-selectall').remove();
  458. if ( 0 === optionsList.find('li:not(.selected)').length ) {
  459. optionsList.before('<a href="#" class="ms-selectall global">' + instance.options.texts.deselectAll + '</a>');
  460. }
  461. else {
  462. optionsList.before('<a href="#" class="ms-selectall global">' + instance.options.texts.selectAll + '</a>');
  463. }
  464. }
  465. // update placeholder text
  466. instance._updatePlaceholderText();
  467. // RESET COLUMN STYLES
  468. optionsWrap.find('ul').css({
  469. 'column-count' : '',
  470. 'column-gap' : '',
  471. '-webkit-column-count': '',
  472. '-webkit-column-gap' : '',
  473. '-moz-column-count' : '',
  474. '-moz-column-gap' : ''
  475. });
  476. // COLUMNIZE
  477. if( select.find('optgroup').length ) {
  478. // float non grouped options
  479. optionsList.find('> li:not(.optgroup)').css({
  480. 'float': 'left',
  481. width: (100 / instance.options.columns) +'%'
  482. });
  483. // add CSS3 column styles
  484. optionsList.find('li.optgroup').css({
  485. clear: 'both'
  486. }).find('> ul').css({
  487. 'column-count' : instance.options.columns,
  488. 'column-gap' : 0,
  489. '-webkit-column-count': instance.options.columns,
  490. '-webkit-column-gap' : 0,
  491. '-moz-column-count' : instance.options.columns,
  492. '-moz-column-gap' : 0
  493. });
  494. // for crappy IE versions float grouped options
  495. if( this._ieVersion() && (this._ieVersion() < 10) ) {
  496. optionsList.find('li.optgroup > ul > li').css({
  497. 'float': 'left',
  498. width: (100 / instance.options.columns) +'%'
  499. });
  500. }
  501. }
  502. else {
  503. // add CSS3 column styles
  504. optionsList.css({
  505. 'column-count' : instance.options.columns,
  506. 'column-gap' : 0,
  507. '-webkit-column-count': instance.options.columns,
  508. '-webkit-column-gap' : 0,
  509. '-moz-column-count' : instance.options.columns,
  510. '-moz-column-gap' : 0
  511. });
  512. // for crappy IE versions float grouped options
  513. if( this._ieVersion() && (this._ieVersion() < 10) ) {
  514. optionsList.find('> li').css({
  515. 'float': 'left',
  516. width: (100 / instance.options.columns) +'%'
  517. });
  518. }
  519. }
  520. },
  521. /* RESET THE DOM */
  522. unload: function() {
  523. $(this.element).next('.ms-options-wrap').remove();
  524. $(this.element).show(function(){
  525. $(this).css('display','').removeClass('jqmsLoaded');
  526. });
  527. },
  528. /* RELOAD JQ MULTISELECT LIST */
  529. reload: function() {
  530. // remove existing options
  531. $(this.element).next('.ms-options-wrap').remove();
  532. $(this.element).removeClass('jqmsLoaded');
  533. // load element
  534. this.load();
  535. },
  536. /** PRIVATE FUNCTIONS **/
  537. // update selected placeholder text
  538. _updatePlaceholderText: function(){
  539. var instance = this;
  540. var placeholder = $(instance.element).next('.ms-options-wrap').find('> button:first-child');
  541. var optionsWrap = $(instance.element).next('.ms-options-wrap').find('> .ms-options');
  542. var select = optionsWrap.parent().prev();
  543. // get selected options
  544. var selOpts = [];
  545. select.find('option:selected').each(function(){
  546. selOpts.push( $(this).text() );
  547. });
  548. // UPDATE PLACEHOLDER TEXT WITH OPTIONS SELECTED
  549. placeholder.text( selOpts.join( ', ' ) );
  550. var copy = placeholder.clone().css({
  551. display : 'inline',
  552. width : 'auto',
  553. visibility: 'hidden'
  554. }).appendTo( optionsWrap.parent() );
  555. // if the jquery.actual plugin is loaded use it to get the widths
  556. var copyWidth = (typeof $.fn.actual !== 'undefined') ? copy.actual( 'width', instance.options.jqActualOpts ) : copy.width();
  557. var placeWidth = (typeof $.fn.actual !== 'undefined') ? placeholder.actual( 'width', instance.options.jqActualOpts ) : placeholder.width();
  558. // if copy is larger than button width use "# selected"
  559. if( copyWidth > placeWidth ) {
  560. placeholder.text( selOpts.length + ' ' + instance.options.texts.selectedOptions );
  561. }
  562. // if options selected then use those
  563. else if( selOpts.length ) {
  564. // trim each element in case of extra spaces
  565. placeholder.text(
  566. selOpts.map(function( element ){
  567. return element.trim();
  568. }).join(', ')
  569. );
  570. }
  571. // replace placeholder text
  572. else {
  573. placeholder.text( instance.options.texts.placeholder );
  574. }
  575. // remove dummy element
  576. copy.remove();
  577. },
  578. // Add option to the custom dom list
  579. _addOption: function( container, option ) {
  580. container.text( option.name );
  581. var thisCheckbox = $('<input type="checkbox" value="" title="" />')
  582. .val( option.value )
  583. .attr( 'title', option.name )
  584. .attr( 'id', 'ms-opt-'+ msCounter );
  585. // add user defined attributes
  586. if( option.hasOwnProperty('attributes') && Object.keys( option.attributes ).length ) {
  587. thisCheckbox.attr( option.attributes );
  588. }
  589. container.prepend( thisCheckbox );
  590. if( option.checked ) {
  591. container.addClass('default');
  592. container.addClass('selected');
  593. container.find( 'input[type="checkbox"]' ).prop( 'checked', true );
  594. }
  595. var label = $('<label></label>').attr( 'for', 'ms-opt-'+ msCounter );
  596. container.wrapInner( label );
  597. msCounter = msCounter + 1;
  598. },
  599. // check ie version
  600. _ieVersion: function() {
  601. var myNav = navigator.userAgent.toLowerCase();
  602. return (myNav.indexOf('msie') != -1) ? parseInt(myNav.split('msie')[1]) : false;
  603. }
  604. };
  605. // ENABLE JQUERY PLUGIN FUNCTION
  606. $.fn.multiselect = function( options ){
  607. var args = arguments;
  608. var ret;
  609. // menuize each list
  610. if( (options === undefined) || (typeof options === 'object') ) {
  611. return this.each(function(){
  612. if( !$.data( this, 'plugin_multiselect' ) ) {
  613. $.data( this, 'plugin_multiselect', new MultiSelect( this, options ) );
  614. }
  615. });
  616. } else if( (typeof options === 'string') && (options[0] !== '_') && (options !== 'init') ) {
  617. this.each(function(){
  618. var instance = $.data( this, 'plugin_multiselect' );
  619. if( instance instanceof MultiSelect && typeof instance[ options ] === 'function' ) {
  620. ret = instance[ options ].apply( instance, Array.prototype.slice.call( args, 1 ) );
  621. }
  622. // special destruct handler
  623. if( options === 'unload' ) {
  624. $.data( this, 'plugin_multiselect', null );
  625. }
  626. });
  627. return ret;
  628. }
  629. };
  630. }(jQuery));