Search code examples
javascriptconditional-operatorotree

otree: ask a question depending on the answer to a previous question (on the same page)


I would like to incorporate a question in Otree that might or might not be asked depending on a previous question. Here is a very simple example:

Question 1: What is your main occupation: A. Work. B. Student. C. Unemployed

Question 2 (ONLY ASKED IF the answer to "Question 1" is "A. Work"): what industry do you work on? A. Transportation B. Mining C. Other

I have managed to do this when Question 1 and Question 2 are on different pages (see code below). However, I would like to have questions 1 and 2 on the same page. Any insights on how I can do this? (I am a beginner using otree/javascript)

from otree.api import *

doc = """
'other' option
"""


class C(BaseConstants):
    NAME_IN_URL = 'option_other'
    PLAYERS_PER_GROUP = None
    NUM_ROUNDS = 1


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    pass


class Player(BasePlayer):
    occupation = models.StringField(label='main occupation?',choices=['Work', 'Student', 'Unemployment'])
    industry = models.StringField(label='what industry do you work on?', choices=['transportation','mining','others'])


# PAGES
class MyPage(Page):
    form_model = 'player'
    form_fields = ['occupation']


class MyPage2(Page):
    @staticmethod
    def is_displayed(player: Player):
        return player.occupation == 'Work'

    form_model = 'player'
    form_fields = ['industry']


page_sequence = [MyPage, MyPage2]

Solution

  • To show a question depending on the answer to another question on the same page, you need a little javascript (as you might have guessed since your question is tagged accordingly). This javascript code can be integrated directly into the HTML template (Page.html), for example like this:

    {{ block styles }}
    <style>
        .do-not-show {
            display: none;
        }
    </style>
    {{ endblock }}
    
    {{ block content }}
        {{ formfield "occupation" }}
    
        <div id="industry-box" class="do-not-show">
            {{ formfield "industry" }}
        </div>
    
        {{ next_button }}
    {{ endblock }}
    
    {{ block scripts }}
    <script>
        let industry_box = document.getElementById("industry-box");
        let industry_select = document.getElementById("id_industry");
        let occupation_select = document.getElementById("id_occupation");
    
        occupation_select.addEventListener("change", function() {
            if (occupation_select.value == "Work") {
                industry_box.classList.remove("do-not-show");
                industry_select.required = true;
            } else {
                industry_box.classList.add("do-not-show");
                industry_select.required = false;
            }
        });
    </script>
    {{ endblock }}
    

    To explain: First, let's hide the second question by wrapping it in a box and creating a CSS class that ensures that this box is not displayed. And then in the javascript block we create an event listener that reacts every time an answer is selected on the first question. If the answer is "Work", we'll display the second question by removing our CSS class. If the answer has a different value, we add our CSS class and hide the question (if it's not already hidden). If you want the second question to be optional (rather than mandatory), you can just remove this two lines: industry_select.required = true/false;.

    It is also important that you add blank=True in the field for the second question in the player model. Otherwise, otree will always expect an answer to this question and throw an error message if the question isn't answered (because a player never saw it, for example):

    class Player(BasePlayer):
        
        occupation = models.StringField(
            label='main occupation?',
            choices=['Work', 'Student', 'Unemployment']
        )
        
        industry = models.StringField(
            label='what industry do you work on?',
            choices=['transportation','mining','others'],
            blank=True
        )
    

    And of course both questions have to be included as form fields in the class of your page:

    class MyPage(Page):
        form_model = 'player'
        form_fields = ['occupation', 'industry']