PluginProbe
Property Hive / 2.2.4
Property Hive v2.2.4
2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 All 261 releases
propertyhive / assets / js / multiselect / jquery.multiselect.js

jquery.multiselect.js in Property Hive 2.2.4, at assets/js/multiselect/jquery.multiselect.js

946 lines 39.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Display a nice easy to use multiselect list
3 * @Version: 2.4.18
4 * @Author: Patrick Springstubbe
5 * @Contact: @JediNobleclem
6 * @Website: springstubbe.us
7 * @Source: https://github.com/nobleclem/jQuery-MultiSelect
8 *
9 * Usage:
10 * $('select[multiple]').multiselect();
11 * $('select[multiple]').multiselect({ texts: { placeholder: 'Select options' } });
12 * $('select[multiple]').multiselect('reload');
13 * $('select[multiple]').multiselect( 'loadOptions', [{
14 * name : 'Option Name 1',
15 * value : 'option-value-1',
16 * checked: false,
17 * attributes : {
18 * custom1: 'value1',
19 * custom2: 'value2'
20 * }
21 * },{
22 * name : 'Option Name 2',
23 * value : 'option-value-2',
24 * checked: false,
25 * attributes : {
26 * custom1: 'value1',
27 * custom2: 'value2'
28 * }
29 * }]);
30 *
31 **/
32 (function($){
33 var defaults = {
34 columns: 1, // how many columns should be use to show options
35 search : propertyhive_multiselect_params.search, // include option search box
36
37 // search filter options
38 searchOptions : {
39 delay : 250, // time (in ms) between keystrokes until search happens
40 showOptGroups: false, // show option group titles if no options remaining
41 searchText : true, // search within the text
42 searchValue : false, // search within the value
43 onSearch : function( element ){} // fires on keyup before search on options happens
44 },
45
46 // plugin texts
47 texts: {
48 placeholder : 'Select options', // text to use in dummy input
49 search : 'Search', // search input placeholder text
50 selectedOptions: ' ' + propertyhive_multiselect_params.selected_text, // selected suffix text
51 selectAll : 'Select all', // select all text
52 unselectAll : 'Unselect all', // unselect all text
53 noneSelected : 'None Selected' // None selected text
54 },
55
56 // general options
57 selectAll : false, // add select all option
58 selectGroup : false, // select entire optgroup
59 minHeight : 200, // minimum height of option overlay
60 maxHeight : null, // maximum height of option overlay
61 maxWidth : null, // maximum width of option overlay (or selector)
62 maxPlaceholderWidth: null, // maximum width of placeholder button
63 maxPlaceholderOpts : 10, // maximum number of placeholder options to show until "# selected" shown instead
64 showCheckbox : true, // display the checkbox to the user
65 checkboxAutoFit : false, // auto calc checkbox padding
66 optionAttributes : [], // attributes to copy to the checkbox from the option element
67
68 // Callbacks
69 onLoad : function( element ){}, // fires at end of list initialization
70 onOptionClick : function( element, option ){}, // fires when an option is clicked
71 onControlClose: function( element ){}, // fires when the options list is closed
72 onSelectAll : function( element, selected ){}, // fires when (un)select all is clicked
73 onPlaceholder : function( element, placeholder, selectedOpts ){}, // fires when the placeholder txt is updated
74 };
75
76 var msCounter = 1; // counter for each select list
77 var msOptCounter = 1; // counter for each option on page
78
79 // FOR LEGACY BROWSERS (talking to you IE8)
80 if( typeof Array.prototype.map !== 'function' ) {
81 Array.prototype.map = function( callback, thisArg ) {
82 if( typeof thisArg === 'undefined' ) {
83 thisArg = this;
84 }
85
86 return $.isArray( thisArg ) ? $.map( thisArg, callback ) : [];
87 };
88 }
89 if( typeof String.prototype.trim !== 'function' ) {
90 String.prototype.trim = function() {
91 return this.replace(/^\s+|\s+$/g, '');
92 };
93 }
94
95 function MultiSelect( element, options )
96 {
97 this.element = element;
98 this.options = $.extend( true, {}, defaults, options );
99 this.updateSelectAll = true;
100 this.updatePlaceholder = true;
101 this.listNumber = msCounter;
102
103 msCounter = msCounter + 1; // increment counter
104
105 /* Make sure its a multiselect list */
106 if( !$(this.element).attr('multiple') ) {
107 throw new Error( '[jQuery-MultiSelect] Select list must be a multiselect list in order to use this plugin' );
108 }
109
110 /* Options validation checks */
111 if( this.options.search ){
112 if( !this.options.searchOptions.searchText && !this.options.searchOptions.searchValue ){
113 throw new Error( '[jQuery-MultiSelect] Either searchText or searchValue should be true.' );
114 }
115 }
116
117 /** BACKWARDS COMPATIBILITY **/
118 if( 'placeholder' in this.options ) {
119 this.options.texts.placeholder = this.options.placeholder;
120 delete this.options.placeholder;
121 }
122 if( 'default' in this.options.searchOptions ) {
123 this.options.texts.search = this.options.searchOptions['default'];
124 delete this.options.searchOptions['default'];
125 }
126 /** END BACKWARDS COMPATIBILITY **/
127
128 // load this instance
129 this.load();
130 }
131
132 MultiSelect.prototype = {
133 /* LOAD CUSTOM MULTISELECT DOM/ACTIONS */
134 load: function() {
135 var instance = this;
136
137 // make sure this is a select list and not loaded
138 if( (instance.element.nodeName != 'SELECT') || $(instance.element).hasClass('jqmsLoaded') ) {
139 return true;
140 }
141
142 // sanity check so we don't double load on a select element
143 $(instance.element).addClass('jqmsLoaded ms-list-'+ instance.listNumber ).data( 'plugin_multiselect-instance', instance );
144
145 // add option container
146 $(instance.element).after('<div id="ms-list-'+ instance.listNumber +'" class="ms-options-wrap"><div class="selected"><span>None Selected</span></div><div class="ms-options"><ul></ul></div></div>');
147
148 var placeholder = $(instance.element).siblings('#ms-list-'+ instance.listNumber +'.ms-options-wrap').find('> .selected:first-child');
149 var optionsWrap = $(instance.element).siblings('#ms-list-'+ instance.listNumber +'.ms-options-wrap').find('> .ms-options');
150 var optionsList = optionsWrap.find('> ul');
151
152 // don't show checkbox (add class for css to hide checkboxes)
153 if( !instance.options.showCheckbox ) {
154 optionsWrap.addClass('hide-checkbox');
155 }
156 else if( instance.options.checkboxAutoFit ) {
157 optionsWrap.addClass('checkbox-autofit');
158 }
159
160 // check if list is disabled
161 if( $(instance.element).prop( 'disabled' ) ) {
162 placeholder.prop( 'disabled', true );
163 }
164
165 // set placeholder maxWidth
166 if( instance.options.maxPlaceholderWidth ) {
167 placeholder.css( 'maxWidth', instance.options.maxPlaceholderWidth );
168 }
169
170 // override with user defined maxHeight
171 if( instance.options.maxHeight ) {
172 var maxHeight = instance.options.maxHeight;
173 }
174 else {
175 // cacl default maxHeight
176 var maxHeight = ($(window).height() - optionsWrap.offset().top + $(window).scrollTop() - 20);
177 }
178
179 // maxHeight cannot be less than options.minHeight
180 maxHeight = maxHeight < instance.options.minHeight ? instance.options.minHeight : maxHeight;
181
182 optionsWrap.css({
183 maxWidth : instance.options.maxWidth,
184 minHeight: instance.options.minHeight,
185 maxHeight: maxHeight,
186 });
187
188 // isolate options scroll
189 // @source: https://github.com/nobleclem/jQuery-IsolatedScroll
190 optionsWrap.on( 'touchmove mousewheel DOMMouseScroll', function ( e ) {
191 if( ($(this).outerHeight() < $(this)[0].scrollHeight) ) {
192 var e0 = e.originalEvent,
193 delta = e0.wheelDelta || -e0.detail;
194
195 if( ($(this).outerHeight() + $(this)[0].scrollTop) > $(this)[0].scrollHeight ) {
196 e.preventDefault();
197 this.scrollTop += ( delta < 0 ? 1 : -1 );
198 }
199 }
200 });
201
202 // hide options menus if click happens off of the list placeholder button
203 $(document).off('click.ms-hideopts').on('click.ms-hideopts', function( event ){
204 if( !$(event.target).closest('.ms-options-wrap').length ) {
205 $('.ms-options-wrap.ms-active > .ms-options').each(function(){
206 $(this).closest('.ms-options-wrap').removeClass('ms-active');
207
208 var listID = $(this).closest('.ms-options-wrap').attr('id');
209
210 var thisInst = $(this).parent().siblings('.'+ listID +'.jqmsLoaded').data('plugin_multiselect-instance');
211
212 // USER CALLBACK
213 if( typeof thisInst.options.onControlClose == 'function' ) {
214 thisInst.options.onControlClose( thisInst.element );
215 }
216 });
217 }
218 // hide open option lists if escape key pressed
219 }).on('keydown', function( event ){
220 if( (event.keyCode || event.which) == 27 ) { // esc key
221 $(this).trigger('click.ms-hideopts');
222 }
223 });
224
225 // handle pressing enter|space while tabbing through
226 placeholder.on('keydown', function( event ){
227 var code = (event.keyCode || event.which);
228 if( (code == 13) || (code == 32) ) { // enter OR space
229 placeholder.trigger( 'mousedown' );
230 }
231 });
232
233 // disable button action
234 placeholder.on( 'mousedown', function( event ){
235 // ignore if its not a left click
236 if( event.which && (event.which != 1) ) {
237 return true;
238 }
239
240 // hide other menus before showing this one
241 $('.ms-options-wrap.ms-active').each(function(){
242 if( $(this).siblings( '.'+ $(this).attr('id') +'.jqmsLoaded')[0] != optionsWrap.parent().siblings('.ms-list-'+ instance.listNumber +'.jqmsLoaded')[0] ) {
243 $(this).removeClass('ms-active');
244
245 var thisInst = $(this).siblings( '.'+ $(this).attr('id') +'.jqmsLoaded').data('plugin_multiselect-instance');
246
247 // USER CALLBACK
248 if( typeof thisInst.options.onControlClose == 'function' ) {
249 thisInst.options.onControlClose( thisInst.element );
250 }
251 }
252 });
253
254 // show/hide options
255 optionsWrap.closest('.ms-options-wrap').toggleClass('ms-active');
256
257 // recalculate height
258 if( optionsWrap.closest('.ms-options-wrap').hasClass('ms-active') ) {
259 optionsWrap.css( 'maxHeight', '' );
260
261 // override with user defined maxHeight
262 if( instance.options.maxHeight ) {
263 var maxHeight = instance.options.maxHeight;
264 }
265 else {
266 // cacl default maxHeight
267 var maxHeight = ($(window).height() - optionsWrap.offset().top + $(window).scrollTop() - 20);
268 }
269
270 if( maxHeight ) {
271 // maxHeight cannot be less than options.minHeight
272 maxHeight = maxHeight < instance.options.minHeight ? instance.options.minHeight : maxHeight;
273
274 optionsWrap.css( 'maxHeight', maxHeight );
275 }
276 }
277 else if( typeof instance.options.onControlClose == 'function' ) {
278 instance.options.onControlClose( instance.element );
279 }
280 }).click(function( event ){ event.preventDefault(); });
281
282 // add placeholder copy
283 if( instance.options.texts.placeholder ) {
284 placeholder.find('span').text( instance.options.texts.placeholder );
285 }
286
287 // add search box
288 if( instance.options.search ) {
289 optionsList.before('<div class="ms-search"><input type="text" value="" placeholder="'+ instance.options.texts.search +'" /></div>');
290
291 var search = optionsWrap.find('.ms-search input');
292 search.on('keyup', function(){
293 // ignore keystrokes that don't make a difference
294 if( $(this).data('lastsearch') == $(this).val() ) {
295 return true;
296 }
297
298 // pause timeout
299 if( $(this).data('searchTimeout') ) {
300 clearTimeout( $(this).data('searchTimeout') );
301 }
302
303 var thisSearchElem = $(this);
304
305 $(this).data('searchTimeout', setTimeout(function(){
306 thisSearchElem.data('lastsearch', thisSearchElem.val() );
307
308 // USER CALLBACK
309 if( typeof instance.options.searchOptions.onSearch == 'function' ) {
310 instance.options.searchOptions.onSearch( instance.element );
311 }
312
313 // search non optgroup li's
314 var searchString = $.trim( search.val().toLowerCase() );
315 if( searchString ) {
316 optionsList.find('li[data-search-term*="'+ searchString +'"]:not(.optgroup)').removeClass('ms-hidden');
317 optionsList.find('li:not([data-search-term*="'+ searchString +'"], .optgroup)').addClass('ms-hidden');
318 }
319 else {
320 optionsList.find('.ms-hidden').removeClass('ms-hidden');
321 }
322
323 // show/hide optgroups based on if there are items visible within
324 if( !instance.options.searchOptions.showOptGroups ) {
325 optionsList.find('.optgroup').each(function(){
326 if( $(this).find('li:not(.ms-hidden)').length ) {
327 $(this).show();
328 }
329 else {
330 $(this).hide();
331 }
332 });
333 }
334
335 instance._updateSelectAllText();
336 }, instance.options.searchOptions.delay ));
337 });
338 }
339
340 // add global select all options
341 if( instance.options.selectAll ) {
342 optionsList.before('<a href="#" class="ms-selectall global">' + instance.options.texts.selectAll + '</a>');
343 }
344
345 // handle select all option
346 optionsWrap.on('click', '.ms-selectall', function( event ){
347 event.preventDefault();
348
349 instance.updateSelectAll = false;
350 instance.updatePlaceholder = false;
351
352 var select = optionsWrap.parent().siblings('.ms-list-'+ instance.listNumber +'.jqmsLoaded');
353
354 if( $(this).hasClass('global') ) {
355 // check if any options are not selected if so then select them
356 if( optionsList.find('li:not(.optgroup, .selected, .ms-hidden)').length ) {
357 // get unselected vals, mark as selected, return val list
358 optionsList.find('li:not(.optgroup, .selected, .ms-hidden)').addClass('selected');
359 optionsList.find('li.selected input[type="checkbox"]:not(:disabled)').prop( 'checked', true );
360 }
361 // deselect everything
362 else {
363 optionsList.find('li:not(.optgroup, .ms-hidden).selected').removeClass('selected');
364 optionsList.find('li:not(.optgroup, .ms-hidden, .selected) input[type="checkbox"]:not(:disabled)').prop( 'checked', false );
365 }
366 }
367 else if( $(this).closest('li').hasClass('optgroup') ) {
368 var optgroup = $(this).closest('li.optgroup');
369
370 // check if any selected if so then select them
371 if( optgroup.find('li:not(.selected, .ms-hidden)').length ) {
372 optgroup.find('li:not(.selected, .ms-hidden)').addClass('selected');
373 optgroup.find('li.selected input[type="checkbox"]:not(:disabled)').prop( 'checked', true );
374 }
375 // deselect everything
376 else {
377 optgroup.find('li:not(.ms-hidden).selected').removeClass('selected');
378 optgroup.find('li:not(.ms-hidden, .selected) input[type="checkbox"]:not(:disabled)').prop( 'checked', false );
379 }
380 }
381
382 var vals = [];
383 optionsList.find('li.selected input[type="checkbox"]').each(function(){
384 vals.push( $(this).val() );
385 });
386 select.val( vals ).trigger('change');
387
388 instance.updateSelectAll = true;
389 instance.updatePlaceholder = true;
390
391 // USER CALLBACK
392 if( typeof instance.options.onSelectAll == 'function' ) {
393 instance.options.onSelectAll( instance.element, vals.length );
394 }
395
396 instance._updateSelectAllText();
397 instance._updatePlaceholderText();
398 });
399
400 // add options to wrapper
401 var options = [];
402 $(instance.element).children().each(function(){
403 if( this.nodeName == 'OPTGROUP' ) {
404 var groupOptions = [];
405
406 $(this).children('option').each(function(){
407 var thisOptionAtts = {};
408 for( var i = 0; i < instance.options.optionAttributes.length; i++ ) {
409 var thisOptAttr = instance.options.optionAttributes[ i ];
410
411 if( $(this).attr( thisOptAttr ) !== undefined ) {
412 thisOptionAtts[ thisOptAttr ] = $(this).attr( thisOptAttr );
413 }
414 }
415
416 groupOptions.push({
417 name : $(this).text(),
418 value : $(this).val(),
419 checked: $(this).prop( 'selected' ),
420 attributes: thisOptionAtts
421 });
422 });
423
424 options.push({
425 label : $(this).attr('label'),
426 options: groupOptions
427 });
428 }
429 else if( this.nodeName == 'OPTION' ) {
430 var thisOptionAtts = {};
431 for( var i = 0; i < instance.options.optionAttributes.length; i++ ) {
432 var thisOptAttr = instance.options.optionAttributes[ i ];
433
434 if( $(this).attr( thisOptAttr ) !== undefined ) {
435 thisOptionAtts[ thisOptAttr ] = $(this).attr( thisOptAttr );
436 }
437 }
438
439 options.push({
440 name : $(this).text(),
441 value : $(this).val(),
442 checked : $(this).prop( 'selected' ),
443 attributes: thisOptionAtts
444 });
445 }
446 else {
447 // bad option
448 return true;
449 }
450 });
451 instance.loadOptions( options, true, false );
452
453 // BIND SELECT ACTION
454 optionsWrap.on( 'click', 'input[type="checkbox"]', function(){
455 $(this).closest( 'li' ).toggleClass( 'selected' );
456
457 var select = optionsWrap.parent().siblings('.ms-list-'+ instance.listNumber +'.jqmsLoaded');
458
459 // toggle clicked option
460 select.find('option[value="'+ instance._escapeSelector( $(this).val() ) +'"]').prop(
461 'selected', $(this).is(':checked')
462 ).closest('select').trigger('change');
463
464 // USER CALLBACK
465 if( typeof instance.options.onOptionClick == 'function' ) {
466 instance.options.onOptionClick(instance.element, this);
467 }
468
469 instance._updateSelectAllText();
470 instance._updatePlaceholderText();
471 });
472
473 // BIND FOCUS EVENT
474 optionsWrap.on('focusin', 'input[type="checkbox"]', function(){
475 $(this).closest('label').addClass('focused');
476 }).on('focusout', 'input[type="checkbox"]', function(){
477 $(this).closest('label').removeClass('focused');
478 });
479
480 // USER CALLBACK
481 if( typeof instance.options.onLoad === 'function' ) {
482 instance.options.onLoad( instance.element );
483 }
484
485 // hide native select list
486 $(instance.element).hide();
487 },
488
489 /* LOAD SELECT OPTIONS */
490 loadOptions: function( options, overwrite, updateSelect ) {
491 overwrite = (typeof overwrite == 'boolean') ? overwrite : true;
492 updateSelect = (typeof updateSelect == 'boolean') ? updateSelect : true;
493
494 var instance = this;
495 var select = $(instance.element);
496 var optionsList = select.siblings('#ms-list-'+ instance.listNumber +'.ms-options-wrap').find('> .ms-options > ul');
497 var optionsWrap = select.siblings('#ms-list-'+ instance.listNumber +'.ms-options-wrap').find('> .ms-options');
498
499 if( overwrite ) {
500 optionsList.find('> li').remove();
501
502 if( updateSelect ) {
503 select.find('> *').remove();
504 }
505 }
506
507 var containers = [];
508 for( var key in options ) {
509 // Prevent prototype methods injected into options from being iterated over.
510 if( !options.hasOwnProperty( key ) ) {
511 continue;
512 }
513
514 var thisOption = options[ key ];
515 var container = $('<li/>');
516 var appendContainer = true;
517
518 // OPTION
519 if( thisOption.hasOwnProperty('value') ) {
520 if( instance.options.showCheckbox && instance.options.checkboxAutoFit ) {
521 container.addClass('ms-reflow');
522 }
523
524 // add option to ms dropdown
525 instance._addOption( container, thisOption );
526
527 if( updateSelect ) {
528 var selOption = $('<option/>', {
529 value: thisOption.value,
530 text : thisOption.name
531 });
532
533 // add custom user attributes
534 if( thisOption.hasOwnProperty('attributes') && Object.keys( thisOption.attributes ).length ) {
535 selOption.attr( thisOption.attributes );
536 }
537
538 // mark option as selected
539 if( thisOption.checked ) {
540 selOption.prop( 'selected', true );
541 }
542
543 select.append( selOption );
544 }
545 }
546 // OPTGROUP
547 else if( thisOption.hasOwnProperty('options') ) {
548 var optGroup = $('<optgroup/>', {
549 label: thisOption.label
550 });
551
552 optionsList.find('> li.optgroup > span.label').each(function(){
553 if( $(this).text() == thisOption.label ) {
554 container = $(this).closest('.optgroup');
555 appendContainer = false;
556 }
557 });
558
559 // prepare to append optgroup to select element
560 if( updateSelect ) {
561 if( select.find('optgroup[label="'+ thisOption.label +'"]').length ) {
562 optGroup = select.find('optgroup[label="'+ thisOption.label +'"]');
563 }
564 else {
565 select.append( optGroup );
566 }
567 }
568
569 // setup container
570 if( appendContainer ) {
571 container.addClass('optgroup');
572 container.append('<span class="label">'+ thisOption.label +'</span>');
573 container.find('> .label').css({
574 clear: 'both'
575 });
576
577 // add select all link
578 if( instance.options.selectGroup ) {
579 container.append('<a href="#" class="ms-selectall">' + instance.options.texts.selectAll + '</a>');
580 }
581
582 container.append('<ul/>');
583 }
584
585 for( var gKey in thisOption.options ) {
586 // Prevent prototype methods injected into options from
587 // being iterated over.
588 if( !thisOption.options.hasOwnProperty( gKey ) ) {
589 continue;
590 }
591
592 var thisGOption = thisOption.options[ gKey ];
593 var gContainer = $('<li/>');
594 if( instance.options.showCheckbox && instance.options.checkboxAutoFit ) {
595 gContainer.addClass('ms-reflow');
596 }
597
598 // no clue what this is we hit (skip)
599 if( !thisGOption.hasOwnProperty('value') ) {
600 continue;
601 }
602
603 instance._addOption( gContainer, thisGOption );
604
605 container.find('> ul').append( gContainer );
606
607 // add option to optgroup in select element
608 if( updateSelect ) {
609 var selOption = $('<option/>', {
610 value: thisGOption.value,
611 text : thisGOption.name
612 });
613
614 // add custom user attributes
615 if( thisGOption.hasOwnProperty('attributes') && Object.keys( thisGOption.attributes ).length ) {
616 selOption.attr( thisGOption.attributes );
617 }
618
619 // mark option as selected
620 if( thisGOption.checked ) {
621 selOption.prop( 'selected', true );
622 }
623
624 optGroup.append( selOption );
625 }
626 }
627 }
628 else {
629 // no clue what this is we hit (skip)
630 continue;
631 }
632
633 if( appendContainer ) {
634 containers.push( container );
635 }
636 }
637 optionsList.append( containers );
638
639 // pad out label for room for the checkbox
640 if( instance.options.checkboxAutoFit && instance.options.showCheckbox && !optionsWrap.hasClass('hide-checkbox') ) {
641 var chkbx = optionsList.find('.ms-reflow:eq(0) input[type="checkbox"]');
642 if( chkbx.length ) {
643 var checkboxWidth = chkbx.outerWidth();
644 checkboxWidth = checkboxWidth ? checkboxWidth : 15;
645
646 optionsList.find('.ms-reflow label').css(
647 'padding-left',
648 (parseInt( chkbx.closest('label').css('padding-left') ) * 2) + checkboxWidth
649 );
650
651 optionsList.find('.ms-reflow').removeClass('ms-reflow');
652 }
653 }
654
655 // update placeholder text
656 instance._updatePlaceholderText();
657
658 // RESET COLUMN STYLES
659 optionsWrap.find('ul').css({
660 'column-count' : '',
661 'column-gap' : '',
662 '-webkit-column-count': '',
663 '-webkit-column-gap' : '',
664 '-moz-column-count' : '',
665 '-moz-column-gap' : ''
666 });
667
668 // COLUMNIZE
669 if( select.find('optgroup').length ) {
670 // float non grouped options
671 optionsList.find('> li:not(.optgroup)').css({
672 'float': 'left',
673 width: (100 / instance.options.columns) +'%'
674 });
675
676 // add CSS3 column styles
677 optionsList.find('li.optgroup').css({
678 clear: 'both'
679 }).find('> ul').css({
680 'column-count' : instance.options.columns,
681 'column-gap' : 0,
682 '-webkit-column-count': instance.options.columns,
683 '-webkit-column-gap' : 0,
684 '-moz-column-count' : instance.options.columns,
685 '-moz-column-gap' : 0
686 });
687
688 // for crappy IE versions float grouped options
689 if( this._ieVersion() && (this._ieVersion() < 10) ) {
690 optionsList.find('li.optgroup > ul > li').css({
691 'float': 'left',
692 width: (100 / instance.options.columns) +'%'
693 });
694 }
695 }
696 else {
697 // add CSS3 column styles
698 optionsList.css({
699 'column-count' : instance.options.columns,
700 'column-gap' : 0,
701 '-webkit-column-count': instance.options.columns,
702 '-webkit-column-gap' : 0,
703 '-moz-column-count' : instance.options.columns,
704 '-moz-column-gap' : 0
705 });
706
707 // for crappy IE versions float grouped options
708 if( this._ieVersion() && (this._ieVersion() < 10) ) {
709 optionsList.find('> li').css({
710 'float': 'left',
711 width: (100 / instance.options.columns) +'%'
712 });
713 }
714 }
715
716 // update un/select all logic
717 instance._updateSelectAllText();
718 },
719
720 /* UPDATE MULTISELECT CONFIG OPTIONS */
721 settings: function( options ) {
722 this.options = $.extend( true, {}, this.options, options );
723 this.reload();
724 },
725
726 /* RESET THE DOM */
727 unload: function() {
728 $(this.element).siblings('#ms-list-'+ this.listNumber +'.ms-options-wrap').remove();
729 $(this.element).show(function(){
730 $(this).css('display','').removeClass('jqmsLoaded');
731 });
732 },
733
734 /* RELOAD JQ MULTISELECT LIST */
735 reload: function() {
736 // remove existing options
737 $(this.element).siblings('#ms-list-'+ this.listNumber +'.ms-options-wrap').remove();
738 $(this.element).removeClass('jqmsLoaded');
739
740 // load element
741 this.load();
742 },
743
744 // RESET BACK TO DEFAULT VALUES & RELOAD
745 reset: function() {
746 var defaultVals = [];
747 $(this.element).find('option').each(function(){
748 if( $(this).prop('defaultSelected') ) {
749 defaultVals.push( $(this).val() );
750 }
751 });
752
753 $(this.element).val( defaultVals );
754
755 this.reload();
756 },
757
758 disable: function( status ) {
759 status = (typeof status === 'boolean') ? status : true;
760 $(this.element).prop( 'disabled', status );
761 $(this.element).siblings('#ms-list-'+ this.listNumber +'.ms-options-wrap').find('button:first-child')
762 .prop( 'disabled', status );
763 },
764
765 /** PRIVATE FUNCTIONS **/
766 // update the un/select all texts based on selected options and visibility
767 _updateSelectAllText: function(){
768 if( !this.updateSelectAll ) {
769 return;
770 }
771
772 var instance = this;
773
774 // select all not used at all so just do nothing
775 if( !instance.options.selectAll && !instance.options.selectGroup ) {
776 return;
777 }
778
779 var optionsWrap = $(instance.element).siblings('#ms-list-'+ instance.listNumber +'.ms-options-wrap').find('> .ms-options');
780
781 // update un/select all text
782 optionsWrap.find('.ms-selectall').each(function(){
783 var unselected = $(this).parent().find('li:not(.optgroup,.selected,.ms-hidden)');
784
785 $(this).text(
786 unselected.length ? instance.options.texts.selectAll : instance.options.texts.unselectAll
787 );
788 });
789 },
790
791 // update selected placeholder text
792 _updatePlaceholderText: function(){
793 if( !this.updatePlaceholder ) {
794 return;
795 }
796
797 var instance = this;
798 var select = $(instance.element);
799 var selectVals = select.val() ? select.val() : [];
800 var placeholder = select.siblings('#ms-list-'+ instance.listNumber +'.ms-options-wrap').find('> .selected:first-child');
801 var placeholderTxt = placeholder.find('span');
802 var optionsWrap = select.siblings('#ms-list-'+ instance.listNumber +'.ms-options-wrap').find('> .ms-options');
803
804 // if there are disabled options get those values as well
805 if( select.find('option:selected:disabled').length ) {
806 selectVals = [];
807 select.find('option:selected').each(function(){
808 selectVals.push( $(this).val() );
809 });
810 }
811
812 // get selected options
813 var selOpts = [];
814 for( var key in selectVals ) {
815 // Prevent prototype methods injected into options from being iterated over.
816 if( !selectVals.hasOwnProperty( key ) ) {
817 continue;
818 }
819
820 selOpts.push(
821 $.trim( select.find('option[value="'+ instance._escapeSelector( selectVals[ key ] ) +'"]').text() )
822 );
823
824 if( selOpts.length >= instance.options.maxPlaceholderOpts ) {
825 break;
826 }
827 }
828
829 // UPDATE PLACEHOLDER TEXT WITH OPTIONS SELECTED
830 placeholderTxt.text( selOpts.join( ', ' ) );
831
832 if( selOpts.length ) {
833 optionsWrap.closest('.ms-options-wrap').addClass('ms-has-selections');
834
835 // USER CALLBACK
836 if( typeof instance.options.onPlaceholder == 'function' ) {
837 instance.options.onPlaceholder( instance.element, placeholderTxt, selOpts );
838 }
839 }
840 else {
841 optionsWrap.closest('.ms-options-wrap').removeClass('ms-has-selections');
842 }
843
844 // replace placeholder text
845 if( !selOpts.length ) {
846 placeholderTxt.text( instance.options.texts.placeholder );
847 }
848 // if copy is larger than button width use "# selected"
849 else if( (placeholderTxt.width() > placeholder.width()) || (selOpts.length != selectVals.length) ) {
850 placeholderTxt.text( selectVals.length + instance.options.texts.selectedOptions );
851 }
852 },
853
854 // Add option to the custom dom list
855 _addOption: function( container, option ) {
856 var instance = this;
857 var optionNameText = $('<div/>').html( option.name ).text();
858
859 var thisOption = $('<label/>', {
860 for : 'ms-opt-'+ msOptCounter
861 }).html( option.name );
862
863 var thisCheckbox = $('<input>', {
864 type : 'checkbox',
865 title: optionNameText,
866 id : 'ms-opt-'+ msOptCounter,
867 value: option.value
868 });
869
870 // add user defined attributes
871 if( option.hasOwnProperty('attributes') && Object.keys( option.attributes ).length ) {
872 thisCheckbox.attr( option.attributes );
873 }
874
875 if( option.checked ) {
876 container.addClass('default selected');
877 thisCheckbox.prop( 'checked', true );
878 }
879
880 thisOption.prepend( thisCheckbox );
881
882 var searchTerm = '';
883 if( instance.options.searchOptions.searchText ) {
884 searchTerm += ' ' + optionNameText.toLowerCase();
885 }
886 if( instance.options.searchOptions.searchValue ) {
887 searchTerm += ' ' + option.value.toLowerCase();
888 }
889
890 container.attr( 'data-search-term', $.trim( searchTerm ) ).prepend( thisOption );
891
892 msOptCounter = msOptCounter + 1;
893 },
894
895 // check ie version
896 _ieVersion: function() {
897 var myNav = navigator.userAgent.toLowerCase();
898 return (myNav.indexOf('msie') != -1) ? parseInt(myNav.split('msie')[1]) : false;
899 },
900
901 // escape selector
902 _escapeSelector: function( string ) {
903 if( typeof $.escapeSelector == 'function' ) {
904 return $.escapeSelector( string );
905 }
906 else {
907 return string.replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g, "\\$&");
908 }
909 }
910 };
911
912 // ENABLE JQUERY PLUGIN FUNCTION
913 $.fn.multiselect = function( options ){
914 if( !this.length ) {
915 return;
916 }
917
918 var args = arguments;
919 var ret;
920
921 // menuize each list
922 if( (options === undefined) || (typeof options === 'object') ) {
923 return this.each(function(){
924 if( !$.data( this, 'plugin_multiselect' ) ) {
925 $.data( this, 'plugin_multiselect', new MultiSelect( this, options ) );
926 }
927 });
928 } else if( (typeof options === 'string') && (options[0] !== '_') && (options !== 'init') ) {
929 this.each(function(){
930 var instance = $.data( this, 'plugin_multiselect' );
931
932 if( instance instanceof MultiSelect && typeof instance[ options ] === 'function' ) {
933 ret = instance[ options ].apply( instance, Array.prototype.slice.call( args, 1 ) );
934 }
935
936 // special destruct handler
937 if( options === 'unload' ) {
938 $.data( this, 'plugin_multiselect', null );
939 }
940 });
941
942 return ret;
943 }
944 };
945 }(jQuery));
946