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

Flask Message Flashing: Sustituir Bootstrap Alerts por Toasts

Mostrar las notificaciones en una posición fija en la parte superior del contenido de la pantalla, no en algún lugar apretado en nuestro diseño.

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

Cuando tienes una aplicación Flask con Bootstrap, probablemente estés usando Bootstrap Alerts para mostrar flashed messages. Yo los uso y funcionan, pero no estoy muy contento. Por defecto, no se ven bien y en la mayoría de los casos ocupan mucho espacio en la pantalla. ¿Y realmente quieres que las notificaciones como 'has iniciado sesión' sean un Bootstrap Alert que debe cerrar el user? ¡Añadiendo un tiempo de espera podemos eliminar automáticamente el Bootstrap Alert pero esto es aún más feo!

Bootstrap Toasts

Con Bootstrap Toasts podemos crear notificaciones que se coloquen en una posición fija encima del contenido de la pantalla, no en algún lugar apretado en nuestro diseño. Usamos 'position-fixed' en el 'toast-container' para hacer visible el toast en el viewport. Puse mi toasts arriba a la derecha pero con un poco de desplazamiento desde la parte superior de la pantalla. También tuve que aumentar el 'z-index' para asegurarme de que el toast está siempre arriba.

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

Si tiene varios toasts, probablemente no quiera que los toasts estén uno encima del otro, sino apilados:

  • El segundo toast se muestra debajo del primero
  • El tercer toast se muestra debajo del segundo
  • etc.

Esto significa que cuando queremos mostrar un toast, necesitamos algún código Javascript / JQuery para:

  • Crear el toast HTML desde una plantilla
  • Añadir el mensaje toast
  • Añadir el toast HTML a nuestro toast-container
  • Decirle a Bootstrap que tenemos un nuevo toast
  • Hacer que Bootstrap muestre el toast

Probablemente ya esté utilizando Flask Message Flashing con categorías:

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

A continuación he definido el toast-container y dos plantillas toast , una plantilla para la categoría 'error' y otra para la categoría 'info'. La 'error' toast requiere la user para cerrarse, la 'info' toast se cierra automáticamente después de unos segundos.
La plantilla de la categoría está identificada por el atributo 'data-stack-toast-category', queremos evitar los Id's duplicados.

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

Aquí está el código para añadir un toast, que se añadirá al archivo Javascript de su sitio web:

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;
}

Ahora probablemente quieras añadir dos botones 'show toast' y algo de código para comprobar que esto funciona.

Modificar la plantilla base

Probablemente tengas una plantilla base con algo como el siguiente código para mostrar el flashed messages con las Alertas Bootstrap :

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

Para utilizar Bootstrap toasts, modificamos este código . Yo estoy usando JSON aquí para evitar errores de Content Security Policy (CSP).

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

Y luego añadir el siguiente código a su sitio web Javascript archivo:

$(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);
    }    

});

Resumen

Hemos sustituido Bootstrap Alerts por Bootstrap Toasts con Flask Message Flashing. Eso está bien, pero probablemente queramos un control más fino. Podríamos añadir categorías adicionales para los casos en los que todavía queremos Bootstrap Alerts, por ejemplo:

  • 'error-alert'
  • info-alerta

También podríamos anular la función 'flash()' y añadir parámetros como

  • notification_type
  • auto_hide

Pero, antes de reinventar la rueda de nuevo, probablemente queramos comprobar primero cómo se muestran las notificaciones y alertas en los teléfonos móviles. En otro momento...

Enlaces / créditos

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

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.