Edit this page on GitHub

Setting variables (and doing other things) with questions

To instruct docassemble to store user input in a variable in response to a question, you need to include in your question a variable name within a directive that indicates how you would like docassemble to ask for the value of the variable.

A note about variable names

Variable names are Python identifiers, which means they can be any sequence of uppercase or lowercase letters, digits, and underscores, except the first character cannot be a digit. No spaces are allowed and no punctuation is allowed except for the underscore, _.

The following are valid variable names:

  • fried_fish1
  • NyanCat
  • nyancat (variables are case-sensitive, so this is not the same as the above)
  • __f645456DG_greij_43 (but why would you use something so ugly?)
  • USER_PHONE_NUMBER (ok, but why are you yelling?)

The following are not valid variable names, and if you try to use such variable names you will may get an error or unexpected results:

  • 8th_plaintiff (you can’t begin a variable name with a number; Python will say “invalid syntax”)
  • Nyan-Cat (this is arithmetic: Nyan minus Cat)
  • fried.fish1 (this is valid code, but Python will think you are referring to the attribute fish1 of the object fried)
  • user's_phone_number (apostrophes are not allowed; Python recognizes them as single quotes)
  • favorite animal (spaces are not allowed)
  • beneficiary#1 (punctuation marks other than _ are not allowed)
  • applicant_résumé (only plain alphabet characters can be used)

See reserved variable names for a list of variable names that you cannot use because they conflict with built-in names that Python and docassemble use.

Multiple choice questions (one variable only)

Yes or no questions

The yesno and noyes statements

The yesno statement causes a question to set a boolean (true/false) variable when answered.

question: |
  Are you at least 18 years of age?
yesno: over_eighteen
yesno

In the example above, the web app will present “Yes” and “No” buttons and will set over_eighteen to True if “Yes” is pressed, and False if “No” is pressed.

The noyes statement is just like yesno, except that “Yes” means False and “No” means True.

question: |
  Are you at least 18 years of age?
noyes: user_is_minor
noyes

Note that yes/no fields can also be gathered on a screen along with other fields; to make screens like that, use fields below.

yesnomaybe or noyesmaybe

These statements are just like yesno and noyes, except that they offer a third choice, “I don’t know.” If the user selects “I don’t know,” the variable is set to None, which is a special Python constant that represents the absence of a value.

question: |
  Is Topeka the capital of Kansas?
yesnomaybe: topeka_is_capital_of_kansas
yesnomaybe

Multiple choice buttons

A question block with a buttons statement will set the variable identified in field to a particular value depending on which of the buttons the user presses.

The buttons statement must always refer to a YAML list, so that docassemble knows the order of the buttons.

If an item under buttons is a YAML key-value pair (written in the form of - key: value), then the key will be the button label that the user sees, and the value will be what the variable identified in field will be set to if the user presses that button.

question: |
  How would you like to pay for your
  car?
field: target_variable
buttons:
  - Buy it: purchaser
  - Lease it: borrower
buttons-labels

An item under buttons can also be plain text; in that case docassemble uses this text for both the label and the variable value.

question: |
  What type of belly button do you
  have?
field: target_variable
buttons:
  - Innie
  - Outie
  - No belly button
buttons

In other words, this:

field: user_gender
question: What is your gender?
buttons:
  - Male
  - Female
buttons-variation-1

is equivalent to this:

field: user_gender
question: What is your gender?
buttons:
  - Male: Male
  - Female: Female
buttons-variation-2

Using code to generate the choices

A powerful feature of buttons (which also works with choices, dropdown, and combobox) is the ability to use Python code to generate button choices. If an item under buttons is a key-value pair in which the key is the word code, then docassemble executes the value as Python code, which is expected to return a list. This code is executed at the time the question is asked, and the code can include variables from the interview. docassemble will process the resulting list and create additional buttons for each item.

field: target_variable
question: |
  Your use of this system does not
  mean that you have a lawyer.  Do
  you understand this?
buttons:
  code: |
    [{'understands':"I understand"}, {'does not understand':"I do not understand"}, {'unsure':"I'm not sure"}]
buttons-code-list

Note that the Python code needs to return a list of key-value pairs (Python dictionaries) where the key is what the variable should be set to and the value is the button label. This is different from the YAML syntax.

This is equivalent to:

field: target_variable
question: |
  Your use of this system does not
  mean that you have a lawyer.  Do
  you understand this?
buttons:
  - "I understand": understands
  - "I do not understand": does not understand
  - "I'm not sure": unsure
buttons-code-list-equivalent

You can mix choices that are specified manually with choices that are specified with code:

field: target_variable
question: |
  Your use of this system does not
  mean that you have a lawyer.  Do
  you understand this?
buttons:
  - "I understand": understands
  - code: |
      [{'does not understand':"I do not understand"}, {'unsure':"I'm not sure"}]
buttons-code-list-partial

As explained below, you can also use code to decorate the buttons with images.

True/False buttons

You can use buttons as an alternative to yesno where you want different text in the labels.

question: |
  Are you satisfied?
field: user_is_satisfied
buttons:
  - "You bet": True
  - "No way": False
yesno-custom

Multiple choice list

To provide a multiple choice question with “radio buttons” and a “Continue” button, use field with a choices list:

question: |
  What type of shoes do you wear?
field: target_variable
choices:
  - Sneakers
  - Sandals
  - Clogs
  - Other
choices

You can specify a default value using default:

question: |
  What type of shoes do you wear?
field: target_variable
default: Sandals
choices:
  - Sneakers
  - Sandals
  - Clogs
  - Other
choices-with-default

Another way to set a default is by adding default: True to the choice that you want to be the default.

question: |
  What type of shoes do you wear?
field: target_variable
choices:
  - Sneakers: sneakers
  - Sandals: sandals
    default: True
  - Clogs: clogs
  - Other: other
choices-with-default-item

You can also provide help text for a radio button using help:

question: |
  What type of shoes do you wear?
field: target_variable
choices:
  - Sneakers: sneakers
    help: |
      Comfortable shoes.
  - Sandals: sandals
    help: |
      For summer.
  - Clogs: clogs
    help: |
      For hippies.
  - Other: other
    default: True
    help: |
      Because the other types suck.
choices-with-help

These modifications can also be specified when building a list of choices using code:

question: |
  What type of shoes do you wear?
field: target_variable
choices:
  code: |
    [{'sneakers': "Sneakers",
      'help': "Comfortable shoes."},
     {'sandals': "Sandals",
      'help': "For summer."},
     {'clogs': "Clogs",
      'help': "For hippies."},
     {'other': "Other",
      'default': True,
      'help': "Because the other types suck."}]
choices-from-code

Multiple choice dropdown

To provide a multiple choice question with a dropdown selector, use field with a dropdown list:

question: |
  What type of shoes do you wear?
field: target_variable
dropdown:
  - Sneakers
  - Sandals
  - Clogs
  - Other
choices-dropdown

Multiple choice combobox

To provide a multiple choice question with a “combobox” selector, use field with a combobox list:

question: |
  What is your favorite color?
field: favorite_color
combobox:
  - Red: red
  - Green: green
  - Purple: purple
choices-combobox

The “combobox” selector allows users to choose a selection from a list or enter a value of their own.

Adding images to buttons and list items

To add a decorative icon to a buttons choice, use a key/value pair and add image as an additional key.

question: |
  What is the most important question
  to ask?
field: interrogatory
buttons:
  - "When?": when
    image: calendar
  - "Where?": where
    image: map
buttons-icons

This works with choices as well:

question: |
  What is the most important question
  to ask?
field: interrogatory
choices:
  - "When?": when
    image: calendar
  - "Where?": where
    image: map
choices-icons

It is not possible to decorate dropdown or combobox choices with images.

In these examples, calendar and map are the names of decorations that are defined in an images or image sets block.

If you create the list of choices with code, you can specify an image by including an additional key/value pair within an item, where the key is image.

question: |
  What is the most important question
  to ask?
field: interrogatory
buttons:
  code: myoptions
---
code: |
  myoptions = \
    [{"when": "When?",
      "image": "calendar"},
     {"where": "Where?",
      "image": "map"}]
buttons-icons-code

There is an additional feature available when you assemble buttons with code: you can use DAFile or DAFileList objects to indicate the image. This example uses an uploaded image file as the source of the image for one of the buttons:

question: |
  What is the most important question
  to ask?
field: interrogatory
buttons:
  code: myoptions
---
code: |
  myoptions = \
    [{"when": "When?",
      "image": uploaded_file},
     {"where": "Where?",
      "image": "map"}]
---
question: |
  Please upload an image.
fields:
  - Image: uploaded_file
    datatype: file
buttons-icons-code-upload

Embedding question and code blocks within multiple choice questions

Multiple choice questions can embed question blocks and code blocks. These questions are just like ordinary questions, except they can only be asked by way of the questions in which they are embedded.

You embed a question by providing a YAML key-value list (a dictionary) (as opposed to text) as the value of a label in a buttons, choices, or dropdown list.

question: What is your favorite color?
buttons:
  - Red:
      question: Dark red or light red?
      field: favorite_color
      buttons:
        - Dark Red
        - Light Red
  - Green:
      question: Dark green or light green?
      field: favorite_color
      buttons:
        - Dark Green
        - Light Green
buttons-code-color

While embedding question blocks can be useful sometimes, it is generally not a good idea to structure interviews with a lot of embedded questions. You will have more flexibility if your questions stand on their own.

It is also possible for multiple-choice questions to embed code blocks that execute Python code. (If you do not know what code blocks are yet, read the section on code blocks first.) This can be useful when you want to set the values of multiple variables with one button.

question: What kind of car do you want?
buttons:
  - Ford Focus:
      code: |
        car_model = "Focus"
        car_make = "Ford"
  - Toyota Camry:
      code: |
        car_model = "Camry"
        car_make = "Toyota"
buttons-code

