|
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 | + selectedListSeparator: ', ', // (str) This allows customization of the list separator. Use ',<br/>' to make the button grow vertically showing 1 selection per line. |
49 | 50 | maxSelected: null, // (int | null) If selected count > maxSelected, then message is displayed, and new selection is undone.
|
50 | 51 | show: null, // (array) An array containing menu opening effects.
|
51 | 52 | hide: null, // (array) An array containing menu closing effects.
|
52 | 53 | autoOpen: false, // (true | false) If true, then the menu will be opening immediately after initialization.
|
53 | 54 | position: {}, // (object) A jQuery UI position object that constrains how the pop-up menu is positioned.
|
54 | 55 | appendTo: null, // (jQuery | DOM element | selector str) If provided, this specifies what element to append the widget to in the DOM.
|
55 |
| - selectedListSeparator: ', ', // (str) This allows customization of the list separator. Use ',<br/>' to make the button grow vertically showing 1 selection per line. |
| 56 | + zIndex: null, // (int) Overrides the z-index set for the menu container. |
56 | 57 | htmlButtonText: false, // (true | false) If true, then the text used for the button's label is treated as html rather than plain text.
|
57 | 58 | htmlOptionText: false, // (true | false) If true, then the text for option label is treated as html rather than plain text.
|
58 | 59 | addInputNames: true, // (true | false) If true, names are created for each option input in the multi-select.
|
|
176 | 177 | .append($header, $checkboxes);
|
177 | 178 |
|
178 | 179 | $button.insertAfter($element);
|
179 |
| - this._getAppendEl().append($menu); |
| 180 | + var appendEl = this._getAppendEl(); |
| 181 | + appendEl.append($menu); |
| 182 | + |
| 183 | + // Set z-index of menu appropriately when it is not appended to a dialog and no z-index specified. |
| 184 | + if ( !options.zIndex && !appendEl.hasClass('.ui-front') ) { |
| 185 | + var $uiFront = this.element.closest('.ui-front, dialog'); |
| 186 | + options.zIndex = Math.max( $uiFront && parseInt($uiFront.css('z-index'), 10) + 1 || 0, |
| 187 | + appendEl && parseInt(appendEl.css('z-index'), 10) + 1 || 0); |
| 188 | + } |
| 189 | + |
| 190 | + if (options.zIndex) { |
| 191 | + $menu.css('z-index', options.zIndex); |
| 192 | + } |
| 193 | + |
| 194 | + // Use $.extend below since the "of" position property may not be able to be supplied via the option. |
| 195 | + options.position = $.extend({'my': 'left top', 'at': 'left bottom', 'of': $button}, options.position || {}); |
180 | 196 |
|
181 | 197 | this._bindEvents();
|
182 | 198 |
|
|
373 | 389 | * Updates cached values used elsewhere in the widget
|
374 | 390 | */
|
375 | 391 | _updateCache: function() {
|
376 |
| - // Invalidate cached dimensions and positioning state to force recalcs. |
| 392 | + // Invalidate cached dimensions to force recalcs. |
377 | 393 | this._savedButtonWidth = 0;
|
378 | 394 | this._savedMenuWidth = 0;
|
379 | 395 | this._ulHeight = 0;
|
380 |
| - this._positioned = false; |
381 | 396 |
|
382 | 397 | // Recreate important cached jQuery objects
|
383 | 398 | this.$header = this.$menu.children('.ui-multiselect-header');
|
|
429 | 444 |
|
430 | 445 | // Check if the menu needs to be repositioned due to button height changing from adding/removing selections.
|
431 | 446 | if (self._isOpen && self._savedButtonHeight != self.$button.outerHeight(false)) {
|
432 |
| - self._position(true); |
| 447 | + self.position(); |
433 | 448 | }
|
434 | 449 | },
|
435 | 450 |
|
|
510 | 525 | var $inputs = $this.next('ul').find('input').filter(':visible:not(:disabled)');
|
511 | 526 | var nodes = $inputs.get();
|
512 | 527 | 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 |
| - } |
519 | 528 |
|
520 | 529 | // trigger before callback and bail if the return is false
|
521 | 530 | if (self._trigger('beforeoptgrouptoggle', e, { inputs:nodes, label:label }) === false) {
|
522 | 531 | return;
|
523 | 532 | }
|
524 | 533 |
|
| 534 | + // if maxSelected is in use, cannot exceed it |
| 535 | + var maxSelected = self.options.maxSelected; |
| 536 | + if (maxSelected && (self.$inputs.filter(':checked').length + $inputs.length > maxSelected) ) { |
| 537 | + return; |
| 538 | + } |
| 539 | + |
525 | 540 | // toggle inputs
|
526 | 541 | self._toggleChecked(
|
527 | 542 | $inputs.filter(':checked').length !== $inputs.length,
|
|
575 | 590 | case 32: // space
|
576 | 591 | $(this).find('input')[0].click();
|
577 | 592 | break;
|
578 |
| - case 65: // Ctrl-A |
| 593 | + case 65: // Alt-A |
579 | 594 | if (e.altKey) {
|
580 | 595 | self.checkAll();
|
581 | 596 | }
|
582 | 597 | break;
|
583 |
| - case 85: // Ctrl-U |
| 598 | + case 70: // Alt-F |
| 599 | + if (e.altKey) { |
| 600 | + self.flipAll(); |
| 601 | + } |
| 602 | + break; |
| 603 | + case 85: // Alt-U |
584 | 604 | if (e.altKey) {
|
585 | 605 | self.uncheckAll();
|
586 | 606 | }
|
|
703 | 723 | self._bindMenuEvents();
|
704 | 724 | self._bindHeaderEvents();
|
705 | 725 |
|
706 |
| - // close each widget when clicking on any other element/anywhere else on the page |
707 |
| - self.document.on('mousedown.' + self._namespaceID, function(event) { |
708 |
| - var target = event.target; |
709 |
| - var button = self.$button.get(0); |
710 |
| - var menu = self.$menu.get(0); |
711 |
| - |
712 |
| - if ( self._isOpen && button !== target && !$.contains(button, target) && menu !== target && !$.contains(menu, target) ) { |
| 726 | + // Close each widget when clicking on any other element/anywhere else on the page |
| 727 | + // or scrolling w/ the mouse wheel outside the menu button. |
| 728 | + self.document.on('mousedown.' + self._namespaceID + ' wheel.' + self._namespaceID + ' mousewheel.' + self._namespaceID, function(event) { |
| 729 | + if ( self._isOpen && !$(event.target).closest('.ui-multiselect,.ui-multiselect-menu').length ) { |
713 | 730 | self.close();
|
714 | 731 | }
|
715 | 732 | });
|
|
780 | 797 | /**
|
781 | 798 | * Sets and caches the width of the button
|
782 | 799 | * Can set a minimum value if less than calculated width of native select.
|
783 |
| - * If the cache is cleared, the menu will be re-positioned on the next open |
784 | 800 | * @param {boolean} recalc true if cached value needs to be re-calculated
|
785 | 801 | */
|
786 | 802 | _setButtonWidth: function(recalc) {
|
787 | 803 | if (this._savedButtonWidth && !recalc) {
|
788 | 804 | return;
|
789 | 805 | }
|
790 | 806 |
|
791 |
| - this._positioned = false; |
792 |
| - |
793 | 807 | // this._selectWidth set in _create() for native select element before hiding it.
|
794 | 808 | var width = this._selectWidth || this._getBCRWidth( this.element );
|
795 | 809 | var buttonWidth = this.options.buttonWidth || '';
|
|
815 | 829 | /**
|
816 | 830 | * Sets and caches the width of the menu
|
817 | 831 | * Will use the width in options if provided, otherwise matches the button
|
818 |
| - * If the cache is cleared, the menu will be re-positioned on the next open |
819 | 832 | * @param {boolean} recalc true if cached value needs to be re-calculated
|
820 | 833 | */
|
821 | 834 | _setMenuWidth: function(recalc) {
|
822 | 835 | if (this._savedMenuWidth && !recalc) {
|
823 | 836 | return;
|
824 | 837 | }
|
825 | 838 |
|
826 |
| - this._positioned = false; |
827 |
| - |
828 | 839 | // Note that it is assumed that the button width was set prior.
|
829 | 840 | var width = this._savedButtonWidth || this._getBCRWidth( this.$button );
|
830 | 841 |
|
|
869 | 880 | * Will use the height provided in the options unless using the select size
|
870 | 881 | * option or the option exceeds the available height for the menu
|
871 | 882 | * Will set a scrollbar if the options can't all be visible at once
|
872 |
| - * If the cache is cleared, the menu will be re-positioned on the next open |
873 | 883 | * @param {boolean} recalc true if cached value needs to be re-calculated
|
874 | 884 | */
|
875 | 885 | _setMenuHeight: function(recalc) {
|
|
878 | 888 | return;
|
879 | 889 | }
|
880 | 890 |
|
881 |
| - self._positioned = false; |
882 | 891 | var $menu = self.$menu;
|
883 | 892 | var $header = self.$header.filter(':visible');
|
884 | 893 | var headerHeight = $header.outerHeight(true) + self._jqHeightFix($header);
|
| 894 | + var headerBottomMargin = 3; |
885 | 895 | var $checkboxes = self.$checkboxes;
|
886 | 896 |
|
887 | 897 | // The maximum available height for the $checkboxes:
|
888 | 898 | var maxHeight = $(window).height()
|
889 | 899 | - headerHeight
|
| 900 | + - headerBottomMargin |
890 | 901 | - this._parse2px( $menu.css('padding-top'), this.element, true ).px
|
891 | 902 | - this._parse2px( $menu.css('padding-bottom'), this.element, true ).px;
|
892 | 903 |
|
|
906 | 917 |
|
907 | 918 | var overflowSetting = 'hidden';
|
908 | 919 | var itemCount = 0;
|
909 |
| - var ulHeight = 0; |
| 920 | + var ulHeight = 0; // Adjustment for hover height included here. |
910 | 921 |
|
911 | 922 | // The following adds up item heights. If the height sum exceeds the option height or if the number
|
912 | 923 | // of item heights summed equal or exceed the native select size attribute, the loop is aborted.
|
|
923 | 934 | });
|
924 | 935 |
|
925 | 936 | $checkboxes.css('overflow', overflowSetting).height(ulHeight);
|
926 |
| - $menu.height(headerHeight + ulHeight); |
| 937 | + $menu.height(headerHeight + headerBottomMargin + ulHeight); |
927 | 938 | self._ulHeight = ulHeight;
|
928 | 939 | },
|
929 | 940 |
|
|
1211 | 1222 | }
|
1212 | 1223 |
|
1213 | 1224 | this._resizeMenu();
|
1214 |
| - this._position(); |
| 1225 | + this.position(); |
1215 | 1226 |
|
1216 | 1227 | // focus the first not disabled option or filter input if available
|
1217 | 1228 | var filter = $header.find(".ui-multiselect-filter");
|
|
1297 | 1308 | this._trigger('beforeFlipAll');
|
1298 | 1309 |
|
1299 | 1310 | var maxSelected = this.options.maxSelected;
|
1300 |
| - if (maxSelected === null || maxSelected > (this.$inputs.length - this.$inputs.filter(':checked').length) ) { |
| 1311 | + if (maxSelected === null || maxSelected >= (this.$inputs.length - this.$inputs.filter(':checked').length) ) { |
1301 | 1312 | this._toggleChecked('!');
|
1302 | 1313 | this._trigger('flipAll');
|
1303 | 1314 | }
|
|
1440 | 1451 | this._updateCache();
|
1441 | 1452 | },
|
1442 | 1453 |
|
1443 |
| - /** |
1444 |
| - * Public version of _position, always ignores the cache |
1445 |
| - */ |
1446 |
| - position: function(){ this._position.call(this, true) }, |
1447 |
| - /** |
1448 |
| - * Positions the menu |
1449 |
| - * Will attempt to use the UI position utility before falling back to a manual |
1450 |
| - * process by offsetting from the button height |
1451 |
| - * Saves a flag to avoid repeating this logic until necessary |
1452 |
| - * @param {boolean} reposition forces the menu to reposition if true |
1453 |
| - */ |
1454 |
| - _position: function(reposition) { |
1455 |
| - if (!!this._positioned && !reposition) { |
1456 |
| - return; |
1457 |
| - } |
| 1454 | + position: function() { |
1458 | 1455 | var $button = this.$button;
|
1459 |
| - // Save this so that we can determine when the button height has changed due adding/removing selections. |
1460 |
| - this._savedButtonHeight = this.$button.outerHeight(false); |
1461 | 1456 |
|
1462 |
| - var pos = $.extend({'my': 'left top', 'at': 'left bottom', 'of': $button}, this.options.position || {}); |
| 1457 | + // Save this so that we can determine when the button height has changed due adding/removing selections. |
| 1458 | + this._savedButtonHeight = $button.outerHeight(false); |
1463 | 1459 |
|
1464 | 1460 | if ($.ui && $.ui.position) {
|
1465 |
| - this.$menu.position(pos); |
| 1461 | + this.$menu.position(this.options.position); |
1466 | 1462 | }
|
1467 | 1463 | else {
|
1468 |
| - pos = $button.position(); |
| 1464 | + var pos = $button.position(); |
1469 | 1465 | pos.top += this._savedButtonHeight;
|
1470 | 1466 | this.$menu.offset(pos);
|
1471 | 1467 | }
|
1472 |
| - this._positioned = true; |
1473 | 1468 | },
|
1474 | 1469 |
|
1475 | 1470 | /**
|
|
1541 | 1536 | this.refresh();
|
1542 | 1537 | }
|
1543 | 1538 | break;
|
1544 |
| - case 'position': |
1545 |
| - this._position(true); // true ignores cached setting |
1546 |
| - break; |
1547 |
| - } |
1548 |
| - $.Widget.prototype._setOption.apply(this, arguments); |
1549 |
| - }, |
| 1539 | + case 'position': |
| 1540 | + if (value !== null && !$.isEmptyObject(value) ) { |
| 1541 | + this.options.position = value; |
| 1542 | + } |
| 1543 | + this.position(); |
| 1544 | + break; |
| 1545 | + case 'zIndex': |
| 1546 | + this.options.zIndex = value; |
| 1547 | + this.$menu.css('z-index', value); |
| 1548 | + break; |
| 1549 | + } |
| 1550 | + $.Widget.prototype._setOption.apply(this, arguments); |
| 1551 | + }, |
1550 | 1552 |
|
1551 | 1553 | });
|
1552 | 1554 |
|
| 1555 | + // Fix for jQuery UI modal dialogs |
| 1556 | + // https://api.jqueryui.com/dialog/#method-_allowInteraction |
| 1557 | + // https://learn.jquery.com/jquery-ui/widget-factory/extending-widgets/ |
| 1558 | + if ($.ui && $.ui.dialog) { |
| 1559 | + $.widget( "ui.dialog", $.ui.dialog, { |
| 1560 | + _allowInteraction: function( event ) { |
| 1561 | + if ( this._super( event ) || $( event.target ).closest('.ui-multiselect-menu' ).length ) { |
| 1562 | + return true; |
| 1563 | + } |
| 1564 | + } |
| 1565 | + }); |
| 1566 | + } |
| 1567 | + |
1553 | 1568 | })(jQuery);
|
0 commit comments