Одной из распространенных задач в веб-приложениях является создание формы, в которую можно вводить заранее неопределённое количество элементов. Этот подход часто используется при вводе пользовательской информации, например, телефонных номеров или адресов.
В примере ниже можно увидеть, как пользователь динамически добавляет дополнительные телефонные номера в форму, нажимая на кнопку "Add another".
<form>
<p>Name: <input type="text" name="name" /></p>
<p>
Phone numbers:
<ul>
<li>Number 1: <input type="text" name="phone_number" /></li>
<li>Number 2: <input type="text" name="phone_number" /></li>
<li>Number 3: <input type="text" name="phone_number" /></li>
</ul>
</p>
<button type="submit">Submit</button>
</form>
phone_numbers = request.form.getlist('phone_number')
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
print(f'Name: {request.form["name"]}')
for number, type_ in zip(
request.form.getlist('phone_number'),
request.form.getlist('phone_type')
):
if number:
print(f'Phone number: {number} ({type_})')
return render_template('index.html')
<form action="" method="post">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" name="name" id="name" autofocus />
</div>
<div class="mb-3">
<label for="phone_type" class="form-label">Phone numbers</label>
<div id="phoneFields">
<div class="phoneField">
<div class="row mb-2">
<div class="col-8">
<input type="text" class="form-control" name="phone_number" />
</div>
<div class="col-4">
<select class="form-select" name="phone_type">
<option value="home" selected>Home</option>
<option value="work">Work</option>
<option value="mobile">Mobile</option>
<option value="other">Other</option>
</select>
</div>
</div>
</div>
</div>
<div class="form-text">
<a href="#" onclick="javascript:return addPhone()">Add another</a>
</div>
</div>
<div class="row form-group">
<div class="col-3">
<input type="submit" class="form-control" value="Submit" />
</div>
</div>
</form>
function addPhone() {
const firstPhone = document.querySelector('.phoneField');
const newPhone = document.createElement('div');
newPhone.classList.add('phoneField');
newPhone.innerHTML = firstPhone.innerHTML;
document.getElementById('phoneFields').appendChild(newPhone);
document.querySelector('.phoneField:last-child input').focus();
return false;
}
class PhoneForm(FlaskForm):
class Meta:
csrf = False # CSRF не нужен здесь, так как это подформаrm
number = StringField('Phone number', validators=[
DataRequired(),
Regexp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}#x27;,
message='Enter a valid phone number.')
])
type_ = SelectField('Phone type', choices=[
('home', 'Home'),
('work', 'Work'),
('mobile', 'Mobile'),
('other', 'Other')
])
class MyForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
phone_numbers = FieldList(FormField(PhoneForm), min_entries=1)
submit = SubmitField('Submit')
@app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm()
if form.validate_on_submit():
print(f'Name: {form.name.data}')
for phone in form.phone_numbers.data:
print(f'Phone: {phone["number"]} ({phone["type_"]})')
return render_template('index.html', form=form)
{% macro text(field, label, autofocus) %}
{% if label %}
{{ field.label(class='form-label') }}
{% endif %}
{% if autofocus %}
{{ field(class='form-control' + (' is-invalid' if field.errors else ''), autofocus="true") }}
{% else %}
{{ field(class='form-control' + (' is-invalid' if field.errors else '')) }}
{% endif %}
{% if field.errors %}
<div class="invalid-feedback">{{ field.errors|join(', ') }}</div>
{% endif %}
{% endmacro %}
{% macro select(field) %}
{{ field(class="form-select") }}
{% endmacro %}
<form action="" method="post">
{{ form.csrf_token }}
<div class="mb-3">
{{ fields.text(form.name, true, true) }}
</div>
<div class="mb-3">
<label for="phone_type" class="form-label">Phone numbers:</label>
<div id="phoneFields">
{% for phone in form.phone_numbers.entries %}
<div class="phoneField">
<div class="row">
<div class="col-8 mb-2">
{{ fields.text(phone.form.number, false, false) }}
</div>
<div class="col-4">
{{ fields.select(phone.form.type_) }}
</div>
</div>
</div>
{% endfor %}
</div>
<div class="form-text">
<a href="#" onclick="javascript:return addPhone()">Add another</a>
</div>
</div>
<div class="row form-group">
<div class="col-3">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
function addPhone() {
const firstPhone = document.querySelector('.phoneField');
const newPhone = document.createElement('div');
newPhone.classList.add('phoneField');
newPhone.innerHTML = firstPhone.innerHTML;
const newIndex = document.querySelectorAll('.phoneField').length;
const number = newPhone.querySelector('#phone_numbers-0-number');
number.id = `phone_numbers-${newIndex}-number`;
number.name = number.id;
number.value = '';
const type = newPhone.querySelector('#phone_numbers-0-type_');
type.id = `phone_numbers-${newIndex}-type_`;
type.name = type.id;
type.value = 'home';
document.getElementById('phoneFields').appendChild(newPhone);
document.querySelector('.phoneField:last-child input').focus();
return false;
}
Источник: miguelgrinberg.com