|
46 | 46 | 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.
|
47 | 47 | 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.
|
48 | 48 | 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. |
50 | 50 | show: null, // (array) An array containing menu opening effects.
|
51 | 51 | hide: null, // (array) An array containing menu closing effects.
|
52 | 52 | autoOpen: false, // (true | false) If true, then the menu will be opening immediately after initialization.
|
|
104 | 104 | var options = this.options;
|
105 | 105 | var classes = options.classes;
|
106 | 106 | var headerOn = options.header;
|
107 |
| - var checkAllText = options.checkAllText; |
| 107 | + var checkAllText = options.maxSelected ? null : options.checkAllText; |
108 | 108 | // Do an extend here to address icons missing from options.iconSet--missing icons default to those in defaultIcons.
|
109 | 109 | var iconSet = $.extend({}, defaultIcons, options.iconSet || {});
|
110 | 110 | var uncheckAllText = options.uncheckAllText;
|
|
119 | 119 | this.speed = $.fx.speeds._default;
|
120 | 120 | this._isOpen = false;
|
121 | 121 |
|
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); |
126 | 125 | // bump unique ID after assigning it to the widget instance
|
127 | 126 | this.multiselectID = multiselectID++;
|
128 | 127 |
|
|
511 | 510 | var $inputs = $this.next('ul').find('input').filter(':visible:not(:disabled)');
|
512 | 511 | var nodes = $inputs.get();
|
513 | 512 | 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 | + } |
514 | 519 |
|
515 | 520 | // trigger before callback and bail if the return is false
|
516 | 521 | if (self._trigger('beforeoptgrouptoggle', e, { inputs:nodes, label:label }) === false) {
|
|
593 | 598 | var $tags = $element.find('option');
|
594 | 599 | var isMultiple = $element[0].multiple;
|
595 | 600 | var $allInputs = self.$inputs;
|
596 |
| - var inputCount = $allInputs.length; |
597 | 601 | var numChecked = $allInputs.filter(":checked").length;
|
598 | 602 | var options = self.options;
|
599 | 603 | var optionText = $input.parent().find("span")[options.htmlOptionText ? 'html' : 'text']();
|
600 |
| - var selectedMax = options.selectedMax; |
| 604 | + var maxSelected = options.maxSelected; |
601 | 605 |
|
602 | 606 | // bail if this input is disabled or the event is cancelled
|
603 | 607 | if (input.disabled || self._trigger('click', e, { value: val, text: optionText, checked: checked }) === false) {
|
604 | 608 | e.preventDefault();
|
605 | 609 | return;
|
606 | 610 | }
|
607 | 611 |
|
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 | + } |
620 | 616 | input.checked = false;
|
621 | 617 | e.preventDefault();
|
622 | 618 | return false;
|
|
1280 | 1276 | },
|
1281 | 1277 |
|
1282 | 1278 | checkAll: function(e) {
|
| 1279 | + this._trigger('beforeCheckAll'); |
1283 | 1280 | this._toggleChecked(true);
|
1284 | 1281 | this._trigger('checkAll');
|
1285 | 1282 | },
|
1286 | 1283 |
|
1287 | 1284 | uncheckAll: function() {
|
| 1285 | + this._trigger('beforeUncheckAll'); |
| 1286 | + |
1288 | 1287 | this._toggleChecked(false);
|
1289 | 1288 | if ( !this.element[0].multiple ) {
|
1290 | 1289 | // Forces the underlying single-select to have no options selected.
|
1291 | 1290 | this.element[0].selectedIndex = -1;
|
1292 | 1291 | }
|
| 1292 | + |
1293 | 1293 | this._trigger('uncheckAll');
|
1294 | 1294 | },
|
1295 | 1295 |
|
1296 | 1296 | 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 ); |
1299 | 1320 | },
|
1300 | 1321 |
|
1301 | 1322 | /**
|
|
1347 | 1368 | return this.$menu;
|
1348 | 1369 | },
|
1349 | 1370 |
|
| 1371 | + /** |
| 1372 | + * @returns {string} namespaceID for use with external event handlers. |
| 1373 | + */ |
| 1374 | + getNamespaceID: function() { |
| 1375 | + return this._namespaceID; |
| 1376 | + }, |
| 1377 | + |
1350 | 1378 | /**
|
1351 | 1379 | * @returns {object} jQuery object for button
|
1352 | 1380 | */
|
|
1466 | 1494 | case 'checkAllText':
|
1467 | 1495 | case 'uncheckAllText':
|
1468 | 1496 | 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 | + } |
1470 | 1500 | break;
|
1471 | 1501 | case 'checkAllIcon':
|
1472 | 1502 | case 'uncheckAllIcon':
|
1473 | 1503 | 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 | + } |
1475 | 1507 | break;
|
1476 | 1508 | case 'openIcon':
|
1477 | 1509 | $menu.find('span.ui-multiselect-open').html(value);
|
|
1491 | 1523 | break;
|
1492 | 1524 | case 'selectedText':
|
1493 | 1525 | case 'selectedList':
|
1494 |
| - case 'selectedMax': |
| 1526 | + case 'maxSelected': |
1495 | 1527 | case 'noneSelectedText':
|
1496 | 1528 | case 'selectedListSeparator':
|
1497 | 1529 | this.options[key] = value; // these all need to update immediately for the update() call
|
|
0 commit comments