web-dev-qa-db-fra.com

remplir le champ de sélection WTForms en utilisant la valeur sélectionnée dans le champ précédent

Nouveauté: essayer de créer une application à la suite d'un didacticiel Flask bien connu, à l'aide de Flask-bootstrap, Flask-wtforms, Jinja, etc.

J'ai un formulaire avec 2 champs sélectionnés et un bouton. 

class Form(FlaskForm): 
    school_year = SelectField('School year', choices=some_tuples_list)
    category = SelectField('Category', choices=[]) 
    submit = SubmitField('submit')

Je souhaite que seul le premier champ soit pré-rempli et que l'autre soit rempli (côté client?) En fonction de la valeur sélectionnée du champ précédent.

Dans le modèle, j'essaie quelque chose comme 

{{ form.school_year(**{"onchange":"getCategories()"}) }}

ce qui fonctionne bien (à condition que je retourne la liste de tuples pour remplir le champ suivant, en utilisant le javascript et la route appropriés), mais je veux quelque chose comme ce qui suit

{{ wtf.form_field(form.school_year(**{"onchange":"getCategories()"})) }}

qui ne fonctionne pas (erreur: l'objet wtforms.widgets.core.HTMLString 'n'a pas d'attribut' flags ')

Donc, je suppose que ma question est vraiment: comment puis-je implémenter un événement onChange sur ce champ de formulaire wtf? (Et est-ce ce que je dois faire, ou existe-t-il un moyen d'utiliser la fonction de vue?)

Merci d'avance.

9
fkaralis

Voici un exemple d'implémentation de cette logique pour travailler avec la fonctionnalité native de WTForms. Le truc ici, c'est que si vous voulez utiliser la validation WTForms, vous devez instancier le formulaire avec toutes les valeurs possibles, puis modifier les options disponibles en Javascript pour afficher les valeurs filtrées en fonction de l'autre sélection.

Pour cet exemple, je vais utiliser le concept d’États et de comtés (je travaille avec beaucoup de données géographiques, il s’agit donc d’une implémentation courante que je construis).

Voici mon formulaire, j'ai attribué des identifiants uniques aux éléments importants pour y accéder à partir de Javascript:

class PickCounty(Form):
    form_name = HiddenField('Form Name')
    state = SelectField('State:', validators=[DataRequired()], id='select_state')
    county = SelectField('County:', validators=[DataRequired()], id='select_county')
    submit = SubmitField('Select County!')

Maintenant, la vue Flask pour instancier et traiter le formulaire:

@app.route('/pick_county/', methods=['GET', 'POST'])
def pick_county():
    form = PickCounty(form_name='PickCounty')
    form.state.choices = [(row.ID, row.Name) for row in State.query.all()]
    form.county.choices = [(row.ID, row.Name) for row in County.query.all()]
    if request.method == 'GET':
        return render_template('pick_county.html', form=form)
    if form.validate_on_submit() and request.form['form_name'] == 'PickCounty':
        # code to process form
        flash('state: %s, county: %s' % (form.state.data, form.county.data))
    return redirect(url_for('pick_county'))

Une vue flacon pour répondre aux demandes XHR des comtés:

@app.route('/_get_counties/')
def _get_counties():
    state = request.args.get('state', '01', type=str)
    counties = [(row.ID, row.Name) for row in County.query.filter_by(state=state).all()]
    return jsonify(counties)

Et enfin, le Javascript à placer au bas de votre template Jinja. Je suppose que parce que vous avez mentionné Bootstrap, vous utilisez jQuery. Je suppose également que ceci est en ligne javascript, j'utilise donc Jinja pour renvoyer l'URL correcte du noeud final.

<script charset="utf-8" type="text/javascript">

$(function() {

    // jQuery selection for the 2 select boxes
    var dropdown = {
        state: $('#select_state'),
        county: $('#select_county')
    };

    // call to update on load
    updateCounties();

    // function to call XHR and update county dropdown
    function updateCounties() {
        var send = {
            state: dropdown.state.val()
        };
        dropdown.county.attr('disabled', 'disabled');
        dropdown.county.empty();
        $.getJSON("{{ url_for('_get_counties') }}", send, function(data) {
            data.forEach(function(item) {
                dropdown.county.append(
                    $('<option>', {
                        value: item[0],
                        text: item[1]
                    })
                );
            });
            dropdown.county.removeAttr('disabled');
        });
    }

    // event listener to state dropdown change
    dropdown.state.on('change', function() {
        updateCounties();
    });

});

</script>
21
abigperson

la réponse de abigperson m'a répondu ... finalement. 

La seule chose à ajouter, qui m'aurait évité des heures de hurlement à l'écran, est que les extraits de code Bootstrap actuels de leur site Web utilisent une version plus fine de JQuery. La version slim ne contient pas certaines des fonctions qu'abigperson utilise dans son code.

Glisser dans la version complète de jQuery a immédiatement réglé le problème. 

Un peu plus de lecture suggère que Bootstrap fonctionnera toujours très bien avec la version complète de jQuery afin que le jeu continue. 

0
Adam Alexander

La réponse de PJ Santoro est excellente. La mise à jour au chargement a été appelée, mais l'écouteur d'événements n'a pas fonctionné pour moi au début. Il s'est avéré que je n'avais pas remplacé «État» par mon propre identifiant de champ, car je pensais qu'il s'agissait d'un mot clé faisant référence à l'état du champ! D'oh! Donc, en cherchant d'autres options, j'ai trouvé que cela fonctionnait aussi, cela pourrait être utile pour quelqu'un:

    // event listener to state dropdown change
$('#state').change(function() {
    updateCounties();
});
0
Vegaskid