Flask 通过 omclick 事件在 Javascript 中处理表单验证错误

问题描述 投票:0回答:1

所以,有一个文件上传表单。

forms.py

from flask_wtf import FlaskForm 

class Form(FlaskForm):
    file = FileField('File',
                     validators=[FileRequired(msg),
                                 FileAllowed(ext, msg),
                                 FileSize(max_, min_, msg)])    
    upload = SubmitField('Upload')

upload.html

<form method="POST" enctype="multipart/form-data">
  {{ form.hidden_tag() }}
  {{ form.file(class="form-control", id="file") }}
  <div id="error" data-error="{{ form.file.errors[0] }}"></div> <!-- * -->
  {{ form.upload(class="btn btn-outline-secondary", id="upload") }}
</form>
  • 这里我尝试存储验证错误文本,以便可以通过 JS 函数访问它。 我需要它,因为我想在漂亮的
    Bootstrap toast
    元素中显示错误消息,该元素需要在 JS 中初始化。
script.js

let fu = document.querySelector('#upload');

fu.addEventListener('click', (ev) => {
    let el = document.querySelector('#error');
    if (el.dataset.error) {
        // Show error message and do nothing else
        let toast = document.querySelector('.toast');
        document.querySelector('.toast-body').innerText = el.dataset.error;
        bootstrap.Toast.getOrCreateInstance(toast).show(); // initialize
    }
    // Let Flask do its job
});

显然,当我按下上传按钮时,JS 函数会立即触发。仍然没有有关发生错误的信息(如果有的话),因为服务器端的验证尚未发生。

我还尝试解决 websockets 的问题:

Python

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = Form()

    if request.method == 'POST':
        if not form.validate():
            socketio.emit('err', {'error': form.file.errors[0]})
    # ...

JS

const ws = io();
ws.on('err', (ev) => (
    // let error = ev.error;
    // Yhe same logic
));

但在客户端收到

Invalid frame header
错误。

终于得到了我想要的。有大量代码和重复的 AJAX 请求调用。 我确信还有更优雅的解决方案。也许我错过了什么。 谢谢。

javascript python flask bootstrap-toast
1个回答
0
投票

一种可能性是使用 Jinja 创建 toast 并在直接执行的脚本中显示它们。在这种情况下,您不需要 Ajax 或 websockets。整个页面将重新加载并显示 toast。

class UploadForm(FlaskForm):
    file = FileField('File',
        validators=[
            FileRequired(msg),
            FileAllowed(ext, msg),  
            FileSize(max_, min_, msg)
        ]
    )
    submit = SubmitField('Upload')

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = UploadForm()
    if form.validate_on_submit():
        # ...
    return render_template('upload.html', **locals())
{% if form.errors -%}
<div class="toast-container position-fixed bottom-0 end-0 p-3">
    {% for k,v in form.errors.items() -%}
    <div class="toast align-items-center" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="d-flex">
            <div class="toast-body">
                {{ v[0] }}
            </div>
            <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
    </div>
    {% endfor -%}
</div>
{% endif -%}
(function() {
    const toastElList = document.querySelectorAll('.toast')
    toastElList.forEach(toastEl => {
        bootstrap.Toast.getOrCreateInstance(toastEl).show();
    });
})();

如果你确实想使用Ajax,你也可以通过脚本创建toast,然后显示它们,如下例所示。页面并未完全重新加载,只是动态创建个别部分。

@app.route('/')
def index():
    form = UploadForm()
    return render_template('index.html', **locals())

@app.post('/upload')
def upload():
    form = UploadForm()
    if form.validate():
        # ...
        return jsonify(errors=[])
    return jsonify(errors=form.errors)
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Index</title>
    <link 
        href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" 
        rel="stylesheet" 
        integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" 
        crossorigin="anonymous">
</head>
<body>
    <main class="container my-4">
        <form name="upload-form" method="POST" enctype="multipart/form-data">
            <div class="mb-3">
                {{ form.hidden_tag() }}
                {{ form.file(class_='form-control') }}
            </div>
            <div class="mb-3 d-grid gap-2">
                {{ form.submit(class_='btn btn-outline-secondary') }}
            </div>
        </form>
    </main>

    <div class="toast-container position-fixed bottom-0 end-0 p-3"></div>

    <script 
        src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" 
        integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" 
        crossorigin="anonymous"></script>
    <script>
        (function() {
            const formEl = document.querySelector('form[name="upload-form"]');
            formEl.addEventListener('submit', function(event) {
                event.preventDefault();
                fetch('/upload', {
                    method: 'POST', 
                    body: new FormData(this)
                }).then(resp => resp.json())
                    .then(data => {
                        if (data && data.errors && Object.keys(data.errors).length) {
                            const toastsEl = document.querySelector('.toast-container');
                            toastsEl.innerHTML = Object.keys(data.errors).map(k => {
                                return `
                                    <div class="toast align-items-center" role="alert" aria-live="assertive" aria-atomic="true">
                                        <div class="d-flex">
                                            <div class="toast-body">
                                                ${ data.errors[k][0] }
                                            </div>
                                            <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
                                        </div>
                                    </div>
                                `;
                            }).join('');
                            toastsEl.querySelectorAll('.toast').forEach(toastEl => {
                                bootstrap.Toast.getOrCreateInstance(toastEl).show();
                            });
                        } else {
                            this.reset();
                        }
                    })
            });
        })();
    </script>
</body>
</html>

我认为没有必要使用websockets。

© www.soinside.com 2019 - 2024. All rights reserved.