Create a navigation sidebar with flask

Posted on Wed 28 August 2019 in flask

Sometimes pages grow to a point that the need a little help in order to keep it intuitive for the user. I want to share one way that I have been able to keep pages organized and intuitive to the user by using a sidebar with Flask. No javascript involved.

Here is what the finished product will look like

Conveyor Sidebar

The example page that we will be updating in this post

The Site Details page on conveyor.dev is a perfect candidate to add a sidebar to. The page has has grown to a point that it needs a little extra help organizing the content. Here is what the page currently looks like:

Without Sidebar

Creating the sidebar

There are many ways that you can go about creating a sidebar. The idea behind this method was to avoid replicating the sidebar code in each template and to not use any javascript to update the sidebar with the selected page.

Method 1: HTML/Jinja2

You can do this with pure HTML, the downside to this approach is that there is a lot of replicated code so making changes can be slightly more tedious.

<ul>
    <a href="{{ url_for("sites.details_page", site_id=site.id) }}">
        <li class="text-lg px-8 py-3 {% if selected == "Site Details" %}border-l-4 bg-gray-200{% endif %}"">Details</li>
    </a>
    <a href="{{ url_for("sites.events_page", site_id=site.id) }}">
        <li class="text-lg px-8 py-3 {% if selected == "Events" %}border-l-4 bg-gray-200{% endif %}"">Events</li>
    </a>
    <a href="{{ url_for("sites.environment_page", site_id=site.id) }}">
        <li class="text-lg px-8 py-3 {% if selected == "Environment" %}border-l-4 bg-gray-200{% endif %}"">Environment Variables</li>
    </a>
    <a href="{{ url_for("sites.nginx_page", site_id=site.id) }}">
        <li class="text-lg px-8 py-3 {% if selected == "NGINX Config" %}border-l-4 bg-gray-200{% endif %}"">NGINX Config</li>
    </a>
    <a href="{{ url_for("sites.ssl_page", site_id=site.id) }}">
        <li class="text-lg px-8 py-3 {% if selected == "SSL" %}border-l-4 bg-gray-200{% endif %}"">SSL Config</li>
    </a>
    <a href="{{ url_for("sites.workers_page", site_id=site.id) }}">
        <li class="text-lg px-8 py-3 {% if selected == "Workers" %}border-l-4 bg-gray-200{% endif %}"">Workers</li>
    </a>
</ul>

Method 2: Using a macro

You can also use a macro that will take a list of pages and generate the HTML for the navigation menu. This is a bit overkill when you can get away with creating the navigation menu completely in HTML. However, this makes updating the HTML in the future a little easier because the HTML is generated by the macro. This approach is a bit more complicated

The macro will accept a list of pages and the currently selected page:

# app/templates/macros.html

{% macro build_site_navigation(pages, selected) %}
<div class="w-1/4 mr-8">
    <ul>
        {% for page in pages %}
        <a href="{{page.url}}">
            <li class="text-lg pl-6 py-3 {% if selected == page.name %}border-l-4 bg-gray-200{% endif %}">{{page.name}}</li>
        </a>
        {% endfor %}
    </ul>
</div>
{% endmacro %}

To use this macro in our template:

  1. import the macro into the template by inserting {% import "macros.html" as macros %} at the top of the template file
  2. add the following code where you would like to insert the sidebar: {{macros.build_site_navigation(pages=pages, selected="Page Name")}}

You will need to pass in a list of pages to the template. I created a function generate_page_list and then pass the resulting pages into the template. The function is used so we can update the pages in a single location and avoid code replication.

# app/routes.py

def generate_page_list(site_id):
    pages = [
        {"name": "Details", "url": url_for("sites.show", site_id=site_id)},
        {"name": "Events", "url": url_for("sites.events_page", site_id=site_id)},
        {"name": "Environment", "url": url_for("sites.environment_page", site_id=site_id)},
        {"name": "SSL", "url": url_for("sites.ssl_page", site_id=site_id)},
        {"name": "NGINX Config", "url": url_for("sites.nginx_config_page", site_id=site_id)},
        {"name": "Workers", "url": url_for("sites.workers_page", site_id=site_id)}
    ]
    return pages

The sidebar will now list all of our pages

Improved Sidebar

Routes

Each page will have an individual route. When you render the template in each route make sure to pass in the selected page if you are using HTML/Jinja2 or

@app.route('servers/<int:server_id>/sites/<int:site_id>', methods=['GET'])
@app.route('servers/<int:server_id>/sites/<int:site_id>/environment', methods=['GET'])
@app.route('servers/<int:server_id>/sites/<int:site_id>/ssl', methods=['GET'])
@app.route('servers/<int:server_id>/sites/<int:site_id>/nginx', methods=['GET'])
@app.route('servers/<int:server_id>/sites/<int:site_id>/workers', methods=['GET'])

Create a template file for each page

app/templates/site/details.html
app/templates/site/environment.html
app/templates/site/ssl.html
app/templates/site/nginx.html
app/templates/site/workers.html

So there is an example of how you can create a sidebar navigation in a Flask application. The application shown as an example can help you manage and deploy your Flask applications with ease! Use this Conveyor.dev promotion link to deploy your first application free.