marmelab / ng-admin

Add an AngularJS admin GUI to any RESTful API
http://ng-admin-book.marmelab.com/
MIT License
3.95k stars 725 forks source link

Left panel menu items maximises and minimises on page reload and menu selection #1347

Open ashwanikumar415 opened 7 years ago

ashwanikumar415 commented 7 years ago

Left panel menu minimises and maximises always for a fraction of second every time when page is refreshed or any menu item is selected plunkr : http://plnkr.co/edit/yTMTofsjfw4XT4EOzint?p=preview

Note: To see issue occuring (especially page refresh case) download the zip from plunkr and run the index.html page individually GIF :

leftpanel_min_max_issue

Expected behavior: Only already opened menu item should remain open with no flickering

Actual behavior: 8/10 times flicker happens, gets worse when the menu list is lengthy

freemember007 commented 7 years ago

how about this issue? i undergo a same question.

ashwanikumar415 commented 7 years ago

Resolved this issue by writing a decorator for directive maMenubar (https://github.com/marmelab/ng-admin/blob/master/src/javascripts/ng-admin/Main/component/directive/maMenuBar.js)

If you observe in maMenubar.js, whenever "locationChangeSuccess" event is fired render() is invoked which causes the whole left menu to be redrawn again. That causes the flicker. So one of the fixes could be calling a method like updateLeftMenuStyling() which would just highlight the activeMenu item and close the other menu items (if at all some of them are opened) using javascript instead of render(). like below `var listener = $rootScope.$on('$locationChangeSuccess', function() {

        scope.path = $location.path();
        updateLeftMenuStyling(activeMenu, activeParentMenu);
        //render();
    });`

    function updateLeftMenuStyling(menu, parentMenu) {
      var elements = getElementsForMenu(menu);
      elements.allUlElements.addClass('collapsed');
      elements.allArrowElements.removeClass('glyphicon-menu-down');
      elements.allArrowElements.addClass('glyphicon-menu-right');
      if (parentMenu) {
        openMenu(parentMenu);
      }
      elements.allAnchorTags.removeClass('active');
      if(menu.link()) { 
        elements.anchorTag.addClass('active');
      }
      elements.anchorTag.blur();
    }

  function getElementsForMenu(menu) {
    let parentLi;
    angular.forEach(element.find('li'), function(li) {
        const liElement = angular.element(li);
        if (liElement.attr('data-menu-id')  === (menu.uuid).toString()) {
            parentLi = liElement;
        }
    });
    const anchorTag = (parentLi) ? parentLi.find('a')[0] : null;
    const ulElements = (parentLi) ? parentLi.find('ul') : null;
    const arrowElement = (anchorTag) ? anchorTag.getElementsByClassName('arrow')[0] : {};
    return {
      arrow: angular.element(arrowElement),
      allArrowElements: angular.element(element.find('span.arrow')),
      ul: (ulElements) ? ulElements.eq(0) : {},
      anchorTag: angular.element(anchorTag),
      allAnchorTags: angular.element(element.find('a')),
      allUlElements: angular.element(element.find('ul'))
    };
  }`

` scope.activateLink = function (menu, parentMenu) {

        activeMenu = menu;
        activeParentMenu = (parentMenu) ? parentMenu : null;
        if (!menu.link()) {
            return;
        }
        if (menu.autoClose()) {
            openMenus = [];
        }
    };`
<div class="navbar-default sidebar" role="navigation" compile="menu.template()">
    <div class="sidebar-nav navbar-collapse collapse" uib-collapse="$parent.isCollapsed">
        <ul class="nav" id="side-menu">
            <li class="entities-repeat" ng-repeat="(key, firstLevelMenu) in ::menu.children()" data-menu-id="{{ ::firstLevelMenu.uuid }}" compile="firstLevelMenu.template()">
                <a ng-if="::firstLevelMenu.hasChild()" ng-click="toggleMenu(firstLevelMenu)" ng-class="::{'active': firstLevelMenu.isActive(path)}">
                    <span compile="::firstLevelMenu.icon()"><span class="glyphicon glyphicon-list"></span></span>

                    {{ firstLevelMenu.title() | translate }}
                    <span class="glyphicon arrow" ng-class="::{'glyphicon-menu-down': isOpen(firstLevelMenu), 'glyphicon-menu-right': !isOpen(firstLevelMenu) }"></span>

                </a>
                <a ng-if="::!firstLevelMenu.hasChild()" href="#{{ firstLevelMenu.link() }}" ng-click="activateLink(firstLevelMenu)" ng-class="::{'active': firstLevelMenu.isActive(path)}">
                    <span compile="::firstLevelMenu.icon()"><span class="glyphicon glyphicon-list"></span></span>

                    {{ firstLevelMenu.title() | translate }}

                </a>
                <ul ng-if="::firstLevelMenu.hasChild()" class="nav nav-second-level collapsible" data-menu-id="{{ ::firstLevelMenu.uuid }}" ng-class="::{'collapsed': !isOpen(firstLevelMenu) }">
                    <li ng-repeat="secondLevelMenu in ::firstLevelMenu.children()" data-menu-id="{{ ::secondLevelMenu.uuid }}" compile="secondLevelMenu.template()">
                        <a href="#{{secondLevelMenu.link()}}" ng-click="activateLink(secondLevelMenu, firstLevelMenu)" ng-class="::{'active': secondLevelMenu.isActive(path)}">
                            <span compile="::secondLevelMenu.icon()"><span class="glyphicon glyphicon-list"></span></span>
                            {{ secondLevelMenu.title() | translate }}
                        </a>
                    </li>
                </ul>
            </li>
        </ul>
    </div>
</div>

If you use any comparator , you will observe the only difference between menubar.html ( https://github.com/marmelab/ng-admin/blob/master/src/javascripts/ng-admin/Main/view/menuBar.html) and the customizedMenubar.html is

ng-click="activateLink(secondLevelMenu, firstLevelMenu)

And some renaming of iterator variables in ng-repeat from menu to firstLevelMenu and SecondLevelMenu.

Note: The above solution will not highlight the relevant menu item when a user changes the address bar to point to some other menu item. To fix that change locationChangeSuccess listener to make it capable to get activeMenu item either based on address bar of browser or from scope.activateLink(this was already done above )

` var listener = $rootScope.$on('$locationChangeSuccess', function() {

      scope.path = $location.path();
      if (!activeMenu) {
        const menuObj = Utils.getActiveAndParentMenuByLocation(scope.menu.children(), scope.path);
        activeMenu = menuObj.activeMenu;
        activeParentMenu = menuObj.activeParentMenu;
      }
      updateLeftMenuStyling(activeMenu , activeParentMenu);
      activeMenu = null;
      activeParentMenu = null;
    });

` where Utils.getActiveAndParentMenuByLocation() method will use Array.find() to get activeMenu and activeParentMenu object by comparing location.path === menu['_link']

Thanks for coming this far and reading it :)

edgarmarkosov commented 7 years ago

Any fix for this issue?

ashwanikumar415 commented 7 years ago

Easiest workaround for this issue not to reproduce is setting autoClose(false) like below

var master = nga.menu(); master.autoClose(false);

Assuming master holds all the other submenu objects

edgarmarkosov commented 7 years ago

But what if I want to use auto close option. I used your previous solution in ng-admin source, it worked. but I wanted to know if there was plan to solve this issue in the original ng-admin repository.

vuquangtam commented 4 years ago

Above solutions don't work for me. Found out my solution for this, add this css

.collapsed  {
    height: 0;
}