fix #835 Collecton selection bug in multiselect menu

This commit is contained in:
Nicolas Le Goff
2012-07-19 18:15:34 +02:00
parent b3ac109d71
commit 650945c642

View File

@@ -10,7 +10,7 @@
* http://www.gnu.org/licenses/gpl.html * http://www.gnu.org/licenses/gpl.html
*/ */
(function($){ (function($){
$.fn.multiSelect = function(opts){ $.fn.multiSelect = function(opts){
opts = $.extend({}, $.fn.multiSelect.defaults, opts); opts = $.extend({}, $.fn.multiSelect.defaults, opts);
@@ -18,22 +18,22 @@
return new MultiSelect(this, opts); return new MultiSelect(this, opts);
}); });
}; };
// counter to dynamically generate label/option IDs if they don't exist // counter to dynamically generate label/option IDs if they don't exist
var multiselectID = 0; var multiselectID = 0;
var MultiSelect = function(select,o){ var MultiSelect = function(select,o){
var $select = $original = $(select), var $select = $original = $(select),
$options, $header, $labels, $options, $header, $labels,
html = [], html = [],
optgroups = [], optgroups = [],
isDisabled = $select.is(':disabled'), isDisabled = $select.is(':disabled'),
id = select.id || 'ui-multiselect-'+multiselectID++; // unique ID for the label & option tags id = select.id || 'ui-multiselect-'+multiselectID++; // unique ID for the label & option tags
html.push('<a id="'+ id +'" class="ui-multiselect ui-widget ui-state-default ui-corner-all' + (isDisabled || o.disabled ? ' ui-state-disabled' : '') + '" style="margin-left: 10px;margin-right: 10px;height:20px;">'); html.push('<a id="'+ id +'" class="ui-multiselect ui-widget ui-state-default ui-corner-all' + (isDisabled || o.disabled ? ' ui-state-disabled' : '') + '" style="margin-left: 10px;margin-right: 10px;height:20px;">');
html.push('<input readonly="readonly" type="text" class="" value="'+ o.allSelectedText +'" title="'+ select.title +'" /></a>'); html.push('<input readonly="readonly" type="text" class="" value="'+ o.allSelectedText +'" title="'+ select.title +'" /></a>');
html.push('<div class="ui-multiselect-options' + (o.shadow ? ' ui-multiselect-shadow' : '') + ' ui-widget ui-widget-content ui-corner-all" style="margin-left: 10px;margin-right: 10px;border:1px solid #212121;">'); html.push('<div class="ui-multiselect-options' + (o.shadow ? ' ui-multiselect-shadow' : '') + ' ui-widget ui-widget-content ui-corner-all" style="margin-left: 10px;margin-right: 10px;border:1px solid #212121;">');
if(o.showHeader){ if(o.showHeader){
html.push('<div class="ui-widget-header ui-helper-clearfix ui-corner-all ui-multiselect-header" >'); html.push('<div class="ui-widget-header ui-helper-clearfix ui-corner-all ui-multiselect-header" >');
html.push('<ul class="ui-helper-reset">'); html.push('<ul class="ui-helper-reset">');
@@ -43,44 +43,44 @@
html.push('</ul>'); html.push('</ul>');
html.push('</div>'); html.push('</div>');
} }
// build options // build options
html.push('<ul class="ui-multiselect-checkboxes ui-helper-reset">'); html.push('<ul class="ui-multiselect-checkboxes ui-helper-reset">');
/* /*
If this select is disabled, remove the actual disabled attribute and let themeroller's .ui-state-disabled class handle it. If this select is disabled, remove the actual disabled attribute and let themeroller's .ui-state-disabled class handle it.
This is a workaround for jQuery bug #6211 where options in webkit inherit the select's disabled property. This This is a workaround for jQuery bug #6211 where options in webkit inherit the select's disabled property. This
won't achieve the same level of 'disabled' behavior (the checkboxes will still be present in form submission), won't achieve the same level of 'disabled' behavior (the checkboxes will still be present in form submission),
but it at least gives you a way to emulate the UI. but it at least gives you a way to emulate the UI.
*/ */
if(isDisabled){ if(isDisabled){
$select.removeAttr("disabled"); $select.removeAttr("disabled");
} }
$select.find('option').each(function(i){ $select.find('option').each(function(i){
var $this = $(this), var $this = $(this),
title = $this.html(), title = $this.html(),
value = this.value, value = this.value,
inputID = this.id || "ui-multiselect-"+id+"-option-"+i, inputID = this.id || "ui-multiselect-"+id+"-option-"+i,
$parent = $this.parent(), $parent = $this.parent(),
hasOptGroup = $parent.is('optgroup'), hasOptGroup = $parent.is('optgroup'),
isDisabled = $this.is(':disabled'), isDisabled = $this.is(':disabled'),
labelClasses = ['ui-corner-all']; labelClasses = ['ui-corner-all'];
if(hasOptGroup){ if(hasOptGroup){
var label = $parent.attr('label'); var label = $parent.attr('label');
if($.inArray(label,optgroups) === -1){ if($.inArray(label,optgroups) === -1){
html.push('<li class="ui-multiselect-optgroup-label"><a href="#" class="ui-multiselect-all" style="color:#FF9000;float:left;width:350px;">' + label + '</a><a href="#" style="float:right;" class="ui-multiselect-toggleclose ui-icon ui-icon-triangle-1-s"></a></li>'); html.push('<li class="ui-multiselect-optgroup-label"><a href="#" class="ui-multiselect-all" style="color:#FF9000;float:left;width:350px;">' + label + '</a><a href="#" style="float:right;" class="ui-multiselect-toggleclose ui-icon ui-icon-triangle-1-s"></a></li>');
optgroups.push(label); optgroups.push(label);
} }
} }
if(value.length > 0){ if(value.length > 0){
if(isDisabled){ if(isDisabled){
labelClasses.push('ui-state-disabled'); labelClasses.push('ui-state-disabled');
} }
html.push('<li class="' + (isDisabled ? 'ui-multiselect-disabled' : '') +'">'); html.push('<li class="' + (isDisabled ? 'ui-multiselect-disabled' : '') +'">');
html.push('<label for="'+inputID+'" class="'+labelClasses.join(' ')+'"><input id="'+inputID+'" type="'+(o.multiple ? 'checkbox' : 'radio')+'" checked="checked" name="'+select.name+'" value="'+value+'" title="'+title+'"'); html.push('<label for="'+inputID+'" class="'+labelClasses.join(' ')+'"><input id="'+inputID+'" type="'+(o.multiple ? 'checkbox' : 'radio')+'" checked="checked" name="'+select.name+'" value="'+value+'" title="'+title+'"');
if($this.is(':selected')){ if($this.is(':selected')){
@@ -99,49 +99,49 @@
$options = $select.next('div.ui-multiselect-options'); $options = $select.next('div.ui-multiselect-options');
$header = $options.find('div.ui-multiselect-header'); $header = $options.find('div.ui-multiselect-header');
$labels = $options.find('label').not('.ui-state-disabled'); $labels = $options.find('label').not('.ui-state-disabled');
//hide by default //hide by default
$options.find('li.ui-multiselect-optgroup-label a.ui-multiselect-toggleclose').parent().nextUntil('li.ui-multiselect-optgroup-label').find('label').hide(); $options.find('li.ui-multiselect-optgroup-label a.ui-multiselect-toggleclose').parent().nextUntil('li.ui-multiselect-optgroup-label').find('label').hide();
// calculate widths // calculate widths
var iconWidth = $select.find('span.ui-icon').outerWidth(), inputWidth = $original.outerWidth(), totalWidth = inputWidth+iconWidth; var iconWidth = $select.find('span.ui-icon').outerWidth(), inputWidth = $original.outerWidth(), totalWidth = inputWidth+iconWidth;
if( /\d/.test(o.minWidth) && totalWidth < o.minWidth){ if( /\d/.test(o.minWidth) && totalWidth < o.minWidth){
inputWidth = o.minWidth-iconWidth; inputWidth = o.minWidth-iconWidth;
totalWidth = o.minWidth; totalWidth = o.minWidth;
} }
// set widths // set widths
$select.width(totalWidth).find('input').width(inputWidth - 6); $select.width(totalWidth).find('input').width(inputWidth - 6);
// build header links // build header links
if(o.showHeader){ if(o.showHeader){
$header.find('a').click(function(e){ $header.find('a').click(function(e){
var $this = $(this); var $this = $(this);
// close link // close link
if($this.hasClass('ui-multiselect-close')){ if($this.hasClass('ui-multiselect-close')){
$options.trigger('close'); $options.trigger('close');
// check all / uncheck all // check all / uncheck all
} else { } else {
var checkAll = $this.hasClass('ui-multiselect-all'); var checkAll = $this.hasClass('ui-multiselect-all');
$options.trigger('toggleChecked', [(checkAll ? true : false)]); $options.trigger('toggleChecked', [(checkAll ? true : false)]);
checkAll ? $options.find('li.ui-multiselect-optgroup-label a.ui-multiselect-all').css("color", "#FF9000") : $options.find('li.ui-multiselect-optgroup-label a.ui-multiselect-all').css("color", "white") ; checkAll ? $options.find('li.ui-multiselect-optgroup-label a.ui-multiselect-all').css("color", "#FF9000") : $options.find('li.ui-multiselect-optgroup-label a.ui-multiselect-all').css("color", "white") ;
o[ checkAll ? 'onCheckAll' : 'onUncheckAll']['call'](this); o[ checkAll ? 'onCheckAll' : 'onUncheckAll']['call'](this);
} }
e.preventDefault(); e.preventDefault();
}); });
} }
var updateSelected = function(){ var updateSelected = function(){
var $inputs = $labels.find('input'), var $inputs = $labels.find('input'),
$checked = $inputs.filter(':checked'), $checked = $inputs.filter(':checked'),
$tot = $inputs.length, $tot = $inputs.length,
value = '', value = '',
numChecked = $checked.length; numChecked = $checked.length;
if(numChecked === 0){ if(numChecked === 0){
value = o.noneSelectedText; value = o.noneSelectedText;
} else if($tot == numChecked ) { } else if($tot == numChecked ) {
@@ -155,11 +155,11 @@
value = o.selectedText.replace('#', numChecked).replace('#', $inputs.length); value = o.selectedText.replace('#', numChecked).replace('#', $inputs.length);
} }
} }
$select.find('input').val(value); $select.find('input').val(value);
return value; return value;
}; };
// the select box events // the select box events
$select.bind({ $select.bind({
click: function(){ click: function(){
@@ -194,12 +194,12 @@
$(this).removeClass('ui-state-focus'); $(this).removeClass('ui-state-focus');
} }
}); });
// bind custom events to the options div // bind custom events to the options div
$options.bind({ $options.bind({
'close': function(e, others){ 'close': function(e, others){
others = others || false; others = others || false;
// hides all other options but the one clicked // hides all other options but the one clicked
if(others === true){ if(others === true){
$('div.ui-multiselect-options') $('div.ui-multiselect-options')
@@ -208,7 +208,7 @@
.prev('a.ui-multiselect') .prev('a.ui-multiselect')
.removeClass('ui-state-active') .removeClass('ui-state-active')
.trigger('mouseout'); .trigger('mouseout');
// hides the clicked options // hides the clicked options
} else { } else {
$select.removeClass('ui-state-active').trigger('mouseout'); $select.removeClass('ui-state-active').trigger('mouseout');
@@ -216,25 +216,25 @@
} }
}, },
'open': function(e, closeOthers){ 'open': function(e, closeOthers){
// bail if this widget is disabled // bail if this widget is disabled
if($select.hasClass('ui-state-disabled')){ if($select.hasClass('ui-state-disabled')){
return; return;
} }
// use position() if inside ui-widget-content, because offset() won't cut it. // use position() if inside ui-widget-content, because offset() won't cut it.
var offset = $select.position(), var offset = $select.position(),
$container = $options.find('ul:last'), $container = $options.find('ul:last'),
top, width; top, width;
// calling select is active // calling select is active
$select.addClass('ui-state-active'); $select.addClass('ui-state-active');
// hide all other options // hide all other options
if(closeOthers || typeof closeOthers === 'undefined'){ if(closeOthers || typeof closeOthers === 'undefined'){
$options.trigger('close', [true]); $options.trigger('close', [true]);
} }
// calculate positioning // calculate positioning
if(o.position === 'middle'){ if(o.position === 'middle'){
top = ( offset.top+($select.height()/2)-($options.outerHeight()/2) ); top = ( offset.top+($select.height()/2)-($options.outerHeight()/2) );
@@ -243,58 +243,58 @@
} else { } else {
top = (offset.top+$select.outerHeight()); top = (offset.top+$select.outerHeight());
} }
// calculate the width of the options menu // calculate the width of the options menu
width = $select.width()-parseInt($options.css('padding-left'),10)-parseInt($options.css('padding-right'),10); width = $select.width()-parseInt($options.css('padding-left'),10)-parseInt($options.css('padding-right'),10);
// select the first option // select the first option
$labels.filter('label:first').trigger('mouseenter').trigger('focus'); $labels.filter('label:first').trigger('mouseenter').trigger('focus');
// show the options div + position it // show the options div + position it
$options.css({ $options.css({
position: 'absolute', position: 'absolute',
top: top+'px', top: top+'px',
left: offset.left+'px', left: offset.left+'px',
width: width+'px' width: width+'px'
}).show(); }).show();
// set the scroll of the checkbox container // set the scroll of the checkbox container
$container.scrollTop(0); $container.scrollTop(0);
// set the height of the checkbox container // set the height of the checkbox container
if(o.maxHeight){ if(o.maxHeight){
$container.css('height', o.maxHeight); $container.css('height', o.maxHeight);
} }
o.onOpen.call($options[0]); o.onOpen.call($options[0]);
}, },
'toggle': function(){ 'toggle': function(){
$options.trigger( $(this).is(':hidden') ? 'open' : 'close' ); $options.trigger( $(this).is(':hidden') ? 'open' : 'close' );
}, },
'traverse': function(e, start, keycode){ 'traverse': function(e, start, keycode){
var $start = $(start), var $start = $(start),
moveToLast = (keycode === 38 || keycode === 37) ? true : false, moveToLast = (keycode === 38 || keycode === 37) ? true : false,
// select the first li that isn't an optgroup label / disabled // select the first li that isn't an optgroup label / disabled
$next = $start.parent()[moveToLast ? 'prevAll' : 'nextAll']('li:not(.ui-multiselect-disabled, .ui-multiselect-optgroup-label)')[ moveToLast ? 'last' : 'first'](); $next = $start.parent()[moveToLast ? 'prevAll' : 'nextAll']('li:not(.ui-multiselect-disabled, .ui-multiselect-optgroup-label)')[ moveToLast ? 'last' : 'first']();
// if at the first/last element // if at the first/last element
if(!$next.length){ if(!$next.length){
var $container = $options.find("ul:last"); var $container = $options.find("ul:last");
// move to the first/last // move to the first/last
$options.find('label')[ moveToLast ? 'last' : 'first' ]().trigger('mouseover'); $options.find('label')[ moveToLast ? 'last' : 'first' ]().trigger('mouseover');
// set scroll position // set scroll position
$container.scrollTop( moveToLast ? $container.height() : 0 ); $container.scrollTop( moveToLast ? $container.height() : 0 );
} else { } else {
$next.find('label').trigger('mouseenter'); $next.find('label').trigger('mouseenter');
} }
}, },
'toggleChecked': function(e, flag, group){ 'toggleChecked': function(e, flag, group){
var $inputs = (group && group.length) ? group : $labels.find('input'); var $inputs = (group && group.length) ? group : $labels.find('input');
$inputs.not(':disabled').attr('checked', (flag ? 'checked' : '')); $inputs.not(':disabled').attr('checked', flag);
updateSelected(); updateSelected();
} }
}) })
@@ -309,19 +309,19 @@
{ {
$(this).css("color", "rgb(255,255,255)"); $(this).css("color", "rgb(255,255,255)");
} }
var $checkboxes = $(this).parent().nextUntil('li.ui-multiselect-optgroup-label').find('input'); var $checkboxes = $(this).parent().nextUntil('li.ui-multiselect-optgroup-label').find('input');
$options.trigger('toggleChecked', [ ($checkboxes.filter(':checked').length === $checkboxes.length) ? false : true, $checkboxes]);Y $options.trigger('toggleChecked', [ ($checkboxes.filter(':checked').length === $checkboxes.length) ? false : true, $checkboxes]);
o.onOptgroupToggle.call(this, $checkboxes.get()); o.onOptgroupToggle.call(this, $checkboxes.get());
e.preventDefault(); e.preventDefault();
}); });
$options.find('li.ui-multiselect-optgroup-label a.ui-multiselect-toggleclose') $options.find('li.ui-multiselect-optgroup-label a.ui-multiselect-toggleclose')
.click(function(e){ .click(function(e){
var $label = $(this).parent().nextUntil('li.ui-multiselect-optgroup-label').find('label'); var $label = $(this).parent().nextUntil('li.ui-multiselect-optgroup-label').find('label');
// optgroup label toggle support // optgroup label toggle support
$label.toggle(); $label.toggle();
e.preventDefault(); e.preventDefault();
}); });
@@ -343,7 +343,7 @@
/**************************************************************************/ /**************************************************************************/
// labels/checkbox events // labels/checkbox events
$labels.bind({ $labels.bind({
/*mouseenter: function(){ /*mouseenter: function(){
@@ -355,14 +355,14 @@
case 27: // esc case 27: // esc
$options.trigger('close'); $options.trigger('close');
break; break;
case 38: // up case 38: // up
case 40: // down case 40: // down
case 37: // left case 37: // left
case 39: // right case 39: // right
$options.trigger('traverse', [this, e.keyCode]); $options.trigger('traverse', [this, e.keyCode]);
break; break;
case 13: // enter case 13: // enter
e.preventDefault(); e.preventDefault();
$(this).click(); $(this).click();
@@ -378,23 +378,23 @@
// remove the original input element // remove the original input element
$original.remove(); $original.remove();
// apply bgiframe if available // apply bgiframe if available
if($.fn.bgiframe){ if($.fn.bgiframe){
$options.bgiframe(); $options.bgiframe();
} }
// open by default? // open by default?
if(o.state === 'open'){ if(o.state === 'open'){
$options.trigger('open', [false]); $options.trigger('open', [false]);
} }
// update the number of selected elements when the page initially loads, and use that as the defaultValue. necessary for form resets when options are pre-selected. // update the number of selected elements when the page initially loads, and use that as the defaultValue. necessary for form resets when options are pre-selected.
$select.find('input')[0].defaultValue = updateSelected(); $select.find('input')[0].defaultValue = updateSelected();
return $select; return $select;
}; };
// close each select when clicking on any other element/anywhere else on the page // close each select when clicking on any other element/anywhere else on the page
$(document).bind('click', function(e){ $(document).bind('click', function(e){
var $target = $(e.target); var $target = $(e.target);
@@ -420,7 +420,7 @@
fadeSpeed: 200, fadeSpeed: 200,
disabled: false, disabled: false,
state: 'closed', state: 'closed',
multiple: true, multiple: true,
onCheck: function(){}, /* when an individual checkbox is clicked */ onCheck: function(){}, /* when an individual checkbox is clicked */
onOpen: function(){}, /* when the select menu is opened */ onOpen: function(){}, /* when the select menu is opened */
onCheckAll: function(){}, /* when the check all link is clicked */ onCheckAll: function(){}, /* when the check all link is clicked */