Question modifiers

There are a number of optional modifiers that can be included in question blocks to control the appearance or behavior of the question. Some of these can also be applied to other types of blocks that set variables, such as code blocks.

Including audio

audio: schumann-clip-1.mp3
question: You need to relax.
subquestion: |
  Listen to some Schumann, and then
  proceed.
field: user_is_relaxed
Screenshot of audio example

The audio modifier allows you to add audio to your questions. An audio player will appear above the question, and the user can press play to hear the sound.

The filename can be constructed with Mako. A plain file path will be assumed to point to a file in the static directory of the package in which the YAML file resides. A package reference may also be included: e.g., docassemble.demo:data/static/schumann-clip-3.mp3. A URL beginning with http or https may also be provided.

You can also play uploaded files:

question: Please record some audio.
fields:
  - MP3 file: user_audio_file
    datatype: file
---
question: |
  Let's listen to what you recorded.
audio: ${ user_audio_file }
mandatory: True
Screenshot of upload_audio example

Note that in this example, we use file as the datatype, which is the standard way to upload files. You can also use the datatype of microphone, which in some browsers (mostly on mobile platforms) will launch an audio recording app to create the file to upload.

question: Please record some audio.
fields:
  - MP3 file: user_audio_file
    datatype: microphone
---
question: |
  Let's listen to what you recorded.
audio: ${ user_audio_file }
mandatory: True
Screenshot of upload_audio_microphone example

docassemble uses the HTML5 audio tag to allow users to play the audio. Not all browsers support every type of audio file. In order to make your audio files accessible to the greatest number of users, then if you provide static audio files, you should include files in both mp3 and ogg format.

For example, if your audio specifier points to a file, such as nyc_question.mp3, then your interview package should contain a file called nyc_question.mp3 in the data/static directory. If you also include an OGG version of this audio file, called nyc_question.ogg, in the same directory, then docassemble will make both files available to the user, and the user’s browser will use whichever file works. In your audio specifier, you can refer to either the mp3 or the ogg file.

Or, if your mp3 and ogg alternatives are located in different directories, you can do this:

---
question: Are you traveling to New York City?
yesno: going_to_nyc
audio:
  - mp3/nyc_question.mp3
  - ogg/nyc_question.ogg
---

Or, if you are using hyperlinks to files on another server, you can include different versions by doing something like this:

---
question: Are you traveling to New York City?
yesno: going_to_nyc
audio:
  - http://example.com/files/audio/51/nyc_question.mp3
  - http://example.com/files/audio/23/nyc_question.ogg
---

If you refer to an uploaded file, docassemble will take care of providing both mp3 and ogg versions. When users upload an audio file, docassemble tries to convert it to the appropriate formats. For this to work, ffmpeg and pacpl must be installed on your system. Currently, docassemble can handle audio files uploaded in mp3, ogg, 3gpp, and wav formats. If you need to be able to process another type of audio file, docassemble’s source code can probably be modified to support that audio type.

Note that there a number of limitations to playing audio in browsers. For example, older Android devices will not play audio retrieved through https, but will play the same audio retrieved through http.

See special variables for information about docassemble’s automatic text-to-speech features.

Including video

The video specifier is just like the audio specifier except that it displays a video instead of an audio file.

---
question: Are you traveling to New York City?
yesno: going_to_nyc
video: nyc_tourism.mp4
---

docassemble uses the HTML5 video tag to allow users to play the audio. Just as you should include both mp3 and ogg audio files, you should include both mp4 and ogv video files, so that users of many different browsers will all be able to see your videos. These are the two formats that the HTML5 video tag most widely supports.

If you refer to an uploaded video file, docassemble will take care of providing both mp4 and ogv versions. When users upload a video file, docassemble tries to convert it to the appropriate formats. For this to work, ffmpeg and pacpl must be installed on your system. Currently, docassemble can handle videos uploaded in mp4, ogv, and mov formats. If you need to be able to process another type of video, docassemble’s source code can probably be modified to support that video type.

You can also use the video specifier to embed YouTube videos. For example, if you want to embed a YouTube video and the URL for the video is https://www.youtube.com/watch?v=9bZkp7q19f0 or https://youtu.be/9bZkp7q19f0, you would write something like this:

field: ready_to_proceed
question: |
  Welcome to the interview.
subquestion: |
  Please watch this introductory video
  before proceeding with the interview.
video: |
  [YOUTUBE 9bZkp7q19f0]
Screenshot of video example

[YOUTUBE ...] assumes that the aspect ratio of the vide is 16:9. If the aspect ratio of the video is 4:3, you can use [YOUTUBE4:3 ...]. You can also explicitly state that the aspect ratio is 16:9 by using [YOUTUBE16:9 ...].

Providing help text to users

question: |
  How much money do you wish
  to seek in damages?
fields:
  - Money: damages_sought
    datatype: currency
help: |
  If you are not sure how much
  money to seek in damages, just ask
  for a million dollars, since you
  want ${ defendant } to suffer.
Screenshot of help-damages example

