hjg
2024-10-30 8cf23534166c07e711aac2a25911ada317ba01f0
提交 | 用户 | 时间
58d006 1 /*
A 2 * MultiSelect v0.9.8
3 * Copyright (c) 2012 Louis Cuny
4 *
5 * This program is free software. It comes without any warranty, to
6 * the extent permitted by applicable law. You can redistribute it
7 * and/or modify it under the terms of the Do What The Fuck You Want
8 * To Public License, Version 2, as published by Sam Hocevar. See
9 * http://sam.zoy.org/wtfpl/COPYING for more details.
10 */
11
12 !function ($) {
13
14   "use strict";
15
16
17  /* MULTISELECT CLASS DEFINITION
18   * ====================== */
19
20   var MultiSelect = function (element, options) {
21     this.options = options;
22     this.$element = $(element);
23
24     this.$container = $('<div/>', { 'class': "ms-container" });
25     this.$selectableContainer = $('<div/>', { 'class': 'ms-selectable' });
26     this.$selectionContainer = $('<div/>', { 'class': 'ms-selection' });
27     this.$selectableUl = $('<ul/>', { 'class': "ms-list", 'tabindex' : '-1' });
28     this.$selectionUl = $('<ul/>', { 'class': "ms-list", 'tabindex' : '-1' });
29     this.scrollTo = 0;
30     this.sanitizeRegexp = new RegExp("\\W+", 'gi');
31     this.elemsSelector = 'li:visible:not(.ms-optgroup-label,.ms-optgroup-container,.'+options.disabledClass+')';
32   };
33
34   MultiSelect.prototype = {
35     constructor: MultiSelect,
36
37     init: function(){
38       var that = this,
39           ms = this.$element;
40
41       if (ms.next('.ms-container').length === 0){
42         ms.css({ position: 'absolute', left: '-9999px' });
43         ms.attr('id', ms.attr('id') ? ms.attr('id') : Math.ceil(Math.random()*1000)+'multiselect');
44         this.$container.attr('id', 'ms-'+ms.attr('id'));
45
46         ms.find('option').each(function(){
47           that.generateLisFromOption(this);
48         });
49
50         this.$selectionUl.find('.ms-optgroup-label').hide();
51
52         if (that.options.selectableHeader){
53           that.$selectableContainer.append(that.options.selectableHeader);
54         }
55         that.$selectableContainer.append(that.$selectableUl);
56         if (that.options.selectableFooter){
57           that.$selectableContainer.append(that.options.selectableFooter);
58         }
59
60         if (that.options.selectionHeader){
61           that.$selectionContainer.append(that.options.selectionHeader);
62         }
63         that.$selectionContainer.append(that.$selectionUl);
64         if (that.options.selectionFooter){
65           that.$selectionContainer.append(that.options.selectionFooter);
66         }
67
68         that.$container.append(that.$selectableContainer);
69         that.$container.append(that.$selectionContainer);
70         ms.after(that.$container);
71
72         that.activeMouse(that.$selectableUl);
73         that.activeKeyboard(that.$selectableUl);
74
75         var action = that.options.dblClick ? 'dblclick' : 'click';
76
77         that.$selectableUl.on(action, '.ms-elem-selectable', function(){
78           that.select($(this).data('ms-value'));
79         });
80         that.$selectionUl.on(action, '.ms-elem-selection', function(){
81           that.deselect($(this).data('ms-value'));
82         });
83
84         that.activeMouse(that.$selectionUl);
85         that.activeKeyboard(that.$selectionUl);
86
87         ms.on('focus', function(){
88           that.$selectableUl.focus();
89         })
90       }
91
92       var selectedValues = ms.find('option:selected').map(function(){ return $(this).val(); }).get();
93       that.select(selectedValues, 'init');
94
95       if (typeof that.options.afterInit === 'function') {
96         that.options.afterInit.call(this, this.$container);
97       }
98     },
99
100     'generateLisFromOption' : function(option){
101       var that = this,
102           ms = that.$element,
103           attributes = "",
104           $option = $(option);
105
106       for (var cpt = 0; cpt < option.attributes.length; cpt++){
107         var attr = option.attributes[cpt];
108
109         if(attr.name !== 'value' && attr.name !== 'disabled'){
110           attributes += attr.name+'="'+attr.value+'" ';
111         }
112       }
113       var selectableLi = $('<li '+attributes+'><span>'+$option.text()+'</span></li>'),
114           selectedLi = selectableLi.clone(),
115           value = $option.val(),
116           elementId = that.sanitize(value, that.sanitizeRegexp);
117
118       selectableLi
119         .data('ms-value', value)
120         .addClass('ms-elem-selectable')
121         .attr('id', elementId+'-selectable');
122
123       selectedLi
124         .data('ms-value', value)
125         .addClass('ms-elem-selection')
126         .attr('id', elementId+'-selection')
127         .hide();
128
129       if ($option.prop('disabled') || ms.prop('disabled')){
130         selectedLi.addClass(that.options.disabledClass);
131         selectableLi.addClass(that.options.disabledClass);
132       }
133
134       var $optgroup = $option.parent('optgroup');
135
136       if ($optgroup.length > 0){
137         var optgroupLabel = $optgroup.attr('label'),
138             optgroupId = that.sanitize(optgroupLabel, that.sanitizeRegexp),
139             $selectableOptgroup = that.$selectableUl.find('#optgroup-selectable-'+optgroupId),
140             $selectionOptgroup = that.$selectionUl.find('#optgroup-selection-'+optgroupId);
141         
142         if ($selectableOptgroup.length === 0){
143           var optgroupContainerTpl = '<li class="ms-optgroup-container"></li>',
144               optgroupTpl = '<ul class="ms-optgroup"><li class="ms-optgroup-label"><span>'+optgroupLabel+'</span></li></ul>';
145           
146           $selectableOptgroup = $(optgroupContainerTpl);
147           $selectionOptgroup = $(optgroupContainerTpl);
148           $selectableOptgroup.attr('id', 'optgroup-selectable-'+optgroupId);
149           $selectionOptgroup.attr('id', 'optgroup-selection-'+optgroupId);
150           $selectableOptgroup.append($(optgroupTpl));
151           $selectionOptgroup.append($(optgroupTpl));
152           if (that.options.selectableOptgroup){
153             $selectableOptgroup.find('.ms-optgroup-label').on('click', function(){
154               var values = $optgroup.children(':not(:selected)').map(function(){ return $(this).val() }).get();
155               that.select(values);
156             });
157             $selectionOptgroup.find('.ms-optgroup-label').on('click', function(){
158               var values = $optgroup.children(':selected').map(function(){ return $(this).val() }).get();
159               that.deselect(values);
160             });
161           }
162           that.$selectableUl.append($selectableOptgroup);
163           that.$selectionUl.append($selectionOptgroup);
164         }
165         $selectableOptgroup.children().append(selectableLi);
166         $selectionOptgroup.children().append(selectedLi);
167       } else {
168         that.$selectableUl.append(selectableLi);
169         that.$selectionUl.append(selectedLi);
170       }
171     },
172
173     'activeKeyboard' : function($list){
174       var that = this;
175
176       $list.on('focus', function(){
177         $(this).addClass('ms-focus');
178       })
179       .on('blur', function(){
180         $(this).removeClass('ms-focus');
181       })
182       .on('keydown', function(e){
183         switch (e.which) {
184           case 40:
185           case 38:
186             e.preventDefault();
187             e.stopPropagation();
188             that.moveHighlight($(this), (e.which === 38) ? -1 : 1);
189             return;
190           case 32:
191             e.preventDefault();
192             e.stopPropagation();
193             that.selectHighlighted($list);
194             return;
195           case 37:
196           case 39:
197             e.preventDefault();
198             e.stopPropagation();
199             that.switchList($list);
200             return;
201         }
202       });
203     },
204
205     'moveHighlight': function($list, direction){
206       var $elems = $list.find(this.elemsSelector),
207           $currElem = $elems.filter('.ms-hover'),
208           $nextElem = null,
209           elemHeight = $elems.first().outerHeight(),
210           containerHeight = $list.height(),
211           containerSelector = '#'+this.$container.prop('id');
212
213       // Deactive mouseenter event when move is active
214       // It fixes a bug when mouse is over the list
215       $elems.off('mouseenter');
216
217       $elems.removeClass('ms-hover');
218       if (direction === 1){ // DOWN
219
220         $nextElem = $currElem.nextAll(this.elemsSelector).first();
221         if ($nextElem.length === 0){
222           var $optgroupUl = $currElem.parent();
223
224           if ($optgroupUl.hasClass('ms-optgroup')){
225             var $optgroupLi = $optgroupUl.parent(),
226                 $nextOptgroupLi = $optgroupLi.next(':visible');
227
228             if ($nextOptgroupLi.length > 0){
229               $nextElem = $nextOptgroupLi.find(this.elemsSelector).first();
230             } else {
231               $nextElem = $elems.first();
232             }
233           } else {
234             $nextElem = $elems.first();
235           }
236         }
237       } else if (direction === -1){ // UP
238
239         $nextElem = $currElem.prevAll(this.elemsSelector).first();
240         if ($nextElem.length === 0){
241           var $optgroupUl = $currElem.parent();
242
243           if ($optgroupUl.hasClass('ms-optgroup')){
244             var $optgroupLi = $optgroupUl.parent(),
245                 $prevOptgroupLi = $optgroupLi.prev(':visible');
246
247             if ($prevOptgroupLi.length > 0){
248               $nextElem = $prevOptgroupLi.find(this.elemsSelector).last();
249             } else {
250               $nextElem = $elems.last();
251             }
252           } else {
253             $nextElem = $elems.last();
254           }
255         }
256       }
257       if ($nextElem.length > 0){
258         $nextElem.addClass('ms-hover');
259         var scrollTo = $list.scrollTop() + $nextElem.position().top - 
260                        containerHeight / 2 + elemHeight / 2;
261
262         $list.scrollTop(scrollTo);
263       }
264     },
265
266     'selectHighlighted' : function($list){
267         var $elems = $list.find(this.elemsSelector),
268             $highlightedElem = $elems.filter('.ms-hover').first();
269
270         if ($highlightedElem.length > 0){
271           if ($list.parent().hasClass('ms-selectable')){
272             this.select($highlightedElem.data('ms-value'));
273           } else {
274             this.deselect($highlightedElem.data('ms-value'));
275           }
276           $elems.removeClass('ms-hover');
277         }
278     },
279
280     'switchList' : function($list){
281       $list.blur();
282       if ($list.parent().hasClass('ms-selectable')){
283         this.$selectionUl.focus();
284       } else {
285         this.$selectableUl.focus();
286       }
287     },
288
289     'activeMouse' : function($list){
290       var that = this;
291
292       $list.on('mousemove', function(){
293         var elems = $list.find(that.elemsSelector);
294
295         elems.on('mouseenter', function(){
296           elems.removeClass('ms-hover');
297           $(this).addClass('ms-hover');
298         });
299       });
300     },
301
302     'refresh' : function() {
303       this.destroy();
304       this.$element.multiSelect(this.options);
305     },
306
307     'destroy' : function(){
308       $("#ms-"+this.$element.attr("id")).remove();
309       this.$element.removeData('multiselect');
310     },
311
312     'select' : function(value, method){
313       if (typeof value === 'string'){ value = [value]; }
314
315       var that = this,
316           ms = this.$element,
317           msIds = $.map(value, function(val){ return(that.sanitize(val, that.sanitizeRegexp)); }),
318           selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable').filter(':not(.'+that.options.disabledClass+')'),
319           selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection').filter(':not(.'+that.options.disabledClass+')'),
320           options = ms.find('option:not(:disabled)').filter(function(){ return($.inArray(this.value, value) > -1); });
321
322       if (selectables.length > 0){
323         selectables.addClass('ms-selected').hide();
324         selections.addClass('ms-selected').show();
325         options.prop('selected', true);
326
327         var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
328         if (selectableOptgroups.length > 0){
329           selectableOptgroups.each(function(){
330             var selectablesLi = $(this).find('.ms-elem-selectable');
331             if (selectablesLi.length === selectablesLi.filter('.ms-selected').length){
332               $(this).find('.ms-optgroup-label').hide();
333             }
334           });
335
336           var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
337           selectionOptgroups.each(function(){
338             var selectionsLi = $(this).find('.ms-elem-selection');
339             if (selectionsLi.filter('.ms-selected').length > 0){
340               $(this).find('.ms-optgroup-label').show();
341             }
342           });
343         } else {
344           if (that.options.keepOrder){
345             var selectionLiLast = that.$selectionUl.find('.ms-selected'); 
346             if((selectionLiLast.length > 1) && (selectionLiLast.last().get(0) != selections.get(0))) {
347               selections.insertAfter(selectionLiLast.last());
348             }
349           }
350         }
351         if (method !== 'init'){
352           ms.trigger('change');
353           if (typeof that.options.afterSelect === 'function') {
354             that.options.afterSelect.call(this, value);
355           }
356         }
357       }
358     },
359
360     'deselect' : function(value){
361       if (typeof value === 'string'){ value = [value]; }
362
363       var that = this,
364           ms = this.$element,
365           msIds = $.map(value, function(val){ return(that.sanitize(val, that.sanitizeRegexp)); }),
366           selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
367           selections = this.$selectionUl.find('#' + msIds.join('-selection, #')+'-selection').filter('.ms-selected'),
368           options = ms.find('option').filter(function(){ return($.inArray(this.value, value) > -1); });
369
370       if (selections.length > 0){
371         selectables.removeClass('ms-selected').show();
372         selections.removeClass('ms-selected').hide();
373         options.prop('selected', false);
374
375         var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
376         if (selectableOptgroups.length > 0){
377           selectableOptgroups.each(function(){
378             var selectablesLi = $(this).find('.ms-elem-selectable');
379             if (selectablesLi.filter(':not(.ms-selected)').length > 0){
380               $(this).find('.ms-optgroup-label').show();
381             }
382           });
383
384           var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
385           selectionOptgroups.each(function(){
386             var selectionsLi = $(this).find('.ms-elem-selection');
387             if (selectionsLi.filter('.ms-selected').length === 0){
388               $(this).find('.ms-optgroup-label').hide();
389             }
390           });
391         }
392         ms.trigger('change');
393         if (typeof that.options.afterDeselect === 'function') {
394           that.options.afterDeselect.call(this, value);
395         }
396       }
397     },
398
399     'select_all' : function(){
400       var ms = this.$element,
401           values = ms.val();
402
403       ms.find('option:not(":disabled")').prop('selected', true);
404       this.$selectableUl.find('.ms-elem-selectable').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').hide();
405       this.$selectionUl.find('.ms-optgroup-label').show();
406       this.$selectableUl.find('.ms-optgroup-label').hide();
407       this.$selectionUl.find('.ms-elem-selection').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').show();
408       this.$selectionUl.focus();
409       ms.trigger('change');
410       if (typeof this.options.afterSelect === 'function') {
411         var selectedValues = $.grep(ms.val(), function(item){
412           return $.inArray(item, values) < 0;
413         });
414         this.options.afterSelect.call(this, selectedValues);
415       }
416     },
417
418     'deselect_all' : function(){
419       var ms = this.$element,
420           values = ms.val();
421
422       ms.find('option').prop('selected', false);
423       this.$selectableUl.find('.ms-elem-selectable').removeClass('ms-selected').show();
424       this.$selectionUl.find('.ms-optgroup-label').hide();
425       this.$selectableUl.find('.ms-optgroup-label').show();
426       this.$selectionUl.find('.ms-elem-selection').removeClass('ms-selected').hide();
427       this.$selectableUl.focus();
428       ms.trigger('change');
429       if (typeof this.options.afterDeselect === 'function') {
430         this.options.afterDeselect.call(this, values);
431       }
432     },
433
434     sanitize: function(value, reg){
435       return(value.replace(reg, '_'));
436     }
437   };
438
439   /* MULTISELECT PLUGIN DEFINITION
440    * ======================= */
441
442   $.fn.multiSelect = function () {
443     var option = arguments[0],
444         args = arguments;
445
446     return this.each(function () {
447       var $this = $(this),
448           data = $this.data('multiselect'),
449           options = $.extend({}, $.fn.multiSelect.defaults, $this.data(), typeof option === 'object' && option);
450
451       if (!data){ $this.data('multiselect', (data = new MultiSelect(this, options))); }
452
453       if (typeof option === 'string'){
454         data[option](args[1]);
455       } else {
456         data.init();
457       }
458     });
459   };
460
461   $.fn.multiSelect.defaults = {
462     selectableOptgroup: false,
463     disabledClass : 'disabled',
464     dblClick : false,
465     keepOrder: false
466   };
467
468   $.fn.multiSelect.Constructor = MultiSelect;
469
470 }(window.jQuery);