Skip to content

Commit a9fcf40

Browse files
committed
Option Groups are now ul elements instead of additional list items.
Added groupColumns option which allows option groups to be displayed as columns. Added addOption and removeOption methods to add and remove options without a refresh. Got rid of an older IE6 css fix.
1 parent fd3135c commit a9fcf40

File tree

7 files changed

+218
-135
lines changed

7 files changed

+218
-135
lines changed

jquery.multiselect.css

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,16 @@
99
.ui-multiselect-header a { text-decoration:none; }
1010
.ui-multiselect-header a:hover { text-decoration:underline; }
1111
.ui-multiselect-header span.ui-icon { float:left; }
12-
.ui-multiselect-header li.ui-multiselect-close { float:right; padding-right:0; text-align:right; }
12+
.ui-multiselect-header .ui-multiselect-close { float:right; padding-right:0; text-align:right; }
1313

1414
.ui-multiselect-menu { display:none; padding:3px; position:absolute; text-align: left; }
1515
.ui-multiselect-checkboxes { overflow-y:auto; position:relative; }
1616
.ui-multiselect-checkboxes label { border:1px solid transparent; cursor:default; display:block; padding:3px 1px; }
1717
.ui-multiselect-checkboxes label input { position:relative; top:1px }
18-
.ui-multiselect-checkboxes li { clear:both; font-size:0.9em; padding-right:3px; }
19-
.ui-multiselect-checkboxes li.ui-multiselect-optgroup-label { border-bottom:1px solid; font-weight:bold; text-align:center; }
20-
.ui-multiselect-checkboxes li.ui-multiselect-optgroup-label a { display:block; margin:1px 0; padding:3px; text-decoration:none; }
21-
22-
/* remove label borders in IE6 because IE6 does not support transparency */
23-
* html .ui-multiselect-checkboxes label { border:none }
24-
18+
.ui-multiselect-checkboxes li { clear:both; font-size:0.9em; list-style: none; padding-right:3px; }
19+
.ui-multiselect-checkboxes .ui-multiselect-optgroup { padding: 3px; }
20+
.ui-multiselect-columns { display: inline-block; vertical-align: top; }
21+
.ui-multiselect-checkboxes .ui-multiselect-optgroup a { border-bottom:1px solid; cursor: pointer; display:block; font-weight:bold; margin:1px 0; padding:3px; text-align:center; text-decoration:none; }
2522

