'Changing the active class of a link with the twitter bootstrap css in python/flask
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)
Solution 1:[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:[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:[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:[4]
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>
Solution 5:[5]
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 6:[6]
I tried different solution for this for the solution 1st by Codegeek didn't work as I have multiple Ul and li under it so I just include my navbar in template.html
{% include 'sidebar.html' %}
then in Navbar file in the li class you can set active with help of "request.endpoint" but then again it will return you entire route instead use split and take last route name and set active if same for exmaple
<li class="{% if request.endpoint.split('.')[1] == 'index' %} active {% else %} {% endif %}">
request.endpoint.split('.')[1]
will return the route eg localhost/example. You will get example which you can compare and use. If you won't split and use request.endpoint than you will get 'file.example' (entire route).
Solution 7:[7]
Add the following CSS somewhere on your page:
a[href $= {{ page_name|default("'/'"|safe) }}]{ [INSERT YOUR ACTIVE STYLING HERE] }
Now, on each template define page_name
, for example:
{% extends "template.html" %}
{% set page_name = "gallery" %}
This seems much simpler and easier to build on, than other options.
EDIT:
Almost 1 year later I'm returning to make this a much simpler fix, because setting the page name on every page is pretty inefficient.
Instead create a function like so:
@app.context_processor
def context_processor():
out = {}
out['request'] = request # Make sure you import request from flask
return out
This will allow you to pass variables implicitly to jinja, in this case we are passing the request for access to request.url_rule
which contains the route the user is accessing. In the previous version, we just change {{ page_name|default("'/'"|safe) }}
to "{{ request.url_rule|safe }}"
. Much cleaner.
Solution 8:[8]
I did not want to have to define the ID in the child pages, as many of the links I have do not have a specific child template.
Using the request.base_url
and if it matches the _external
url_for
the route, then render that nav item as active.
{% set nav_items = [
("public.home", "Home"),
("public.downloads", "Downloads"),
("public.about", "About")
("account.login", "Login"),
]
-%}
...
<ul class="navbar-nav mr-auto">
{% for route, display_text in nav_items %}
<li class={% if request.base_url == url_for(route, _external=True) %}"nav-item active"{% else %}"nav-item"{% endif %}>
<a class="nav-link" href="{{ url_for(route) }}">{{ display_text }}
{% if request.base_url == url_for(route, _external=True) %}<span class="sr-only">(current)</span>{% endif %}
</a>
</li>
{% endfor %}
</ul>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | varun |
Solution 3 | |
Solution 4 | Ramesh Ponnusamy |
Solution 5 | |
Solution 6 | Adriaan |
Solution 7 | |
Solution 8 | Justin Smith |