I've been working with Apigee for the last few months, building out an API portal for a client. Initially we were going to build it with Drupal 8, but Apigee had just released it's new Integrated Portal, so the client decided on that option to avoid managing extra hosting for Drupal, maintenance, etc. The system is pretty straight-forward, albeit limited in what it can do: you create pages in the UI (much like NetlifyCMS) and you have a textarea for SASS variables and another for regular SASS. The default theme is Material Angular and site is using Angular as the front-end. So basically, my job was to theme it out with SASS, and create a bunch of pages in Markdown and/or HTML.

I finally got everything looking great and even working in IE11. Thankfully only a few changes were needed for IE, and I used the following CSS hack that only IE10+ recognizes (this code uses a background image for the user profile instead of the default Material Icons font option):

@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {  
  width: 50px;
  background: url(/files/account_circle_18px-for-IE.svg);
  background-repeat: no-repeat;
  background-position: 50% 50%;
}

OK, so on to the Javascript. The client wanted to highlight the active menu item, but for some odd reason, the Angular app doesn't identify the active menu item, so I needed some custom code for that. Again, the Apigee admin UI has a textarea where you can paste in some script code, and it gets output before the opening body tag. There is currently no option for output before the closing body tag.

I quickly discovered that the script I was trying to use didn't work. It couldn't see the DOM it was supposed to work on. Adding in a setTimeout() call, I could get it to work, but that seemed ugly. Then I did what I should have done first: read the documentation. And, of course, there is a special way to get scripts to run once the DOM is actually available (being an Angular app, the DOM gets loaded later, and even if you look at the page source afterwards, it doesn't show the real DOM - you have to use Inspect to see it).

So here is the final script I used (after some reading and a couple of iterations...ok, maybe more than just a couple):

window.portal = {};
window.portal.pageEventListeners = {
  onLoad: (path) => {
    // Add active class to the selected header menu item.
    var navItems = document.querySelectorAll('.nav-header .menu-item');

    for (let i = 0; i < navItems.length; i++) {
       if (path !== navItems[i].getAttribute('href')) {
        navItems[i].classList.remove('active');
      }
      else {
        navItems[i].classList.add('active');
      }
    }
  },
}

The first 3 lines are the critical ones. And isn't it nice you get the path right there as part of onLoad?

The script waits for the DOM to be loaded, then finds all the header menu items. It iterates through each one, removing the active class from the non-current items, until it finds the menu item with the matching path, where it adds in the active class. The ordering might seem odd - why not check for a match first, then remove in the else statement - but doing it that way left active classes on previously selected menu items.

So now the client gets their active menu item styled correctly, with a minimum of fuss.