The question above tells docassemble that if the interview logic calls for either car_model or car_make, the question should be tried. When the user clicks on one of the buttons, the code will be executed and the variables will be set.

A simple “continue” button that sets a variable

question: |
  Welcome to the interview!
subquestion: |
  Your participation means a lot to us.
field: user_saw_intro
continue-participation

A question with a field and no buttons will offer the user a “Continue” button. When the user presses “Continue,” the variable indicated by field will be set to True.

Uploads

Storing files as variables

Users can upload files, and the files are stored as a variable in docassemble.

question: |
  Please upload a picture of yourself.
fields:
  - Picture: user_picture
    datatype: file
---
question: |
  You're so adorable, François!
subquestion: |
  ${ user_picture }
mandatory: True
# ---
# features:
#   maximum image size: 150
upload

Note that this question uses the fields statement, which is explained in more detail below.

When set, the variable user_picture will be a special object of type DAFileList. For instructions about how to make use of uploaded files, see inserting images.

Gathering the user’s signature into a file variable

The signature directive presents a special screen in which the user can sign his or her name with the trackpad or other pointing device.

question: |
  Sign your name
subquestion: |
  By signing your name, you agree to
  our terms and conditions.
signature: target_variable
under: |
  ${ user }
signature

On the screen, the question text appears first, then the subquestion text, then the signature area appears, and then the under text appears.

In this example, the user_signature variable will be set to an object of type DAFile. This variable can be included in the same way that a document upload can be included. For example:

---
question: |
  Is this your signature?
subquestion: |
  ${ user_signature }
yesno: user_signature_verified
---

or, if you want to control the width of the image:

---
question: |
  Is this your signature?
subquestion: |
  ${ user_signature.show(width='1in') }
yesno: user_signature_verified
---

Signatures can be also be inserted into assembled documents in the same way. They can also be inserted into .docx fill-in forms and PDF fill-in forms.

On a small screen, users need as much of the screen as possible to write their signature. For this reason, docassemble will reduce the size of the navigation bar and put the question text into the navigation bar. For this reason, you should make sure your question text is very brief – no longer than “Sign your name.” You should also make the subquestion text as brief as possible. Although you may be developing your app on a desktop or laptop monitor, your users are probably using smartphones, so test your app on a smartphone.

Setting multiple variables with one screen

Creating a list of fields

The fields statement is used to present the user with a list of fields.

question: Tell me about yourself
fields:
  - Favorite color: user_favorite_color
  - Description of your ideal vacation: user_ideal_vacation
    datatype: area
    required: False
text-field-example

The fields must consist of a list in which each list item consists of one or more key/value pairs. One of these keys (typically) is the label the user sees, where the value associated with the key is the name of the variable that will store the user-provided information for that field. The other key/value pairs in the item (if any) are modifiers that allow you to customize how the field is displayed to the user.

These modifiers are distinguished from label/variable pairs based on the key; if the key is uses one of the names listed below, it will be treated as a modifier; if it is anything else, it will be treated as a label.

Customizing each field

The following are the keys that have special meaning:

datatype

datatype affects how the data will be collected, validated and stored. For a full explanation of how this is used, see below.

input type

The input type is similar to datatype. It is used in situations where the datatype might be date, number, etc., but you want the field to use a particular type of multiple-choice input element, such as a list of radio buttons or a combobox.

required

required affects whether the field will be optional or required. If a field is required, it will be marked with a red asterisk, and input validation will be enforced to make sure the user provides a value.

If the user skips a non-required field, the variable will be blank for text-based fields and None for multiple-choice and yes/no fields.

Some datatypes are never marked with a red asterisk. For example, range and yesno fields are set to real values by default, so the user cannot actuall skip the question.

The value of required can be True or False. By default, all fields are required, so you never need to write required: True unless you want to.

question: |
  What are your favorite things to eat?
subquestion: |
  You may not like any vegetables,
  but at least tell me your favorite
  fruit.
fields:
  - Vegetable: target_variable
    required: False
  - Fruit: other_target_variable
optional-field

Instead of writing True or False, you can write Python code. This code will be evaluated for whether it turns out to be true or false. For example, instead of True or False, you could use the name of a variable that is defined by a yesno question (as long as that variable was defined before the screen loads; the red asterisk cannot be toggled in real time within the browser).

question: Do you like soda?
yesno: user_likes_soda
---
question: |
  What are your favorite
  things to drink?
fields:
  - Favorite Beverage Overall: favorite_beverage
  - Favorite Soda: favorite_soda
    required: user_likes_soda
required-code

hint

You can guide users as to how they should fill out a text field by showing greyed-out text in a text box that disappears when the user starts typing in the information. In HTML, this text is known as the placeholder. You can set this text for a text field by setting hint. You can use Mako templates.

question: |
  What are your favorite things to eat?
subquestion: |
  Please be specific.
fields:
  - Vegetable: target_variable
    hint: e.g., eggplant, turnips
  - Fruit: other_target_variable
    hint: e.g., apples, oranges
text-hint

The hint is also used to provide the default text the user sees when they fill out a multiple-choice dropdown or a combobox input element within a fields question.

help

You can provide contextual help to the user regarding the meaning of a field using the help modifier. The label will be green to indicate that it can be clicked on, and the value of help will appear on the screen when the user clicks the green text. You can use Mako templates.

question: |
  What are your favorite things to eat?
subquestion: |
  If you don't know what a vegetable
  or fruit is, click the green text.
fields:
  - Vegetable: target_variable
    help: |
      A plant.
  - Fruit: other_target_variable
    help: |
      The pulpy, edible seed vessels
      of certain plants.
text-help

default

You can provide a default value to a field using default. You can also use Mako templates.

question: |
  What are your favorite things to eat?
subquestion: |
  Please be specific.
fields:
  - Vegetable: target_variable
    default: eggplant
  - Fruit: other_target_variable
    default: |
      ${ greatest_fruit }
---
code: |
  greatest_fruit = "apples"
text-default

choices

The choices modifier is used with multiple-choice fields. It must refer to a list of possible options. Can be a list of key/value pairs (key is what the variable will be set to; value is the label seen by the user) or a list of plain text items (in which case the label and the variable value are the same).

question: |
  What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
    choices:
      - "${ 'Apples' }": apple
      - Oranges: orange
      - Pears: pear
fields-choices

When the datatype is object, object_radio, or object_checkboxes, choices indicates a list of objects from which the user will choose. For more information about using objects in multiple choice questions, see the section on selecting objects, below.

code

If you have a multiple-choice question and you want to reuse the same selections several times, you do not need to type in the whole list every time. You can define a variable to contain the list and a code block that defines the variable.

Adding code to a field makes it a multiple-choice question. The code itself refers to Python code that generates a list of possible options for a multiple choice field.

question: |
  What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
    datatype: radio
    code: |
      myoptions
---
question: |
  What is your brother's favorite
  fruit?
fields:
  - Fruit: favorite_fruit_of_brother
    datatype: radio
    code: |
      myoptions
---
code: |
  myoptions = [
                {'apple': "Apples"},
                {'orange': "Oranges"},
                {'pear': "Pears"}
              ]
fields-mc

The Python code runs at the time the question is asked. Therefore, you can use the code feature to create multiple-choice questions that have dynamically-created lists of choices.

The Python code needs to be a single expression. The result of the expression can take several forms.

It can be a list of single-item dictionaries, as in the example above.

It can be a dictionary (in which case you cannot control the order of items):

code: |
  myoptions = {
                'apple': "Apples",
                'orange': "Oranges",
                'pear': "Pears"
              }
fields-mc-2

It can be a list of text items (in which case the values and labels will be the same):

code: |
  myoptions = ["Apples", "Oranges", "Pears"]
fields-mc-3

It can be a list of two-element lists:

code: |
  myoptions = [
                ['apple', 'Apples'],
                ['orange', 'Oranges'],
                ['pear', 'Pears']
              ]
fields-mc-4

You can specify a default by including a three-element list where the third element is True if the choice should be selected by default.

code: |
  myoptions = [
                ['apple', 'Apples'],
                ['orange', 'Oranges', True],
                ['pear', 'Pears']
              ]
fields-mc-5

You can include “help text” for a choice by including a fourth element in one of the lists, where the element contains the help text you want to be available. The user can see the help text by touching the question mark button.

code: |
  myoptions = [
                ['apple', 'Apples', None, 'Apples are good in pies.'],
                ['orange', 'Oranges', None, 'Oranges are a type of citrus fruit.'],
                ['pear', 'Pears', None, 'Pears are an acquired taste.']
              ]
fields-mc-6

If your code is a list of dictionaries, you can include a 'default' key in the dictionary indicating a true or false value that represents whether the choice should be selected by default.

code: |
  myoptions = [
                {'apple': "Apples",
                 'default': True},
                {'orange': "Oranges"},
                {'pear': "Pears"}
              ]
fields-mc-7

Similarly, you can include help text in a list of dictionaries by including a 'help' key in the dictionary indicating the help text that should be available to the user.

code: |
  myoptions = [
                {'apple': "Apples",
                 'default': True,
                 'help': "Apples are good in pies."},
                {'orange': "Oranges",
                 'help': "Oranges are a type of citrus fruit."},
                {'pear': "Pears",
                 'help': "Pears are an acquired taste."}
              ]
fields-mc-8

exclude

If you build the list of choices with code, you can exclude items from the list using exclude, where the value of exclude is Python code.

question: |
  What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
    datatype: radio
    code: |
      myoptions
---
question: |
  What is your brother's favorite
  fruit, assuming he does not like
  ${ favorite_fruit }?
fields:
  - Fruit: favorite_fruit_of_brother
    datatype: radio
    code: |
      myoptions
    exclude: |
      favorite_fruit
---
code: |
  myoptions = [
                {'apple': "Apples"},
                {'orange': "Oranges"},
                {'pear': "Pears"}
              ]
fields-mc-exclude

In this example, the value of exclude is a single variable. If given a list of things, it will exclude any items that are in the list.

