19

I have a layout Jade view that has a menu via unordered list, and I want to set the <li> to be <li class="active">...</li> when the current page is rendered in the browser.

I assume I will have to access the current request to determine when to set the attribute on the <li>

I can't find any examples of how to do this so hoping someone can help

Thanks

Jon
  • 38,814
  • 81
  • 233
  • 382

5 Answers5

38

Try this before your call res.render() in your route:

res.locals.path = req.path;
res.render('/page');

or

res.render('/page', { path: req.path });

Then you would have to do a bunch of if/else statements in your view (as the above solution suggests).

- if(currentUrl === '/')
    li(class='active')
        a(href='/') Current Driver Standings
- else
    li
        a(href='/') Current Driver Standings

I however, prefer to do this on client side instead, to keep my template files free from as much logic as possible:

In page template file (this is ejs, not sure how to echo in jade):

<body data-path="<%= path %>">

Then with jQuery you can grab the path from body and attach an active class:

$(function(){
    var path = $('body').attr('data-path');
    $('nav li a[href='+path+']').parents('li').addClass('active');
});

Update: You can also just use var path = window.location.pathname instead of saving it to an attribute on body

//no need to save path to <body> tag first:

$(function(){
    var path = window.location.pathname;
    $('nav li a[href='+path+']').parents('li').addClass('active');
});
chovy
  • 72,281
  • 52
  • 227
  • 295
  • 1
    The only thing I would say about it, is that you are relying on the user having JS (not a huge concern these days) but you may end up with other things existing as attributes for some reason and a lot of ugly markup produced – Jon Oct 11 '12 at 22:05
  • Also is there a typo in your example? Is req.path available in EJS? Shouldn't it be the property of your view model? – Jon Oct 11 '12 at 22:11
  • sorry, in my routes I do res.locals.req = req so I have access to all that info. But I just realized you can also just use window.location.pathname instead of saving it on body tag. – chovy Oct 11 '12 at 23:51
  • Oh yeah! Thanks a lot :) Have you a link about locals? – Jon Oct 12 '12 at 07:29
  • you can also use app.locals for global stuff you want to expose to all templates. res.locals is just for the route. – chovy Oct 12 '12 at 08:22
  • @Jon -- i don't worry about sans-javascript these days. I think accessibility is a dying concern infavor of mobile. – chovy Oct 12 '12 at 08:23
  • Thanks for the tip. The Jade snippet would be the follows: script $(function(){ var path = $('body').attr('data-path'); $('ul li a[href=\"#{path}\"]').parents('li').addClass('active'); }); – BloodAxe Oct 10 '13 at 08:50
  • Depending on your path, you may want to add quotes around the jQuery selector to make this work: $('nav li a[href="'+path+'"]').parents('li').addClass('active'); – Kelvin Feb 26 '14 at 16:30
8

Here's a much neater way of doing it, server-side:

In your routes.js (or wherever) define an array of objects representing your nav like such:

var navLinks = [
  { label: 'Home', key: 'home', path: '' },
  { label: 'About', key: 'about', path: '/about' },
  { label: 'Contact', key: 'contact', path: '/contact' }
]

Pass the navLinks variable to your view, as well as the key of the item you'd like hilighted:

res.render('home', { title: 'Welcome!', section: 'home', navLinks: navLinks });

You can also add the navLinks variable to app.locals and save yourself always having to provide it explicitly to views.

Then in your jade template loop through the array of links and set the active class on the one whose key matches the provided section:

ul(class='nav nav-list')
  - navLinks.forEach(function(link){
    - var isActive = (link.key == section ? 'active' : '')
    li(class=isActive)
      a(href=link.path)= link.label
  - })
Jed Watson
  • 20,150
  • 3
  • 33
  • 43
  • Nice one. It's a while since it was posted but it's still a good idea. I'm still new to Pug templates so for now I just concatenate another string to isActive since I'm using a Bootstrap navbar. – Kioshiki Sep 20 '18 at 20:37
2

Pass the req.originalUrl in your routes file. example: in your /routes/about.js

router.get('/', function(req, res) {
 res.render('about', { 
  url: req.originalUrl
 });
});

Then, write if else condition on your jade template

    if(url==='/about-us')
     li(class='active')
      a(href='about-us') About Us
    else
     li
      a(href='about-us') About Us
Jur P
  • 103
  • 6
2

you can use global variables in app.js like :

// Global vars
app.use( function ( req, res, next ) {
    
    // rest of your code ...
    
    res.locals.current_url = req.path;
    
    // rest of your code ...
    
   next();
    
} );

// then in your .jade file:
ul.navbar-nav.mr-auto
    li(class="nav-item #{ current_url === '/page1' ? 'active' : ''}")
         a.nav-link(href='/page1') Page1

like this you are able to use "current_url" globally all around your view files

Softmixt
  • 1,658
  • 20
  • 20
0

I came up with this which works however I'm not sure if its best practice. Please let me know either way:

response.render("current/currentSchedule", {
                title: "Current Race Schedule",
                currentUrl: req.path,
            });


ul(class='nav nav-list')
    li(class='nav-header') Current Season
    - if(currentUrl === '/')
        li(class='active')
            a(href='/') Current Driver Standings
    - else
        li
            a(href='/') Current Driver Standings
    - if(currentUrl === '/constructor-standings')
        li(class='active')
            a(href='/constructor-standings') Current Constructor Standings
    - else
        li
            a(href='/constructor-standings') Current Constructor Standings
    - if(currentUrl === '/current-schedule')
        li(class='active')
            a(href='/current-schedule') Current Race Schedule
    - else
        li
            a(href='/current-schedule') Current Race Schedule
Jon
  • 38,814
  • 81
  • 233
  • 382
  • 2
    This is quite difficult to maintain because you're repeating the class names and links several times, and is also a lot of code. See my answer for an easier way. It could also be adapted to work off currentUrl as in your answer, but often primary nav items should remain hilighted for sub-paths which won't happen with the method you've used. – Jed Watson Oct 18 '12 at 15:50