Conditional display of specific parts of a form

I’m building a form with Django and trying to get the following behaviour:

Among 4 possible answers on question genre, if the user chooses the answer AUTRE, display question genre_preciser and make the answer mandatory. Otherwise, do not display genre_preciser.

I’d like to use something similar to htmx, as my proficiency in Javascript is close to 0.

I’ve been asking the question to the htmx community and understand that on htmx side, I would have to deal with hx-get. However I’m rather lost from here: what URL do I have to pass? The whole form is on a single page, I don’t have any separate URL.

How would you do? Is there an easier way to do this?

Thanks in advance!

models.py

class Questionnaire(models.Model):

    class Genre(models.TextChoices): 
        FEMININ = 'Féminin', _('Féminin')
        MASCULIN = 'Masculin', _('Masculin')
        AUTRE = 'Autre', _('Autre (préciser)')
    genre = models.CharField(
        choices=Genre.choices,
        null=True,
        blank=False
    )
    genre_preciser = models.TextField(
        null=True,
        blank=True
    )

forms.py

class QuestionnaireForm(forms.ModelForm):
  
  class Meta:
    model = Questionnaire
    fields = ['genre', 'genre_preciser']

views.py

class CreerSoumission(CreateView):
    model = Questionnaire
    form_class = QuestionnaireForm
    template_name = 'template.html'

urls.py

urlpatterns = [
    path('', views.CreerSoumission.as_view(), name='soumettre'),
]

template.html

  <form action="" method="post">
    {% csrf_token %}
    <table>
    {{ form.as_table }}
    </table>
    <input type="submit" value="Submit">
  </form>

Keep in mind that if you modify the form in the browser, you will also need to know to do this in the view when you’re binding the submitted data to the form.

Yes, you could do this using HTMX, but I think you’re going to find that it’s not going to save you a lot of effort without doing some refactoring of your code, form, and templates.

When we have a requirement like this, we still find it easier to use JavaScript. (If you really want to avoid writing JavaScript and would prefer using a more declarative method, check out Alpine.js.)

What we do is create the form, with the additional field(s) in a div with a css class having a display:none. You can then attach an event listener to genre, and on a change event check the value. If the value is now AUTRE, remove that class from the div.

Your form’s clean method will then need to check the genre field to determine whether the genre_preciser field has been filled out.

Otherwise, to do this appropriately with HTMX, you’re looking at creating a new view for HTMX to call, a new form for this new field, and a template to render that field. (And you still need to create the clean method on the form as well as figure out how you’re going to bind the data when it’s POSTed.)

(You could simplify this a little by taking an approach similar to the JavaScript in that you define the form as having that field. Then, in processing the GET for the view, you remove that field from the form before rendering it. This handles the binding issue for the data as the field is always defined in the form, but that’s about all it does for you.)

There may be an easier way - but like I started out with, we’ve found it easiest in this situation just to use the JavaScript.

1 Like

Thank you Ken for your answer. I went the jQuery route and it ended up working fine with a dropdown list of choices (one single choice possible). Here’s the script I wrote for reference (and for others!):

$(document).ready(function() {
  const genre = $("[name='qdem01_genre']");
  const genrePreciser = $("[name='qdem01rx_genre_precis']").closest(".questionWrapper");
  genrePreciser.addClass('hidden');
  genre.on('change', function() {
    if (this.value === "Autre") {
      genrePreciser.removeClass('hidden');
    } else {
      genrePreciser.addClass('hidden');
    }
  });
});

However, I’m running now into a similar issue, but with a different type of field, and this approach doesn’t seem to work here.

The field qdem05_statut has type ModelMultipleChoiceField. I want to display the following field, qdem05b_statut_preciser, only when the value Autre : précisez is selected as a choice. I’ve been trying to do it with jQuery but with no success so far.

Here is the data (including the script at the end):

forms.py

class AdherentForm(forms.ModelForm):
    qdem05_statut = forms.ModelMultipleChoiceField(
        queryset=Statut.objects.order_by('id'),
        widget=forms.CheckboxSelectMultiple,
        label=('Quel est votre statut actuel ? (Plusieurs réponses possibles) *')
        )

    class Meta:
        model = Adherent
        fields = ['qdem05_statut', 'qdem05b_statut_preciser']

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('label_suffix', '')
        super(AdherentForm, self).__init__(*args, **kwargs)

models.py

class Statut(models.Model):
    name = models.CharField(
        max_length=100,
        unique=True,
        blank=False
    )

    def __str__(self):
        return self.name


