Home > front end >  Show message in flask while using AJAX
Show message in flask while using AJAX

Time:01-15

I have this code: (template)

// Send the changed input data to the server
(function() {
    window.addEventListener('DOMContentLoaded', () => {
        document.querySelectorAll('input').forEach(elem => {
            elem.addEventListener('change', function() {
                if(this.name.substring(0,3) != "add")
                {
                    const formData = new FormData();
                    formData.append(this.name, this.value);
                    fetch('/settings', {
                        method: 'post',
                        body: formData
                    });
                }
            });
        });
    });
})();

Here needs showing message in template (app.py)

if len(value) > 30:
   flash("Error! Header title max length is 30 characters", "error")
   return redirect("/settings")

layout.html has:

<div  role="alert">
   {{ get_flashed_messages() | join(" ") }}
   </div>

As redirect() not working in case of form submission with AJAX, How can show appropriate message about submitted data?

Or how can send back the conditional result from app.py to template with AJAX and show message in a <div>?

CodePudding user response:

You can validate the entries made on the server. If the validation fails, the error messages can be returned as JSON and you can assign them to the respective input field.
A possible implementation of this strategy is to use webargs if it suits your application.

from flask import (
    Flask, 
    jsonify, 
    render_template, 
    request
)
from webargs import fields, validate
from webargs.flaskparser import use_args

app = Flask(__name__)

@app.route('/settings', methods=['GET', 'POST'])
@use_args(
    {
        'title': fields.Str(
            validate=[
                validate.Length(min=3, max=30), 
                validate.Regexp(r'^(?! ).*(?<! )$', 
                    error='No leading or trailing whitespace allowed.'
                ), 
            ]
        ), 
        'other': fields.Str(
            validate=[
                validate.Length(min=3, max=30), 
                validate.Regexp(r'^(?! ).*(?<! )$', 
                    error='No leading or trailing whitespace allowed.'
                ), 
            ]
        )
    }, 
    location='form'
)
def settings(args):
    if request.method == 'POST':
        print(args)
    return render_template('settings.html')

@app.errorhandler(422)
@app.errorhandler(400)
def handle_error(err):
    headers = err.data.get("headers", None)
    messages = err.data.get("messages", ["Invalid request."])
    if headers:
        return jsonify({"errors": messages}), err.code, headers
    else:
        return jsonify({"errors": messages}), err.code
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Config</title>
    <link 
        href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" 
        rel="stylesheet" 
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" 
        crossorigin="anonymous">
</head>
<body>

    <main >

        <div id="alert-placeholder">
            {% with messages = get_flashed_messages(with_categories=true) -%}
                {% if messages -%}
                    {% for category, message in messages -%}
                    <div  role="alert">
                        <div>{{ message }}</div>
                        <button type="button"  data-bs-dismiss="alert" aria-label="Close"></button>
                    </div>
                    {% endfor -%}
                {% endif -%}
            {% endwith -%}
        </div>

        <form method="post">
            <div >
                <label for="title" >Title</label>
                <input type="text" name="title" id="title"  minlength="3" maxlength="30" required />
                <div id="title-error" ></div>
            </div>
            <div >
                <label for="other" >Other</label>
                <input type="text" name="other" id="other"  minlength="3" maxlength="30" required />
                <div id="other-error" ></div>
            </div>
        </form>

    </main>

    <script 
        src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" 
        integrity="sha384-MrcW6ZMFYlzcLA8Nl NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP JcXn/tWtIaxVXM" 
        crossorigin="anonymous"></script>
    <script type="text/javascript">
        (function() {

            window.addEventListener('DOMContentLoaded', () => {

                const alertPlaceholder = document.getElementById('alert-placeholder');
                
                // Show flash messages with JavaScript.
                function flash(message, type) {
                    const wrapper = document.createElement('div');
                    wrapper.innerHTML = [
                        `<div  role="alert">`, 
                        `   <div>${message}</div>`, 
                        '   <button type="button"  data-bs-dismiss="alert" aria-label="Close"></button>', 
                        '</div>'
                    ].join('');
                    alertPlaceholder.append(wrapper);

                    const alert = wrapper.querySelector('.alert');
                    new bootstrap.Alert(alert);
                    setTimeout(() => {
                        bootstrap.Alert.getInstance(alert).close();
                    }, 3000);
                }


                // Query all input fields whose name does not start with add.
                document.querySelectorAll('input:not([name^="add"])').forEach(elem => {
                    // Add a change listener to each.
                    elem.addEventListener('change', async function() {

                        // Submit form data.
                        const formData = new FormData();
                        formData.append(this.name, this.value);
                        const resp = await fetch('/settings', {
                            method: 'post', 
                            body: formData
                        });

                        // Clear error messages for this input field.
                        this.classList.remove('is-invalid');
                        document.querySelector(`[id="${this.name}-error"]`).innerText = '';

                        // If the status code is not 200, parse the json data.
                        if (!resp.ok) { 
                            const data = await resp.json();
                            if (data.errors && data.errors.form) {
                                // Show the error messages.
                                this.classList.add('is-invalid');
                                const errors = data.errors.form;
                                Object.keys(errors).forEach(key => {
                                    document
                                        .querySelector(`#${key}-error`)
                                        .innerHTML = '<ul>' 
                                              errors[key].map(err => `<li>${err}</li>`).join('') 
                                              '</ul>';
                                });
                            }
                        } else {
                            // If everything is ok, show success message.
                            flash(`Successfully updated ${this.name}.`, 'info');
                        }

                    });
                });
            });
        })();
    </script>
</body>
</html>

CodePudding user response:

Flash message will appear only when you reload the page as they are rendered on the server. Redrawing the body with the response should solve the problem. try the following:

window.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('input').forEach(elem => {
        elem.addEventListener('change', function() {
            if(this.name.substring(0,3) != "add")
            {
                const formData = new FormData();
                formData.append(this.name, this.value);
                
                var xhr = new XMLHttpRequest();
                xhr.open('POST', '/settings');
                xhr.onload = function() {
                    if (xhr.status === 200) {
                        body.innerHTML = xhr.responseText;
                    } else {
                       console.log('An error occurred');
                    }
               };
              xhr.send(fData);
            }
        });
    });
});

Probably you need to redraw the whole page with the response.

  • Related