none of the above

If you use datatype: checkboxes, then by default a “None of the above” choice is added.

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples
      - Peaches
      - Pears
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-nota

You can turn off the “None of the above” choice by setting the none of the above option to False.

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples
      - Peaches
      - Pears
    none of the above: False
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-nota-false

You can also change the phrase from “None of the above” to something else, even a Mako expression. Just set none of the above to the text you want to be displayed.

question: |
  Please fill in the following information.
fields:
  - Requested options: car_options
    datatype: checkboxes
    choices:
      - Sunroof
      - Automatic transmission
      - Heated seats
    none of the above: |
      Nothing ${ "at all" }
fields-mc-nota

When the list of choices is empty

If the list of choices for a multiple choice question is empty, docassemble will try to deal with the situation gracefully. If there is only a single field listed under fields, or the question is a standalone multiple choice question, then the variable that will be set by the user’s selection will be set to None, and the question (or the field, if there are other fields listed under fields) will be skipped.

If the datatype is checkboxes, the variable will be set to an empty DADict (a type of dictionary specific to docassemble). If the datatype is object_checkboxes, the variable will be set to an empty DAList (a type of list specific to docassemble).

shuffle

shuffle can be used on multiple-choice fields (defined with code or choices). When True, it randomizes the order of the list of choices; the default is not to “shuffle” the list.

question: |
  For which of the following obscure
  candidates do you wish to vote?
fields:
  - Candidate: candidate
    datatype: radio
    shuffle: True
    choices:
      - Aaron Aardvark
      - Albert Arnold
      - Felicia Fellowes
      - Miranda Moore
      - Zachariah Zephyr
shuffle

show if

You can use the show if modifier if you want the field to be hidden under certain conditions. There are three methods of using show if, which have different syntax.

Using the first method, the field will appear or disappear in the web browser depending on the value of another field in the fields list. Under this method, show if refers to a YAML dictionary with two keys: variable and is, where variable refers to the variable name of the other field, and is refers to the value of the other field that will cause this field to be shown.

This can be useful when you have a multiple-choice field that has an “other” option, where you want to capture a text field but only if the user selects the “other” option.

question: |
  What kind of car do you drive?
fields:
  - Make: car_make
    choices:
      - Honda
      - Toyota
      - Mazda
      - Other
  - Other make: car_make
    show if:
      variable: car_make
      is: Other
other

The second method is like the first, but is for the special case where the other field in fields is a yes/no variable. Under this method, show if refers to the other field’s variable name. If that variable is true, the field will be shown, and if it is not true, the field will be hidden.

question: |
  Please fill in the following information.
fields:
  - "Do you like fruit?": likes_fruit
    datatype: yesnoradio
  - "What's your favorite fruit?": favorite_fruit
    show if: likes_fruit
showif-boolean

If a show if statement refers to a variable that is itself hidden by a show if, then the condition is considered to be false.

question: |
  Please fill in the following information.
fields:
  - "Do you like fruit?": likes_fruit
    datatype: yesnoradio
  - "Do you like apples?": likes_apples
    datatype: yesnoradio
    show if: likes_fruit
  - "Why do you like apples?": reason_for_liking_apples
    show if: likes_apples
  - "Why do you hate apples?": reason_for_hating_apples
    show if:
      variable: likes_apples
      is: False
  - "Do you like Fuji apples?": likes_fuji
    datatype: yesnoradio
    show if: likes_apples
  - "Why do you like Fuji apples?": reason_for_liking_fuji_apples
    show if: likes_fuji
showif-nested

Under the third method, the field is either shown or not shown on the screen when it loads, and it stays that way. You can use Python code to control whether the field is shown or not. Unlike the first method, you are not limited to using variables that are part of the fields list; you can use any Python code; however, you cannot refer to any of the variables that are defined by the current question. Under this method, show if must refer to a YAML dictionary with one key, code, where code contains Python code. The code will be evaluated and if it evaluates to a positive value, the field will be shown.

question: |
  Please fill in the following information.
fields:
  - Favorite fruit: fruit
  - Favorite vegetable: vegetable
  - Favorite fungus: mushroom
    show if:
      code: |
        2 + 2 == 3
showif

With all of these methods, if any field is not visible on the screen when the user presses the Continue button, no variable will be set to anything for that field; it as if the field was never part of the question.

hide if

This works just like show if, except that it hides the field instead of showing it.

question: |
  Please fill in the following information.
fields:
  - "Do you have fruit?": has_fruit
    datatype: yesnoradio
  - "What fruit do you need?": fruit
    hide if: has_fruit
hideif-boolean

disable others

If disable others is set to True, then when the user changes the value of the field to something, all the other fields in the question will be disabled.

question: |
  What is your favorite color?
fields:
  - Favorite color: favorite_color
    choices:
      - Red
      - Blue
      - Green
      - Purple
    disable others: True
  - note: |
      If your favorite color is
      not listed, enter your
      favorite color here.
  - Other: favorite_color
---
question: |
  Your favorite color is
  ${ favorite_color }.
mandatory: True
disable-others

Alternatively, disable others can be set to a list of variables on the same screen that should be disabled.

question: |
  What is your favorite color?
fields:
  - Favorite color: favorite_color
    choices:
      - Red
      - Blue
      - Green
      - Purple
    disable others:
      - favorite_color
      - second_favorite_color
  - note: |
      If your favorite color is
      not listed, enter your
      favorite color here.
  - Other: favorite_color
  - Second favorite color: second_favorite_color
  - Explain your reasoning: reason
    datatype: area
---
question: |
  Your favorite color is
  ${ favorite_color }.
subquestion: |
  Your reasoning is:

  ${ reason }
mandatory: True
disable-others-list

note

The value of note is Markdown text that will appear on the screen; useful for providing guidance to the user on how to enter information.

question: |
  Please fill in the following information.
fields:
  - Favorite fruit: fruit
  - Favorite vegetable: vegetable
  - note: |
      In case you did not know, a 
      mushroom is a fungus, not a
      vegetable.
  - Favorite fungus: mushroom
note

html

The html directive is like note, except the format is expected to be raw HTML. It can be used in combination with the css and script modifiers.

question: |
  I was thinking about your birthday.
fields:
  - html: |
      The date and time today is
      <span class="mytime" id="today_time"></span>.
  - "When is your next birthday?": birth_date
    datatype: date
css: |
  <style>
    .mytime {
       color: green;
    }
  </style>
script: |
  <script>
    $("#today_time").html(Date());
  </script>
html

no label

If you use no label as the label for your variable, the label will be omitted. On wide screens, the field will fill more of the width of the screen if the label is set to no label.

question: |
  What is your Zodiac sign?
fields:
  - no label: target_variable
no-label-field

To keep the width of the field normal, but have a blank label, use "" as the label.

question: |
  What is your Zodiac sign?
fields:
  - "": target_variable
blank-label-field

label and field

Instead of expressing your labels and variable names in the form of - Label: variable_name, you can specify a label using the label key and the variable name using the field key.

question: |
  What are your favorite things to eat?
fields:
  - label: Vegetable
    field: favorite_vegetable
  - label: Fruit
    field: favorite_fruit
label

Data types

Within a fields question, there are many possible datatype values, which affect what the user sees and how the input is stored in a variable. The following sections describe the available datatypes and input types.

Plain text

A datatype: text provides a single-line text input box. This is the default, so you never need to specify it unless you want to.

question: |
  What are your favorite things to eat?
subquestion: |
  Please be specific.
fields:
  - Vegetable: target_variable
  - Fruit: other_target_variable
text-field

datatype: area provides a multi-line text area.

question: |
  Tell me the story of your life.
fields:
  - Life Story: target_variable
    datatype: area
text-box-field

Passwords

datatype: password provides an input box suitable for passwords.

question: |
  Enter your username and password.
fields:
  - Username: user_name
  - Password: target_variable
    datatype: password
password-field

Dates

datatype: date provides a date entry input box. The style of the input box depends on the browser.

question: |
  What is your date of birth?
fields:
  - Birthdate: target_variable
    datatype: date
date-field

Validation is applied to ensure that the date can be parsed by dateutil.parser.parse.

Starting with version 0.2.23, the variable resulting from datatype: date is a special Python object of the class DADateTime, which is a subclass of the standard Python class datetime.datetime. So if the name of the date variable is date_of_filing, then you can do things like:

question: |
  When did you file the complaint?
fields:
  - Date: date_of_filing
    datatype: date
---
code: |
  response_deadline = date_of_filing.plus(days=20)
  christmas = response_deadline.replace(month=12, day=25)
---
mandatory: True
question: |
  % if christmas > response_deadline:
  Your response is due
  ${ int(date_difference(starting=date_of_filing, ending=christmas).weeks) }
  weeks before Christmas.
  % else:
  Your response is due soon after Christmas!
  % endif
date-demo

Note that the field on the screen only asks for a date, but DADateTime represents both a date and a time. The time portion of the DADateTime object will be set to midnight of the date. If you want a DADateTime with a time other than midnight, you can use the .replace_time() or .replace() methods of DADateTime to generate a new object with the same date but a different time.

Before version 0.2.23, however, the variable that results from datatype: date is simply a plain text string. This means you will need to use the as_datetime() function to convert this string to an object if you want to be able to use the comparison operators < and >.

For more information about working with date variables, see the documentation for the date functions. These functions are generally very flexible about formats, so you can pass a string like '12/25/2018' or a date object, and the function will produce the correct result either way.

In particular, if you want to format a date variable for inclusion in a document or a question, you will probably want to use the .format_date() method or the format_date() function.

If you set a default value for a date field, write the date in the format YYYY-MM-DD. Many browsers have built-in “date pickers” that expect dates to be in this format. See Mozilla’s documentation of the date input field. If the browser uses a date picker, then your interview will see text values in the form YYYY-MM-DD, but on other browsers, like Firefox, the format may be some other format.

