I am building a form using flask-wtf forms. I have a field where users can select multiple options. If the choice they want isn't in my list, I would like to allow users to select "other" and specify their option via a textfield.
I have found no way to do this in wtforms. I have googled around and cannot seem to find an answer anywhere. My scenario here is not at all unique, and I believe it would be quite common. So I think I could be thinking/going about it wrong.
question1_options = [('rice','rice'),('chips','chips'),('tuna','tuna'), ('other', 'other')]
question2_options = [('yes', 'yes'),('no', 'no')]
class MyForm(FlaskForm):
q1 = SelectMultipleField('favourite food?', choices=question1_options)
q2 = RadioField('do you like football', choices=question2_options)
What I want to achieve:
Combine SelectMultipleField
with a StringField
so that if the appropriate options are not in the list, users can select "other" and input what they would like.
I couldn't find method to combine two fields.
I think it should be two separated fields visible all time
q1_options = [('rice','rice'),('chips','chips'),('tuna','tuna')] #, ('other', 'other')]
class MyForm(FlaskForm):
q1 = SelectMultipleField('Favourite food?', choices=q1_options)
q1_other = StringField('Other favourite food?')
or it should use JavaScript
to show StringField
when you select other
.
I tried to create minimal working code.
Because it uses multiple select
so it needs more complex JavaScript
code.
from flask import Flask, render_template_string
from flask_wtf import FlaskForm
from wtforms import SelectMultipleField, StringField, RadioField
from wtforms.validators import DataRequired, Optional
# --- form ---
q1_options = [('rice','rice'),('chips','chips'),('tuna','tuna'), ('other', 'other')]
class MyForm(FlaskForm):
q1_list = SelectMultipleField('Favourite food?', choices=q1_options, validators=[DataRequired()])
q1_other = StringField('Other', validators=[Optional()])
# --- app ---
app = Flask(__name__)
app.config['SECRET_KEY'] = 'qwerty123456'
@app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm()
#print(dir(form))
print(form.validate_on_submit())
#if form.validate_on_submit():
if form.is_submitted():
print('list:', form.q1_list.data)
print('other:', form.q1_other.data)
return render_template_string('''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<form method="POST">
{{ form.q1_list.label }}<br>
{{ form.q1_list(onchange="get_selection(this);") }}<br>
{{ form.q1_other(style="display:none") }}<br>
<input type="submit" value="Go">
</form>
<script language="javascript">
//var q1_list = document.querySelector("#q1_list");
var q1_other = document.querySelector("#q1_other");
function get_selection(select){
//console.log(select);
//console.log(select.value);
//console.log(select.options);
var opts = [];
var opt;
var len = select.options.length;
for(var i = 0; i < len; i++) {
opt = select.options[i];
if (opt.selected) {
opts.push(opt.value);
}
}
//console.log(opts);
if(opts.includes('other')){
q1_other.style.display = 'block';
}else{
q1_other.style.display = 'none';
}
}
</script>
</body>
</html>
''', form=form)
# --- start ---
if __name__ == '__main__':
app.debug = True
app.run()
The same with addEventListener
instead of onchange
<form method="POST">
{{ form.q1_list.label }}<br>
{{ form.q1_list() }}<br>
{{ form.q1_other(style="display:none") }}<br>
<input type="submit" value="Go">
</form>
<script language="javascript">
//var q1_list = document.querySelector("#q1_list");
var q1_other = document.querySelector("#q1_other");
q1_list.addEventListener('input', function(event) {
//console.log(event);
//console.log(event.target.value);
//console.log(event.target.options);
var select = event.target;
//console.log(select.options);
var opts=[];
var opt;
var len = select.options.length;
for(var i = 0; i < len; i++) {
opt = select.options[i];
if (opt.selected) {
opts.push(opt.value);
}
}
//console.log(opts);
if(opts.includes('other')){
q1_other.style.display = 'block';
}else{
q1_other.style.display = 'none';
}
}, false);
</script>