如何使用jQuery动态向FieldList添加WTForms的TextField?
我想用按钮来添加或删除新的WTForm输入框,使用Jquery,就像这里说的那样:http://www.sanwebe.com/2013/03/addremove-input-fields-dynamically-with-jquery/comment-page-1,不过我想用我自己的表单字段。
这是我的表单:
class EditBook(Form):
title = TextField('title', validators = [Required()])
authors = FieldList(TextField())
问题是我不能直接添加,比如说
$(InputsWrapper).append("{{form.authors(size=20)}}");
这样做只是打印出这段文字。
3 个回答
我觉得potar
的建议很有用。这里有一个使用https://github.com/Rhyzz/repeatable-fields的例子。
# from db import insert_row
from flask import Flask, redirect, render_template_string
from flask_wtf import FlaskForm
from wtforms.fields import (
StringField,
EmailField,
FieldList,
FormField,
SubmitField
)
from wtforms.validators import DataRequired, Length
from wtforms import Form
# simplify rendering
# https://github.com/helloflask/bootstrap-flask
# pip install -U bootstrap-flask
from flask_bootstrap import Bootstrap5
app = Flask(__name__)
app.secret_key = 'fixme!'
Bootstrap5(app)
class StoreForm(Form): # IMPORTANT !!! Using FORM and not FLASKFORM avoids issues with CSRF tokens.
store_name = StringField(
"Store Name",
validators=[DataRequired(), Length(2, 200)],
)
store_website = StringField(
"Store Website",
validators=[DataRequired(), Length(2, 200)],
)
class RegistrationForm(FlaskForm):
city_name = StringField(
"City Name",
validators=[DataRequired(), Length(2, 100)]
)
city_email = EmailField(
"City Email",
validators=[DataRequired(), Length(4, 100)],
)
stores = FieldList(
FormField(StoreForm),
label="Stores",
min_entries=1, # Show 1 entry on a new form.
max_entries=5, # Should a user submit over 5 stores, data will be cut off after 5 entries. We mitigate this through the client UI, but if a user uses other means to POST, their data will be lost.
)
submit = SubmitField("Submit")
@app.route("/", methods=["GET", "POST"])
def register():
"""Register 1 to N stores."""
form = RegistrationForm()
print('Store entries:')
for entry in form.stores.entries:
print(entry.data) # how to access each store entry
if form.validate_on_submit():
# insert_row(form.data) # save data
print('Success')
return redirect("/")
return render_template_string(HTML_REGISTER, form=form)
HTML_REGISTER = """
{% from 'bootstrap5/form.html' import render_field %}
<h1>Registration Form</h1>
<form id="registration_form" action="/" method="post" novalidate>
<h2>City Information</h2>
<div>
{{ render_field(form.city_name) }}
{{ render_field(form.city_email) }}
</div>
<h2>Store Information</h2>
<div class="rf_repeat">
<div class="rf_wrapper">
<div class="rf_container">
{% for store in form.stores %}
<div class="rf_template rf_row">
{{ render_field(store.form.store_name) }} <!-- store.store_name works too -->
{{ render_field(store.form.store_website) }}
<div>
<span class="rf_remove">Remove</span>
</div>
</div>
{% endfor %}
</div>
<div>
<span class="rf_add">Add</span>
</div>
</div>
</div>
{{ form.hidden_tag() }} <!-- a single hidden_tag() call for the parent form -->
{{ render_field(form.submit) }}
</form>
"""
app.run(host="0.0.0.0", port=5500, debug=True)
现在,使用类似下面的方式把JS文件链接到你的布局模板中,
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
<script src="{{ url_for('static', filename='js/registration_form.js') }}"></script>
// /static/js/registration_from.js
// Copy and paste the jQuery script from https://github.com/Rhyzz/repeatable-fields/blob/master/repeatable-fields.js here.
// set the default jQuery selector configs
self.default_settings = {
wrapper: '.rf_wrapper',
container: '.rf_container',
row: '.rf_row',
add: '.rf_add',
remove: '.rf_remove',
move: '.rf_move',
move_up: '.rf_move-up',
move_down: '.rf_move-down',
move_steps: '.rf_move-steps',
template: '.rf_template',
is_sortable: false,
before_add: null,
after_add: self.after_add,
before_remove: null,
after_remove: self.after_remove,
sortable_options: null,
row_count_placeholder: '0', // This is the most important config as it aligns to WTForm <input> `name` attributes (e.g. store-0-store_name, store-1-store_name, ...).
};
// if needed, modify the script
var STORE_COUNT = 0;
const MAX_STORE_COUNT = 2;
STORE_COUNT += 1;
if (STORE_COUNT >= MAX_STORE_COUNT ){
alert('Max store count reached.');
$('.rf_repeat .rf_add').hide();
}
// a possibly important edit to make would be to
// change the cloned and disabled input ids.
// You can use $(this).prop('id', '_' + this.id); beneath $(this).prop('disabled', true);
// You can use $(this).prop('id', this.id.substr(this.id.indexOf('_') + 1)); beneath $(this).prop('disabled', false);
你的例子把服务器生成的文本和你在浏览器中用JavaScript添加的文本搞混了。你不能使用{{ }}
这种语法,因为这是服务器端模板引擎用的。在渲染的时候,这些语法会被展开并替换成通过网络传输到客户端的HTML。你需要自己构建这些内置模板函数本来会生成的DOM元素。
你实际上想要创建的DOM元素看起来是这样的:
<input id="authors-0" name="authors-0" type="text" value="Author Guy"></input>
<input id="authors-1" name="authors-1" type="text" value="Other Guy"></input>
然后,这可以被编码成一个multipart/form-data
流,WTForms可以处理这个流。所以你用jQuery的代码需要创建一个看起来像这样的字段:
$('<input>').attr({
type: 'text',
id: 'authors-' + index ,
name: 'authors-' + index,
value: value
}).appendTo('form');
其中index
是下一个位置索引(这个可以存储在一个包裹的ul
的data
属性中),而value
是你想要赋给这个框的值(如果有的话)。
另外,为了了解FieldList
渲染的内容,你可以在命令行运行下面的代码。它会打印出WTForms在渲染过程中生成的实际文本。
from wtforms import Form, TextField, FieldList
from webob.multidict import MultiDict
class TestForm(Form):
authors = FieldList(TextField())
data_in = {'authors': ['Author Guy', 'Other Guy']}
test_form2 = TestForm(data=MultiDict(data_in))
print("HTML Render: %s" % test_form2.authors())
这个回答是基于nsfyn55的解释(第一段)。
我遇到过类似的问题。解决办法是使用:https://github.com/Rhyzz/repeatable-fields
所以,你只需要查看WTForms生成的html代码片段,然后把它当作“模板”放在repeatable-fields插件中使用就可以了(具体细节可以查看它的文档)。