Changing the active class of a link with the twitter bootstrap css in python/flask

26,204

Solution 1

Have you looked at this ? https://jinja.palletsprojects.com/en/3.0.x/tricks/#highlighting-active-menu-items

Highlighting Active Menu Items

Often you want to have a navigation bar with an active navigation item. This is really simple to achieve. Because assignments outside of blocks in child templates are global and executed before the layout template is evaluated it’s possible to define the active menu item in the child template:

{% extends "layout.html" %}
{% set active_page = "index" %}

The layout template can then access active_page. Additionally it makes sense to define a default for that variable:

{% set navigation_bar = [
    ('/', 'index', 'Index'),
    ('/downloads/', 'downloads', 'Downloads'),
    ('/about/', 'about', 'About')
] -%}

{% set active_page = active_page|default('index') -%}
...
<ul id="navigation">
    {% for href, id, caption in navigation_bar %}
    <li{% if id == active_page %} class="active"{% endif
    %}><a href="{{ href|e }}">{{ caption|e }}</a>
    </li>
{% endfor %}
</ul>

Solution 2

Here is another simpler way if you have menus distributed all over the page. This way uses inline if statements to print out the class active.

<ul>

<li class="{{ 'active' if active_page == 'menu1' else '' }}">
<a href="/blah1">Link 1</a>
</li>

<li class="{{ 'active' if active_page == 'menu2' else '' }}">
<a href="/blah2"> Link 2 </a>
</li>

</ul>

Class active is for highlighting

You still need to set the variable on every page to mark them

{% extends "master.html" %}
{% set active_page = "menu1" %}

or

{% set active_page = "menu2" %}

Solution 3

For jinja/flask/bootstrap users:

If you define your nav like they did in the blog example http://getbootstrap.com/examples/blog/ simply assign ids to your links that match your url_for arguments and you just need to modify the layout-template, the rest just works #magic.

<nav class="blog-nav">
  <a id="allposts"  class="blog-nav-item" href="{{ url_for('allposts')}}">All Posts</a>
  <a id="index"     class="blog-nav-item" href="{{ url_for('index')}}">Index</a>
  <a id="favorites" class="blog-nav-item" href="{{ url_for('favorites')}}">Favorites</a>
</nav>

At the bottom of your base/layout template just add this

<script>
  $(document).ready(function () {
  $("#{{request.endpoint}}").addClass("active"); })
</script>

and the right elements will be set active.

EDIT: If you have a layout with elements in a list, like this:

<nav class="blog-nav">
  <ul class="nav navbar-nav">
    <li>
        <a id="allposts"  class="blog-nav-item" href="{{ url_for('allposts')}}">All Posts</a>
    </li>
    <li>
        <a id="index"     class="blog-nav-item" href="{{ url_for('index')}}">Index</a>
    </li>
    <li>
        <a id="favorites" class="blog-nav-item" href="{{ url_for('favorites')}}">Favorites</a>
    </li>
  </ul>
</nav>

use the parent() function to get the li element instead of the link.

<script>
    $(document).ready(function () {
    $("#{{request.endpoint}}").parent().addClass("active"); })
</script>

Solution 4

I liked @philmaweb's approach, but there's really no reason to require duplicating the endpoint in the id of each element.

base.js:

$(document).ready(function () {
    var scriptElement = $('#baseScript')[0];
    var path = scriptElement.getAttribute('data-path');
    $('a[href="'+path+'"]').addClass("active");
});

base.html

<script id="baseScript" src="{{ url_for('static', filename='js/base.js') }}"
data-path="{{ request.path }}"></script>

Why not just put this script inline? You could, of course, but allowing inline JS is a security nightmare. You should be using a CSP on your site (e.g. Flask-Talisman) which will not allow inline JS. With data-* attributes, it's not hard to do this in a secure way.

NB: If you have multiple links leading to the same, current page and you want only ONE of them to be marked "active"—then this approach may not work for you.

Solution 5

we can make class active by using jinja if statements

<ul class="nav navbar-nav">
     <li class="{% if request.endpoint=='home' %}active{%endif %}"><a href="{{  url_for('home') }}">home</a></li>
     <li class="{% if request.endpoint=='add_client' %}active{%endif %}"><a href="{{  url_for('add_client') }}">Add Report</a></li>
    </li>
  </ul>
Share:
26,204
Azeirah
Author by

Azeirah

Second year computer engineering student. Developer of SMG music display, helping streamers connect with their audiences through their music. https://smgmusicdisplay.com

Updated on July 09, 2022

Comments

  • Azeirah
    Azeirah almost 2 years

    I got the following html snippet from my page template.html.

    <ul class='nav'>
        <li class="active"><a href='/'>Home</a></li>
        <li><a href='/lorem'>Lorem</a></li>
    
        {% if session['logged_in'] %}
            <li><a href="/account">Account</a></li>
            <li><a href="/projects">Projects</a>
            <li><a href="/logout">Logout</a></li>
        {% endif %}
    
        {% if not session['logged_in'] %}
            <li><a href="/login">Login</a></li>
            <li><a href="/register">Register</a></li>
        {% endif %}
    </ul>
    

    As you can see on line 2, there's the class active. This highlights the active tab with the twitter bootstrap css file. Now, this will work fine if I would visit www.page.com/ but not when I would visit www.page.com/login for example. It would still highlight the home link as the active tab.

    Of course, I could easily do this with Javascript/jQuery but I'd rather not use that in this situation.

    There's already a working solution for ruby on rails but I don't know how to convert that into python/jinja (I'm rather new to jinja/flask, never worked with ruby at all)

  • Azeirah
    Azeirah over 10 years
    That's plain beautiful, especially after seeing the ruby code, this makes so much more sense. I had no idea this was even possible with jinja, thanks for the link as well, I'm sure I can figure this out now :)
  • Jake Berger
    Jake Berger over 10 years
    why escape href and caption (e.g. href|e) when you already control the values?
  • mhenry
    mhenry over 9 years
    While this is a valid option, it will not work in the case when the user disables JavaScript in the browser. I know, who does that? But it happens! As a result, you don't want to use JavaScript for things that can easily be handled by Jinja.
  • philmaweb
    philmaweb over 9 years
    You are right, but then the functionality of the site is basically reduced to zero, as AJAX stops working, that is used to load most of the content.
  • mhenry
    mhenry over 9 years
    AJAX is not a requirement. If you're using AJAX, chances are you aren't using Jinja either. You'll want to generate your HTML templates on the client side with a javascript templating engine, like Handlebars, to properly format the results from your AJAX requests.
  • adonese
    adonese about 7 years
    It doesn't work for me. I don't know actually how it gets the current page name, so I modified your code to be like this: {% if request.endpoint == id %}... the rest of the code is the same. But I'm curious about how does your snippest know the endpoint and do the comparsion.
  • Nicholas
    Nicholas almost 7 years
    @adonese it is working, don't forget to set active_page in every template ex: contacts, about, works etc.
  • Carlos
    Carlos almost 7 years
    @Nicholas is right and it's what I did with my little project. This trick works like a champ.
  • Eziz Durdyyev
    Eziz Durdyyev over 5 years
    the simplest yet best solution!
  • S3DEV
    S3DEV almost 4 years
    This is absolutely brilliant, would upvote 10 times if I could, thank you!
  • Mersan Canonigo
    Mersan Canonigo about 2 years
    plus points from me!