Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Make dropdown menu go upwards if it is at the bottom of the browser window #1317

Closed
michaelhunziker opened this issue Nov 27, 2013 · 11 comments

Comments

@michaelhunziker
Copy link

Dear all!

We're having a little issue with dropdowns. We're using dropdown menus tables (context menus). As you can see in the fiddle below, the dropdown menus of the rows at the bottom of the browser window are not completely visible (you would have to use your scrolling wheel to see the whole menu).

http://jsfiddle.net/mhu23/YZx7R/22/

What I learned from bootstrap was that you can use the class "dropup" to open the menus upwards (see code comment in fiddle). But now I'm stuck implementing an intelligent solution. Should I create a directive which dynamically adds the class dropup? But how do I decide if the menu is "rendered" outside the view scope?

Thanks a lot for your help!

Michael

@caitp
Copy link
Contributor

caitp commented Nov 27, 2013

Maybe (similar to tooltips) it would be good to have an optional "where" option which defaults to down, but could be overridden?

@michaelhunziker
Copy link
Author

You're talking about the "tooltip-placement" attribute right?

The problem is this is still pretty static... What I'd like to implement is a solution which dynamically decides whether the dropdown is rendered upwards or downwards. But I don't know how to do this.

@jfairley
Copy link

The "dynamic decision" solution sounds nice, but I'm going to guess it's complicated, since this issue is quite dormant. Too bad there is still no solution to this. 😦

@rvanbaalen
Copy link
Contributor

Related: #1012, #375

gorork added a commit to gorork/bootstrap that referenced this issue May 23, 2015
Makes dropdown menu go upwards if it's at the bottom of the browser window.
Moves DOM logic to directive.

CLoses angular-ui#1317
@rysilva
Copy link
Contributor

rysilva commented Jun 19, 2015

Could this be fixed by modifying the $position service to behave more like the jquery-ui position, which takes the viewport into account? http://api.jqueryui.com/position/

Somewhat related to #3820 -- I could fix that by changing the $position call to look for dropdown-menu-right and position accordingly, but if $position was smarter that would take care of the dropup behavior as well.

gorork added a commit to gorork/bootstrap that referenced this issue Oct 4, 2015
Makes dropdown menu go upwards if it's at the bottom of the browser window.
Moves DOM logic to directive.

CLoses angular-ui#1317
@Ellesedil
Copy link

There's definitely a usability problem with dropdowns that drop down at the bottom of the page or drop up at the top of the page. I'm in a situation where I can't easily just arbitrarily pick up or down because I have a set of dropdowns that could be found at the top of the browser or the bottom, depending on what other data is loaded (the quantity of which will always be variable). So, functionality that could make an intelligent decision on that would be really useful.

@Ellesedil
Copy link

I had to have this functionality in order to have a good user experience with dropdowns that could be located all over the window. So I created a controller to handle this.

http://plnkr.co/edit/wzrfiP0vsvgkg3McUvh0?p=preview

Requires jQuery in order to find the child ul that contains all of the dropdown menu options.

When a dropdown hooked into the DropupController is clicked, the toggle method will check the dropdown's position in the window, the height of the dropdown container div, and the height of the dropdown menu ul element. It will also determine the window height.

Then it calculates if there's room to drop up and if there's room to drop down. If (yes and no), then it will drop up. Else, it will drop down.

This can be plugged into any dropdown on a page and each dropdown will act independently of each other. So, some long dropdowns could drop up while other shorter dropdowns on the same row could drop down. Each dropdown needs the DropupController initialized in it's parent container - div, td, whatever. If there's 4 dropdowns on a page, then you need to initialize DropupController 4 times.

I spent way too much time creating this functionality, so I'm sharing it to hopefully alleviate that time sink for others. If someone could figure out how to break the dependency on jQuery, that'd be great. I don't mind, since I'm using jQuery in my applications anyway.

@icfantv
Copy link
Contributor

icfantv commented Oct 28, 2015

Closing in favor of #4762.

@icfantv icfantv closed this as completed Oct 28, 2015
@rijosh
Copy link

rijosh commented Jan 28, 2017

I want dropdown menu position dynamic. i mean based on window position. if there is no space in right it should move automatically left. same as top and bottom. Any one please help me. please provide fiddle or codepen link...

@abdelrady
Copy link

@Ellesedil reused your code in "scope.$watch('isOpen', function (isOpen, wasOpen) {" part and tweaked it a little bit it to fix this issue temporarily (drop-up).

var dropdownContainer = $element[0]; var position = dropdownContainer.getBoundingClientRect().top; var buttonHeight = dropdownContainer.getBoundingClientRect().height; var dropdownMenu = $(self.dropdownMenu); var menuHeight = dropdownMenu.outerHeight(); if (position > menuHeight && $($window).height() - position < buttonHeight + menuHeight) { css.top = pos.top - (buttonHeight + menuHeight) + 'px'; }

here is full section

`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';
            }
        }

        var dropdownContainer = $element[0];
        var position = dropdownContainer.getBoundingClientRect().top;
        var buttonHeight = dropdownContainer.getBoundingClientRect().height;
        var dropdownMenu = $(self.dropdownMenu);
        var menuHeight = dropdownMenu.outerHeight();
        if (position > menuHeight && $($window).height() - position < buttonHeight + menuHeight) {
            css.top = pos.top - (buttonHeight + menuHeight) + '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);
    }
});`

a snapshot is here
https://www.screencast.com/t/5X5Uub9K

Note: I'm using ui-bootstrap-tpls-1.3.2 version.
Thought this may help someone till a full fix/feature is provided to support this.

@fadak
Copy link

fadak commented Aug 22, 2017

I managed it in dropdown's button onclick event to manage horizontal aligning :

<div class="btn-group" uib-dropdown>
<button id="export-button" type="button" class="btn btn-default" uib-dropdown-toggle
onclick="$(this).offset().left <200 ? $(this).next().addClass('dropdown-menu-left').removeClass('dropdown-menu-right') : $(this).next().addClass('dropdown-menu-right').removeClass('dropdown-menu-left')">

<span class="fa fa-lg fa-file-word-o"></span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" uib-dropdown-menu ...

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.