In the web app, users can use the navigation bar to toggle between the “Question” tab and the “Help” tab. The contents of the “Help” tab consist of the contents of any help specifiers in the question being presented, followed by the contents of any interview help blocks contained within the interview.

You can add audio to your help text:

question: |
  How much money do you wish
  to seek in damages?
fields:
  - Money: damages_sought
    datatype: currency
help:
  content: |
    If you are not sure how much
    money to seek in damages, just ask
    for a million dollars, since you
    want ${ defendant } to suffer.
  audio: |
    message_re_damages.mp3
Screenshot of help-damages-audio example

You can also add video to help text using the video specifier.

When interview help is available but question-specific help is not available, the help tab is merely labeled “Help.” When question-specific help is available, the help tab is bright yellow and is marked with a star. If you want the label to be something other than “Help”, you can add a label inside the help specifier:

question: |
  How much money do you wish
  to seek in damages?
fields:
  - Money: damages_sought
    datatype: currency
help:
  label: |
    More info
  content: |
    If you are not sure how much
    money to seek in damages, just ask
    for a million dollars, since you
    want ${ defendant } to suffer.
Screenshot of help-damages-label example

If the question help button feature is enabled, and question-specific help is available, a “Help” button will be available on the button bar, which when pressed will show the help tab. The button label is “Help” by default, but if a label is provided to the question-specific help, the button will bear this label instead. When a help button is present, the help tab in the navigation bar will always be labeled “Help,” and it will never be highlighted in yellow.

The default label “Help” can be changed on a per-interview basis. If you set an interview help initial block and provide a label as part of it, the value of this label will be used instead of “Help” as the name of the “Help” tab in the navigation bar. You can also use screen parts methods to set this value (which is called help label).

Adding images to question: decoration

decoration: kids
question:
  Do you have children?
yesno: has_children
Screenshot of decoration example

The decoration modifier adds an icon to the right of the question text. In the example above, kids has been defined in an image sets or images block.

By default, if a decoration modifier refers to an image that has not been defined in an image sets or images block, users will see an error message. However, if you set the use font awesome directive in the Configuration to True, then any reference to an image not defined with image sets or images will be treated as the name of a Font Awesome icon.

mandatory: True
decoration: chart-bar
question: |
  Third quarter metrics
subquestion: |
  We are making more money
  :far-fa-money-bill-alt: than
  we did in the second quarter.

  So you can sleep well tonight! :bed:
Screenshot of font-awesome example

This method also works with inline icons.

Adding JavaScript: script

If you know how to write JavaScript and CSS, you can add JavaScript code and CSS formatting to a question.

question: |
  Do you want to build a snowman?
subquestion: |
  <span id="beg"></span>
yesno: wants_to_build_snowman
script: |
  <script>
    setTimeout(function(){
      $("#beg").html("Pretty please?");
    }, 3000);
  </script>
Screenshot of script example

This JavaScript will be executed when the screen loads.

To add JavaScript or CSS to all questions, you can use a features block to include JavaScript and CSS files on the web page.

Note that the docassemble front end is a “single page application,” which means that when the user presses the Continue button or the Back button, the page does not reload; rather, an Ajax request is sent to the server and then the DOM is redrawn. The code in the script modifier is run whenever the question is shown. That means that the script code may run more than once in the user’s browser session.

Therefore, you should not use code in a script modifier to attach a daPageLoad listener, because that could mean that the listener will be attached more than once. Therefore, only attach daPageLoad listeners from a JavaScript file that you include using the javascript feature.

Adding CSS: css

The css modifier contains raw HTML that will be appended to the HTML <head>.

question: |
  Do you want to build a snowman?
yesno: wants_to_build_snowman
css: |
  <style>
    body {
      background-image: url('${ url_of('docassemble.base:data/static/snowman.png') }');
      background-repeat: repeat;
    }
  </style>
Screenshot of css example

It is best only to include CSS that is tied to specified HTML elements you include in your questions, rather than include CSS that has global effects (like the example above). Because of the way docassemble interviews work, CSS applied in one question will affect later questions until the screen is reloaded.

In the vast majority of situations, you should not use the css modifier, but should instead write a CSS file that you include using css in a features block.

The progress bar

A docassemble interview can be configured to show a progress bar. This will show the user a progress indicator to give the user a sense of how much longer the interview will take.

The progress along the bar at any question needs to be set with the progress modifier. For example:

features:
  progress bar: True
---
question: Are you doing well?
yesno: user_is_well
progress: 20
---
question: Done with the interview.
subquestion: |
  % if user_is_well:
  I am glad you are doing well.
  % else:
  I am sorry to hear that!
  % endif
progress: 100
mandatory: True
Screenshot of progress example

The value of progress needs to be a number between 0 and 100. If the value is zero, the progress bar is hidden for the current question. If the value is greater than 100, a full progress bar will be shown. If the value is None (null in YAML), then the progress bar will be hidden and will not advance until it is set to something else.

You can also control the progress meter with the get_progress() and set_progress() functions.

