Skip to content

Commit 7142177

Browse files
SteveTheTechiemlh758
authored andcommitted
Max selected and misc updates (#747)
* Max selected and misc 1. Fix/finish maxSelected functionality. 2. Fix related test. 3. Fix namespace period issue. 4. Add new event triggers requested by open PR.
1 parent ecb9a54 commit 7142177

File tree

2 files changed

+61
-29
lines changed

2 files changed

+61
-29
lines changed

src/jquery.multiselect.js

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
noneSelectedText: 'Select options', // (str | null) The text to show in the button where nothing is selected. Set to null to use the native select's placeholder text.
4747
selectedText: '# of # selected', // (str) A "template" that indicates how to show the count of selections in the button. The "#'s" are replaced by the selection count & option count.
4848
selectedList: 0, // (int) The actual list selections will be shown in the button when the count of selections is <= than this number.
49-
selectedMax: null, // (int | function) If selected count > selectedMax or if function returns 1, then message is displayed, and new selection is undone.
49+
maxSelected: null, // (int | null) If selected count > maxSelected, then message is displayed, and new selection is undone.
5050
show: null, // (array) An array containing menu opening effects.
5151
hide: null, // (array) An array containing menu closing effects.
5252
autoOpen: false, // (true | false) If true, then the menu will be opening immediately after initialization.
@@ -104,7 +104,7 @@
104104
var options = this.options;
105105
var classes = options.classes;
106106
var headerOn = options.header;
107-
var checkAllText = options.checkAllText;
107+
var checkAllText = options.maxSelected ? null : options.checkAllText;
108108
// Do an extend here to address icons missing from options.iconSet--missing icons default to those in defaultIcons.
109109
var iconSet = $.extend({}, defaultIcons, options.iconSet || {});
110110
var uncheckAllText = options.uncheckAllText;
@@ -119,10 +119,9 @@
119119
this.speed = $.fx.speeds._default;
120120
this._isOpen = false;
121121

122-
// create a unique namespace for events that the widget
123-
// factory cannot unbind automatically. Use eventNamespace if on
124-
// jQuery UI 1.9+, and otherwise fallback to a custom string.
125-
this._namespaceID = this.eventNamespace || ('multiselect' + multiselectID);
122+
// Create a unique namespace for events that the widget
123+
// factory cannot unbind automatically.
124+
this._namespaceID = this.eventNamespace.slice(1);
126125
// bump unique ID after assigning it to the widget instance
127126
this.multiselectID = multiselectID++;
128127

@@ -511,6 +510,12 @@
511510
var $inputs = $this.next('ul').find('input').filter(':visible:not(:disabled)');
512511
var nodes = $inputs.get();
513512
var label = this.textContent;
513+
514+
// if maxSelected is in use, cannot exceed it
515+
var maxSelected = self.options.maxSelected;
516+
if (maxSelected && (self.$inputs.filter(':checked').length + $inputs.length > maxSelected) ) {
517+
return;
518+
}
514519

515520
// trigger before callback and bail if the return is false
516521
if (self._trigger('beforeoptgrouptoggle', e, { inputs:nodes, label:label }) === false) {
@@ -593,30 +598,21 @@
593598
var $tags = $element.find('option');
594599
var isMultiple = $element[0].multiple;
595600
var $allInputs = self.$inputs;
596-
var inputCount = $allInputs.length;
597601
var numChecked = $allInputs.filter(":checked").length;
598602
var options = self.options;
599603
var optionText = $input.parent().find("span")[options.htmlOptionText ? 'html' : 'text']();
600-
var selectedMax = options.selectedMax;
604+
var maxSelected = options.maxSelected;
601605

602606
// bail if this input is disabled or the event is cancelled
603607
if (input.disabled || self._trigger('click', e, { value: val, text: optionText, checked: checked }) === false) {
604608
e.preventDefault();
605609
return;
606610
}
607611

608-
if ( selectedMax && checked
609-
&& ( typeof selectedMax === 'function' ? !!selectedMax.call(input, $allInputs) : numChecked > selectedMax ) ) {
610-
var saveText = options.selectedText;
611-
612-
// The following warning is shown in the button and then cleared after a second.
613-
options.selectedText = "<center><b>LIMIT OF " + (numChecked - 1) + " REACHED!</b></center>";
614-
self.update();
615-
setTimeout( function() {
616-
options.selectedText = saveText;
617-
self.update();
618-
}, 1000 );
619-
612+
if ( maxSelected && checked && numChecked > maxSelected) {
613+
if ( self._trigger('maxselected', e, { labels: self.$labels, inputs: $allInputs }) !== false ) {
614+
self.buttonMessage("<center><b>LIMIT OF " + (numChecked - 1) + " REACHED!</b></center>");
615+
}
620616
input.checked = false;
621617
e.preventDefault();
622618
return false;
@@ -1280,22 +1276,47 @@
12801276
},
12811277

12821278
checkAll: function(e) {
1279+
this._trigger('beforeCheckAll');
12831280
this._toggleChecked(true);
12841281
this._trigger('checkAll');
12851282
},
12861283

12871284
uncheckAll: function() {
1285+
this._trigger('beforeUncheckAll');
1286+
12881287
this._toggleChecked(false);
12891288
if ( !this.element[0].multiple ) {
12901289
// Forces the underlying single-select to have no options selected.
12911290
this.element[0].selectedIndex = -1;
12921291
}
1292+
12931293
this._trigger('uncheckAll');
12941294
},
12951295

12961296
flipAll: function() {
1297-
this._toggleChecked('!');
1298-
this._trigger('flipAll');
1297+
this._trigger('beforeFlipAll');
1298+
1299+
var maxSelected = this.options.maxSelected;
1300+
if (maxSelected === null || maxSelected > (this.$inputs.length - this.$inputs.filter(':checked').length) ) {
1301+
this._toggleChecked('!');
1302+
this._trigger('flipAll');
1303+
}
1304+
else {
1305+
this.buttonMessage("<center><b>Flip All Not Permitted.</b></center>");
1306+
}
1307+
},
1308+
1309+
/**
1310+
* Flashes a message in the button caption for 1 second.
1311+
* Useful for very short warning messages to the user.
1312+
* @param {string} HTML message to show in the button.
1313+
*/
1314+
buttonMessage: function(message) {
1315+
var self = this;
1316+
self.$buttonlabel.html(message);
1317+
setTimeout( function() {
1318+
self.update();
1319+
}, 1000 );
12991320
},
13001321

13011322
/**
@@ -1347,6 +1368,13 @@
13471368
return this.$menu;
13481369
},
13491370

1371+
/**
1372+
* @returns {string} namespaceID for use with external event handlers.
1373+
*/
1374+
getNamespaceID: function() {
1375+
return this._namespaceID;
1376+
},
1377+
13501378
/**
13511379
* @returns {object} jQuery object for button
13521380
*/
@@ -1466,12 +1494,16 @@
14661494
case 'checkAllText':
14671495
case 'uncheckAllText':
14681496
case 'flipAllText':
1469-
$menu.find('a.ui-multiselect-' + {'checkAllText': 'all', 'uncheckAllText': 'none', 'flipAllText': 'flip'}[key] + ' span').eq(-1).text(value); // eq(-1) finds the last span
1497+
if (key !== 'checkAllText' || !this.options.maxSelected) {
1498+
$menu.find('a.ui-multiselect-' + {'checkAllText': 'all', 'uncheckAllText': 'none', 'flipAllText': 'flip'}[key] + ' span').eq(-1).text(value); // eq(-1) finds the last span
1499+
}
14701500
break;
14711501
case 'checkAllIcon':
14721502
case 'uncheckAllIcon':
14731503
case 'flipAllIcon':
1474-
$menu.find('a.ui-multiselect-' + {'checkAllIcon': 'all', 'uncheckAllIcon': 'none', 'flipAllIcon': 'flip'}[key] + ' span').eq(0).replaceWith(value); // eq(0) finds the first span
1504+
if (key !== 'checkAllIcon' || !this.options.maxSelected) {
1505+
$menu.find('a.ui-multiselect-' + {'checkAllIcon': 'all', 'uncheckAllIcon': 'none', 'flipAllIcon': 'flip'}[key] + ' span').eq(0).replaceWith(value); // eq(0) finds the first span
1506+
}
14751507
break;
14761508
case 'openIcon':
14771509
$menu.find('span.ui-multiselect-open').html(value);
@@ -1491,7 +1523,7 @@
14911523
break;
14921524
case 'selectedText':
14931525
case 'selectedList':
1494-
case 'selectedMax':
1526+
case 'maxSelected':
14951527
case 'noneSelectedText':
14961528
case 'selectedListSeparator':
14971529
this.options[key] = value; // these all need to update immediately for the update() call

tests/unit/options.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,19 @@
8282
el.multiselect("destroy").remove();
8383
});
8484

85-
QUnit.test("selectedMax", function (assert) {
85+
QUnit.test("maxSelected", function (assert) {
8686
var html = '<select multiple><option value="foo">foo &quot;with quotes&quot;</option><option value="bar">bar</option><option value="baz">baz</option></select>';
8787

8888
el = $(html).appendTo("body").multiselect({
89-
selectedMax: 2
89+
maxSelected: 2
9090
});
9191

9292
var checkboxes = el.multiselect("widget").find(":checkbox");
9393
checkboxes.eq(0).trigger('click');
9494
checkboxes.eq(1).trigger('click');
9595
checkboxes.eq(2).trigger('click');
9696

97-
assert.equal(menu().find("input").filter(":checked").length, 2, 'after clicking each checkbox, count of checked restored to selectedMax of 2');
97+
assert.equal(menu().find("input").filter(":checked").length, 2, 'after clicking each checkbox, count of checked restored to maxSelected of 2');
9898
el.multiselect("destroy").remove();
9999
});
100100

@@ -202,7 +202,7 @@
202202
width = "3in";
203203
el.multiselect("option", "menuWidth", width).multiselect('refresh');
204204
assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), 3 * 96.0, 'menuWidth supports strings suffixed with "in" unit as well as integer "px" values');
205-
205+
206206
el.multiselect("destroy");
207207
});
208208

0 commit comments

Comments
 (0)