Times

datatype: time provides an input box for times. The style of the input box depends on the browser.

Validation is applied to ensure that the time can be parsed by dateutil.parser.parse.

question: |
  What time is your appointment?
fields:
  - Time: target_variable
    datatype: time
time-field

The resulting variable will be an object of type datetime.time.

If you want to format a time variable for inclusion in a document or a question, see the .strftime() method or the format_time() function.

If you want to gather both a date and a time from a user, and combine the values together into a single DADateTime object, you can do so with the .replace_time() method. For example:

question: |
  When is your appointment?
fields:
  - Date: appt_date
    datatype: date
  - Time: appt_time
    datatype: time
---
code: |
  appt_datetime = appt_date.replace_time(appt_time)
date-and-time-fields

If you want to format a date and time for inclusion in a document or a question, see the .format_datetime() method or the format_datetime() function.

Combined dates and times

datatype: datetime provides an input box for dates and times together in one field. The style of the input box depends on the browser. Note: not all browsers have a “widget” for combined date and times, and users might be confused if they are presented with a plain text box. For this reason, use of datatype: datetime is not recommended until browser support for the datetime-local becomes more widespread.

Validation is applied to ensure that the time can be parsed by dateutil.parser.parse.

question: |
  When is your appointment?
fields:
  - Date and time: target_variable
    datatype: datetime
datetime-field

The resulting variable will be an object of type DADateTime. The object can be formatted using the .format_datetime() method or the format_datetime() function.

E-mail addresses

datatype: email provides an e-mail address input box.

question: |
  What is your e-mail address?
fields:
  - E-mail: target_variable
    datatype: email
    required: False
email-field

Numbers

datatype: integer indicates that the input should be a valid whole number.

datatype: number indicates that the input should be a valid numeric value.

question: |
  Describe your possessions.
fields:
  - Number of cars: number_cars
    datatype: integer
  - Ounces of gold: gold_ounces
    datatype: number
number-field

You can use the optional modifier step to limit the number to a certain number of decimal places and to control the way the browser widget controls work:

question: |
  How much gold and silver do you have?
fields:
  - Ounces of gold: gold_ounces
    datatype: number
  - Ounces of silver: silver_ounces
    datatype: number
    step: 0.001
number-field-step

Currency

datatype: currency indicates that the input should be a valid numeric value. In addition, the input box shows a currency symbol based on locale defined in the configuration.

question: |
  How much is your house worth?
fields:
  - Value: target_variable
    datatype: currency
money-field

The variable will be set to a number, just as if datatype: number was used. For information about how to display currency values, see the currency() function.

Sliders

datatype: range shows a slider that the user can use to select a number within a given range. The range must be supplied by providing min and max values. An option step value can also be provided, the default of which is 1.

question: |
  On a scale from 1 to 10, how
  much do you like these animals?
fields:
  - Possums: possum_preference
    datatype: range
    min: 1
    max: 10
    step: 0.5
  - Rabbits: rabbit_preference
    datatype: range
    min: 1
    max: 10
range

You can also include an optional scale, which you can set to logarithmic.

question: |
  What is the airspeed velocity,
  in miles per hour, of an
  unladen swallow?
fields:
  - Velocity: velocity
    datatype: range
    min: 1
    max: 10000
    step: 10
    default: 10
    scale: logarithmic
range-log

File uploads

Using the file or files datatypes within a fields list, you can allow users to upload one or more files.

datatype: file indicates that the user can upload a single file. The variable is set to a DAFileList object containing the necessary information about the uploaded file.

question: |
  Please upload a picture of yourself.
fields:
  - Picture: user_picture
    datatype: file
---
question: |
  You're so adorable, François!
subquestion: |
  ${ user_picture }
mandatory: True
# ---
# features:
#   maximum image size: 150
upload

datatype: files indicates that the user can upload one or more files. The variable is set to a DAFileList object containing the necessary information about the uploaded files.

question: |
  Please upload pictures of yourself.
fields:
  - Pictures: user_pictures
    datatype: files
---
question: |
  Look at all those adorable photos!
subquestion: |
  ${ user_pictures }
mandatory: True
upload-multiple

If your users upload digital photos into your interviews, the uploads may take a long time. You can configure an upload field so that images are reduced in size before they are uploaded by modifying your field definition with a maximum image size. The image will be reduced in size so that is no taller than or wider than the number of pixels designated by maximum image size.

In this example, images will be reduced in size to no more than 100 pixels in height or width:

question: |
  Please upload a picture of yourself.
fields:
  - Picture: user_picture
    datatype: file
    maximum image size: None
---
question: |
  You're so small!
subquestion: |
  ${ user_picture }
mandatory: True
upload-max-image-size

Note that the image file type of the uploaded file may be changed to PNG during the conversion process. Different browsers behave differently.

If you have a lot of document upload fields, you can set a default maximum image size on an interview-wide basis with the maximum image size interview feature and on a site-wide basis with the maximum image size configuration directive. If you have a default set up, but you want to override it for a particular field, you can set the maximum image size field modifier to None.

There are a few other data types that result in file uploads:

datatype: camera is just like file, except with an HTML5 input type that suggests using the device’s camera to take a picture. On many devices, this is no different from datatype: file.

datatype: user is just like camera, except with an HTML5 input type that suggests using the device’s front (user-facing) camera.

datatype: environment is just like camera, except with an HTML5 input type that suggests using the device’s rear (environment-facing) camera.

datatype: camcorder is just like camera, except for recording a video.

datatype: microphone is just like camera, except for recording an audio clip.

Whether these special data types do anything different from the file data type is dependent on the web browser. Mobile browsers are the most likely to respond to these features.

Yes/no fields

datatype: yesno will show a checkbox with a label, aligned with labeled fields. datatype: noyes is like datatype: yesno, except with True and False inverted.

question: |
  Please provide the following information.
fields:
  - "What is your favorite food?": favorite_food
  - note: Check which foods you like.
  - Apples: likes_apples
    datatype: yesno
  - Turnips: dislikes_turnips
    datatype: noyes
fields-yesno

datatype: yesnowide will show a checkbox with a label that fills the full width of area. datatype: noyeswide is like datatype: yesnowide, except with True and False inverted.

question: |
  Please provide the following information.
fields:
  - note: Check which foods you like.
  - Peaches: likes_peaches
    datatype: yesnowide
  - Pears: dislikes_pears
    datatype: noyeswide
fields-yesnowide

Sometimes, when you are using a series of these checkboxes, you might want to have a “none of the above” selection. To do this, add a field for the selection, and associate it with a variable. (Your interview does not need to use the variable.) Then modify the field with uncheck others: True.

question: |
  Please provide the following information.
fields:
  - "What is your favorite food?": favorite_food
  - note: Check which foods you like.
  - Apples: likes_apples
    datatype: yesnowide
  - Turnips: likes_turnips
    datatype: yesnowide
  - Neither: dislikes_both_foods
    datatype: yesnowide
    uncheck others: True
fields-yesno-uncheck-others

This will cause the field to act as a “none of the above” field for all the other yes/no checkbox fields on the page. If you want the field to only relate to specific other fields, use a list of the variable names of those fields instead of True.

question: |
  Please provide the following information.
fields:
  - "What is your favorite food?": favorite_food
  - note: Check which foods you like.
  - Apples: likes_apples
    datatype: yesno
  - Turnips: likes_turnips
    datatype: yesno
  - Neither: dislikes_both_foods
    datatype: yesno
    uncheck others:
      - likes_turnips
      - likes_apples
  - note: Check which rocks you like.
  - Granite: likes_granite
    datatype: yesno
  - Obsidian: likes_obsidian
    datatype: yesno
  - I do not like these rocks: dislikes_both_rocks
    datatype: yesno
    uncheck others:
      - likes_granite
      - likes_obsidian
fields-yesno-uncheck-others-list

datatype: yesnoradio will show radio buttons offering choices “Yes” and “No.”

datatype: noyesradio is like datatype: yesnoradio, except with True and False inverted.

question: |
  Please provide the following information.
fields:
  - "Do you like apricots?": likes_apricots
    datatype: yesnoradio
  - "Do you like pineapple?": dislikes_pineapple
    datatype: noyesradio
fields-yesnoradio

datatype: yesnomaybe will show radio buttons offering choices “Yes,” “No,” and “I don’t know.”

question: |
  Please answer the following question.
fields:
  - "Is Topeka the capital of Kansas?": topeka_is_capital_of_kansas
    datatype: yesnomaybe
fields-yesnomaybe

datatype: noyesmaybe is like datatype: yesnomaybe, except with True and False inverted.

question: |
  Please answer the following question.
fields:
  - "Was Washington the first U.S. president?": washington_not_the_first_president
    datatype: noyesmaybe
fields-noyesmaybe

Checkboxes

datatype: checkboxes will show the choices list as checkboxes. The variable will be a DADict (a type of dictionary specific to docassemble) with items set to True or False depending on whether the option was checked. No validation is done to see if the user selected at least one, regardless of the value of required.

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples: apple
      - Peaches: peach
      - Pears: pear
  - "What is your favorite fruit overall?": favorite_fruit
---
question: |
  Thank you for your thoughts.
subquestion: |
  % if likes_fruit['apple']:
  You like apples.
  % endif
  % if likes_fruit['peach']:
  You like peaches.
  % endif
  % if likes_fruit['pear']:
  You like pears.
  % endif
  Your favorite, though, is ${ favorite_fruit }.

  In Python, `likes_fruit` is
  `${ repr(likes_fruit) }`.
mandatory: True
fields-checkboxes

As you can see in this example, the keys of the resulting dictionary are the names of fruit, the values that are checked are True, and the values that were not checked are False.

In the example above, the keys of the dictionary are the same as the labels displayed to the user. If you want labels to be different from the keys, you can specify the choices in the following manner:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples: apple
      - Peaches: peach
      - Pears: pear
  - "What is your favorite fruit overall?": favorite_fruit
