如何使用jQuery动态向FieldList添加WTForms的TextField?

4 投票
3 回答
6411 浏览
提问于 2025-04-18 07:50

我想用按钮来添加或删除新的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 个回答

0

我觉得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);
10

你的例子把服务器生成的文本和你在浏览器中用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是下一个位置索引(这个可以存储在一个包裹的uldata属性中),而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())
4

这个回答是基于nsfyn55的解释(第一段)。

我遇到过类似的问题。解决办法是使用:https://github.com/Rhyzz/repeatable-fields

所以,你只需要查看WTForms生成的html代码片段,然后把它当作“模板”放在repeatable-fields插件中使用就可以了(具体细节可以查看它的文档)。

撰写回答