If the progress bar is enabled and the interview encounters a question that does not have a progress setting, the progress bar will advance automatically. The amount by which the progress bar automatically advances gets smaller as the progress bar gets closer to 100%.

As a result, you do not need to attach a progress setting to every question; you can just set progress on a few questions, and let the automatic advancing mechanism take care of increasing the progress.

If the interview reaches a question with a progress setting that is less than the current position of the progress bar, the position of the progress bar will stay the same. This ensures that the user does not see the progress bar go backward.

If you want the progress bar to go back or reset, you can use the set_progress() function to force the progress bar setting to a particular value. For example:

mandatory: True
code: |
  first_part_done
  second_part_done
  final_screen
---
code: |
  user_is_well
  user_is_bigger_than_a_breadbox
  ready_for_second_part
  set_progress(0)
  first_part_done = True
---
code: |
  user_likes_turnips
  user_likes_clownfish
  second_part_done = True
Screenshot of progress-multi example

The section navigation bar

A docassemble interview can be configured to show a left-hand navigation bar on screens large enough to show one. The navigation bar will contain a list of the sections in the interview, as specified in the sections initial block or using the nav.set_sections() function. In the navigation bar, the current section will be highlighted.

Adding the section modifier to a question will update the current section when the interview asks the question. This section will continue to be the current section until another question is reached that contains a section modifier that specifies a different section.

As explained in the documentation for the sections initial block, you have the option of referring to a section by a keyword that is different from the name of the section that is displayed to the user. If you are using this feature, your section modifier needs to refer to the keyword, not the displayed name.

sections:
  - Introduction
  - About you:
    - Contact info
    - Demographics
  - Preferences
  - Conclusion
---
features:
  navigation: True
  progress bar: True
---
mandatory: True
code: |
  menu_items = [ action_menu_item('Roadmap', 'road_map') ]
---
initial: True
code: |
  if returning_user(minutes=0.5):
    welcome_back
---
mandatory: True
question: |
  Welcome to the interview
subquestion: |
  If you are not on a
  smartphone-sized device,
  you should see a navigation
  bar to the left.
field: sees_nav_bar
---
mandatory: True
question: |
  I am going to ask you some
  questions about yourself.
field: intro_to_about_you
section: About you
---
mandatory: True
question: |
  What is your name?
fields:
  - First Name: first_name
  - Last Name: last_name
section: Contact info
---
mandatory: True
question: |
  What is your e-mail address?
fields:
  - E-mail: email_address
    datatype: email
---
mandatory: True
question: |
  What is your gender?
field: gender
choices:
  - Male
  - Female
  - Something else
section: Demographics
---
mandatory: True
question: |
  What kind of belly button
  do you have?
subquestion: |
  To see what a user would
  see after returning to
  the interview after a period
  of absence, try waiting
  thirty seconds, then
  [click into the
  interview](${ interview_url(local=True) }).

  In addition, there is a similar
  screen available on the Menu in the
  upper-right, under "Roadmap."
field: belly_button
choices:
  - Innie
  - Outie
---
mandatory: True
question: |
  What is your favorite fruit?
fields:
  - Favorite fruit: favorite_fruit
section: Preferences
---
mandatory: True
question: |
  What is your favorite vegetable?
fields:
  - Favorite vegetable: favorite_vegetable
---
progress: 100
mandatory: True
question: Thank you.
subquestion: |
  ${ first_name },

  Your answers mean a lot to me.
  
  I am going to go eat some
  ${ favorite_vegetable }
  now.
section: Conclusion
---
event: welcome_back
question: |
  Welcome back!
subquestion: |
  You are currently in the
  **${ nav.get_section(display=True) }**
  section.

  ${ nav }

  Press "Continue" to pick up
  where you left off.
buttons:
  Continue: continue
---
event: road_map
question: |
  Roadmap
subquestion: |
  You are currently in the
  **${ nav.get_section(display=True) }**
  section.

  ${ nav }

  Press "Continue" to resume the
  interview.
buttons:
  Continue: continue
Screenshot of sections example

The section can include Mako templating.

You can also set the current section using the nav.set_section() function.

Disable the back button: prevent going back

Normally, docassemble allows the user to click the back button to get back to earlier steps in the interview. Sometimes, it is necessary to prevent the user from doing so.

If you add a prevent going back specifier to a question, the web app will not offer the user a back button while showing the question.

prevent going back: True
question: |
  Your application for ${ service }
  has been submitted.
mandatory: True
Screenshot of prevent-going-back example

There is also a prevent_going_back() function that accomplishes the same thing from Python code. This may be more useful than the prevent going back modifier if the need to prevent the user from clicking the back button depends on the outcome of a process.

Adding a back button inside the question

You can add a “Back” button to the buttons at the bottom of the screen by setting the back button modifier.

question: Is the sky blue?
yesno: sky_is_blue
back button: |
  not user_is_well
Screenshot of question-back-button-sometimes example

If back button is set to True or to Python code that evaluates to a true value, then the button will be shown.

You can configure this on an interview-wide basis by setting the question back button feature.

Changing the text of the back button

