Edit this page on GitHub

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.

Including audio

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

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
upload_audio

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
upload_audio_microphone

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 declaration 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 declaration, 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 declaration is just like the audio declaration 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 declaration to embed YouTube and Vimeo 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]
video

If you want to embed a Vimeo video, the URL of which is https://vimeo.com/96044910, you would write:

field: ready_to_proceed
question: |
  Welcome to the interview.
subquestion: |
  Please watch this introductory video
  before proceeding with the interview.
video: |
  [VIMEO 96044910]
vimeo

Note that you could not have written the above as this:

---
field: ready_to_proceed
question: |
  Welcome to the interview.
subquestion: |
  Please watch this introductory video
  before proceeding with the interview.
video: [VIMEO 96044910]
---

This would generate an error because YAML thinks square brackets indicate a list of items, not plain text. If you want to write the declaration on one line, write video: "[VIMEO 96044910]".

[YOUTUBE ...] and [VIMEO ...] assume 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 ...] or [VIMEO4:3 ...]. You can also explicitly state that the aspect ratio is 16:9 by using [YOUTUBE16:9 ...] or [VIMEO16: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.
help-damages

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 statements 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
help-damages-audio

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

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 statement:

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.
help-damages-label

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.

Adding images to question: decoration

decoration: kids
question:
  Do you have children?
yesno: has_children
decoration

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:
font-awesome

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.

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

The script modifier contains raw HTML to be appended to the bottom of the web page for the 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>
script

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

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.

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
progress

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
progress-multi

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
---
modules:
  docassemble.base.util
---
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
---
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
sections

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 directive 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
prevent-going-back

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
question-back-button-sometimes

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
question-back-button-sometimes-label

The text of the label can include Mako templating.

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.
question-terms

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.

If you want the terms to be highlighted every time they are used, whether in curly brackets or not, use auto terms.

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.
question-autoterms

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.

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 asks 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 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
continue-button-label

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.

Reusable questions: generic object

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

generic object is a very powerful feature in docassemble that allows authors 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)
generic-object-ein

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
reload

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 directive to do so.

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

In the absence of an id directive, 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 changing order of precedence feature discussed below, which use id directives.

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.

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

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 }.
supersede-regular

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 directives:

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 }.
supersede

In this example, the id and supersedes directives 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
if

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.

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 running the interview, 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 }.
scan-for-variables-original

However, this interview does something the author 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 author 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 author 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 }.
scan-for-variables

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 }.
scan-for-variables-sets

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.

Specifying which variables a block sets

As discussed in the previous section, docassemble looks at every block in your interview and tries to discern what variables each block is capable of setting. However, sometimes it is not able to determine this correctly.

For example, if you have a code block that uses a method to set an attribute on an object, docassemble will not be able to see that the code block is capable of setting the attribute. Here is one such code block:

---
code: |
  user.initializeAttribute('allergies', DAList)
---

If you want docassemble to run this code block when it needs a definition of user.allergies, then you need to add a sets modifier:

---
sets: user.allergies
code: |
  user.initializeAttribute('allergies', DAList)
---

You can also give sets a list of variables:

---
sets: 
  - user.allergies
  - user.skills
code: |
  user.initializeAttribute('allergies', DAList)
  user.initializeAttribute('skills', DAList)
---

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 statement. 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.
comment-weather