---
question: |
  Thank you for your thoughts.
subquestion: |
  % if likes_fruit['apple']:
  You like apples.
  % endif
  % if likes_fruit['peach']:
  You like peaches.
  % endif
  % if likes_fruit['pear']:
  You like pears.
  % endif
  Your favorite, though, is ${ favorite_fruit }.

  In Python, `likes_fruit` is
  `${ variable_as_code }`.
mandatory: True
fields-checkboxes-different-labels

You can generate checkbox choices with code:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    code: |
      [
        {'apple': 'Apples'},
        {'peach': 'Peaches'},
        {'pear': 'Pears'}
      ]
  - "What is your favorite fruit overall?": favorite_fruit
---
question: |
  Thank you for your thoughts.
subquestion: |
  % if likes_fruit['apple']:
  You like apples.
  % endif
  % if likes_fruit['peach']:
  You like peaches.
  % endif
  % if likes_fruit['pear']:
  You like pears.
  % endif
  Your favorite, though, is ${ favorite_fruit }.
mandatory: True
fields-checkboxes-code

The all_true(), all_false(), any_true() and any_false() methods can be used to analyze the values set by a checkboxes field.

Default values for checkboxes

To set default values in a checkbox list, you have a few options.

If you want to select just one option, just indicate the name of the option:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples
      - Peaches
      - Pears
    default: Pears
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-default-0

If you want to select multiple options, indicate a YAML list:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples
      - Peaches
      - Pears
    default:
      - Pears
      - Apples
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-default-1

You can also indicate your defaults in the form of a YAML dictionary:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples
      - Peaches
      - Pears
    default:
      Pears: True
      Apples: True
      Peaches: False
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-default-2

You can also use Python code to generate the defaults:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples
      - Peaches
      - Pears
    default:
      code: |
        ['Pears', 'Apples']
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-default-3

Your Python code can also return a dictionary:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    choices:
      - Apples
      - Peaches
      - Pears
    default:
      code: |
        dict(Pears=False, Peaches=False, Apples=True)
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-default-4

If you generate the checkbox options with code, you can include defaults directly within your code when you use a list of dictionaries:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    code: |
      [
        {'apple': 'Apples', 'default': True},
        {'peach': 'Peaches'},
        {'pear': 'Pears'}
      ]
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-default-5

This also works if your code returns a list of lists:

question: |
  Please tell me what you think.
fields:
  - "Select the fruits you like": likes_fruit
    datatype: checkboxes
    code: |
      [
        ['apple', 'Apples', True],
        ['peach', 'Peaches'],
        ['pear', 'Pears']
      ]
  - "What is your favorite fruit overall?": favorite_fruit
fields-checkboxes-default-6

Multiple-choice dropdown

If you provide a list of choices or some choice-generating code for a field within a list of fields, the user will see a dropdown. The variable will be set to the value of the selected choice.

question: | 
  What type of shoes do you wear?
fields:
  - Shoe Type: target_variable
    choices: 
      - Sneakers
      - Sandals
      - Clogs
      - Other
fields-choices-dropdown

Multiple-choice combobox

input type: combobox shows a choices list as a combobox instead of as a dropdown select element (which is the default).

question: |
  What is your favorite color?
fields:
  - Color: favorite_color
    input type: combobox
    choices: 
      - Red
      - Green
      - Purple
---
mandatory: True
question: All done
subquestion: |
  Your favorite color is
  ${ favorite_color }.
fields-choices-combobox

The “combobox” selector allows users to choose a selection from a list or enter a value of their own.

Radio buttons

input type: radio shows a choices list as a list of radio buttons instead of as a dropdown select element (which is the default). The variable will be set to the value of the selected choice.

question: |
  Describe your car.
fields:
  - Number of wheels: wheels_on_car
    datatype: integer
  - Type: car_type
    input type: radio
    choices:
      - Convertible
      - Hatchback
      - Sedan
  - Model: car_country
    input type: radio
    choices:
      - BMW: Germany
      - Buick: United States
      - Honda: Japan
      - Toyota: Japan
radio-list

Multiple-choice with objects

datatype: object is used when you would like to use a variable to refer to an existing object. You need to include choices, which can be a list of objects.

objects:
  protagonist: Individual
  antagonist: Individual
---
code: |
  protagonist.name.first = "Harry"
  protagonist.name.last = "Potter"
  antagonist.name.first = "Tom"
  antagonist.name.last = "Riddle"
---
question: Who is the villain?
fields:
  - The villain is: villain
    datatype: object
    default: antagonist
    choices:
      - protagonist
      - antagonist
object

If choices refers to a variable that is a list of things, the list will be unpacked and used as the list of items from which the user can select. Python code can be used here.

objects:
  protagonist: Individual
  antagonist: Individual
  actors: PartyList
---
mandatory: True
code: |
  protagonist.name.first = "Harry"
  protagonist.name.last = "Potter"
  antagonist.name.first = "Tom"
  antagonist.name.last = "Riddle"
  actors.append(protagonist)
  actors.append(antagonist)
  actors.auto_gather = False
  actors.gathered = True
---
question: Who is the villain?
fields:
  - The villain is: villain
    datatype: object
    choices: actors
object-selections

By using datatype: object in combination with disable others, you can create questions that either set the attributes of an object or set the object equal to another object.

objects:
  cook: Individual
  gardener: Individual
  maid: Individual
---
question: |
  Who is the cook?
fields:
  - Somebody already mentioned: cook
    datatype: object
    disable others: True
    choices: |
      [person for person in [cook, gardener, maid] if person.name.defined()]
  - First Name: cook.name.first
  - Last Name: cook.name.last
  - Suffix: cook.name.suffix
    required: False
    code: name_suffix()
---
question: |
  Who is the gardener?
fields:
  - Somebody already mentioned: gardener
    datatype: object
    disable others: True
    choices: |
      [person for person in [cook, gardener, maid] if person.name.defined()]
  - First Name: gardener.name.first
  - Last Name: gardener.name.last
  - Suffix: gardener.name.suffix
    required: False
    code: name_suffix()
someone-already-mentioned

In this example, if the gardener and the cook are the same person, the interview effectively does the following in Python:

gardener = cook

Please note that datatype: object cannot be used with the generic object modifier if the variable being set is x.

datatype: object_radio is like datatype: object, except the user interface uses radio buttons rather than a pull-down list.

objects:
  protagonist: Individual
  antagonist: Individual
---
code: |
  protagonist.name.first = "Harry"
  protagonist.name.last = "Potter"
  antagonist.name.first = "Tom"
  antagonist.name.last = "Riddle"
---
question: Who is the villain?
fields:
  - The villain is: villain
    datatype: object_radio
    default: antagonist
    choices:
      - protagonist
      - antagonist
object-radio

For a fuller discussion on using multiple-choice object selectors, see the section on selecting objects, below.

datatype: object_checkboxes is used when you would like to use a question to set the elements of an object of type DAList (or a subtype thereof). The choices in choices (optionally modified by exclude) will be presented to the user as checkboxes. The .gathered attribute of the variable will be set to True after the elements are set. See groups for more information.

objects:
  protagonist: Individual
  antagonist: Individual
---
mandatory: True
code: |
  protagonist.name.first = "Harry"
  protagonist.name.last = "Potter"
  antagonist.name.first = "Tom"
  antagonist.name.last = "Riddle"
---
question: Who are the villains, if any?
fields:
  no label: villain
  datatype: object_checkboxes
  choices:
    - protagonist
    - antagonist
object-checkboxes-dalist

You can also use datatype: object_checkboxes on variables that already exist in your interview. You would need to do this if you wanted the variable to be a subtype of DAList. If you use a variable name that already exists, note that the question will only be used when the .gathered attribute is needed. To avoid questions asking for .there_are_any and .there_is_another, set .auto_gather to False. For example:

objects:
  protagonist: Individual
  antagonist: Individual
---
mandatory: True
code: |
  protagonist.name.first = "Harry"
  protagonist.name.last = "Potter"
  antagonist.name.first = "Tom"
  antagonist.name.last = "Riddle"
---
question: Who are the villains, if any?
fields:
  no label: villain
  datatype: object_checkboxes
  choices:
    - protagonist
    - antagonist
object-checkboxes-dalist

Another advantage of using an already-existing variable is that the choices in the question will default to the current elements in the list. In this example, we use the .append() method to initialize the list of villains.

mandatory: True
objects:
  protagonist: Individual
  antagonist: Individual
  villain: PartyList
---
mandatory: True
code: |
  protagonist.name.first = "Harry"
  protagonist.name.last = "Potter"
  antagonist.name.first = "Tom"
  antagonist.name.last = "Riddle"
  villain.auto_gather = False
  villain.append(antagonist)
---
mandatory: True
question: Who are the villains, if any?
fields:
  no label: villain
  datatype: object_checkboxes
  choices:
    - protagonist
    - antagonist
object-checkboxes-default

Machine learning

From the user’s perspective, datatype: ml works just like datatype: text (which is the default if no datatype is indicated), and datatype: mlarea works just like datatype: area.

From the interview author’s perspective, however, the variable that is set is not a piece of text, but an object representing a classification of the user’s input, based on a machine learning model that is “trained” to classify user input.

question: |
  Describe how you feel.
fields:
  - no label: mood
    datatype: ml
---
mandatory: True
question: |
  You sound ${ mood }.
predict-happy-sad

For more information about how to use machine learning variables, see the machine learning section.

Input validation

Some datatypes, such as numbers, dates, and e-mail addresses, have validation features that prevent the user from moving to the next page if the input value does not meet the requirements of the data type. The jQuery Validation Plugin is used.

For some field types, you can require additional input validation by adding the following to the definition of a field:

  • min: for currency and number data types, require a minimum value. This is passed directly to the jQuery Validation Plugin.
  • max: for currency and number data types, require a maximum value. This is passed directly to the jQuery Validation Plugin.