When you add a “Back” button to the buttons at the bottom of the screen by setting the back button modifier or the question back button feature, you can change the text of the button using the back button label modifier.

question: Is the sky blue?
yesno: sky_is_blue
back button: True
back button label: |
  Wait, go back
Screenshot of question-back-button-sometimes-label example

The text of the label can include Mako templating.

You can change the back button in the upper-left corner by using the corner back button label modifier.

For information about other ways to set a default value for back button labels, see the screen parts section.

Vocabulary terms and auto terms

Using the modifiers terms or auto terms, you can specify the definitions of particular vocabulary terms, and docassemble will turn them into green hyperlinks. When the user clicks on the hyperlink, a popup appears with the word’s definition.

You can define the vocabulary terms using terms and then put curly brackets around the instances of the words that you want to become hyperlinks.

question: Have you ever met a {creeper}?
subquestion: |
  If you have met a {zombie pigman}, you
  have almost certainly met a creeper.
yesno: met_a_creeper
terms:
  creeper: |
    A tall ${ creeper_color } creature
    that explodes if you get too close.
  zombie pigman: |
    A harmless creature who carries a gold
    sword.
Screenshot of question-terms example

Alternatively, you can define the vocabulary terms using auto terms, in which case you do not need to use curly brackets, and the terms will be highlighted in green every time they appear in the question.

question: Have you ever met a creeper?
subquestion: |
  If you have met a zombie pigman, you
  have almost certainly met a creeper.
yesno: met_a_creeper
auto terms:
  creeper: |
    A tall ${ creeper_color } creature
    that explodes if you get too close.
  zombie pigman: |
    A harmless creature who carries a gold
    sword.
Screenshot of question-autoterms example

Using auto terms can lead to ambiguities, so it is generally better to use terms if you can. If you have two terms, green apple and apple, then auto terms will try to make a term within a term, which will lead to unpredictable behavior. auto terms can also cause problems if the term is contained inside of HTML. So while auto terms is convenient when it works, it might not always work, so using terms is preferable.

If you want to refer to a term but you want the text of the hyperlink to be different from the name of the term, you can use the pipe character | and write the alternate text after the |.

question: Have you ever met a {creeper}?
subquestion: |
  If you have met a
  {zombie pigman|zombified pigman},
  you have almost certainly met a creeper.
yesno: met_a_creeper
terms:
  creeper: |
    A tall ${ creeper_color } creature
    that explodes if you get too close.
  zombie pigman: |
    A harmless creature who carries a gold
    sword.
Screenshot of question-terms-alternate example

Alternatively, in your definition of the terms, you can specify that multiple phrases should be associated with a single definition. You write your terms as a list of dictionaries, and if a dictionary has two keys, phrases and definition, where phrases refers to a list of terms and definition refers to a definition, that definition will be used for each of the phrases:

terms:
  - phrases:
      - charged creeper
      - creeper
    definition: |
      A tall green creature that explodes if
      you get too close.
  - zombie pigman: |
      A harmless creature who carries a gold
      sword.

If you want vocabulary terms to be highlighted throughout the interview, not just for a specific question, you can use terms and auto terms as initial blocks. You can also define interview-wide terms using Python code by calling the update_terms() function.

The language of the question

---
question: |
  What is the meaning of life?
fields:
  - Meaning of life: meaning_life
---
language: es
question: |
  ¿Cuál es el significado de la vida?
fields:
  - Significado de la Vida: meaning_life
---

docassemble’s language support allows a single interview to ask questions different ways depending on the user’s language. You can write questions in different languages that set the same variables. docassemble will use whatever question matches the active language.

The value of language must be a two-character lowercase ISO-639-1 or ISO-639-3 code. For example, Spanish is es, French is fr, and Arabic is ar.

For more information about how to set the active language, see language support.

Instead of explicitly setting a language for every question, you can use default language to apply a particular language to the remaining questions in the file (see initial blocks).

Changing the continue button label

Some types of questions feature a “Continue” button. If you want the label on the button to be something other than the word “Continue,” add a continue button label modifier.

question: |
  What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
continue button label: Keep going
Screenshot of continue-button-label example

The types of questions that feature a “Continue” button include:

This modifier also allows you to customize the “Done” button that appears in signature questions.

For information about other ways to set a default value for the “Continue” button label, see the screen parts section.

Changing the continue button color

In addition to modifying the label of the “Continue” button, you can set its color to one of the Bootstrap colors.

question: |
  What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
continue button label: Keep going
continue button color: success
Screenshot of continue-button-color example

Reusable questions: generic object

generic object: Individual
question: |
  Does ${ x } like cats?
yesno: x.likes_cats
Screenshot of generic-object example

generic object is a very powerful feature in docassemble that allows interview developers to express questions in general terms.

The above example will cause docassemble to ask “Does Sally Smith like cats?” if the interview logic calls for neighbor.likes_cats and neighbor is an object of type Individual whose name has been set to “Sally Smith.” The same question will also ask “Does William Jones like cats?” if the interview logic calls for teacher.likes_cats, and teacher is an object of type Individual whose name has been set to “William Jones.”

