Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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