question: |
  What percentage of your friends
  are turnips?
fields:
  - Percent: percent
    datatype: number
    min: 0
    max: 100
min
  • minlength: require a minimum number of characters in a textbox, number of checkboxes checked, etc. This is passed directly to the jQuery Validation Plugin.
  • maxlength: require a maximum number of characters in a textbox, number of checkboxes checked, etc. This is passed directly to the jQuery Validation Plugin.
question: |
  What is the nuclear launch code?
fields:
  - Code: launch_code
    minlength: 5
    maxlength: 15
minlength

You can also use Python code to validate an input field. To do so, add a validate directive to the field description that refers to the name of a function that returns True (or something that Python considers “true”) if the value is valid, and False (or something that Python considers “not true”) if the value is invalid.

modules:
  - docassemble.demo.validationfuncs
---
mandatory: True
question: |
  There were ${ number_of_tires }
  tires in the garage.
---
question: |
  How many car tires are in the
  garage?
fields:
  - Tires: number_of_tires
    datatype: integer
    validate: is_multiple_of_four
validation-test

In this example, the function is_multiple_of_four is defined as follows:

def is_multiple_of_four(x):
    return x/4 == int(x/4)

This Python code is in the validationfuncs.py file. The modules block includes this code. The function returns True if 4 divides the input value into a whole number

The error message that the user will see is a generic error message, “Please enter a valid value.” In most cases you will want to explain to the user why the input did not validate. To provide a more descriptive error message, your function can call the validation_error() function with the error message the user should see.

modules:
  docassemble.demo.validationfuncstwo
---
mandatory: True
question: |
  There were ${ number_of_tires }
  tires in the garage.
---
question: |
  How many car tires are in the
  garage?
fields:
  - Tires: number_of_tires
    datatype: integer
    validate: is_multiple_of_four
validation-test-two

In this example, the function is_multiple_of_four is defined as follows:

from docassemble.base.util import *

def is_multiple_of_four(x):
    if x/4 != int(x/4):
        validation_error("The number must be a multiple of four")
    return True

This Python code is in the validationfuncstwo.py file. If 4 does not divide the input value into a whole number, then validation_error() is called. The validation_error() function raises an exception, which means that code stops processing once the validation_error() function is called. That is, if validation_error() is called, the return True statement will not be executed.

The text passed to validation_error() is the text the user will see if the value does not validate. If 4 does divide the input value by a whole number, the function returns True, which indicates that the input is valid.

One limitation of these validation functions is that they can only test for characteristics inherent in the variable being validated; they cannot compare the variable to other variables.

You can get around this restriction using the validation code modifier.

question: |
  There are 10 fruit in all.
subquestion: |
  How many apples and oranges
  are there?
fields:
  - Apples: number_of_apples
    datatype: integer
  - Oranges: number_of_oranges
    datatype: integer
validation code: |
  if number_of_oranges + number_of_apples != 10:
    validation_error("The number of fruit must add up to 10!")
validation-code

Note that the code under validation code is not within a function, so it should not try to return any values. If the code runs through to the end, this indicates that the input for the question is valid. If validation_error() is called, or an exception is raised, the input for the question is considered invalid.

If the input is invalid, the user will see a message at the top of the screen containing the error message passed to validation_error(), or the error message for the error that was otherwise raised.

Address autocomplete

If you have defined a google maps api key in the Configuration, you can use the Place Autocomplete feature of the Google Places API to help your users enter addresses. Address suggestions will be provided as the user begins to type. To use this feature, modify the street address (.address) field by setting address autocomplete to True.

question: |
  What is the address of the adverse
  party?
fields:
  - Address: defendant.address.address
    address autocomplete: True
  - City: defendant.address.city
  - State: defendant.address.state
    code: |
      states_list()
  - Zip: defendant.address.zip
    required: False
  - County: defendant.address.county
address-autocomplete

For more information on using this feature, see the documentation for the Address object.

This feature can be used internationally with a variety of address types. Here is an example that illustrates all of the possible attributes of the Address object that can be set by Place Autocomplete.

question: |
  Test address autocomplete
fields:
  - address: the_address.address
    address autocomplete: True
  - note: |
      The following attribute names are Google Place components.
  - "administrative_area_&#8203;level_1": the_address.administrative_area_level_1
    required: False
  - "administrative_area_&#8203;level_2": the_address.administrative_area_level_2
    required: False
  - "administrative_area_&#8203;level_3": the_address.administrative_area_level_3
    required: False
  - "administrative_area_&#8203;level_4": the_address.administrative_area_level_4
    required: False
  - "administrative_area_&#8203;level_5": the_address.administrative_area_level_5
    required: False
  - colloquial_area: the_address.colloquial_area
    required: False
  - country: the_address.country
    required: False
  - floor: the_address.floor
    required: False
  - intersection: the_address.intersection
    required: False
  - locality: the_address.locality
    required: False
  - neighborhood: the_address.neighborhood
    required: False
  - post_box: the_address.post_box
    required: False
  - postal_code: the_address.postal_code
    required: False
  - postal_code_prefix: the_address.postal_code_prefix
    required: False
  - postal_code_suffix: the_address.postal_code_suffix
    required: False
  - postal_town: the_address.postal_town
    required: False
  - premise: the_address.premise
    required: False
  - room: the_address.room
    required: False
  - route: the_address.route
    required: False
  - suite: the_address.suite
    required: False
  - street_number: the_address.street_number
    required: False
  - sublocality: the_address.sublocality
    required: False
  - sublocality_level_1: the_address.sublocality_level_1
    required: False
  - sublocality_level_2: the_address.sublocality_level_2
    required: False
  - sublocality_level_3: the_address.sublocality_level_3
    required: False
  - sublocality_level_4: the_address.sublocality_level_4
    required: False
  - sublocality_level_5: the_address.sublocality_level_5
    required: False
  - subpremise: the_address.subpremise
    required: False
  - note: |
      The following attribute names are built in to **docassemble**.
      They have different names than Google Place component names.
  - unit: the_address.unit
    required: False
  - city: the_address.city
    required: False
  - state: the_address.state
    required: False
  - zip: the_address.zip
    required: False
address-autocomplete-test

A comprehensive example

Here is a lengthy example that illustrates many of these features.

question: Tell me more about yourself
fields:
  - Description: user_description
    datatype: area
    hint: |
      E.g., you can describe your
      hair color, eye color, 
      favorite movies, etc.
  - Annual income: user_annual_income
    datatype: currency
    min: 100
  - E-mail address: user_email_address
    datatype: email
  - Been vaccinated: user_vaccinated
    datatype: yesno
  - Seen Mount Rushmore: mount_rushmore_visited
    datatype: yesnowide
  - Opinion of turnips: turnip_rating
    datatype: range
    min: 1
    max: 10
  - Belly button type: belly_button
    datatype: radio
    choices:
      - Innie
      - Outie
  - html: |
      The date and time is
      <span class="mytime"
      id="today_time"></span>.
  - Number of friends: num_friends
    datatype: radio
    choices:
      - One: 1
      - Two: 2
      - Three: 3
  - Degrees obtained: degrees
    datatype: checkboxes
    choices:
      - High school
      - College
      - Graduate school
  - State you grew up in: home_state
    code: |
      us.states.mapping('abbr', 'name')
  - note: |
      #### Politics

      Tell me about your political
      views.
  - no label: political_views
    default: I have no political views
    maxlength: 30
  - Party: political_party
    datatype: radio
    shuffle: True 
    choices:
      - Republican
      - Democrat
      - Independent
css: |
  <style>
    .mytime {
       color: green;
    }
  </style>
script: |
  <script>
    $("#today_time").html(Date());
  </script>
fields

Assigning existing objects to variables

Using Mako template expressions (Python code enclosed in ${ }), you can present users with multiple-choice questions for which choices are based on information gathered from the user. For example:

include: basic-questions.yml
---
question: |
  What is your favorite date?
fields:
  - Greatest Date Ever: favorite_date
    datatype: date
    choices:
      - ${ client.birthdate }
      - ${ advocate.birthdate }
---
question: |
  The best day in the history of
  the world was ${ favorite_date }.
mandatory: True
object-try-1

But what if you wanted to use a variable to refer to an object, such as a person? You could try something like this:

question: |
  Who is the tallest?
fields:
  - Tallest person: tallest_person
    choices:
      - ${ client }
      - ${ advocate }
object-try-2

In this case, tallest_person would be set to the name of the client or the name of the advocate. But what if you wanted to then look at the birthdate of the tallest person, or some other attribute of the person? If all you had was the person’s name, you would not be able to do that. Instead, you would want tallest_person to be defined as the object client or the object advocate, so that you can refer to tallest_person.birthdate just as you would refer to client.birthdate.

You can accomplish this by setting datatype to object within a fields list, where the choices are the names of the objects from which to choose. (Optionally, you can set a default value, which is also the name of a variable.)

For example:

include: basic-questions.yml
---
question: Who is the villain?
fields:
  no label: villain
  datatype: object
  default: client
  choices:
    - client
    - advocate
---
question: |
  The villain, ${ villain }, was
  born on
  ${ format_date(villain.birthdate) }.
mandatory: True
object-try-3

Note that this interview incorporates the basic-questions.yml file which defines objects that are commonly used in legal applications, including client and advocate. It also contains questions for asking for the names of these people.

The interview above presents the names of the client and the advocate and asks which of these people is the villain.

If the user clicks the name of the advocate, then docassemble will define the variable villain and set it equal to advocate.

Note that because advocate is an object, villain will be an alias for advocate, not a copy of advocate. If you subsequently set advocate.birthdate, you will immediately be able retrieve that value by looking at villain.birthdate, and vice-versa.

Also because villain is an alias, if you refer to villain.favorite_food and it is not yet defined, docassemble will go searching for a question that offers to define advocate.favorite_food. This is because docassemble objects have an intrinsic identity, a unique name given to them at the time they are created. (You can inspect this by referring to villain.instanceName in a question and will see that it returns advocate.) For more information about this, see the discussion in the documenation for DAObject. (All docassemble objects are subtypes of DAObject.)