x is a special variable that should only be used in generic object questions. The above question definition tells docassemble that if it ever needs the likes_cats attribute for any object of type Individual, it can get an answer by asking this question.

If your interview needs a definition for spouse.likes_cats, where spouse is an object of type Individual, docassemble will first look for a question that offers to define spouse.likes_cats. If no such question exists, it will then look for a question that offers to defined x.likes_cats, where the generic object is Individual. If no such question exists, it will look for generic object questions for the parent types of Individual. The variables that will be sought, in the order in which they will be sought, are:

  • spouse.likes_cats
  • x.likes_cats where generic object is Individual.
  • x.likes_cats where generic object is Person.
  • x.likes_cats where generic object is DAObject.

This way, you can provide layers of generic object blocks to handle special cases as well as general cases, based on the object type. For example, suppose your interview uses objects of type Individual, Organization, and Person. An Individual is a special type of Person, and an Organization is also a special type of Person. Suppose you have a general way of asking for a mailing address (“What is so-and-so’s address?”), but you want to have a special way of asking the question if you need the mailing address of an Organization (e.g., “What is ABC Incorporated’s primary place of business?”). You would write a question with generic object: Person for the general case, and a question with generic object: Organization for the special case. The general question would be used for objects of type Individual and Person, and the special question would be used for objects of type Organization.

You can also use generic object code blocks in a fallback arrangement to capture special cases within object types. Suppose you have a function retrieve_ein() that can automatically determine an organization’s Employer Identification Number (EIN), but only for organizations organized as non-profits. For organizations not organized as non-profits, you will need to ask the user for the EIN. You could use the following two blocks to accomplish this:

generic object: Organization
question: |
  What is the EIN of ${ x }?
fields:
  - EIN: x.ein
---
generic object: Organization
code: |
  if x.tax_status == '501c3':
    x.ein = retrieve_ein(x.name)
Screenshot of generic-object-ein example

Whenever the .ein of an organization is needed, the code block will be run, but the attribute will not be set if the organization is not a non-profit. In that case, docassemble will notice that the attribute is still not defined, and it will “fall back” to the question that asks the user to manually enter the EIN.

As explained in the fallback section of the documentation, the order in which these two blocks appear in the YAML file matters; the code block will be tried first only if it appears later in the YAML file than the question block.

The generic object modifier can be used on any block that sets a variable, including question, code, template, table, attachment, and objects, objects from file, data, data from code.

A similar feature to generic object and its special variable x is the special index variable i. For more information about this feature, see the index variable documentation and the documentation in the groups section.

The role of the question

---
role: advocate
question: Is the client's explanation a sound one?
subquestion: |
  ${ client } proposed the following explanation:
  
  > ${ explanation }

  Is this a legally sufficient explanation?
yesno: explanation_is_sound
---

If your interview uses the roles feature for multi-user interviews, the role modifier in a question block will tell docassemble that if it ever tries to ask this question, the user will need to have a particular role in order to proceed.

role can be a list.

role:
  - advocate
  - supervisor

In this case, the user’s role can either “advocate” or “supervisor” in order to be asked the question.

If the user does not have an appropriate role, docassemble will look for a question in the interview in which event has been set to role_event.

Automatically reload the screen

To cause the screen to reload in the web browser after a number of seconds, use the reload modifier.

reload: True
question: |
  You have viewed this screen
  ${ counter }
  ${ noun_plural('time', counter) }.
field: acknowledged
Screenshot of reload example

If you set reload to True, the screen on which the question is asked will reload every 10 seconds. To use a different number of seconds, set reload to the number of seconds you wish to use. E.g.,

reload: 5

Since it is not good to reload the screen too quickly, you cannot use a number of seconds less than four. If the number of seconds is less than four, four seconds will be used as the number of seconds.

You can use Mako to determine the number of seconds. If the reload value evaluates to False or None, the screen will not reload.

Tag a block with a unique id

In some situations, you may need to tag a block in your interview with a unique ID. You can use the id specifier to do so.

---
id: initialize
mandatory: True
code: |
  initial_value = 48
---

In the absence of an id specifier, docassemble would refer to a block like this with a name like Question_3 (if this block was the third block in the interview). But with id set to initialize, docassemble will internally refer to this block with the ID initialize.

In most cases, your blocks do not need to have unique IDs. However, there are some features in docassemble, such as the Google Analytics integration feature, the CSS custom class feature, and the changing order of precedence feature discussed below, all of which use id specifiers.

Also, in some situations, it can be important to tag your interview blocks with a unique name that does not change when the blocks in the interview YAML file change. This is because when docassemble stores interview answers, it not only stores the current state of the interview variables, but it also stores information about which mandatory blocks have run to completion. When it does so, it tracks the block using the ID for the block. If the IDs are arbitrary names like Question_3, users could encounter problems

