pythonformsflaskflask-wtformswtforms

How to combine different field types in flask-wtf wtforms


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.


Solution

  • 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>