If any of the objects listed under choices represent lists of objects, such as case.defendant or client.child (objects of type PartyList, those lists will be expanded and every item will be included. You can also include under choices Python code, such as case.parties() or case.all_known_people().

The datatype of object presents the list of choices as a pull-down. If you prefer to present the user with radio buttons, set the datatype to object_radio.

Embedding fields within a paragraph

Within a fields question, you can include fill-in fields within the text of the subquestion using markup of the form [FIELD variable_name].

question: |
  Fill in the blanks.
subquestion: |
  I went downtown to get some
  [FIELD grocery_item].
  I slipped on the ice and
  fell in the
  [FIELD unfortunate_place].
fields:
  - Grocery item: grocery_item
  - no label: unfortunate_place
    choices:
      - storm drain
      - gutter
      - mineshaft
embed

Any variable name referenced in [FIELD ...] must be one of the variable names listed in the fields: list. If a field is referenced this way in the subquestion, it will not be displayed the way that fields are ordinarily displayed, but will be moved into the subquestion, where it will be formatted differently. Any fields in the fields: list that are not referenced in the subquestion will appear on the screen in the normal fashion.

The label of an embedded field is used as the tooltip of the field.

When you are using embedded fields, you can add the field modifier inline width to change the initial width of the field. For example, if you include inline width: 15em, the CSS will be altered so that the field is 15em wide. This modifier has no effect when embedded fields are not being used.

Generating fields with code

You can use Python code to generate items inside a fields. To do so, simply add an entry under fields that contains code (and nothing more). The contents of code will be evaluated as a Python expression.

The expression must evaluate to a list of dictionaries, and the format must be the Python equivalent of a regular fields item, which you would normally express in YAML.

For example, if you want the fields to be like this:

question: |
  How many of each fruit?
fields:
  - Apples: num_apples
    datatype: integer
  - Oranges: num_oranges
    datatype: integer

you would write this:

question: |
  How many of each fruit?
fields:
  - code: |
      [{'Apples': 'num_apples', 'datatype': 'integer'},
       {'Oranges': 'num_oranges', 'datatype': 'integer'}']

Here is an example that asks for the names of a number of people on a single screen:

objects:
  - people: DAList.using(object_type=Individual, ask_number=True)
---
question: |
  How many people are there?
fields:
  - Number: people.target_number
    datatype: integer
    min: 1
    max: 10
---
sets:
  - people[i].name.first
question: |
  What are the peoples' names?
fields:
  code: name_list
---
code: |
  name_list = list()
  for index in range(people.number()):
    name_list.append(
      {"label": ordinal(index, capitalize=True) + " person's given name",
       "field": "people[" + str(index) + "].name.first"
      })
    name_list.append(
      {"label": ordinal(index, capitalize=True) + " person's surname",
       "field": "people[" + str(index) + "].name.last"
      })
---
mandatory: True
question: |
  List of people
subquestion: |
  The people include:
  
  % for person in people:
  * ${ person }
  % endfor
fields-code

Note that it is necessary to use the sets modifier on the question to manually indicate that the question will define people[i].name.first. Normally, docassemble automatically detects what variables a question is capable of defining, but when the fields are dynamically generated with code, it is not able to do so.

Note also that this example uses the label and field method for indicating the label and the variable name for each field. This is not required, but it may make field-generating code more readable.

Dynamically-created lists of fields can be paired with dynamically-created subquestion text that embeds the fields.

objects:
  - people: DAList.using(object_type=Individual, ask_number=True)
---
question: |
  How many people are there?
fields:
  - Number: people.target_number
    datatype: integer
    min: 1
    max: 10
---
sets:
  - people[i].name.first
question: |
  What are the peoples' names?
subquestion: |
  ${ name_display }
fields:
  code: name_list
---
code: |
  name_list = list()
  name_display = ""
  for index in range(people.number()):
    name_list.append(
      {"label": ordinal(index, capitalize=True) + " person's given name",
       "field": "people[" + str(index) + "].name.first"
      })
    name_list.append(
      {"label": ordinal(index, capitalize=True) + " person's surname",
       "field": "people[" + str(index) + "].name.last"
      })
    name_display += "The " + ordinal(index) + " person's name is "\
                  + "[FIELD people[" + str(index) + "].name.first] "\
                  + "[FIELD people[" + str(index) + "].name.last].\n\n"
---
mandatory: True
question: |
  List of people
subquestion: |
  The people include:
  
  % for person in people:
  * ${ person }
  % endfor
fields-code-embed

It is also possible to mix dynamic fields with non-dynamic fields:

question: |
  Tell me about your food preferences.
fields:
  - Favorite fruit: favorite_fruit
  - code: food_list
  - Favorite vegetable: favorite_vegetable
---
reconsider: True
code: |
  food_list = [{'Favorite candy: 'favorite_candy'}]
  if likes_legumes:
    food_list.append({'Favorite legume': 'favorite_legume'})

Writing Python code that generates a list of fields can be pretty complex. This should be considered an advanced feature. Note that the code above uses the Python function str() to reduce the index of a list (which is an integer) into a string, for purposes of constructing variable names like people[0].name.first and people[1].name.first.

If you work with dictionaries (DADict objects) instead of lists (DAList objects), a useful function is the Python function repr(), which returns a string containing a string with quotation marks around it.

For example, suppose you want to replicate this:

question: |
  Tell me about the seeds.
fields:
  - label: Seeds of a kiwi
    field: fruit['kiwi'].seeds
  - label: Seeds of a tomato
    field: fruit['tomato'].seeds

You could do something like the following:

question: |
  Tell me about the seeds.
fields:
  - code: field_list
---
code: |
  field_list = list()
  for key in fruit:
    field_list.append({"label": "Seeds of a " + key, 
                       "field": "fruit[" + repr(key) + "].seeds"})

The alternative is to try to provide the quotation marks manually, which can look messier, and then you have to worry about what to do if the key string contains an apostrophe; will that cause a syntax error? The repr() function takes care of this problem by producing a robust Python representation of the string.

Generalizing questions

docassemble lets you write a single question that can be re-used throughout an interview.

For example, suppose you want to gather the following variables:

  • spouse.birthdate
  • mother.birthdate
  • father.birthdate

or:

  • plaintiff[0].served
  • plaintiff[1].served
  • plaintiff[2].served

It would be tedious to have to write separate questions for each of these variables.

Luckily, there are two features in docassemble that allow you to write questions in a generalized way: the generic object modifier, and index variables.

The generic object modifier

The generic object modifier is explained more fully in the section on modifiers, but here is an example:

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

The special variable x stands in for any object of type Individual.

If you are not yet familiar with the concept of “objects,” see the objects section.

Index variables

If you have an object that is a type or subtype of DAList or DADict, you can refer generically to any item within the object using an index variable.

question: |
  What is the ${ ordinal(i) }
  person's name?
fields:
  - First: people[i].name.first
  - Last: people[i].name.last
index-variable

The special variable i will stand in for the index of whichever list member your interview asks about.

You can nest iterators up to six levels, using the variables i, j, k, l, m, and n, but you have to use them in this order.

mandatory: True
code: |
  veggies.object_type = DAList
  veggies.new('potato', 'turnip')
  veggies.gathered = True
  for item in veggies:
    veggies[item].there_are_any = True
---
question: |
  Is there another ${ i }?
yesno: veggies[i].there_is_another
---
question: |
  How much does the
  ${ ordinal(j) }
  ${ i }
  weigh?
fields:
  - Grams: veggies[i][j]
    datatype: number
nested-veggies

For more information about populating groups of things, see the groups section.

For more information about how docassemble identifies what question to ask in order to define a given variable, see the interview logic section.

Special screens

Performing special actions requested by the user

In docassemble, you can allow users to click links or menu items that take the user to a special screen that the user would not ordinarily encounter in the course of the interview. You can create such a screen using the event statement.

An event statement acts just like sets: it advertises to docassemble that the question will potentially define a variable.

In the following example, the variable show_date is never defined; it is simply sought. The task_not_yet_performed() function is used to make sure that the dialog box only appears once.

mandatory: True
code: |
  if task_not_yet_performed('show current date'):
    mark_task_as_performed('show current date')
    show_date
---
event: show_date
question: |
  The current date is ${ format_date(current_datetime()) }.
buttons:
  - Ok: continue
dialog-box

The event statement is important if you use the roles feature to conduct multi-user interviews.

event: role_event
question: All done for now.
subquestion: |
  Someone else needs to answer
  questions now.  You will be notified
  when you can resume the interview.
buttons:
  - Exit: leave
event-role-event

In the example above, the event line tells docassemble that this question should be displayed to the user if docassemble encounters the role_event, which is a special “event” that can happen in multi-user interviews. The event is triggered when the interview reaches a point when a person other than the current user needs to answer a question. For example, while a client is filling out an interview, the interview logic might call for a variable that can only be set by an advocate who reviews the client’s answers. In this scenario, a role_event will be triggered. When this happens, docassemble will look for a question or code block that defines the variable role_event, and it will find the example question above.

This directive can also be used to create screens that the user can reach from the menu or from hyperlinks embedded in question text. For information and examples, see url_action(), process_action(), action_menu_item(), and menu_items.

Creating a special screen where the user can review his or her answers

A review block allows interview authors to provide a screen where users can review and edit their answers. Typically, the user will get to this screen by selecting an option from the web app menu (e.g., “Review Answers”), or by clicking on a hyperlink within subquestion text (e.g., “to review the answers you have provided so far, click here”).

Here is an example of a review block that is launched from the menu:

event: review_answers
question: |
  Revisit questions
subquestion: |
  These are the questions you have
  answered so far.  Click to revisit.
review:
  - Favorite fruit: fruit
  - Favorite vegetable: vegetable
  - Favorite fungus: fungi
---
mandatory: True
code: |
  menu_items = [ action_menu_item('Review Answers', 'review_answers') ]
review-1

If you click “Favorite fruit,” you are taken to a question where you can edit the value of fruit. This has the same effect as calling force_ask() on 'fruit' or running an action on 'fruit'; whatever block in your interview offers to define fruit will be used. After the user edits the value of the variable, the user will return to the review screen again.

Note that the review block does not show a link for “Favorite fungus” because the variable fungi has not been defined yet. However, once fungi is defined, the review block would show it.

This behavior is different from the typical behavior of docassemble blocks. Normally, referring to a variable that has not yet been defined will trigger the asking of a question that will define that variable. In the review block, however, the presence of an undefined variable simply causes the item to be omitted from the display.

For more information about adding menu items, see the sections on special variables and functions.

In the above example, note that the review screen is tagged with event: review_answers. For more information about how events work, see above. The interview will show this screen whenever it seeks out the definition of the variable review_answers. Since the screen is displayed based on an event, it can be called as many times during your interview as the user likes. Depending on which variables have been defined, the user will see different things.

Customizing the display of review options

You can provide the user with a review of answers and buttons that the user can press to revisit an answer:

event: review_answers
question: |
  Revisit your answers
review:
  - Revisit fruit: fruit
    button: |
      You said your favorite fruit was
      ${ fruit }.
  - Revisit vegetable: vegetable
    button: |
      You said your favorite vegetable
      was ${ vegetable }.
  - Revisit fungus: fungi
    button: |
      You said your favorite fungus 
      was ${ fungi }.
review-2

In addition, the review block, like the fields block, allows you to use note and html entries.

If these are modified with the optional show if modifier, they will only be displayed if the variable referenced by the show if modifier has been defined. In addition, if any of these entries refer to a variable that has not been defined yet, they will be omitted.

event: review_answers
question: |
  Revisit your answers
review:
  - note: |
      Revisit your food preferences.
    show if: fruit
  - Favorite fruit: fruit
  - Favorite vegetable: vegetable
  - Favorite fungus: fungi
review-3

The review block allows you to add help text to an entry, in which case the text is shown underneath the hyperlink. If this text expects a variable to be defined that has not actually been defined, the item will not be shown. Note: this is not available with the button display format.

event: review_answers
question: |
  Revisit your answers
review:
  - Favorite fruit: fruit
    help: |
      You indicated you liked
      ${ fruit }.
  - Favorite vegetable: vegetable
    help: |
      You indicated you liked
      ${ vegetable }.
  - Favorite fungus: fungi
    help: |
      You indicated you liked
      ${ fungi }.
review-4

By referring to a list of variables instead of a single variable, you can indicate that more than one variable should be sought. The fields mentioned will not appear on the review screen until all have been gathered.

event: review_answers
question: |
  Revisit your answers
review:
  - Edit:
      - fruit
      - vegetable
      - fungi
    button: |
      Your favorite fruit is ${ fruit }.
      
      Your favorite vegetable is ${ vegetable }.
      
      Your favorite fungus is ${ fungi }.
review-5

You can also indicate more than one variable when using show if:

event: review_answers
question: |
  Revisit your answers
review:
  - note: |
      Thank you for telling me
      about your food preferences.
    show if:
      - fruit
      - vegetable
      - fungi
  - Favorite fruit: fruit
  - Favorite vegetable: vegetable
  - Favorite fungus: fungi
review-6

Some of the variables that you use in your interview might be computed by code based on answers to questions, rather than defined directly by asking the user a question. Thus, if the user changes the answers to these underlying questions, you may want your interview to recompute the values of these variables. This recalculation does not happen automatically; however, you can cause it to happen in your review block by including recompute in the list of variables to be re-asked.

event: review_answers
question: |
  Revisit your answers
review:
  - Edit:
      - fruit
      - vegetable
      - recompute:
          - salad
      - fungi
    button: |
      Your favorite fruit is ${ fruit }.
      
      Your favorite vegetable is ${ vegetable }.
      
      Your favorite fungus is ${ fungi }.

      We expect you would enjoy a ${ salad }.
---
question: |
  What is your favorite fruit?
fields:
  - no label: fruit
---
question: |
  What is your favorite vegetable?
fields:
  - no label: vegetable
---
code: |
  salad = fruit + "-" + vegetable + " salad"
---
question: |
  What is your favorite fungi?
fields:
  - no label: fungi
review-7

In this example, it would not have worked to merely include the variable salad in the list of variables, as follows:

  - Edit:
      - fruit
      - vegetable
      - salad
      - fungi

Here, the presence of salad in this list means “ask a question to redefine the variable salad.” If there is no question that defines salad, the interview will generate an error. Including salad in a recompute list, as in the above interview, indicates that it is ok if the variable is defined by code.

You might also want to use recompute with variables that are defined by code in some circumstances but are defined by questions in other circumstances.

When you write lists of operations to be performed when a user clicks a link on a review page, you will probably want to make sure that at least one of the variables in the list will trigger the asking of a question. Otherwise, the user might click the link and be returned back to the same page again, and when that happens they may assume that clicking the link didn’t do anything, and the app is broken.

There are two other special commands that you can use in a list of variables in a review block: set and undefine. The following interview illustrates set:

objects:
  address: Address
---
event: review_answers
question: |
  Revisit your answers
review:
  - label: Edit
    fields:
      - address.address
      - set:
          - address.geolocated: False
      - recompute:
          - address.county
    button: |
      Your address is:

      ${ address }

      This address is located in
      ${ address.county }.
---
question: |
  What is your address?
fields:
  - "Street address": address.address
    address autocomplete: True
  - 'Apt/Unit': address.unit
    required: False
  - 'City': address.city
  - 'State': address.state
    code: states_list()
  - 'Zip': address.zip
---
question: |
  In which county in
  ${ state_name(address.state) }
  do you live?
fields:
  - County: address.county
---
sets: address.county
code: |
  address.geolocate()
---
mandatory: True
question: All done
subquestion: |
  You live in ${ address.county }.

  [Review your answers](${ url_action('review_answers')})
review-8

This interview demonstrates how to re-do the geolocation of an Address. When you call .geolocate() on an Address the first time, the address is geolocated and the .geolocated attribute of the object is changed from False to True. If you call .geolocate() on the object again, the first thing it does is check the .geolocated attribute, and if it is True, it will immediately return without doing anything. This is useful for avoiding unnecessary API calls, which can slow down the responsiveness of your app. However, if the user edits the underlying attributes of the address, you need to “reset” the geolocation in order to get it to run again.

In the above interview, the set command sets address.geolocated to False, which means that when the address.county is recomputed, and the .geolocate() method is run again by the code block, then the .geolocate() method will actually geolocate the new address.

Placing a review block within the interview logic

In the examples above, the review block is identified with an event like event: review_answers, meaning that the variable review_answers does not actually get defined, though it gets sought.

As a result, a review screen identified with an event can only be shown when triggered by a user action (e.g., clicking a link, selecting an item from the menu), or with code.

If you would like to insert a review screen into the normal course of an interview, so that it appears to the user one time, you can use field instead of event.

question: |
  Revisit your answers
review:
  - Revisit fruit: fruit
    button: |
      You said your favorite fruit was
      ${ fruit }.
  - Revisit vegetable: vegetable
    button: |
      You said your favorite vegetable
      was ${ vegetable }.
  - Revisit fungus: fungi
    button: |
      You said your favorite fungus 
      was ${ fungi }.
field: answers_reviewed
---
mandatory: True
code: |
  fruit
  vegetable
  fungi
  answers_reviewed
  final_screen
review-field

In this example, the variable answers_reviewed actually gets defined; it gets set to True when the user clicks “Continue.” It works much like a standard question with a “Continue” button that sets a variable to True.

The interview flow in this interview is set by the code block. First the interview asks about the user’s favorite fruit, vegetable, and fungus. Then the review screen is shown. Then the final screen is shown.

Ensuring variables are defined first

By default, when a review block encounters and undefined variable, it does not seek out its definition. This is so you can have a single review block that is used throughout your interview (or a section of your interview), where the user only sees that fields that have already been asked about.

If you would like to use the functionality of a review block, but you want all the variables to be defined first, set skip undefined to True:

skip undefined: True
question: |
  Review your answers
review:
  ...

This enables you to use tables in your review block. Ordinarily, tables are always undefined (so that their contents always reflect the current state of the list, so a review block would never display them.

Customizing the Resume button

By default, the review block puts a “Resume” button at the bottom of the screen. If you want the label on the button to be something other than the word “Resume,” add a resume button label modifier.

event: review_answers
question: |
  Revisit your answers
review:
  - Revisit fruit: fruit
    button: |
      You indicated you liked
      ${ fruit }.
  - Revisit vegetable: vegetable
    button: |
      You indicated you liked
      ${ vegetable }.
  - Revisit fungus: fungi
    button: |
      You indicated you liked
      ${ fungi }.
resume button label: Keep going
resume-button-label

However, if review is used with field, a “Continue” button is used. The “Continue” button can be customized using the modifier continue button label.

Why can’t review blocks be automatically generated?

The list of variables to display to the user in a review block needs to be specified by the interview author. There are several reasons why this needs to be done manually as opposed to automatically:

  1. Variables in your interview may be interdependent. You do not necessarily want to allow the interviewee to edit any past answer at will because this may result in internal inconsistencies or violations of the logic of your interview. For example, if your interview has a variable called eligible_for_medicare, which is set after the user answers a series of questions, you would not want the user to be able to go back and set his or her age to 30, at least not without a reconsideration of the definition of eligible_for_medicare. Therefore, it is important that the interview author control what the user can edit.
  2. A list of answers already provided might not be user-friendly unless the interview author presents it in a logically organized fashion. The order in which the questions were asked is not necessarily the most logical way to present the information for editing.