class Adherent(models.Model):

    qdem05a_statut = models.ManyToManyField(
        Statut, 
        verbose_name="Quel est votre statut actuel ? (Plusieurs réponses possibles) *",
        related_name='adherent_statut'
    )
    qdem05b_statut_preciser = models.TextField(
        _('Préciser :'),
        null=True,
        blank=True
    )

template.html

   <div name="statut" class="questionWrapper" id="statut">

      <p>{{ form.qdem05_statut.errors }}</p>
      <h5>{{ form.qdem05_statut.label_tag }}</h5>
      <p>{{ form.qdem05_statut }}</p>
    </div>

    <div name="statut_preciser" class="questionWrapper" id="statut_preciser">
      <p>{{ form.qdem05b_statut_preciser.errors }}</p>
      <p>{{ form.qdem05b_statut_preciser }}</p>
    </div>

script.js

$(document).ready(function() {
    function toggleStatutPreciser() {
        var selectedValues = $('input[name="qdem05_statut"]:checked').map(function() {
            return this.value;
        }).get();

        if (!selectedValues.includes("Autres : précisez")) {
            $('#statut_preciser').hide();
        } else {
            $('#statut_preciser').show();
        }
    }

    toggleStatutPreciser();

    $('input[name="qdem05_statut"]').on('change', toggleStatutPreciser);
});

Any suggestions would be very welcome. Thanks!

Depending upon a couple of different factors, such as the possibility of a custom widget being used, your css selector may not be correct.

In a simple test case that I just looked at, the selector ended up being:
$('select[name="q05"]>option:selected')...

You’ll want to verify this in your own environment.

I tried this:

$(document).ready(function() {
    function toggleStatutPreciser() {
        var selectedValues = $('select[name="qdem05_statut"]>option:selected').map(function() {
            return this.value;
        }).get();

        if (!selectedValues.includes("Autres : précisez")) {
            $('#statut_preciser').hide();
        } else {
            $('#statut_preciser').show();
        }
    }

    toggleStatutPreciser();
    $('input[name="qdem05_statut"]').on('change', toggleStatutPreciser);
});

the element that has statut_preciser as id is hidden at load, but nothing happens when I select/check Autres : précisez

(I should mention also that qdem05_statut has the id statut)

Without seeing the actual html that has been rendered and sent to the browser, there’s no way for us to know what you need to use here. There’s not going to be a generic answer that we can provide.

Also keep in mind that your selector for the event needs to match as well.

Right. Of course :man_facepalming: Thanks for taking the time to clearly state the obvious :slight_smile:

Using firefox dev tools inspector, here’s what the targeted checkbox looks like:

<label for="id_qdem05_statut_18">
  <input type="checkbox" name="qdem05_statut" value="20" id="id_qdem05_statut_18">
   Autres : précisez
 </label>

So the id is id_qdem05_statut_18, which is new to me but makes sense.

I tried to modify the script as follows but it still doesn’t seem to work:

$(document).ready(function() {
    function toggleStatutPreciser() {
        var selectedValues = $('#id_qdem05_statut_18 option:selected').map(function() {
            return this.value;
        }).get();

        if (!selectedValues.includes("Autres : précisez")) {
            $('#statut_preciser').hide();
        } else {
            $('#statut_preciser').show();
        }
    }

    toggleStatutPreciser();
    $('#id_qdem05_statut_18').on('change', toggleStatutPreciser);
});

I tried another approach, with no success either yet:

$(document).ready(function() {
    function toggleStatutPreciser() {
// Here I alternatively asked for the value "Autres : précisez" or for 20
        var isChecked = $('#id_qdem05_statut_18 input[value="Autres : précisez"]').is(':checked');

        if (isChecked) {
            $('#statut_preciser').show();
        } else {
            $('#statut_preciser').hide();
        }
    }

    toggleStatutPreciser();
    $('#id_qdem05_statut_18 input').on('change', toggleStatutPreciser);
});

So this is different than a regular multi-select list - you’re rendering each option as a checkbox which does change things.

You don’t need to “search” for the element, you can reference it directly to see whether or not it’s checked.

I’m not sure you can necessarily guarantee that the id is going to remain a constant - especially if other options are added to the set. I believe the selector you may want would be:
$('input[name="qdem05_statut"][value="20"]:checked')
You could then check the length attribute of that to see whether a value was returned.
(I suggest you check this out in your browser’s console to make sure I’ve got the syntax right - I’m winging this right now.)