For example, think about what would happen if a user started working an interview on April 3, and got half-way through, and then saved her answers and logged out, intending to log back in on April 10. Then suppose that on April 8, you install a new version of the interview, adding new functionality. When the user logs back in on April 10, her interview answers might not be compatible with your new version of the interview. For example, suppose that on April 3, the mandatory code block known as Question_12 ran to completion. But when the user logs in on April 10 and resumes the interview, the code block formerly known as Question_12 is now known as Question_14. When docassemble evaluates her interview session, it will determine that the mandatory code block known as Question_14 has not run yet, so it will run that code block. This might cause information in the user’s session to be overwritten. You can avoid problems like these by tagging your code blocks with id tags, so that the names of the blocks do not change between versions of your interview.

Another way to avoid problems with the impact of software upgrades on existing sessions is to use a different interview YAML file for each version of an interview. So a user that starts docassemble.tax:data/questions/tax-controversy-v2.yml will always use the same YAML file, even when users who started later are using docassemble.tax:data/questions/tax-controversy-v3.yml.

The id of a question needs to be set in order to use the forget_result_of() function.

Google Analytics IDs

If you are using the Google Analytics integration feature, by default, the id will be used as part of the pseudo-path sent to Google Analytics. If you would rather use a different ID, you can explicitly set a ga id:

id: lead certification
ga id: landlordLeadCert
question: |
  Does your landlord have a valid lead certification?
yesno: lead_certification_exists

Segment IDs

If you are using the Segment integration feature, by default, the id will be used as part of the pseudo-path sent to Google Analytics. If you would rather use a different ID, you can explicitly set a segment id:

id: lead certification
segment id: landlordLeadCert
question: |
  Does your landlord have a valid lead certification?
yesno: lead_certification_exists

You can also send Segment messages with arguments, using the segment specifier pointing to a dictionary with keys for id and arguments:

id: lead certification
segment:
  id: landlord
  arguments:
    certification: lead
question: |
  Does your landlord have a valid lead certification?
yesno: lead_certification_exists

Breadcrumb name

If you are using a DABreadCrumbs object to display breadcrumbs to the user, you can use the breadcrumb modifier on any question to indicate the title of the question as it should appear in the breadcrumbs display. Mako can be used in a breadcrumb modifier. If no breadcrumb modifier is present, the text of the question specifier is used instead. For more information about using breadcrumbs, see the documentation for DABreadCrumbs.

Manually indicating that a block sets a variable

Usually, docassemble can figure out which variables a block is capable of defining. If a code block consists of:

---
code: |
  if hell.temperature_in_celcius == 0:
    claim_is_valid = True
---

then docassemble will try to run it if it needs a definition for claim_is_valid. Sometimes, however, docassemble needs a hint.

You can explicitly indicate that a block sets a variable using sets:

---
sets: claim_is_valid
code: |
  if hell.temperature_in_celcius == 0:
    claim_is_valid = True
---

It also accepts multiple values:

---
sets: 
  - claim_is_valid
  - type_of_claim
code: |
  if hell.temperature_in_celcius == 0:
    claim_is_valid = True
    type_of_claim = 'tort'
---

Indicating that a block only sets certain variables

The only sets modifier acts like sets, but also indicates scan for variables: False.

The following two blocks are equivalent.

scan for variables: False
sets: property_assessed
code: |
  if total_property > 50000
    property_deduction_available = False
  property_assessed = True
only sets: property_assessed
code: |
  if total_property > 50000
    property_deduction_available = False
  property_assessed = True

Indicating that a block is permitted to set a variable

If you have enabled restrict input variables in your Configuration, then you will not be able to use custom JavaScript to cause question blocks to set variables other than the variables specified in the YAML of the question blocks. The allowed to set modifier allows you to get around this limitation by listing specific variable names that the question block should be allowed to set. You can set allowed to set to a YAML list of variable names or to a Python expression that evaluates to a list of variable names.

Changing order of precedence

As explained in how docassemble finds questions for variables, if there is more than one question or code block that offers to define a particular variable, blocks that are later in the YAML file will be tried first.

For example, suppose your friend developed a YAML file with questions and code blocks that define the variables client.age, client.eligible, and docket_number. In your interview, you would like to define client.age and client.eligible the same way your friend does. You can accomplish this by using include to incorporate by reference your friend’s YAML file. But suppose you don’t like the way your friend asks the question to determine docket_number. No problem; just write a question in your own YAML file that defines docket_number, and make sure that this question appears after the include block that incorporates your friend’s YAML file. That way, your question will be used instead of your friend’s.

However, there may be times when the relative placement of blocks within the YAML file is not a convenient way for you to designate which questions override other questions.

For example, suppose there are two question blocks in your interview that define favorite_fruit. The second one is always used because it appears later in the YAML; the second question supersedes the first.

question: |
  What the heck is your favorite fruit?
fields:
  Fruit: favorite_fruit
---
question: |
  What is your favorite fruit?
fields:
  Fruit: favorite_fruit
---
mandatory: True
question: |
  Your favorite fruit is
  ${ favorite_fruit }.
Screenshot of supersede-regular example

If you wanted the first question to be asked instead, you could rearrange the order of questions, but what if you wanted to keep the order the same?