2623
@media print{
2724
.ui-multiselect-menu {display: none;}

src/jquery.multiselect.filter.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
if(e.which === 13) {
6969
e.preventDefault();
7070
} else if(e.which === 27) {
71-
elem.multiselect('instance').close();
71+
elem.multiselect('close');
72+
e.preventDefault();
7273
} else if(e.which === 9 && e.shiftKey) {
7374
elem.multiselect('close');
7475
e.preventDefault();
@@ -79,10 +80,10 @@
7980
$(this).val('').trigger('input', '');
8081
break;
8182
case 65:
82-
elem.multiselect('instance').checkAll();
83+
elem.multiselect('checkAll');
8384
break;
8485
case 85:
85-
elem.multiselect('instance').uncheckAll();
86+
elem.multiselect('uncheckAll');
8687
break;
8788
case 76:
8889
elem.multiselect('instance').labels.first().trigger("mouseenter");
@@ -152,7 +153,8 @@
152153

153154
// speed up lookups
154155
rows = this.rows, inputs = this.inputs, cache = this.cache;
155-
156+
var $groups = this.instance.menu.find(".ui-multiselect-optgroup");
157+
$groups.show();
156158
if(!term) {
157159
rows.show();
158160
} else {
@@ -171,14 +173,13 @@
171173
}
172174

173175
// show/hide optgroups
174-
this.instance.menu.find(".ui-multiselect-optgroup-label").each(function() {
176+
$groups.each(function() {
175177
var $this = $(this);
176-
var isVisible = $this.nextUntil('.ui-multiselect-optgroup-label').filter(function() {
177-
return $.css(this, "display") !== 'none';
178-
}).length;
179-
180-
$this[isVisible ? 'show' : 'hide']();
178+
if(!$this.children("li:visible").length) {
179+
$this.hide();
180+
}
181181
});
182+
this.instance._setMenuHeight();
182183
},
183184

184185
_reset: function() {
@@ -187,7 +188,7 @@
187188

188189
updateCache: function() {
189190
// each list item
190-
this.rows = this.instance.menu.find(".ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup-label)");
191+
this.rows = this.instance.labels.parent();
191192

192193
// cache
193194
this.cache = this.element.children().map(function() {

src/jquery.multiselect.js

Lines changed: 119 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
appendTo: null,
4848
menuWidth:null,
4949
selectedListSeparator: ', ',
50-
disableInputsOnToggle: true
50+
disableInputsOnToggle: true,
51+
groupColumns: false
5152
},
5253

5354
_getAppendEl: function() {
@@ -70,6 +71,7 @@
7071

7172
this.speed = $.fx.speeds._default; // default speed for effects
7273
this._isOpen = false; // assume no
74+
this.inputIdCounter = 0;
7375

7476
// create a unique namespace for events that the widget
7577
// factory cannot unbind automatically. Use eventNamespace if on
@@ -154,60 +156,78 @@
154156
}
155157
},
156158

159+
_makeOption: function(option) {
160+
var title = option.title ? option.title : null;
161+
var value = option.value;
162+
var id = this.element.attr('id') || multiselectID; // unique ID for the label & option tags
163+
var inputID = 'ui-multiselect-' + multiselectID + '-' + (option.id || id + '-option-' + this.inputIdCounter++);
164+
var isDisabled = option.disabled;
165+
var isSelected = option.selected;
166+
var labelClasses = [ 'ui-corner-all' ];
167+
var liClasses = [];
168+
var o = this.options;
169+
170+
if(isDisabled) {
171+
liClasses.push('ui-multiselect-disabled');
172+
labelClasses.push('ui-state-disabled');
173+
}
174+
if(option.className) {
175+
liClasses.push(option.className);
176+
}
177+
if(isSelected && !o.multiple) {
178+
labelClasses.push('ui-state-active');
179+
}
180+
181+
var $item = $("<li/>").addClass(liClasses.join(' '));
182+
var $label = $("<label/>").attr({
183+
"for": inputID,
184+
"title": title
185+
}).addClass(labelClasses.join(' ')).appendTo($item);
186+
var $input = $("<input/>").attr({
187+
"name": "multiselect_" + id,
188+
"type": o.multiple ? "checkbox" : "radio",
189+
"value": value,
190+
"title": title,
191+
"id": inputID,
192+
"checked": isSelected ? "checked" : null,
193+
"aria-selected": isSelected ? "true" : null,
194+
"disabled": isDisabled ? "disabled" : null,
195+
"aria-disabled": isDisabled ? "true" : null
196+
}).data($(option).data()).appendTo($label);
197+
198+
$("<span/>").text($(option).text()).appendTo($label);
199+
200+
return $item;
201+
},
202+
203+
_buildOptionList: function(element, $appendTo) {
204+
var self = this;
205+
element.children().each(function() {
206+
var $this = $(this);
207+
if(this.tagName === 'OPTGROUP') {
208+
var $optionGroup = $("<ul/>").addClass('ui-multiselect-optgroup ' + this.className).appendTo($appendTo);
209+
if(self.options.groupColumns) {
210+
$optionGroup.addClass("ui-multiselect-columns");
211+
}
212+
$("<a/>").text(this.getAttribute('label')).appendTo($optionGroup);
213+
self._buildOptionList($this, $optionGroup);
214+
} else {
215+
var $listItem = self._makeOption(this).appendTo($appendTo);
216+
}
217+
});
218+
219+
},
220+
157221
refresh: function(init) {
158-
var inputIdCounter = 0;
222+
var self = this;
159223
var el = this.element;
160224
var o = this.options;
161225
var menu = this.menu;
162226
var checkboxContainer = this.checkboxContainer;
163227
var html = "";
164228
var $dropdown = $("<ul/>").addClass('ui-multiselect-checkboxes ui-helper-reset');
165-
var id = el.attr('id') || multiselectID++; // unique ID for the label & option tags
166-
167-
function makeItem(option, isInOptionGroup) {
168-
var title = option.title ? option.title : null;
169-
var value = option.value;
170-
var inputID = 'ui-multiselect-' + multiselectID + '-' + (option.id || id + '-option-' + inputIdCounter++);
171-
var isDisabled = option.disabled;
172-
var isSelected = option.selected;
173-
var labelClasses = [ 'ui-corner-all' ];
174-
var liClasses = [];
175-
176-
if(isDisabled) {
177-
liClasses.push('ui-multiselect-disabled');
178-
labelClasses.push('ui-state-disabled');
179-
}
180-
if(option.className) {
181-
liClasses.push(option.className);
182-
}
183-
if(isSelected && !o.multiple) {
184-
labelClasses.push('ui-state-active');
185-
}
186-
if(isInOptionGroup) {
187-
liClasses.push('ui-multiselect-optgrp-child');
188-
}
229+
this.inputIdCounter = 0;
189230

190-
var $item = $("<li/>").addClass(liClasses.join(' '));
191-
var $label = $("<label/>").attr({
192-
"for": inputID,
193-
"title": title
194-
}).addClass(labelClasses.join(' ')).appendTo($item);
195-
var $input = $("<input/>").attr({
196-
"name": "multiselect_" + id,
197-
"type": o.multiple ? "checkbox" : "radio",
198-
"value": value,
199-
"title": title,
200-
"id": inputID,
201-
"checked": isSelected ? "checked" : null,
202-
"aria-selected": isSelected ? "true" : null,
203-
"disabled": isDisabled ? "disabled" : null,
204-
"aria-disabled": isDisabled ? "true" : null
205-
}).data($(option).data()).appendTo($label);
206-
207-
$("<span/>").text($(option).text()).appendTo($label);
208-
209-
return $item;
210-
}//makeItem
211231

212232
// update header link container visibility if needed
213233
if (this.options.header) {
@@ -218,22 +238,7 @@
218238
}
219239
}
220240

221-
//Turn all the options and optiongroups into list items
222-
el.children().each(function(i) {
223-
var $this = $(this);
224-
225-
if(this.tagName === 'OPTGROUP') {
226-
var $groupLabel = $("<li/>").addClass('ui-multiselect-optgroup-label ' + this.className).appendTo($dropdown);
227-
var $link = $("<a/>").attr("href", "#").text(this.getAttribute('label')).appendTo($groupLabel);
228-
229-
$this.children().each(function() {
230-
var $listItem = makeItem(this, true).appendTo($dropdown);
231-
});
232-
} else {
233-
var $listItem = makeItem(this).appendTo($dropdown);
234-
}
235-
236-
});
241+
this._buildOptionList(el, $dropdown);
237242

238243
this.menu.find(".ui-multiselect-checkboxes").remove();
239244
this.menu.append($dropdown);
@@ -335,13 +340,13 @@
335340
_bindMenuEvents: function() {
336341
var self = this;
337342
// optgroup label toggle support
338-
this.menu.delegate('li.ui-multiselect-optgroup-label a', 'click.multiselect', function(e) {
343+
this.menu.delegate('.ui-multiselect-optgroup a', 'click.multiselect', function(e) {
339344
e.preventDefault();
340345

341346
var $this = $(this);
342-
var $inputs = $this.parent().nextUntil(':not(.ui-multiselect-optgrp-child)').find('input:visible:not(:disabled)');
347+
var $inputs = $this.parent().find('input:visible:not(:disabled)');
343348
var nodes = $inputs.get();
344-
var label = $this.parent().text();
349+
var label = $this.text();
345350

346351
// trigger event and bail if the return is false
347352
if(self._trigger('beforeoptgrouptoggle', e, { inputs:nodes, label:label }) === false) {
@@ -556,7 +561,7 @@
556561
_setMenuHeight: function() {
557562
var headerHeight = this.menu.children(".ui-multiselect-header:visible").outerHeight(true);
558563
var ulHeight = 0;
559-
this.menu.find(".ui-multiselect-checkboxes li").each(function(idx, li) {
564+
this.menu.find(".ui-multiselect-checkboxes li, .ui-multiselect-checkboxes a").each(function(idx, li) {
560565
ulHeight += $(li).outerHeight(true);
561566
});
562567
if(ulHeight > this.options.height) {
@@ -581,7 +586,11 @@
581586
var moveToLast = which === 38 || which === 37;
582587

583588
// select the first li that isn't an optgroup label / disabled
584-
var $next = $start.parent()[moveToLast ? 'prevAll' : 'nextAll']('li:not(.ui-multiselect-disabled, .ui-multiselect-optgroup-label):visible').first();
589+
var $next = $start.parent()[moveToLast ? 'prevAll' : 'nextAll']('li:not(.ui-multiselect-disabled, .ui-multiselect-optgroup):visible').first();
590+
// we might have to jump to the next/previous option group
591+
if(!$next.length) {
592+
$next = $start.parents(".ui-multiselect-optgroup")[moveToLast ? "prev" : "next" ]();
593+
}
585594

586595
// if at the first/last element
587596
if(!$next.length) {
@@ -594,7 +603,7 @@
594603
$container.scrollTop(moveToLast ? $container.height() : 0);
595604

596605
} else {
597-
$next.find('label:visible').trigger('mouseover');
606+
$next.find('label:visible')[ moveToLast ? "last" : "first" ]().trigger('mouseover');
598607
}
599608
},
600609

@@ -701,7 +710,7 @@
701710
return;
702711
}
703712

704-
var $container = menu.find('ul').last();
713+
var $container = menu.find('.ui-multiselect-checkboxes');
705714
var effect = o.show;
706715

707716
// figure out opening effects/speeds
@@ -717,7 +726,7 @@
717726
}
718727

719728
// set the scroll of the checkbox container
720-
$container.scrollTop(0).height(o.height);
729+
$container.scrollTop(0);
721730

722731
// show the menu, maybe with a speed/effect combo
723732
$.fn.show.apply(menu, args);
@@ -824,6 +833,45 @@
824833
return this.button;
825834
},
826835

836+
getMenu: function() {
837+
return this.menu;
838+
},
839+
840+
getLabels: function() {
841+
return this.labels;
842+
},
843+
844+
addOption: function(attributes, text, groupLabel) {
845+
var $option = $("<option/>").attr(attributes).text(text);
846+
var optionNode = $option.get(0);
847+
if(groupLabel) {
848+
this.element.children("OPTGROUP").filter(function() {
849+
return $(this).prop("label") === groupLabel;
850+
}).append($option);
851+
this.menu.find(".ui-multiselect-optgroup").filter(function() {
852+
return $(this).find("a").text() === groupLabel;
853+
}).append(this._makeOption(optionNode));
854+
} else {
855+
this.element.append($option);
856+
this.menu.find(".ui-multiselect-checkboxes").append(this._makeOption(optionNode));
857+
}
858+
//update cached elements
859+
this.labels = this.menu.find('label');
860+
this.inputs = this.labels.children('input');
861+
},
862+
863+
removeOption: function(value) {
864+
if(!value) {
865+
return;
866+
}
867+
this.element.find("option[value=" + value + "]").remove();
868+
this.labels.find("input[value=" + value + "]").parents("li").remove();
869+
870+
//update cached elements
871+
this.labels = this.menu.find('label');
872+
this.inputs = this.labels.children('input');
873+
},
874+
827875
position: function() {
828876
var pos = {
829877
my: "top",

0 commit comments

Comments
 (0)