angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Flask Message Flashing: Replace Bootstrap Alerts by Toasts

Show notifications at a fixed position on top of the screen content, not somewhere squeezed into our layout.

25 July 2022 Updated 25 July 2022
post main image
https://www.pexels.com/nl-nl/@goumbik

When you have a Flask application with Bootstrap, you are probably using Bootstrap Alerts to show flashed messages. I use them, and they work, but I am not really happy. By default, they do not look nice and in most cases they take a lot of space on the screen. And do you really want notifications like 'you are logged in' to be a Bootstrap Alert that must closed by the user? By adding a timeout we can automatically remove the Bootstrap Alert but this is even more ugly!

Bootstrap Toasts

With Bootstrap Toasts we can create notifications that are placed at a fixed position on top of the screen content, not somewhere squeezed into our layout. We use 'position-fixed' in the 'toast-container' to make the toast visible in the viewport. I put my toasts top right but with some offset from the top of the screen. I also had to increase the 'z-index' to make sure that the toast is always on top.

<style>
.toast-container-extra {
    top: 30px;
    z-index: 10000;
}
</style>

If you have multiple toasts, you probably do not want toasts on top of each other, but stacked:

  • The second toast is shown below the first one
  • The third toast is shown below the second one
  • Etc.

This means that when we want to show a toast, we need some Javascript / JQuery code to:

  • Create the toast HTML from a template
  • Add the toast message
  • Append the toast HTML to our toast-container
  • Tell Bootstrap we have a new toast
  • Make Bootstrap show the toast

You probably already are using Flask Message Flashing with categories:

flash('Invalid password provided', 'error')
...
flash('Invalid password provided', 'info')

Below I defined the toast-container and two toast templates, one template for the category 'error' and one template for the category 'info'. The 'error' toast requires the user to close it, the 'info' toast automatically closes after a few seconds.
The category template is identified by the 'data-stack-toast-category' attribute, we want to avoid duplicate Id's.

{# stacked toasts container - start #}
<div class="toast-container position-fixed end-0 p-3 pt-0 toast-container-extra" id="toast-stack-container">
</div>
{# stacked toasts container - end #}

{# stacked toast error template - start #}
<div 
    class="toast mt-0" 
    role="alert" 
    aria-live="assertive" 
    aria-atomic="true" 
    data-bs-autohide="false"
    data-stack-toast-category="error">
    <div class="toast-header border-bottom border-white">
        <strong class="me-auto">
            Error
        </strong>
        <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
    </div>
    <div class="toast-body toast-message"></div>
</div>
{# stacked toast error template - end #}

{# stacked toast info template - start #}
<div class="toast mt-0" 
    role="status"
    aria-live="polite"
    aria-atomic="true"
    data-stack-toast-category="info">
    <div class="toast-header border-bottom border-white">
        <strong class="me-auto">
            Info
        </strong>
        <button type="button"  class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
    </div>
    <div class="toast-body toast-message"></div>
</div>
{# stacked toast info template - end #}

Here is the code to add a toast, to be added to your website Javascript file:

const toast_stack_container_elem = document.getElementById('toast-stack-container');
const error_toast_template_elem = document.querySelector('[data-stack-toast-category="error"]');
const info_toast_template_elem = document.querySelector('[data-stack-toast-category="info"]');

function create_toast_add_to_stack(category, message){
    var new_toast;
    switch(category){
    case 'error':
        new_toast = error_toast_template_elem.cloneNode(true);
        break;
    default:
        new_toast = info_toast_template_elem.cloneNode(true);
    }
    var toast_message_elem = new_toast.querySelector('.toast-message');
    if(toast_message_elem){
        toast_message_elem.innerHTML = message;
    }
    toast_stack_container_elem.append(new_toast);
    const toast = bootstrap.Toast.getOrCreateInstance(new_toast);
    toast.show();
    return;
}

Now you probably want to add two 'show toast' buttons and some code to check that this is working.

Modifying the base template

You probably have a base template with something like the following code to show the flashed messages with Bootstrap Alerts:

{% with messages = get_flashed_messages(with_categories=true) -%}
    {% for category, message in messages -%}
        {% if category == 'error' -%}
            {% set alert_class = 'alert-danger' -%}
        {% else -%}
            {% set alert_class = 'alert-info' -%}
        {% endif -%}
        <div class="alert {{ alert_class }} alert-dismissible mb-3" role="alert">
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
            {{ message }}
        </div>
    {% endfor -%}
{% endwith -%}

To use Bootstrap toasts, we modify this code . I am using JSON here to avoid Content Security Policy (CSP) errors.

{% with messages = get_flashed_messages(with_categories=true) -%}

{% set flashed_messages_page_data_js={} -%}
{% set x=flashed_messages_page_data_js.__setitem__('messages', messages) -%}
<script type="application/json" data-selector="flashed-messages-page-data-js">
{{ flashed_messages_page_data_js|tojson }}
</script>            

{% endwith -%}

And then add the following code to your website Javascript file:

$(document).ready(function(){
    try {
        if($('script[data-selector="flashed-messages-page-data-js"]').length){
            var flashed_messages_page_data_js = JSON.parse($('script[data-selector="flashed-messages-page-data-js"]').html());
            if(flashed_messages_page_data_js.hasOwnProperty('messages')){
                var messages = flashed_messages_page_data_js.messages;
                for(var i = 0; i < messages.length; i++){
                    var category = messages[i][0];
                    var message = messages[i][1];
                    create_toast_add_to_stack(category, message);
                }
            }
        }
    }
    catch(error){
        // no-op
        console.log('flashed messages, error = ');
        console.log(error);
    }    

});

Summary

We replaced Bootstrap Alerts by Bootstrap Toasts with Flask Message Flashing. That is nice but we probably want more fine-grained control. We could add extra categories for cases where we still want Bootstrap Alerts, for example:

  • 'error-alert'
  • 'info-alert'

We could also override the 'flash()' function and add parameters like:

  • notification_type
  • auto_hide

But, before reinventing the wheel again, we probably want to check first how notifications and alerts are shown on mobile phones. Another time ...

Links / credits

Bootstrap - Alerts
https://getbootstrap.com/docs/5.1/components/alerts

Bootstrap - Toasts
https://getbootstrap.com/docs/5.2/components/toasts

Flask - Message Flashing
https://flask.palletsprojects.com/en/2.1.x/patterns/flashing

Inline Data With a Content Security Policy
https://itnext.io/inline-data-with-a-content-security-policy-ab30dde2feb3

Toasts
https://preview.keenthemes.com/start/documentation/base/toasts.html

Leave a comment

Comment anonymously or log in to comment.

Comments

Leave a reply

Reply anonymously or log in to reply.