PluginProbe
RSVP and Event Management / trunk
RSVP and Event Management vtrunk
trunk 0.5.0 0.6.0 0.7.0 0.8.0 0.9.0 0.9.5 1.0.0 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.5.0 1.6.0 1.6.1 1.6.2 1.6.5 1.7.0 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 All 136 releases
rsvp / multi-select / js / jquery.multi-select.js

jquery.multi-select.js in RSVP and Event Management trunk, at multi-select/js/jquery.multi-select.js

533 lines 18.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * MultiSelect v0.9.11
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 this.$container = $('<div/>', { 'class': "ms-container" });
24 this.$selectableContainer = $('<div/>', { 'class': 'ms-selectable' });
25 this.$selectionContainer = $('<div/>', { 'class': 'ms-selection' });
26 this.$selectableUl = $('<ul/>', { 'class': "ms-list", 'tabindex' : '-1' });
27 this.$selectionUl = $('<ul/>', { 'class': "ms-list", 'tabindex' : '-1' });
28 this.scrollTo = 0;
29 this.elemsSelector = 'li:visible:not(.ms-optgroup-label,.ms-optgroup-container,.'+options.disabledClass+')';
30 };
31
32 MultiSelect.prototype = {
33 constructor: MultiSelect,
34
35 init: function(){
36 var that = this,
37 ms = this.$element;
38
39 if (ms.next('.ms-container').length === 0){
40 ms.css({ position: 'absolute', left: '-9999px' });
41 ms.attr('id', ms.attr('id') ? ms.attr('id') : Math.ceil(Math.random()*1000)+'multiselect');
42 this.$container.attr('id', 'ms-'+ms.attr('id'));
43 this.$container.addClass(that.options.cssClass);
44 ms.find('option').each(function(){
45 that.generateLisFromOption(this);
46 });
47
48 this.$selectionUl.find('.ms-optgroup-label').hide();
49
50 if (that.options.selectableHeader){
51 that.$selectableContainer.append(that.options.selectableHeader);
52 }
53 that.$selectableContainer.append(that.$selectableUl);
54 if (that.options.selectableFooter){
55 that.$selectableContainer.append(that.options.selectableFooter);
56 }
57
58 if (that.options.selectionHeader){
59 that.$selectionContainer.append(that.options.selectionHeader);
60 }
61 that.$selectionContainer.append(that.$selectionUl);
62 if (that.options.selectionFooter){
63 that.$selectionContainer.append(that.options.selectionFooter);
64 }
65
66 that.$container.append(that.$selectableContainer);
67 that.$container.append(that.$selectionContainer);
68 ms.after(that.$container);
69
70 that.activeMouse(that.$selectableUl);
71 that.activeKeyboard(that.$selectableUl);
72
73 var action = that.options.dblClick ? 'dblclick' : 'click';
74
75 that.$selectableUl.on(action, '.ms-elem-selectable', function(){
76 that.select($(this).data('ms-value'));
77 });
78 that.$selectionUl.on(action, '.ms-elem-selection', function(){
79 that.deselect($(this).data('ms-value'));
80 });
81
82 that.activeMouse(that.$selectionUl);
83 that.activeKeyboard(that.$selectionUl);
84
85 ms.on('focus', function(){
86 that.$selectableUl.focus();
87 })
88 }
89
90 var selectedValues = ms.find('option:selected').map(function(){ return $(this).val(); }).get();
91 that.select(selectedValues, 'init');
92
93 if (typeof that.options.afterInit === 'function') {
94 that.options.afterInit.call(this, this.$container);
95 }
96 },
97
98 'generateLisFromOption' : function(option, index, $container){
99 var that = this,
100 ms = that.$element,
101 attributes = "",
102 $option = $(option);
103
104 for (var cpt = 0; cpt < option.attributes.length; cpt++){
105 var attr = option.attributes[cpt];
106
107 if(attr.name !== 'value' && attr.name !== 'disabled'){
108 attributes += attr.name+'="'+attr.value+'" ';
109 }
110 }
111 var selectableLi = $('<li '+attributes+'><span>'+that.escapeHTML($option.text())+'</span></li>'),
112 selectedLi = selectableLi.clone(),
113 value = $option.val(),
114 elementId = that.sanitize(value);
115
116 selectableLi
117 .data('ms-value', value)
118 .addClass('ms-elem-selectable')
119 .attr('id', elementId+'-selectable');
120
121 selectedLi
122 .data('ms-value', value)
123 .addClass('ms-elem-selection')
124 .attr('id', elementId+'-selection')
125 .hide();
126
127 if ($option.prop('disabled') || ms.prop('disabled')){
128 selectedLi.addClass(that.options.disabledClass);
129 selectableLi.addClass(that.options.disabledClass);
130 }
131
132 var $optgroup = $option.parent('optgroup');
133
134 if ($optgroup.length > 0){
135 var optgroupLabel = $optgroup.attr('label'),
136 optgroupId = that.sanitize(optgroupLabel),
137 $selectableOptgroup = that.$selectableUl.find('#optgroup-selectable-'+optgroupId),
138 $selectionOptgroup = that.$selectionUl.find('#optgroup-selection-'+optgroupId);
139
140 if ($selectableOptgroup.length === 0){
141 var optgroupContainerTpl = '<li class="ms-optgroup-container"></li>',
142 optgroupTpl = '<ul class="ms-optgroup"><li class="ms-optgroup-label"><span>'+optgroupLabel+'</span></li></ul>';
143
144 $selectableOptgroup = $(optgroupContainerTpl);
145 $selectionOptgroup = $(optgroupContainerTpl);
146 $selectableOptgroup.attr('id', 'optgroup-selectable-'+optgroupId);
147 $selectionOptgroup.attr('id', 'optgroup-selection-'+optgroupId);
148 $selectableOptgroup.append($(optgroupTpl));
149 $selectionOptgroup.append($(optgroupTpl));
150 if (that.options.selectableOptgroup){
151 $selectableOptgroup.find('.ms-optgroup-label').on('click', function(){
152 var values = $optgroup.children(':not(:selected, :disabled)').map(function(){ return $(this).val() }).get();
153 that.select(values);
154 });
155 $selectionOptgroup.find('.ms-optgroup-label').on('click', function(){
156 var values = $optgroup.children(':selected:not(:disabled)').map(function(){ return $(this).val() }).get();
157 that.deselect(values);
158 });
159 }
160 that.$selectableUl.append($selectableOptgroup);
161 that.$selectionUl.append($selectionOptgroup);
162 }
163 index = index == undefined ? $selectableOptgroup.find('ul').children().length : index + 1;
164 selectableLi.insertAt(index, $selectableOptgroup.children());
165 selectedLi.insertAt(index, $selectionOptgroup.children());
166 } else {
167 index = index == undefined ? that.$selectableUl.children().length : index;
168
169 selectableLi.insertAt(index, that.$selectableUl);
170 selectedLi.insertAt(index, that.$selectionUl);
171 }
172 },
173
174 'addOption' : function(options){
175 var that = this;
176
177 if (options.value) options = [options];
178 $.each(options, function(index, option){
179 if (option.value && that.$element.find("option[value='"+option.value+"']").length === 0){
180 var $option = $('<option value="'+option.value+'">'+option.text+'</option>'),
181 index = parseInt((typeof option.index === 'undefined' ? that.$element.children().length : option.index)),
182 $container = option.nested == undefined ? that.$element : $("optgroup[label='"+option.nested+"']")
183
184 $option.insertAt(index, $container);
185 that.generateLisFromOption($option.get(0), index, option.nested);
186 }
187 })
188 },
189
190 'escapeHTML' : function(text){
191 return $("<div>").text(text).html();
192 },
193
194 'activeKeyboard' : function($list){
195 var that = this;
196
197 $list.on('focus', function(){
198 $(this).addClass('ms-focus');
199 })
200 .on('blur', function(){
201 $(this).removeClass('ms-focus');
202 })
203 .on('keydown', function(e){
204 switch (e.which) {
205 case 40:
206 case 38:
207 e.preventDefault();
208 e.stopPropagation();
209 that.moveHighlight($(this), (e.which === 38) ? -1 : 1);
210 return;
211 case 37:
212 case 39:
213 e.preventDefault();
214 e.stopPropagation();
215 that.switchList($list);
216 return;
217 case 9:
218 if(that.$element.is('[tabindex]')){
219 e.preventDefault();
220 var tabindex = parseInt(that.$element.attr('tabindex'), 10);
221 tabindex = (e.shiftKey) ? tabindex-1 : tabindex+1;
222 $('[tabindex="'+(tabindex)+'"]').focus();
223 return;
224 }else{
225 if(e.shiftKey){
226 that.$element.trigger('focus');
227 }
228 }
229 }
230 if($.inArray(e.which, that.options.keySelect) > -1){
231 e.preventDefault();
232 e.stopPropagation();
233 that.selectHighlighted($list);
234 return;
235 }
236 });
237 },
238
239 'moveHighlight': function($list, direction){
240 var $elems = $list.find(this.elemsSelector),
241 $currElem = $elems.filter('.ms-hover'),
242 $nextElem = null,
243 elemHeight = $elems.first().outerHeight(),
244 containerHeight = $list.height(),
245 containerSelector = '#'+this.$container.prop('id');
246
247 $elems.removeClass('ms-hover');
248 if (direction === 1){ // DOWN
249
250 $nextElem = $currElem.nextAll(this.elemsSelector).first();
251 if ($nextElem.length === 0){
252 var $optgroupUl = $currElem.parent();
253
254 if ($optgroupUl.hasClass('ms-optgroup')){
255 var $optgroupLi = $optgroupUl.parent(),
256 $nextOptgroupLi = $optgroupLi.next(':visible');
257
258 if ($nextOptgroupLi.length > 0){
259 $nextElem = $nextOptgroupLi.find(this.elemsSelector).first();
260 } else {
261 $nextElem = $elems.first();
262 }
263 } else {
264 $nextElem = $elems.first();
265 }
266 }
267 } else if (direction === -1){ // UP
268
269 $nextElem = $currElem.prevAll(this.elemsSelector).first();
270 if ($nextElem.length === 0){
271 var $optgroupUl = $currElem.parent();
272
273 if ($optgroupUl.hasClass('ms-optgroup')){
274 var $optgroupLi = $optgroupUl.parent(),
275 $prevOptgroupLi = $optgroupLi.prev(':visible');
276
277 if ($prevOptgroupLi.length > 0){
278 $nextElem = $prevOptgroupLi.find(this.elemsSelector).last();
279 } else {
280 $nextElem = $elems.last();
281 }
282 } else {
283 $nextElem = $elems.last();
284 }
285 }
286 }
287 if ($nextElem.length > 0){
288 $nextElem.addClass('ms-hover');
289 var scrollTo = $list.scrollTop() + $nextElem.position().top -
290 containerHeight / 2 + elemHeight / 2;
291
292 $list.scrollTop(scrollTo);
293 }
294 },
295
296 'selectHighlighted' : function($list){
297 var $elems = $list.find(this.elemsSelector),
298 $highlightedElem = $elems.filter('.ms-hover').first();
299
300 if ($highlightedElem.length > 0){
301 if ($list.parent().hasClass('ms-selectable')){
302 this.select($highlightedElem.data('ms-value'));
303 } else {
304 this.deselect($highlightedElem.data('ms-value'));
305 }
306 $elems.removeClass('ms-hover');
307 }
308 },
309
310 'switchList' : function($list){
311 $list.blur();
312 this.$container.find(this.elemsSelector).removeClass('ms-hover');
313 if ($list.parent().hasClass('ms-selectable')){
314 this.$selectionUl.focus();
315 } else {
316 this.$selectableUl.focus();
317 }
318 },
319
320 'activeMouse' : function($list){
321 var that = this;
322
323 $('body').on('mouseenter', that.elemsSelector, function(){
324 $(this).parents('.ms-container').find(that.elemsSelector).removeClass('ms-hover');
325 $(this).addClass('ms-hover');
326 });
327
328 $('body').on('mouseleave', that.elemsSelector, function () {
329 $(this).parents('.ms-container').find(that.elemsSelector).removeClass('ms-hover');;
330 });
331 },
332
333 'refresh' : function() {
334 this.destroy();
335 this.$element.multiSelect(this.options);
336 },
337
338 'destroy' : function(){
339 $("#ms-"+this.$element.attr("id")).remove();
340 this.$element.css('position', '').css('left', '')
341 this.$element.removeData('multiselect');
342 },
343
344 'select' : function(value, method){
345 if (typeof value === 'string'){ value = [value]; }
346
347 var that = this,
348 ms = this.$element,
349 msIds = $.map(value, function(val){ return(that.sanitize(val)); }),
350 selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable').filter(':not(.'+that.options.disabledClass+')'),
351 selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection').filter(':not(.'+that.options.disabledClass+')'),
352 options = ms.find('option:not(:disabled)').filter(function(){ return($.inArray(this.value, value) > -1); });
353
354 if (method === 'init'){
355 selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
356 selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection');
357 }
358
359 if (selectables.length > 0){
360 selectables.addClass('ms-selected').hide();
361 selections.addClass('ms-selected').show();
362
363 options.prop('selected', true);
364
365 that.$container.find(that.elemsSelector).removeClass('ms-hover');
366
367 var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
368 if (selectableOptgroups.length > 0){
369 selectableOptgroups.each(function(){
370 var selectablesLi = $(this).find('.ms-elem-selectable');
371 if (selectablesLi.length === selectablesLi.filter('.ms-selected').length){
372 $(this).find('.ms-optgroup-label').hide();
373 }
374 });
375
376 var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
377 selectionOptgroups.each(function(){
378 var selectionsLi = $(this).find('.ms-elem-selection');
379 if (selectionsLi.filter('.ms-selected').length > 0){
380 $(this).find('.ms-optgroup-label').show();
381 }
382 });
383 } else {
384 if (that.options.keepOrder && method !== 'init'){
385 var selectionLiLast = that.$selectionUl.find('.ms-selected');
386 if((selectionLiLast.length > 1) && (selectionLiLast.last().get(0) != selections.get(0))) {
387 selections.insertAfter(selectionLiLast.last());
388 }
389 }
390 }
391 if (method !== 'init'){
392 ms.trigger('change');
393 if (typeof that.options.afterSelect === 'function') {
394 that.options.afterSelect.call(this, value);
395 }
396 }
397 }
398 },
399
400 'deselect' : function(value){
401 if (typeof value === 'string'){ value = [value]; }
402
403 var that = this,
404 ms = this.$element,
405 msIds = $.map(value, function(val){ return(that.sanitize(val)); }),
406 selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
407 selections = this.$selectionUl.find('#' + msIds.join('-selection, #')+'-selection').filter('.ms-selected').filter(':not(.'+that.options.disabledClass+')'),
408 options = ms.find('option').filter(function(){ return($.inArray(this.value, value) > -1); });
409
410 if (selections.length > 0){
411 selectables.removeClass('ms-selected').show();
412 selections.removeClass('ms-selected').hide();
413 options.prop('selected', false);
414
415 that.$container.find(that.elemsSelector).removeClass('ms-hover');
416
417 var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
418 if (selectableOptgroups.length > 0){
419 selectableOptgroups.each(function(){
420 var selectablesLi = $(this).find('.ms-elem-selectable');
421 if (selectablesLi.filter(':not(.ms-selected)').length > 0){
422 $(this).find('.ms-optgroup-label').show();
423 }
424 });
425
426 var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
427 selectionOptgroups.each(function(){
428 var selectionsLi = $(this).find('.ms-elem-selection');
429 if (selectionsLi.filter('.ms-selected').length === 0){
430 $(this).find('.ms-optgroup-label').hide();
431 }
432 });
433 }
434 ms.trigger('change');
435 if (typeof that.options.afterDeselect === 'function') {
436 that.options.afterDeselect.call(this, value);
437 }
438 }
439 },
440
441 'select_all' : function(){
442 var ms = this.$element,
443 values = ms.val();
444
445 ms.find('option:not(":disabled")').prop('selected', true);
446 this.$selectableUl.find('.ms-elem-selectable').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').hide();
447 this.$selectionUl.find('.ms-optgroup-label').show();
448 this.$selectableUl.find('.ms-optgroup-label').hide();
449 this.$selectionUl.find('.ms-elem-selection').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').show();
450 this.$selectionUl.focus();
451 ms.trigger('change');
452 if (typeof this.options.afterSelect === 'function') {
453 var selectedValues = $.grep(ms.val(), function(item){
454 return $.inArray(item, values) < 0;
455 });
456 this.options.afterSelect.call(this, selectedValues);
457 }
458 },
459
460 'deselect_all' : function(){
461 var ms = this.$element,
462 values = ms.val();
463
464 ms.find('option').prop('selected', false);
465 this.$selectableUl.find('.ms-elem-selectable').removeClass('ms-selected').show();
466 this.$selectionUl.find('.ms-optgroup-label').hide();
467 this.$selectableUl.find('.ms-optgroup-label').show();
468 this.$selectionUl.find('.ms-elem-selection').removeClass('ms-selected').hide();
469 this.$selectableUl.focus();
470 ms.trigger('change');
471 if (typeof this.options.afterDeselect === 'function') {
472 this.options.afterDeselect.call(this, values);
473 }
474 },
475
476 sanitize: function(value){
477 var hash = 0, i, character;
478 if (value.length == 0) return hash;
479 var ls = 0;
480 for (i = 0, ls = value.length; i < ls; i++) {
481 character = value.charCodeAt(i);
482 hash = ((hash<<5)-hash)+character;
483 hash |= 0; // Convert to 32bit integer
484 }
485 return hash;
486 }
487 };
488
489 /* MULTISELECT PLUGIN DEFINITION
490 * ======================= */
491
492 $.fn.multiSelect = function () {
493 var option = arguments[0],
494 args = arguments;
495
496 return this.each(function () {
497 var $this = $(this),
498 data = $this.data('multiselect'),
499 options = $.extend({}, $.fn.multiSelect.defaults, $this.data(), typeof option === 'object' && option);
500
501 if (!data){ $this.data('multiselect', (data = new MultiSelect(this, options))); }
502
503 if (typeof option === 'string'){
504 data[option](args[1]);
505 } else {
506 data.init();
507 }
508 });
509 };
510
511 $.fn.multiSelect.defaults = {
512 keySelect: [32],
513 selectableOptgroup: false,
514 disabledClass : 'disabled',
515 dblClick : false,
516 keepOrder: false,
517 cssClass: ''
518 };
519
520 $.fn.multiSelect.Constructor = MultiSelect;
521
522 $.fn.insertAt = function(index, $parent) {
523 return this.each(function() {
524 if (index === 0) {
525 $parent.prepend(this);
526 } else {
527 $parent.children().eq(index - 1).after(this);
528 }
529 });
530 }
531
532 }(window.jQuery);
533