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 in flask.

Here is what the finished product will look like

Conveyor Sidebar


Want to increase your productivity and launch your applications quickly?

Create an account at Conveyor.dev between July 20-July 27 and deploy your first site FREE!

2020-07-24-173606_964x437.png


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.

To accomplish this we can use a simple macro in the template to provide the simple css changes we need to bring everything together

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>{{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="Site Details")}}

To populate the links in the sidebar I created a function generate_page_list and then pass pages into the template

# app/routes.py

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

This gives us a simple sidebar

Simple Sidebar

Styling the sidebar

So the sidebar is now functioning but it needs some styles. Let's improve the macro a little bit to include styling with tailwindcss

# 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 %}

Now we are getting somewhere, here's an updated view

Improved Sidebar

Add routes to support new views

To make this fully functional some code changes must be made with the current routes. Previously the only route that was necessary to show this looked like this:

# app/routes.py

@app.route('servers/<int:server_id>/sites/<int:site_id>', methods=['GET'])
def show(server_id, site_id):
    ...

To support all of our new routes for the sidebar we are going to add a few more:

# app/routes.py

# this will now show the site details by default
@app.route('servers/<int:server_id>/sites/<int:site_id>', methods=['GET'])

# additional routes for new views
@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'])

Add template files for the routes

Everything was previously held within a single template file, now we need to create a separate template file for each view

# the following files were created and their html was transfered from the 
# original template file into their own

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