One alternative is to use the id and supersedes specifiers:

id: informal favorite fruit question
supersedes: regular favorite fruit question
question: |
  What the heck is your favorite fruit?
fields:
  Fruit: favorite_fruit
---
id: regular favorite fruit question
question: |
  What is your favorite fruit?
fields:
  Fruit: favorite_fruit
---
mandatory: True
question: |
  Your favorite fruit is
  ${ favorite_fruit }.
Screenshot of supersede example

In this example, the id and supersedes specifiers tell the interview that the first question takes precedence over the second.

Another way of changing the order of precedence is to use the order initial block.

Putting conditions on whether a question is applicable

If you have multiple questions in your interview that define a given variable, you can tell docassemble under what conditions a given question may be asked. You do so by using the if modifier.

question: |
  Describe your intelligence.
field: user_intelligence
choices:
  - Smart
  - Dumb
---
if: |
  user_intelligence == 'Smart'
question: |
  What is the square root of 50% of 32?
fields:
  - Answer: answer
    datatype: integer
---
if: |
  user_intelligence == 'Dumb'
question: |
  What is 2+2?
fields:
  - Answer: answer
    datatype: integer
---
mandatory: True
question: |
  % if answer == 4:
  That is correct.
  % else:
  Wrong answer.
  % endif
Screenshot of if example

Here’s how this works:

  • The mandatory question requires a definition of answer, so the interview looks for blocks that offer to define answer.
  • The interview considers asking the “What is 2+2?” question. It considers this question first because it appears last in the YAML source.
  • This question has a condition, so the interview evaluates the Python expression. However, the expression depends on the variable user_intelligence, which is undefined, so the interview asks a question to determine that value of this variable.
  • When the user answers the user_intelligence question, the interview tries to ask the mandatory question again, then looks for a definition of answer, then considers asking the “What is 2+2?” question, then evaluates the if expression.
  • If the expression evaluates to true, then the interview asks “What is 2+2?”
  • If the expression evaluates to false, then the interview skips the question and moves on to the “What is the square root of 50% of 32?” question. It evaluates the if statement, and will ask the question if the expression evaluates to true.

The content of the if modifier must be a Python expression or a list of Python expressions. If a list of expressions is provided, each expression must evaluate to true in order for the question to be asked.

Conditionally hiding the Continue button based on a Python expression

Many question blocks have “Continue” buttons. If you want the “Continue” button to be hidden in some circumstances, so that the user cannot continue, you can set hide continue button to a Python expression that, if true, will cause the Continue button to be unavailable.

You might use this on a question that contains an action the user can perform in order to make a correction, which may cause the Continue button to appear.

question: |
  % if user_number == number_to_enter:
  You have passed the short-term memory test.
  % else:
  You are wrong.
  % endif
action buttons:
  - label: |
      Try again
    action: user_number
    show if: |
      user_number != number_to_enter
    color: primary
hide continue button: |
  user_number != number_to_enter
continue button field: threshold_screen
Screenshot of hide-continue-button example

Note that the condition specified by hide continue button is a Python expression. This expression can only evaluate values of variables that have already been defined.

If you want a Continue button to be shown or hidden based on the values of fields on the screen, you would need to write your own JavaScript to reveal the button. To reveal the button with JavaScript, you can run $("#da-continue-button").show().

Note that because the button can be revealed with JavaScript, the hide continue button feature should be considered cosmetic, not something that you should rely on for enforcing interview logic. A user with experience with JavaScript could figure out how to reveal the button and press it.

Unless you are a JavaScript programmer, you should not use hide continue button on a question that uses fields, choices, dropdown, etc., because the user would have no way to submit the input. hide continue button is primarily useful for a screen that should either be either a dead-end screen or a continue button field screen, depending on a condition expressed in Python.

Conditionally disabling the Continue button based on a Python expression

disable continue button is just like hide continue button except that instead of hiding the Continue button, it shows the button with the disabled attribute set.

question: |
  % if user_number == number_to_enter:
  You have passed the short-term memory test.
  % else:
  You are wrong.
  % endif
subquestion: |
  % if user_number != number_to_enter:
  ${ action_button_html(url_action('user_number'), label="Try again") }
  % endif
disable continue button: |
  user_number != number_to_enter
continue button field: threshold_screen
Screenshot of disable-continue-button example

To enable the button using JavaScript, run $("#da-continue-button").prop("disabled", false).

Turn off variable scanning

By default, docassemble looks at every block in your interview and automatically discerns what variables each block is capable of setting. Then, when it is evaluating the interview logic, if it encounters an undefined variable it goes through all the blocks that are capable of defining the variable. As discussed above, if there are multiple blocks that are capable of defining a variable, it tries the ones that are later in the file first, unless an order initial block or a supersedes modifier alters that order.

Sometimes, however, a block that docassemble tries to use to define a variable is one that you don’t docassemble to even consider when looking for a way to define a variable.

This is particularly likely to happen when you have code that changes the values of previously-defined variables.

