This is an automated email from the ASF dual-hosted git repository. heneveld pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brooklyn-ui.git
commit 17bc3ee4e134a9a1ca0c27e940bf70fcbbedaf3f Author: Alex Heneveld <[email protected]> AuthorDate: Wed Oct 5 13:54:31 2022 +0100 exact copy of uib dropdown.js, to allow for nested dropdowns --- .../inspect/activities/detail/dropdown-nested.js | 440 +++++++++++++++++++++ 1 file changed, 440 insertions(+) diff --git a/ui-modules/app-inspector/app/views/main/inspect/activities/detail/dropdown-nested.js b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/dropdown-nested.js new file mode 100644 index 00000000..cbfe420f --- /dev/null +++ b/ui-modules/app-inspector/app/views/main/inspect/activities/detail/dropdown-nested.js @@ -0,0 +1,440 @@ +angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position']) + + .constant('uibDropdownConfig', { + appendToOpenClass: 'uib-dropdown-open', + openClass: 'open' + }) + + .service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) { + var openScope = null; + var openedContainers = $$multiMap.createNew(); + + this.isOnlyOpen = function(dropdownScope, appendTo) { + var openedDropdowns = openedContainers.get(appendTo); + if (openedDropdowns) { + var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) { + if (dropdown.scope === dropdownScope) { + return dropdown; + } + + return toClose; + }, {}); + if (openDropdown) { + return openedDropdowns.length === 1; + } + } + + return false; + }; + + this.open = function(dropdownScope, element, appendTo) { + if (!openScope) { + $document.on('click', closeDropdown); + } + + if (openScope && openScope !== dropdownScope) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + + if (!appendTo) { + return; + } + + var openedDropdowns = openedContainers.get(appendTo); + if (openedDropdowns) { + var openedScopes = openedDropdowns.map(function(dropdown) { + return dropdown.scope; + }); + if (openedScopes.indexOf(dropdownScope) === -1) { + openedContainers.put(appendTo, { + scope: dropdownScope + }); + } + } else { + openedContainers.put(appendTo, { + scope: dropdownScope + }); + } + }; + + this.close = function(dropdownScope, element, appendTo) { + if (openScope === dropdownScope) { + $document.off('click', closeDropdown); + $document.off('keydown', this.keybindFilter); + openScope = null; + } + + if (!appendTo) { + return; + } + + var openedDropdowns = openedContainers.get(appendTo); + if (openedDropdowns) { + var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) { + if (dropdown.scope === dropdownScope) { + return dropdown; + } + + return toClose; + }, {}); + if (dropdownToClose) { + openedContainers.remove(appendTo, dropdownToClose); + } + } + }; + + var closeDropdown = function(evt) { + // This method may still be called during the same mouse event that + // unbound this event handler. So check openScope before proceeding. + if (!openScope || !openScope.isOpen) { return; } + + if (evt && openScope.getAutoClose() === 'disabled') { return; } + + if (evt && evt.which === 3) { return; } + + var toggleElement = openScope.getToggleElement(); + if (evt && toggleElement && toggleElement[0].contains(evt.target)) { + return; + } + + var dropdownElement = openScope.getDropdownElement(); + if (evt && openScope.getAutoClose() === 'outsideClick' && + dropdownElement && dropdownElement[0].contains(evt.target)) { + return; + } + + openScope.focusToggleElement(); + openScope.isOpen = false; + + if (!$rootScope.$$phase) { + openScope.$apply(); + } + }; + + this.keybindFilter = function(evt) { + if (!openScope) { + // see this.close as ESC could have been pressed which kills the scope so we can not proceed + return; + } + + var dropdownElement = openScope.getDropdownElement(); + var toggleElement = openScope.getToggleElement(); + var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target); + var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target); + if (evt.which === 27) { + evt.stopPropagation(); + openScope.focusToggleElement(); + closeDropdown(); + } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) { + evt.preventDefault(); + evt.stopPropagation(); + openScope.focusDropdownEntry(evt.which); + } + }; + }]) + + .controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + templateScope, + appendToOpenClass = dropdownConfig.appendToOpenClass, + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, + keynavEnabled = false, + selectedOption = null, + body = $document.find('body'); + + $element.addClass('dropdown'); + + this.init = function() { + if ($attrs.isOpen) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + + keynavEnabled = angular.isDefined($attrs.keyboardNav); + }; + + this.toggle = function(open) { + scope.isOpen = arguments.length ? !!open : !scope.isOpen; + if (angular.isFunction(setIsOpen)) { + setIsOpen(scope, scope.isOpen); + } + + return scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.getAutoClose = function() { + return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' + }; + + scope.getElement = function() { + return $element; + }; + + scope.isKeynavEnabled = function() { + return keynavEnabled; + }; + + scope.focusDropdownEntry = function(keyCode) { + var elems = self.dropdownMenu ? //If append to body is used. + angular.element(self.dropdownMenu).find('a') : + $element.find('ul').eq(0).find('a'); + + switch (keyCode) { + case 40: { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = 0; + } else { + self.selectedOption = self.selectedOption === elems.length - 1 ? + self.selectedOption : + self.selectedOption + 1; + } + break; + } + case 38: { + if (!angular.isNumber(self.selectedOption)) { + self.selectedOption = elems.length - 1; + } else { + self.selectedOption = self.selectedOption === 0 ? + 0 : self.selectedOption - 1; + } + break; + } + } + elems[self.selectedOption].focus(); + }; + + scope.getDropdownElement = function() { + return self.dropdownMenu; + }; + + scope.focusToggleElement = function() { + if (self.toggleElement) { + self.toggleElement[0].focus(); + } + }; + + function removeDropdownMenu() { + $element.append(self.dropdownMenu); + } + + scope.$watch('isOpen', function(isOpen, wasOpen) { + var appendTo = null, + appendToBody = false; + + if (angular.isDefined($attrs.dropdownAppendTo)) { + var appendToEl = $parse($attrs.dropdownAppendTo)(scope); + if (appendToEl) { + appendTo = angular.element(appendToEl); + } + } + + if (angular.isDefined($attrs.dropdownAppendToBody)) { + var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope); + if (appendToBodyValue !== false) { + appendToBody = true; + } + } + + if (appendToBody && !appendTo) { + appendTo = body; + } + + if (appendTo && self.dropdownMenu) { + if (isOpen) { + appendTo.append(self.dropdownMenu); + $element.on('$destroy', removeDropdownMenu); + } else { + $element.off('$destroy', removeDropdownMenu); + removeDropdownMenu(); + } + } + + if (appendTo && self.dropdownMenu) { + var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true), + css, + rightalign, + scrollbarPadding, + scrollbarWidth = 0; + + css = { + top: pos.top + 'px', + display: isOpen ? 'block' : 'none' + }; + + rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); + if (!rightalign) { + css.left = pos.left + 'px'; + css.right = 'auto'; + } else { + css.left = 'auto'; + scrollbarPadding = $position.scrollbarPadding(appendTo); + + if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { + scrollbarWidth = scrollbarPadding.scrollbarWidth; + } + + css.right = window.innerWidth - scrollbarWidth - + (pos.left + $element.prop('offsetWidth')) + 'px'; + } + + // Need to adjust our positioning to be relative to the appendTo container + // if it's not the body element + if (!appendToBody) { + var appendOffset = $position.offset(appendTo); + + css.top = pos.top - appendOffset.top + 'px'; + + if (!rightalign) { + css.left = pos.left - appendOffset.left + 'px'; + } else { + css.right = window.innerWidth - + (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px'; + } + } + + self.dropdownMenu.css(css); + } + + var openContainer = appendTo ? appendTo : $element; + var dropdownOpenClass = appendTo ? appendToOpenClass : openClass; + var hasOpenClass = openContainer.hasClass(dropdownOpenClass); + var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo); + + if (hasOpenClass === !isOpen) { + var toggleClass; + if (appendTo) { + toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass'; + } else { + toggleClass = isOpen ? 'addClass' : 'removeClass'; + } + $animate[toggleClass](openContainer, dropdownOpenClass).then(function() { + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + } + + if (isOpen) { + if (self.dropdownMenuTemplateUrl) { + $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { + templateScope = scope.$new(); + $compile(tplContent.trim())(templateScope, function(dropdownElement) { + var newEl = dropdownElement; + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + $document.on('keydown', uibDropdownService.keybindFilter); + }); + }); + } else { + $document.on('keydown', uibDropdownService.keybindFilter); + } + + scope.focusToggleElement(); + uibDropdownService.open(scope, $element, appendTo); + } else { + uibDropdownService.close(scope, $element, appendTo); + if (self.dropdownMenuTemplateUrl) { + if (templateScope) { + templateScope.$destroy(); + } + var newEl = angular.element('<ul class="dropdown-menu"></ul>'); + self.dropdownMenu.replaceWith(newEl); + self.dropdownMenu = newEl; + } + + self.selectedOption = null; + } + + if (angular.isFunction(setIsOpen)) { + setIsOpen($scope, isOpen); + } + }); + }]) + + .directive('uibDropdown', function() { + return { + controller: 'UibDropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init(); + } + }; + }) + + .directive('uibDropdownMenu', function() { + return { + restrict: 'A', + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { + return; + } + + element.addClass('dropdown-menu'); + + var tplUrl = attrs.templateUrl; + if (tplUrl) { + dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; + } + + if (!dropdownCtrl.dropdownMenu) { + dropdownCtrl.dropdownMenu = element; + } + } + }; + }) + + .directive('uibDropdownToggle', function() { + return { + require: '?^uibDropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if (!dropdownCtrl) { + return; + } + + element.addClass('dropdown-toggle'); + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if (!element.hasClass('disabled') && !attrs.disabled) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.on('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function(isOpen) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.off('click', toggleDropdown); + }); + } + }; + });
