This topic contains 6 replies, has 3 voices, and was last updated by Sean Sean 2 years, 2 months ago.

Issue with handleMainMenuActiveLink when using ui-sref


  • eggersa
    Participant

    Posts: 23
    Member Reply #6607

    Hello

    I am using the Admin 3 Template 4.5.4 with AngularJS. Within the file layout3\scripts\layout.js there is a method handleMainMenuActiveLink which is obviously responsible for highlighting the active menu. The method looks for the active menu by querying the current browser location and parsing that location:

    var handleMainMenuActiveLink = function(mode, el) {
    var url = location.hash.toLowerCase();
    ...

    However, since I am using AngularJS, I am also using states and have to use the ng-sref attribut within the anchor tags of the menus:

    <_a ui-sref="customers">Customers</_a>

    _a required to stop the board from parsing the a tag

    However, the handleMainMenuActiveLink executes before the browser location changes and highlights the menu item of the previous page (since its using the old url).

    How can I fix that issue?

     

    • This topic was modified 2 years, 5 months ago by  eggersa.
    • This topic was modified 2 years, 5 months ago by  eggersa.
    • This topic was modified 2 years, 5 months ago by  eggersa.
    • This topic was modified 2 years, 5 months ago by  eggersa.
    • This topic was modified 2 years, 5 months ago by  eggersa.
    • This topic was modified 2 years, 5 months ago by  eggersa.

    eggersa
    Participant

    Posts: 23
    Member Reply #6628

    After another round of trying to fix the error I found the actual problem for the issue which resides in the ngSpinnerBar directive. The handleMainMenuActiveLink is invoked (via setMainMenuActiveLink) in the $stateChangeSuccess event fired by AngularJs after a state has changed successfully:

    $rootScope.$on('$stateChangeSuccess', function () {
    	element.addClass('hide'); // hide spinner bar
    	$('body').removeClass('page-on-load'); 
    	Layout.setMainMenuActiveLink('match'); 
    	setTimeout(function () {
    		App.scrollTop();
    	}, $rootScope.settings.layout.pageAutoScrollOnLoad);
    });

    However, handleMainMenuActiveLink accesses the hash of the current url by leveraging JavaScrpts native location object, which is updated by AngularJs at a later point in the workflow. Luckily, Angulars wrapper service $location is at this stage up to date. To finally solve the issue with the most least code modification I came up with the following solution:

    // ...
    // quirks to fix issue with active menus.
    location.hash = '#' + $location.path(); 
    Layout.setMainMenuActiveLink('match');
    // ...

    Maybe there is a better solution for this problem that does not involve modifying metronics code?

    • This reply was modified 2 years, 5 months ago by  eggersa.
    • This reply was modified 2 years, 5 months ago by  eggersa.
    • This reply was modified 2 years, 5 months ago by  eggersa.
    Sean
    Sean
    Keymaster

    Posts: 4527
    Support Staff Reply #6650

    Hi :),

    Thanks for sharing this. Looks a promising. We will check this further and use it in the next update with Angular2 version.
    Stay tuned!

    Thanks.


    volosoft
    Participant

    Posts: 22
    Member Reply #7534

    Hi,

    We had a similar issue with admin4 and angularjs. We have solved the issue by following the steps below.

    It will be very nice if you apply those changes to next version of metronic with angular 1.x if there will be one.

    1) changed initSidebar in layout.js in metronic/assets/admin/layout4/scripts to

    initSidebar: function ($state) {

        //layout handlers

        handleFixedSidebar(); // handles fixed sidebar menu

        handleSidebarMenu(); // handles main menu

        handleSidebarToggler(); // handles sidebar hide/show

     

    if (App.isAngularJsApp()) {

    handleSidebarMenuActiveLink(‘match’, null, $state); // init sidebar active links

    }

     

    App.addResizeHandler(handleFixedSidebar); // reinitialize fixed sidebar on window resize

    }

     

    2) and changed handleSidebarMenuActiveLink  to

    var handleSidebarMenuActiveLink = function (mode, el, $state) {

            var url = location.hash.toLowerCase();

     

            var menu = $(‘.page-sidebar-menu’);

     

            if (mode === ‘click’ || mode === ‘set’) {

                el = $(el);

            } else if (mode === ‘match’) {

                menu.find(“li > a”).each(function () {

                    //ADJUST USING $STATE IN METRONIC

                    var state = $(this).attr(‘ui-sref’);

                    if ($state && state) {

                        if ($state.is(state)) {

                            el = $(this);

                            return;

                        }

                    } else {

                        var path = $(this).attr(“href”).toLowerCase();

                        // url match condition         

                        if (path.length > 1 && url.substr(1, path.length – 1) == path.substr(1)) {

                            el = $(this);

                            return;

                        }

                    }

                });

            }

     

            if (!el || el.size() == 0) {

                return;

            }

     

            if (el.attr(‘href’).toLowerCase() === ‘javascript:;’ || el.attr(‘href’).toLowerCase() === ‘#’) {

                return;

            }

     

            var slideSpeed = parseInt(menu.data(“slide-speed”));

            var keepExpand = menu.data(“keep-expanded”);

     

            // begin: handle active state

            if (menu.hasClass(‘page-sidebar-menu-hover-submenu’) === false) {

                menu.find(‘li.nav-item.open’).each(function () {

                    var match = false;

                    $(this).find(‘li’).each(function () {

                        if ($(this).find(‘ > a’).attr(‘href’) === el.attr(‘href’)) {

                            match = true;

                            return;

                        }

                    });

     

                    if (match === true) {

                        return;

                    }

     

                    $(this).removeClass(‘open’);

                    $(this).find(‘> a > .arrow.open’).removeClass(‘open’);

                    $(this).find(‘> .sub-menu’).slideUp();

                });

            } else {

                menu.find(‘li.open’).removeClass(‘open’);

            }

     

            menu.find(‘li.active’).removeClass(‘active’);

            menu.find(‘li > a > .selected’).remove();

            // end: handle active state

     

            el.parents(‘li’).each(function () {

                $(this).addClass(‘active’);

                $(this).find(‘> a > span.arrow’).addClass(‘open’);

     

                if ($(this).parent(‘ul.page-sidebar-menu’).size() === 1) {

                    $(this).find(‘> a’).append(‘<span class=”selected”></span>’);

                }

     

                if ($(this).children(‘ul.sub-menu’).size() === 1) {

                    $(this).addClass(‘open’);

                }

            });

     

            if (mode === ‘click’) {

                if (App.getViewPort().width < resBreakpointMd && $(‘.page-sidebar’).hasClass(“in”)) { // close the menu on mobile view while laoding a page 

                    $(‘.page-header .responsive-toggler’).click();

                }

            }

        };

    3) When calling Layout.initSidebar from angular controller, we injected $state to controller and passed that to initSidebar like this.

    Layout.initSidebar($state);

    Sean
    Sean
    Keymaster

    Posts: 4527
    Support Staff Reply #7540

    Hi :),

    Thanks for sharing this solution. We will make sure it will be added in the next update very soon.

    Thanks.


    volosoft
    Participant

    Posts: 22
    Member Reply #8003

    Hi,

     

    Thank you for including this one into latest release v4.7.

    After my tests, if have found a  little bug which is my fault. Can you also change that ?

    When we are calling Layout.setAngularJsSidebarMenuActiveLink(‘match’, null, $state); in directives.js, instead of $state we should use event.currentScope.$state and we should get event as the first parameter for $stateChangeSuccess event.

    Final version of $stateChangeSuccess event should look like this. Can you also make this change after your tests.


    // hide the spinner bar on rounte change success(after the content loaded)
    $rootScope.$on('$stateChangeSuccess', function (event) {
    element.addClass('hide'); // hide spinner bar
    $('body').removeClass('page-on-load'); // remove page loading indicator
    Layout.setAngularJsSidebarMenuActiveLink('match', null, event.currentScope.$state); // activate selected link in the sidebar menu

    // auto scorll to page top
    setTimeout(function () {
    App.scrollTop(); // scroll to the top on content load
    }, $rootScope.settings.layout.pageAutoScrollOnLoad);
    });

    Thanks a lot.

    • This reply was modified 2 years, 2 months ago by  volosoft.
    • This reply was modified 2 years, 2 months ago by  volosoft.
    • This reply was modified 2 years, 2 months ago by  volosoft.
    • This reply was modified 2 years, 2 months ago by  volosoft.
    Sean
    Sean
    Keymaster

    Posts: 4527
    Support Staff Reply #8016

    Hi :),

    We just submitted v4.7.1 and seems we missed this fix.
    But we will add it in the next quick update v4.7.2 very soon.

    Many thanks for your contribution.

You must be logged in to reply to this topic.