For example, in this interview, the intention is that:

  • A variable is gathered from the user
  • The variable is reported back to the user
  • Then variable is changed through code
  • The variable is reported to the user again.
question: |
  What is the best color?
fields:
  - Color: best_color
---
question: |
  What is the time of day?
field: time_of_day
choices:
  - Night
  - Day
---
mandatory: True
question: |
  The best color is
  ${ best_color }.
field: initial_color_seen
---
mandatory: True
code: |
  if time_of_day == 'Night':
    best_color = 'black'
  else:
    best_color = 'blue'
---
mandatory: True
question: |
  The best color is now
  ${ best_color }.
Screenshot of scan-for-variables-original example

However, this interview does something the interview developer did not intend: when it goes looking for a definition for best_color, the first thing it does is run the mandatory code block that depends on time_of_day. So the first question that gets asked is time_of_day, not best_color. “Ugh!” the developer thinks, “that’s not what I wanted! I only wanted that mandatory code block to be run later in the interview.”

To fix this problem, the developer can modify the code block with scan for variables: False:

question: |
  What is the best color?
fields:
  - Color: best_color
---
question: |
  What is the time of day?
field: time_of_day
choices:
  - Night
  - Day
---
mandatory: True
question: |
  The best color is
  ${ best_color }.
field: initial_color_seen
---
scan for variables: False
mandatory: True
code: |
  if time_of_day == 'Night':
    best_color = 'black'
  else:
    best_color = 'blue'
---
mandatory: True
question: |
  The best color is now
  ${ best_color }.
Screenshot of scan-for-variables example

Now, when docassemble goes searching for a block that will define best_color, it will disregard the code block that depends on time_of_day.

This modifier can be used on any block that sets variables to make it effectively “invisible” to docassemble’s automatic logic. If a block is marked with scan for variables: False, the event and sets modifiers will still be effective, so you can use them to explicitly indicate that a block should be tried when the interview needs a definition of a particular variable.

In this variation of the interview, for example, we first want to gather best_color from the user. Then we want to determine best_thing based on the time of day, and we want a side effect of setting best_thing to be setting best_color to something different.

question: |
  What is the best color?
fields:
  - Color: best_color
---
question: |
  What is the time of day?
field: time_of_day
choices:
  - Night
  - Day
---
scan for variables: False
sets:
  - best_thing
code: |
  if time_of_day == 'Night':
    best_thing = 'astronomy'
    best_color = 'black'
  else:
    best_thing = 'the beach'
    best_color = 'blue'
---
mandatory: True
field: initial_screen
question: |
  According to you, the best color
  is ${ best_color }.
---
mandatory: True
question: |
  Since the best thing is
  ${ best_thing }, the best color is
  ${ best_color }.
Screenshot of scan-for-variables-sets example

If we did not use scan for variables: False, then the interview would never ask the user for best_color; the code block would have been used to get an initial definition of best_color. But by turning off automatic variable scanning and explicitly indicating that the code block should only be used for determining the definition of best_thing, we were able to get the interview to behave the way we wanted it to.

If you want to use scan for variables: False along with sets, a shorthand is to use only sets.

question: |
  What is the best color?
fields:
  - Color: best_color
---
question: |
  What is the time of day?
field: time_of_day
choices:
  - Night
  - Day
---
only sets: best_thing
code: |
  if time_of_day == 'Night':
    best_thing = 'astronomy'
    best_color = 'black'
  else:
    best_thing = 'the beach'
    best_color = 'blue'
---
mandatory: True
field: initial_screen
question: |
  According to you, the best color
  is ${ best_color }.
---
mandatory: True
question: |
  Since the best thing is
  ${ best_thing }, the best color is
  ${ best_color }.
Screenshot of only-sets example

Indicate variables that are prerequisites

You can add a need specifier to a block to indicate that before the block is used, the definitions of one or more variables must be obtained. This specifier is explained in the need subsection of the Logic section.

Indicate prerequisite variables that invalidate a block

You can add a depends on specifier to a block to indicate a list of variables that, when changed, should invalidate the variables set by the block. It also indicates that the listed variables should be asked first (the functionality of the need modifier). This specifier is explained in the depends on subsection of the Logic section.

Undefine variables when a question is asked

You can add an undefine specifier to a block to indicate that before the block is used, one or more variables must be undefined if they are defined. This specifier is explained in the undefine subsection of the Logic section.

Obtain new values of variables when a question is asked

You can add a reconsider specifier to a block to indicate that before the block is used, the definitions of one or more variables must be obtained and re-obtained if they are already obtained. This specifier is explained in the reconsider subsection of the Logic section.

Include additional buttons on the screen

The action buttons modifier allows you to include additional buttons on the screen.

Hidden comments

To make a note to yourself about a question, which will not be seen by the end user, you can use a comment specifier. It will be ignored by docassemble, so it can contain any valid YAML.

question: |
  Do you agree the weather
  is nice today? 
yesno: day_is_nice
comment: |
  We might wish to consider
  taking out this question.
  It does not seem necessary.
Screenshot of comment-weather example