Skip to content

Latest commit

 

History

History
3990 lines (3077 loc) · 164 KB

File metadata and controls

3990 lines (3077 loc) · 164 KB
layout docs
title Setting variables (and doing other things) with questions
short_title Setting Variables

To instruct docassemble to store user input that it receives in response to a [question], you need to include in your [question] a [variable name](#variable names) to hold the information. You also need to indicate what type of variable it is (e.g., text, a date, a number), and how you want to ask for the input (e.g., with a label).

This section explains the many ways that variables can be populated using [question]s.

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)
  • user.__ssn (attributes beginning with __ are not allowed)
  • for or license.for (attributes cannot share names with built-in Python syntax. Names like for, while, or in will result in errors if used as variable names or attribute names. See [reserved variable names] for a list of names that cannot be used.

If you find yourself using variable names like automobile_one and automobile_two, you should learn about [groups] and generalizing. It would make more sense to work with variables automobile[0] and automobile[1], or automobile[i].

If you find yourself using variable names like employment_income, self_employment_income, and retirement_income, you should learn about the [DADict] (a type of [group]). It would make more sense to work with variables like income['employment'], income['self-employment'], and income['retirement']. Then you could generalize the questions you ask.

And if you find yourself using variable names like defendant_spouse_ssn and defendant_spouse_date_of_birth, you should learn about [objects]. It would make more sense to work with variables like defendant.spouse.ssn and defendant.spouse.birthdate. There are many advantages of working with objects, such as being able to write defendant.age_in_years() and defendant.spouse.age_in_years() to calculate the ages of people based on their birthdates.

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

yesno and noyes

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

{% include side-by-side.html demo="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.

noyes is just like yesno, except that "Yes" means False and "No" means True.

{% include side-by-side.html demo="noyes" %}

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

yesnomaybe or noyesmaybe

These 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.

{% include side-by-side.html demo="yesnomaybe" %}

Note that both False and None are considered to be "false" values in [Python]. So if you write:

{% highlight text %} % if not topeka_is_capital_of_kansas: You are a dummy! % endif {% endhighlight %}

then the phrase "You are a dummy!" will be shown both if the value is False and also if the value is None. If you need to test specifically for the "I don't know" answer, use is None.

Multiple choice buttons

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

buttons must always refer to a 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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="buttons" %}

In other words, this:

{% include side-by-side.html demo="buttons-variation-1" %}

is equivalent to this:

{% include side-by-side.html demo="buttons-variation-2" %}

You can also provide the label and the corresponding value by using a dictionary containing keys label and value.

You can customize the appearance of buttons by specifying a css class and/or a color in the dictionary.

{% include side-by-side.html demo="buttons-css-class-color" %}

The color should refer to one of the [Bootstrap colors] (primary, secondary, success, danger, warning, info, light, link, or dark).

If you want one of the choices to be shown or not shown conditionally, you can use show if to specify a [Python expression]. If the expression evaluates to true, then the choice will be included, and if it evaluates to false, it will be excluded.

{% include side-by-side.html demo="buttons-show-if" %}

Using code to generate the choices

A powerful feature of buttons (which also works with [choices](#field with choices), [dropdown](#field with dropdown), and [combobox](#field with 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.

{% include side-by-side.html demo="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:

{% include side-by-side.html demo="buttons-code-list-equivalent" %}

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

{% include side-by-side.html demo="buttons-code-list-partial" %}

Instead of using key-value pairs to represent what the variable is set to and the label, you can use value and label as keys in the dictionary. You can also use the dictionary keys css class and color to modify the appearance of the buttons, and show if to conditionally include the button.

{% include side-by-side.html demo="buttons-code-list-label-value" %}

The color should refer to one of the [Bootstrap colors] (primary, secondary, success, danger, warning, info, light, link, or dark).

As explained [below](#image button), you can also use code to [decorate the buttons with images](#image button).

If you need the variable to have a data type other than text, you need to specify a datatype.

{% include side-by-side.html demo="buttons-code-list-label-value-datatype" %}

The possible datatype values include boolean (True or False), threestate (True, False, or None), and other [data types](#data types).

True/False buttons

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

{% include side-by-side.html demo="yesno-custom" %}

In order for the variable to be set to the special [Python] values True and False, you need to make sure that the only values you list are True and False, and nothing else, just like in the example above. If you include a different value, your variable will be set to 'True' or 'False', which could cause problems.

Multiple choice list

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

{% include side-by-side.html demo="choices" %}

You can specify a default value using default:

{% include side-by-side.html demo="choices-with-default" %}

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

{% include side-by-side.html demo="choices-with-default-item" %}

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

{% include side-by-side.html demo="choices-with-help" %}

You can customize the appearance of a particular item by specifying color and css class:

{% include side-by-side.html demo="choices-css-class-color" %}

The color should refer to one of the [Bootstrap colors] (primary, secondary, success, danger, warning, info, light, link, or dark).

If you want a choice to be included conditionally, you can use show if to specify a [Python expression] that indicates when the choice should be included or not.

{% include side-by-side.html demo="choices-show-if" %}

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

{% include side-by-side.html demo="choices-from-code" %}

Multiple choice dropdown

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

{% include side-by-side.html demo="choices-dropdown" %}

Multiple choice combobox

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

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="buttons-icons" %}

This works with choices as well:

{% include side-by-side.html demo="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](#code generated buttons), you can specify an image by including an additional key/value pair within an item, where the key is image.

{% include side-by-side.html demo="buttons-icons-code" %}

There is an additional feature available when you assemble buttons with [code](#code generated buttons): 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:

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="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. Embedded blocks cannot use the generic object modifier or [index variables](#index variables).

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.

{% include side-by-side.html demo="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.

To undo a user's choice on a [question] that embeds blocks, tag the [question] with an [id] and call the [forget_result_of()] function with the ID.

Questions with only a "continue" button

{% include side-by-side.html demo="continue-participation" %}

A [question] with merely a continue button field will offer the user a "Continue" button. When the user presses "Continue," the variable indicated by continue button field will be set to True.

If you are using [fields] and you want the "Continue" button to set a variable to True the way that this [question] type does, you can add the [continue button field] specifier.

Questions that collect one or more fields on a screen

So far, we have discussed questions that set a single multiple-choice variable and the use of [continue button field] to set a single variable to True. These are helpful when you are collecting True or False values or multiple choice values. However, docassemble's primary tool for collecting information in is the fields specifier. fields allows you to collect many different [types of information](#data types) and to collect more than one piece of information on a screen.

{% include side-by-side.html demo="text-field-example" %}

The fields specifier must refer to a [YAML] list of one or more "fields". Each list item must consist 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) allow you to modify how the field is displayed to the user.

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

The next section describes the different types of variables you can gather with fields and the different types of user interfaces you can use.

Data types and input types

Within a [fields] question, there are many possible [datatype] values that you can use. These affect what the user sees and how the input is stored in a variable.

The possible values of [datatype] are:

In most cases, [datatype] controls both the user interface and the format in which the data is stored. But for certain multiple choice questions, you can use [datatype] to indicate how you want the data stored, and use [input type] to indicate the type of user interface to use. The possible values of [input type] are:

The following subsections describe the available [datatype]s and [input type]s that you can assign to a field within [fields].

Plain text

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

{% include side-by-side.html demo="text-field" %}

input type: area provides a multi-line text area.

{% include side-by-side.html demo="text-box-field" %}

You can change the number of rows in the text area using the rows specifier:

{% include side-by-side.html demo="text-box-field-rows" %}

The default number of rows is four.

Passwords

datatype: password provides an input box suitable for passwords.

{% include side-by-side.html demo="password-field" %}

Dates

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

{% include side-by-side.html demo="date-field" %}

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

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:

{% include side-by-side.html demo="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.

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.

To set a default value, you can set [default] to any value that can be understood as a date.

{% include side-by-side.html demo="date-default" %}

Likewise, to set limits, you can set min and/or max to a string that can be recognized as a date.

{% include side-by-side.html demo="date-limit" %}

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].

{% include side-by-side.html demo="time-field" %}

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

To indicate a default time, write a [default] value in the format 13:43:23. If you have a [datetime.time] variable called meeting_start and you want the value of meeting_start to be the default time for a field, you can set the [default] value to ${ meeting_start }. This has the same effect as str(meeting_start) or meeting_start.strftime('%H:%M:%S').

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:

{% include side-by-side.html demo="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].

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="number-field" %}

You can use the optional field modifier step to limit the number to a certain number of decimal places:

{% include side-by-side.html demo="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 the [locale] defined in the [configuration].

{% include side-by-side.html demo="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.

If the locale convention places the currency symbol after the number, the currency symbol will be placed before the field; otherwise it will be placed after the field.

If the currency symbol defined by the locale is not the currency you want to use, you can include an [initial] block that calls [set_locale()] with the currency_symbol keyword parameter set to the symbol you want to use. This will set a default value for datatype: currency fields and for the [currency()] function.

Keep in mind that the variable stored by a datatype: currency field is just a number, so it is not aware of the currency denomination that was presented to the user when the information was collected.

You can also override the currency symbol on a field-by-field basis by setting the currency symbol field modifier.

{% include side-by-side.html demo="money-field-euro" %}

As this interview demonstrates, the [currency()] function accepts an optional keyword parameter symbol that allows you to override the symbol that is displayed.

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.

{% include side-by-side.html demo="range" %}

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

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="upload-multiple" %}

If you want to limit uploads to particular file types, you can use accept to specify [Python] code that returns a custom [accept] attribute. The value of accept is passed directly into the [accept] attribute in the HTML and into the [accept method] of the [jQuery Validation Plugin].

{% include side-by-side.html demo="upload-accept" %}

Note that the syntax is very specific: the double quotation marks are part of the string itself.

Since the [accept method] of the [jQuery Validation Plugin] only works with MIME types, you can only specify MIME types here, not file extensions.

By default, docassemble styles the upload using the [Bootstrap File Input] plugin. If you do not want the [Bootstrap File Input] plugin to be used, you can set file css class to None.

{% include side-by-side.html demo="upload-plain" %}

If you set file css class to None, then the class of the <input type="file"> element will be form-control, which is the standard class that [Bootstrap] uses to style file input elements. You can set file css class to any other class of your choosing if you want to use a different class than form-control. The file css class modifier can use [Mako] templating.

If no file css class is specified, the class of the input element will be dafile, which causes the [Bootstrap File Input] plugin to be activated.

Note that file css class is different from css class; the css class modifier simply adds additional classes to the class attribute of the input element, whereas file css class replaces the default class, which is dafile.

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:

{% include side-by-side.html demo="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.

If you are using maximum image size, you can also cause images to be converted to [PNG], [JPEG], or [BMP] by the browser during the upload process by setting the image upload type to png, jpeg, or bmp.

{% include side-by-side.html demo="upload-max-image-size-type" %}

By default, any file that a user uploads during a session will be deleted when that session is deleted. If you want the file to continue to exist after the session is deleted, you can set the field modifier persistent to True. The modifier also accepts [Python] code; if the code evaluates to a true value, the file will persist. This has the same effect as calling the [.set_attributes()] method on the file variable using the keyword attribute persistent.

By default, any file that a user uploads will only be downloadable by the user or by an administrator. If you want the file to be accessible to anyone, set the field modifier private to False. The modifier also accepts [Python] code; if the code evaluates to a false value, the file will be available to anyone. This has the same effect as calling the [.set_attributes()] method on the file variable using the keyword attribute persistent.

If you set private: False, then the file is available to anyone, including non-logged in users. Even a bot that guesses URLs could download the file. If you want to share with particular users, you can indicate specific users using the allow users modifier.

{% highlight yaml %} fields:

{% highlight yaml %} fields:

  • Your file: file_variable datatype: file allow users:
    • 1
    • 2 {% endhighlight %}

If allow users refers to a [YAML] list, the list is expected to be a list of e-mail addresses of users or integers indicating the numeric user IDs of users. If allow users refers to text, the text is treated as a single item.

{% highlight yaml %} fields:

  • Your file: file_variable datatype: file allow users: peter@abc.com {% endhighlight %}

[Mako] is not available; however, if allow users refers to a [YAML] dictionary, the single key of which is code, you can specify users with [Python] code. The code is expected to evalute to an e-mail address, an integer user ID, an [Individual] with the email attribute set, or a list or [DAList] of any of the above.

{% highlight yaml %} fields:

  • Your file: file_variable datatype: file allow users: code: | [advocate] + ([user_info().id] if user_logged_in() else []) {% endhighlight %}

You can also use the [.user_access()] method to control which users have access to a file.

Instead of granting access to specific other users, you can use the allow privileges field modifier to grant access to categories of users by referencing [privileges] by name, such as user, developer, or advocate. The allow privileges modifier works much like the allow users modifier. If the allow privileges modifier refers to a [YAML] list, the list items are expected to be text items like user or developer. If allow privileges refers to a string, it is treated as a single item. [Mako] is not allowed. If allow privileges refers to a [YAML] dictionary, the single key of which is code, you can define the privileges using [Python] code, which is expected to evaluate to text (e.g., 'user') or a list of text strings (e.g., ['user', 'developer']). You can also use the [.privilege_access()] method to control which users have access to a file.

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

datatype: camera is just like file, except it limits the allowable file types to image files.

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 file, except it limits the allowable file types to video files.

datatype: microphone is just like file, except it limits the allowable file types to audio files.

For more information about uploading files, and for instructions on uploading signature images, see the Uploads subsection.

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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="fields-yesno-uncheck-others-list" %}

Other times, when you are using a series of these checkboxes, you might want to have an "all 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 check others: True.

{% include side-by-side.html demo="fields-yesno-check-others" %}

This will cause the field to act as an "all 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.

{% include side-by-side.html demo="fields-yesno-check-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.

{% include side-by-side.html demo="fields-yesnoradio" %}

datatype: yesnomaybe will show radio buttons offering choices "Yes," "No," and "I don't know." The resulting Python values are True, False, and None.

{% include side-by-side.html demo="fields-yesnomaybe" %}

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

{% include side-by-side.html demo="fields-noyesmaybe" %}

When you provide help text for a yesno field, the help will be available as a popup accessible from a button located to the right of the field.

{% include side-by-side.html demo="fields-yesno-help" %}

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.

{% include side-by-side.html demo="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:

{% include side-by-side.html demo="fields-checkboxes-different-labels" %}

You can also express the checkboxes as a list of dictionaries where each dictionary has the keys label and value.

{% include side-by-side.html demo="fields-checkboxes-label-value" %}

The [all_true()], [all_false()], [any_true()], [any_false()], [true_values()], and [false_values()] methods of [DADict] can be used to analyze the values set by a checkboxes field. For example:

{% include side-by-side.html demo="fields-checkboxes-dadict" %}

If you want to require the user to select a minimum or maximum number of checkboxes, you can use the [minlength] and/or [maxlength] field modifiers.

You can generate checkbox choices with code:

{% include side-by-side.html demo="fields-checkboxes-code" %}

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:

{% include side-by-side.html demo="fields-checkboxes-default-0" %}

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

{% include side-by-side.html demo="fields-checkboxes-default-1" %}

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

{% include side-by-side.html demo="fields-checkboxes-default-2" %}

You can also use [Python] code to generate the defaults:

{% include side-by-side.html demo="fields-checkboxes-default-3" %}

Your [Python] code can also return a [dictionary]:

{% include side-by-side.html demo="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]:

{% include side-by-side.html demo="fields-checkboxes-default-5" %}

This also works if your code returns a [list] of [list]s:

{% include side-by-side.html demo="fields-checkboxes-default-6" %}

Multiselect

datatype: multiselect works much like datatype: checkboxes, except it uses the [HTML] <select> element with the multiple flag set. On desktop browsers, multiple items can be selected by clicking items while holding down the Ctrl or Command key and clicking each item.

{% include side-by-side.html demo="fields-multiselect" %}

Unlike datatype: checkboxes, the datatype: multiselect field does not support the use of a "None of the above" option.

You can use the rows specifier to indicate how many rows tall the multiselect box should be:

{% include side-by-side.html demo="fields-multiselect-rows" %}

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.

{% include side-by-side.html demo="fields-choices-dropdown" %}

You can also include input type: dropdown:

{% include side-by-side.html demo="fields-choices-dropdown-input-type" %}

The input type: dropdown does not actually have any effect, since dropdown is the default input type. (The other options for input type are radio, combobox, and datalist.)

The code option, which uses [Python] code to generate the list of choices, is often used in combination with exclude, which excludes one or more items from the list of choices.

Multiple-choice combobox

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

{% include side-by-side.html demo="fields-choices-combobox" %}

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

Combobox that fetches choices from the server

input type: ajax looks like a [combobox], but is really a dropdown selector that retrieves its choices from the server using [Ajax], based on what the user types. It is useful when the number of possible values in the dropdown is too large to send to the browser all at once, and you want to allow the user to find the item they want to select by typing the start of a word or phrase.

To use input type: ajax, you also need to supply an action specifier. The browser will use the [JavaScript] function [url_action_call()] to call the given action. The text that the user types into the field will be passed to the [action] as the wordstart argument. The [action] needs to return a [JSON] list of items.

The following example uses the [words file] (from the [wamerican] package) as a data source for the combobox options.

{% include side-by-side.html demo="fields-ajax" %}

The [code] block that carries out the action should always begin with set_save_status('ignore'). If you leave this out, then a step will be added to the interview each time the results are fetched. The [code] block should always end by calling [json_response()] that returns the relevant choice or choices.

The data that you pass to [json_response()] can be in one of three forms:

  1. A list of pieces of text;
  2. A dict in which the keys are the underlying values (what the variable will be set to) and the values are labels (what the user sees and types); or
  3. A list of lists, where the first item in each sub-list is the underlying value and the second item is the label.

If you use the second or third option, note that docassemble will only store the underlying value in the variable, even though the user typed the label. In order for your datatype: ajax field to function properly if the question is revisited during a review process or the use of the Back button, your action needs to be able to accept as input either the underlying value or the label. In the following example, note the special return value if the wordstart argument matches a key in the dictionary.

{% include side-by-side.html demo="fields-ajax-2" %}

If you press the Back button to return to the datatype: ajax field, the initial value of the field will be 'x234', 'y432', or 'h293'. This value will be sent to the action to be looked up, and then the screen will show the label rather than the value.

In order to avoid sending too many requests to the system, the requests are throttled so that they happen no more than once every two seconds.

The list will not start showing results until the user types at least four characters. If you want to use a different number of characters as the minimum, set trigger at. For example:

{% highlight yaml %} question: | What is your favorite word? fields:

  • Word: favorite_word input type: ajax action: wordlist trigger at: 3 {% endhighlight %}

Multiple-choice datalist

Similar to the [combobox], input type: datalist provides a text box into which the user can type any value they want, while drop-down selections appear when the user starts typing. The user is not required to enter a value from the list of choices.

{% include side-by-side.html demo="fields-choices-datalist" %}

Unlike the [combobox], there is no button in the UI that can be clicked to show the entire drop-down list; a selection of dropdown values is only shown when the user presses the down arrow key or starts typing. The browser filters the list of dropdown values based on what the user has typed. The dropdown list shows not only the list of choices, but also values that the browser suggests, based on values that the user has entered in the past.

While the [combobox] is built from JavaScript, the datalist uses the HTML5 [datalist] element, so the functionality is entirely determined by how the user's browser implements [datalist]. Although [datalist] has been part of the HTML5 specification for many years, its implementation in browsers has been incomplete, and many developers have chosen not to use [datalist]. However, the [datalist] element could have superior [accessibility] because screen readers may have a built-in method for presenting the [datalist] choices to the user.

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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="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. If choices refers to a string, that string is expected to be a [Python expression] that returns a list of objects. If choices refers to a YAML list, then each item in the list is expected to be a [Python expression] that returns either an object or a list of objects.

{% include side-by-side.html demo="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.

{% include demo-side-by-side.html demo="someone-already-mentioned" %}

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

{% highlight python %} gardener = cook {% endhighlight %}

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.

{% include side-by-side.html demo="object-radio" %}

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

datatype: object_checkboxes is similar to datatype: object, except it results in an object of type [DAList] (or a subtype thereof) consisting of zero or more items selected by the user. The choices specified 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.

{% include side-by-side.html demo="object-checkboxes-dalist" %}

You can 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:

{% include demo-side-by-side.html demo="object-checkboxes-custom" %}

Note the placement of the objects block that defines villain as a PartyList. If this objects block came before the question that defines villain, then the question block would take precedence over the objects block and define villain as a plain DAList. Since the objects block is placed after the question, it supersedes the question, and defines villain as a PartyList. The question will still be asked, however, because even if villain is defined, it is not yet gathered; the question will be asked when a definition of villain.gathered is needed.

When you use an already-existing DAList, you can set default values of the checkboxes in the object_checkboxes list. In this example, we use the [.append()] method to initialize the list of villains.

{% include side-by-side.html demo="object-checkboxes-default" %}

This example illustrates a second method of making sure that villain gets defined as a PartyList: marking the objects block with mandatory: True. This causes each variable in the objects list to be defined as an object before the rest of the interview logic is evaluated.

datatype: object_multiselect is similar to datatype: object_checkboxes, except it uses the [HTML] <select> element with the multiple flag set. On desktop browsers, multiple items can be selected by clicking items while holding down the Ctrl or Command key and clicking each item.

{% include side-by-side.html demo="object-multiselect-dalist" %}

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

{% include demo-side-by-side.html demo="predict-happy-sad" %}

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

Hidden field

input type: hidden results in an invisible field that can only be changed from its default value by [JavaScript].

{% include side-by-side.html demo="fields-hidden" %}

This can be useful if you want fields to be populated by the [address autocomplete] feature but you do not want the fields to be shown to the user.

If you think you need to use input type: hidden, but you are not using the [address autocomplete] feature and you have not written your own [JavaScript] code to populate the field, then you most likely should not use input type: hidden, and should perhaps use a [code] block instead. The input type: hidden feature exists solely for interacting with [JavaScript] and is not part of docassemble's [logic system].

No browser-based input validation is performed on a field with input type: hidden. If you need input validation on a input type: hidden field, use [validation code]. An error message cannot be displayed next to a hidden field.

{% include side-by-side.html demo="fields-hidden-autocomplete" %}

Raw data

By default, inputs with the datatype of text (which is the default) will be sanitized of any HTML. If you want to allow users to include HTML, set the datatype to raw.

{% include side-by-side.html demo="fields-raw" %}

Custom data types

You can use custom data types by declaring a subclass of CustomDataType in a Python module with class attributes that describe the data type. For example, here is an example that defines a [Social Security number] (SSN) as a data type:

{% highlight python %} from docassemble.base.util import CustomDataType, DAValidationError, word import re

class SSN(CustomDataType): name = 'ssn' container_class = 'da-ssn-container' input_class = 'da-ssn' javascript = """
$.validator.addMethod('ssn', function(value, element, params){ return value == '' || /^[0-9]{3}-?[0-9]{2}-?[0-9]{4}$/.test(value); }); """ jq_rule = 'ssn' jq_message = 'You need to enter a valid SSN.' @classmethod def validate(cls, item, variable_name, data): item = str(item).strip() m = re.search(r'^[0-9]{3}-?[0-9]{2}-?[0-9]{4}$', item) if item == '' or m: return True raise DAValidationError("A SSN needs to be in the form xxx-xx-xxxx") @classmethod def transform(cls, item, variable_name, data): item = str(item).strip() m = re.search(r'^([0-9]{3})-?([0-9]{2})-?([0-9]{4})$', item) if m: return m.group(1) + '-' + m.group(2) + '-' + m.group(3) else: return item {% endhighlight %}

This will allow you to write:

{% highlight yaml %} question: | What is your Social Security Number? fields:

  • SSN: user.ssn datatype: ssn {% endhighlight %}

The user will not be able to proceed without entering a valid SSN, and if the user enters an SSN without hyphens, the input will be accepted, but hyphens will be added to the variable user.ssn.

The available class attributes are:

  • name (required) - the datatype name. The only valid characters are alphanumeric characters, the hyphen, and the underscore.
  • container_class - a [CSS] class for the parent container. By default, this will be set to da-field-container-datatype- followed by the name.
  • input_class - a [CSS] class for the <input> element. By default, this will be set to da followed by the name.
  • input_type - the type for the <input> element. By default, this will be set to text.
  • javascript - [JavaScript] code related to the data type. By default, this will be None. If not None, the attribute is treated as [JavaScript] code that is run when every interview on your server first loads. This is typically used to extend the capabilities of the [jQuery Validation Plugin]. In the example above, the javascript defines a new validation rule. For more information on how to extend the [jQuery Validation Plugin], see the documentation for [jQuery.validator.addMethod()]. The javascript code can be used to initialize any fields on the screen that have the custom datatype. For example, if your container_class is da-ssn, your javascript could be:

{% highlight javascript %} $(document).on('daPageLoad', function(){ $(".da-ssn").each(function(){ $(this).after("

You better get this right!
"); }); }); {% endhighlight %}

  • jq_rule - the name of the [jQuery Validation Plugin] rule to enable on the field, if any. This is typically used to refer to a rule you define in the javascript attribute. If you want to enable multiple [jQuery Validation Plugin] rules, you can set this to a list of rule names.
  • jq_message - the message to display to the user when the jq_rule is not satisfied. The message will pass through the [word()] function to support multiple languages, and it can be overridden with [validation messages]. Instead of setting jq_message to a string, you can set it to a dictionary in which the keys are [jQuery Validation Plugin] rule names and the values are error messages. For example, if you want a custom message to display when the user leaves the field blank, you can set jq_message to something like jq_message = {'ssn': 'You need to enter a valid SSN.', 'required': 'We really need your SSN.'}. If no message is provided for a rule, a generic message is used. If jq_rule is a list of rules, jq_message must be expressed in dictionary format.
  • skip_if_empty - the default is True. This is rarely used, so you can probably ignore it. This is relevant when the datatype is used on a multiple-choice question and there are zero choices to present to the user. If skip_if_empty is True, then the variable is not set to any value. If skip_if_empty is False, then the variable will be set to the output of the .empty() class method.
  • is_object - the default is False. If you have a transform() class method that returns something that cannot be defined with repr(), set this to True.
  • parameters - the default is []. If you want to pass parameters from the YAML to data attributes of the resulting <input>, you can list the parameter names here. For example, if parameters is set to ['kind'], and you include kind: basic as a field modifier in the YAML of the field, then the [HTML] of the <input> element will contain data-kind="basic". You can extract these data values using [JavaScript].
  • code_parameters - the default is []. This is just like parameters, except the values in the YAML are treated as a [Python expression], and the data value is set to the output of this evaluation. Make sure that the [Python expression] evaluates to something sensible, like a string.
  • mako_parameters - the default is []. This is just like parameters, except the values in the YAML are treated as [Mako], and the data value is set to the rendered text.

The available class methods are:

  • validate() - the validate() class method is used for server-side input validation. The raw value from the POST request is passed to this method as the first positional parameter. The second positional parameter is the name of the variable, as a string. The third positional parameter is a dictionary containing the parameters specified in the YAML (see parameters, etc., above). The method should return True if the value is valid. If the value is invalid, the method should raise a DAValidationError exception with a message. The message given to DAValidationError will pass through the [word()] function before it is presented to the user, so you can use the [words] directive in the [Configuration] to support multiple languages. If the method returns a false value, the error message will be "You need to enter a valid value." If a validate() class method is not provided, no input validation will be performed.
  • transform - the transform() class method is used to perform any necessary transformations on the data received from the browser. The raw value from the POST request is passed to this method as the first positional parameter. The second positional parameter is the name of the variable, as a string. The third positional parameter is a dictionary containing the parameters specified in the YAML (see parameters, etc., above). The method should return the transformed value. In the example above, the transform() class method ensures that Social Security numbers that are entered without hyphens (which are accepted) will contain hyphens when the variable user.ssn is actually defined. If a transform() class method is not provided, the variable will be set to the raw value from the POST request (a string).
  • default_for - the default_for() method is used when the user visits a question when the variable is already defined. If the output of your transform method is not suitable for placing into the field as the default value, you can define a default_for() class method that returns the text that should be inserted into the field. The first positional parameter for the default_for() class method is the value of the variable. The second positional parameter is the name of the variable, as a string. The third positional parameter is a dictionary containing the parameters specified in the YAML (see parameters, etc., above). If your transform() method returns a [Python object], you can write a default_for() class method that takes the object as the first positional parameter and returns plain text (an str) that is a suitable default value to use for the HTML field. If you do not define a default_for() class method, an attempt will be made to obtain a default value by running str() on the variable.
  • empty - this is rarely used, so you can probably ignore it. The empty class method is used when skip_if_empty is False. Instead of not defining the variable, docassemble will set the value of the variable to the output of this method. If no empty() method is provided, the value None is used.

Here is an example that demonstrates the use of the transform(), validate(), and default_for() methods.

{% highlight python %} from docassemble.base.util import CustomDataType, Thing, DAValidationError, word

class CustomFruit(CustomDataType): name = 'customfruit' container_class = 'da-fruit-container' input_class = 'da-fruit' is_object = True parameters = ['seeds'] code_parameters = ['max words'] @classmethod def transform(cls, item, variable_name, data): new_object = Thing(variable_name) new_object.name.text = item new_object.seeds = data.get('seeds', 0) return new_object @classmethod def validate(cls, item, variable_name, data): max_words = data.get('max words', 2) if len(item.split()) <= max_words: return True raise DAValidationError(word("You cannot write more than %d words") % (max_words, )) @classmethod def default_for(cls, item, variable_name, data): if isinstance(item, Thing): return item.name.text return str(item) {% endhighlight %}

An example of using this data type would be:

{% highlight yaml %} question: | Tell me about the fruit. fields:

  • Fruit name: favorite_fruit datatype: customfruit max words: 3 seeds: 10 {% endhighlight %}

The favorite_fruit variable will become a Thing object. The value of the HTML <input> element becomes the .name.text attribute of the object. In addition, the .seeds attribute of the object is defined using the custom parameter seeds. The validation method limits the number of words that can be used, based on the parameter max words, which can contain a Python expression.

Note that the default_for() and transform() objects are complementary; the default_for() converts a Thing object to a string, and transform() converts a string into a Thing object. The default_for() method needs to be flexible because the default value of the field that is passed to the default_for() method may be plain text or a Thing object, depending on the circumstances. The value will be a string if a default field modifier is used (which is always treated as a Mako expression that returns a string), or if there is a validation error, in which case the default value becomes the string that was found invalid.

Python classes, when loaded into the Python web application, are loaded globally across all threads of the server; they are not loaded just for one interview or just for one session. Likewise, the name attribute associated with a CustomDataType class is also global on the server. For this reason, you may wish to "namespace" your CustomDataType names, using a name like aag_ssn instead of of ssn (e.g., if your company name is AAG). That way, if a number of different packages are used together, it is less likely there will be "name collision."

By default, docassemble will load the JavaScript for any CustomDataType data types that are used in the interview YAML. However, if fields are created by Python code, docassemble cannot detect what CustomDataType data types will be used. In this circumstance, you can manually list them under [custom datatypes to load] specifier under [features], and the necessary JavaScript will be loaded.

How custom data types work

When the server starts, it looks through all of the packages under the docassemble namespace and loads every .py file that contains a class definition (unless the line # do not pre-load is present). It is during this initial loading time that CustomDataType class definitions are processed and loaded. As a result, you do not need to load the .py file in your interview with [modules] or [imports] when you use a datatype from a CustomDataType class in your YAML.

When choosing a name for your CustomDataType, do not use a name that someone else might use. Because CustomDataType class definitions are processed when the server starts, all CustomDataType definitions are global to the server; they are not specific to a particular interview, session, or user. If another module is installed on the system (including in the Playground of some other user) that defines a CustomDataType with the same name as that which you are trying to define, one definition will overwrite the other and you may get confusing results.

Options for items in fields

The following are the keys that have special meaning within a list item under [fields].

datatype

datatype affects how the data will be collected, validated and stored. For a full explanation of how this is used, see [above](#data types).

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, a datalist, or a combobox. For a full explanation of how this is used, see [above](#input types).

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, 0.0 for number and currency fields, 0 for integer fields, and None for multiple-choice, yes/no, and file fields.

Some datatypes are never marked with a red asterisk. For example, range and [yesno](#fields yesno) fields are set to real values by default, so the user cannot actually 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.

{% include side-by-side.html demo="optional-field" %}

Instead of writing True or False, you can write a [Python expression]. This expression 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.

{% include side-by-side.html demo="required-code" %}

Instead of using a true/false variable, you could use a conditional expression such as favorite_fruit == 'apple'.

Note that the [Python expression] is evaluated on the server, before the screen loads in the browser. Whether a field is required or not cannot be controlled in real time when the user is looking at the screen.

disabled

If the disabled field modifier is set to True or to a Python expression that evaluates to a true value, the field will be displayed as normal, but will be disabled.

{% include side-by-side.html demo="disabled-field" %}

When the field is disabled, this also has the effect of required: False.

When the user presses the Continue button, the variable associated with the field will be ignored; it will not be defined or changed.

Note that if the interview logic has arrived at the question because it needs the value of a variable that is listed as a field under fields in the question, but you have disabled the field, the interview logic will ask the question again after the user presses Continue. You can avoid this problem by defining the variable with a different block, and using a different variable name for the disabled field.

{% include side-by-side.html demo="disabled-field-dummy" %}

Note that the disabled modifier is unrelated to the [disable if], [js disable if], and [disable others] modifiers.

under text

You can guide users as to how they should fill out a text field by displaying text under the field. You can use [Mako] templates within under text.

{% include side-by-side.html demo="under-text" %}

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 within hints.

{% include side-by-side.html demo="text-hint" %}

The hint is also used to provide the default text the user sees when they fill out a [multiple-choice dropdown], a [datalist], 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 field modifier. A question mark icon can be clicked on to show the help text in a popup. You can use [Mako] templates within help text.

{% include side-by-side.html demo="text-help" %}

default

You can provide a default value to a field using default. You can use [Mako] templates in default text.

{% include side-by-side.html demo="text-default" %}

choices

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

{% include side-by-side.html demo="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 (radio buttons, checkboxes, dropdown) 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. The code field modifier is used in place of a choices field modifer, which you would use to specify the choices manually.

{% include side-by-side.html demo="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 [Python expression] (which can be a simple variable, or something more complex, like a [list comprehension]). 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):

{% include side-by-side.html demo="fields-mc-2" %}

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

{% include side-by-side.html demo="fields-mc-3" %}

It can be a [list] of two-element [list]s:

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="fields-mc-6" %}

If your code is a [list] of [tuples], it will be treated the same as a [list] of [list]s.

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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="fields-mc-8" %}

Instead of specifying the choices using key-value pairs where the keys are what the variable is set to and the values are the labels, you can use keys label and value to reference the label and the corresponding variable value.

{% include side-by-side.html demo="fields-mc-9" %}

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.

{% include side-by-side.html demo="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](#fields checkboxes), then by default a "None of the above" choice is added.

{% include side-by-side.html demo="fields-checkboxes-nota" %}

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

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="fields-mc-nota" %}

If you use datatype: object_radio, you can use none of the above in the same way. If the user selects the "none of the above option," the variable will not be defined when the user presses Continue.

This option can be useful when you are using the [disable others] feature:

{% include side-by-side.html demo="someone-already-mentioned3" %}

You can also use datatype: object_radio and none of the above in combination with [show if](#show if):

{% include side-by-side.html demo="object-radio-nota" %}

all of the above

If you use [datatype: checkboxes](#fields checkboxes), you can optionally include an "All of the above" option.

{% include side-by-side.html demo="fields-checkboxes-aota" %}

You can change the phrase from "All of the above" to something else, even a [Mako] expression. Just set all of the above to the text you want to be displayed.

{% include side-by-side.html demo="fields-mc-aota" %}

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.

{% include side-by-side.html demo="shuffle" %}

show if

You can use the show if field 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 that is visible on the screen. 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.

{% include side-by-side.html demo="other" %}

Note that you can only use this syntax to refer to other fields on the screen; you cannot refer to arbitrary Python variables in your interview answers. This method of show if is JavaScript-based, and takes place in the browser. The interview answers are in Python, on the server. The web browser does not have access to all of the Python variables in the interview answers; it only has access to the values of fields that are displayed in the user interface.

The second method is like the first, but is a shorthand syntax 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 yes/no input is set to a "yes" value, the field will be shown, and otherwise the field will be hidden.

{% include side-by-side.html demo="showif-boolean" %}

As with the first method, the variable name referred to by show if: must be a variable name associated with a field on the screen (listed under fields); it cannot refer to any arbitrary Python variable.

Note that if show if refers to a field that is itself hidden by a show if, then the condition is considered to be false.

{% include side-by-side.html demo="showif-nested" %}

Under the third show if 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 associated with fields in 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.

{% include side-by-side.html demo="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. Therefore, you should always make sure that your [interview logic] (including a document that your [interview logic] assembles) does not expect these hidden fields to have a definition.

For example, suppose you have this question:

{% highlight yaml %} question: What is your favorite fruit? fields:

  • Fruit: favorite_fruit choices:
    • Apple
    • Orange
    • Peach
  • Favorite apple: favorite_apple show if: variable: favorite_fruit is: Apple {% endhighlight %}

Suppose your interview assembles a document that contains this content:

Favorite fruit: {% raw %}{{ favorite_fruit }}{% endraw %}

Favorite apple: {% raw %}{{ favorite_apple }}{% endraw %} {: .blockquote}

In this case, you may find that when favorite_fruit is Orange or Peach and you press Continue, you will end up back at the same screen again. This is because your document is requiring a definition of favorite_apple. You may have assumed that favorite_apple will be defined as the empty string, but that is not how it works.

The way to fix this is to put your logic into the document:

Favorite fruit: {% raw %}{{ favorite_fruit }}{% endraw %}

Favorite apple: {% raw %}{% if favorite_fruit == 'Apple' %}{{ favorite_apple }}{% else %}N/A{% endif %}{% endraw %} {: .blockquote}

This way, your [interview logic] will not include the value of favorite_apple unless it is applicable.

You may be tempted to write something like this:

Favorite apple: {% raw %}{% if defined('favorite_apple') %}{{ favorite_apple }}{% else %}N/A{% endif %}{% endraw %} {: .blockquote}

However, this is a bad practice that will lead to problems. For example, if your users revise their answers, the interview answers could reach a state in which favorite_apple is defined but favorite_fruit is not Apple, in which case it would be inappropriate to display the favorite_apple in the document. Or, the user might change favorite_fruit from Orange to Apple, in which case favorite_apple would be undefined even though it should be defined. If you weren't using [defined()], the assembly of your document would have ensured that the favorite_apple question would be asked. Always base your [interview logic] on actual facts, not the defined-ness of variables.

If you need to set a default value of a field that could be hidden by a show if, you can specify a code block following the question:

{% highlight yaml %} question: What is your favorite fruit? fields:

  • Fruit: favorite_fruit choices:
    • Apple
    • Orange
    • Peach
  • Favorite apple: favorite_apple show if: variable: favorite_fruit is: Apple

code: | if favorite_fruit != 'Apple': favorite_apple = 'N/A' depends on:

  • favorite_fruit {% endhighlight %}

The [depends on] modifier will ensure that favorite_apple is invalidated if and when the value of favorite_fruit changes.

Note that the first and second methods (as well as the js show if methods discussed below) are [JavaScript]-based ("client side"), while the third is [Python]-based ("server side"). The client-side [JavaScript] code context is only aware of fields that exist on the screen in the user's web browser, not the variables in the interview answers; the user's browser does not know the values of all the Python variables in the interview answers. Conversely, the server-side [Python] context is only aware of the interview answers, and is not aware of the values of fields on the screen.

The show if field modifer is not intended to be used as a primary mechanism of controlling [interview logic]; it is more of a feature for customizing the user interface. Thus whatever logic you express in show if will probably have to be repeated elsewhere. If instead of using show if you gathered the field in a separate question, you would only need to specify the logic in one place.

hide if

This works just like [show if](#show if), except that it hides the field instead of showing it.

{% include side-by-side.html demo="hideif-boolean" %}

hide if cannot be combined with show if.

enable if and disable if

The enable if and disable if field modifiers work just like show if and hide if, except that instead of visibly hiding the fields and labels, it disables the input elements.

The use of code inside of enable if and disable if is not supported. The [disabled] modifier allows you to cause a field to be disabled based on a Python expression.

enable if and disable if cannot be combined with show if or hide if on the same field.

js show if

Sometimes you might want to do more complicated evaluations with on-screen variables than you can do with show if and hide if. When you use the show if and hide if field modifiers to refer to fields that are on the screen, you are able to test whether the fields are true, or have particular values, but you cannot do anything more complex, such as test whether the value is one of two values, or the values of two fields. You also can't combine show if, hide if, and disable if on the same field.

The js show if and js hide if features allow you to use any arbitrary [JavaScript] expression to determine whether a field should be shown or not. In these expressions, the special [JavaScript] function [val()] is used to obtain the values of fields. Given the name of an on-screen field as a string, the [val()] function returns the current value of that field.

JavaScript is its own complete language with different syntax than Python, but with some similarities.

  • Instead of and, use &&
  • Instead of or, use ||
  • Instead of not, use !
  • Instead of ==, use === (== will often work as well but may have subtle differences)
  • Just like in Python, you can group expressions with parentheses ()
  • Instead of True, False and None, JavaScript has true, false, and null as well as undefined

{% include side-by-side.html demo="jsshowif" %}

The string that is passed to [val()] must perfectly match the variable name that is used in the underlying [question].

You can use any JavaScript expression that evaluates to true or false with the js show if feature, but your expression must contain at least one val() reference to a field that is actually on the screen. docassemble scans your expression for the use of val("some_variable") in order to know which on-screen variables need to be monitored for changes. If your expression does not use val() to refer to a field that is actually on the screen, docassemble will not be able to tie the field to the appropriate event triggers, and the field will always be hidden.

The variable mentioned inside val() must be a literal string to tell Docassemble to monitor it, and it must refer to a variable that is defined on the screen. Your expression is parsed, but is not evaluated, when determining what fields your expression references with [val()]. Thus, if you pass something other than a literal string to [val()], you may find that the showing or hiding is not triggered, even though [val()] would return the appropriate value.

It is possible to write a js show if that is not actually conditional on the value of a variable on the screen, but you still need to reference a field on the screen. For example, your condition could be someCondition && (true || val("variable")) (where variable is the name of a field on the screen). This JavaScript expression will be evaluated when the screen loads and whenever the value of the variable field changes.

While val() must refer to a variable defined on the same screen, you can refer to a variable defined on the previous screen by using Mako syntax. The Mako will be inserted literally into the JavaScript expression. Note that Python's built-in values True, False, and None will need to be converted into JavaScript's true, false, and null. The json.dumps() method can be useful for converting Python data into [JSON].

For example:

{% highlight yaml %} js show if: | val("on_screen_var") && ${ json.dumps(some_boolean_variable) } {% endhighlight %}

Or, for a string value:

{% highlight yaml %} js show if: | val("on_screen_var") && ${ json.dumps(some_string_variable) } === "Literal value" {% endhighlight %}

js hide if

This works just like [js show if](#js show if), except that it hides the field instead of showing it.

js enable if and js disable if

The js enable if and js disable if field modifiers work just like js show if and js hide if, except that instead of visibly hiding the fields and labels, it disables the input elements.

js enable if and js disable if cannot be combined with js show if or js hide if on the same field.

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.

{% include side-by-side.html demo="disable-others" %}

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

{% include side-by-side.html demo="disable-others-list" %}

note

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

If the note is by itself as its own "field" in the list of fields, the text appears along with the other fields:

{% include side-by-side.html demo="note" %}

However, if the note is used as a field modifier, the note will appear to the right of field on wide screens. On small screens, the note will appear after the field:

{% include side-by-side.html demo="side-note" %}

On wide screens, the location of each notes is based on the location of the field itself. This means that if you have notes on two adjacent fields, and one of the notes is lengthy, the notes could overlap on the screen. Therefore, make sure to keep your notes short.

html

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

If html is by itself as its own "field" in the list of fields, the HTML will appear along with the other fields:

{% include side-by-side.html demo="html" %}

However, if the html is used as a modifier for a field, the HTML will appear to the right of field on wide screens. On small screens, the HTML will appear after the field:

{% include side-by-side.html demo="side-html" %}

raw html

raw html is like html, except that the HTML is not placed into a <div>; it is simply inserted into the page without any enclosing elements. This allows you to alter the structure of the HTML in the list of fields.

{% include side-by-side.html demo="raw-html" %}

The help and under text modifiers have no effect on a raw html field. If raw html is used as a modifier for a field, it will act just like html.

Note that when using the tabular form of a review list, the raw html will go into the <tbody> of the <table>, where a <tr> would normally be placed. Otherwise, the raw html will be inserted into the <form> element.

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.

{% include side-by-side.html demo="no-label-field" %}

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

{% include side-by-side.html demo="blank-label-field" %}

It is generally a good idea to always use a label for every field, especially if some of your users may be using screen readers. If you are inclined to use no label because you want the field to be wider, consider using [label above field].

css class

If you specify a css class, then the HTML input element will have the specified class, and the <div> containing the field will have the same class, except with -container appended to it.

{% include side-by-side.html demo="field-css-class" %}

In this example, the contents of fruit.css are:

{% highlight css %} .fruit-container { background-color: #aa88ff; padding-top: 2rem; padding-bottom: 2rem; border-radius: 1rem; }

.fruit { background-color: #000000; color: #00dd00; } {% endhighlight %}

[Mako] can be used inside css class.

label above field

If you set label above field to True, then the label will be positioned above the field and not to the left of it. (By default, on larger screens, the label is positioned to the left of the field.)

{% include side-by-side.html demo="label-above-field" %}

You can set label above field to True, False, or a Python expression.

You can use the [labels above fields] feature to make this the default setting for all fields in your interview.

floating label

If you set floating label to True, then the label will be formatted using [Bootstrap]'s [floating labels] style.

{% include side-by-side.html demo="floating-label" %}

You can set floating label to True, False, or a Python expression.

You can use the [floating labels] feature to make this the default setting for all fields in your interview.

grid

Using the grid field modifier, you can place fields side-by-side on the screen. docassemble uses the [grid system] of [Bootstrap], which is based on [flexbox], to implement this. Instead of representing widths as percentages between 0 and 100, the [grid system] uses numbers from 1 to 12.

{% include side-by-side.html demo="grid1" %}

In the above example, grid: 7 means 7/12ths of the width of the enclosing HTML element, and grid: 5 means 5/12ths of the width of the enclosing HTML element. The enclosing element here is the central column on the screen. Since 7+5=12, the two fields together fill the width of the central column.

If you specify grid values for adjacent fields, the fields will be placed together in the same [Bootstrap] "row." If the sum of grid widths of adjacent fields add up to a value greater then 12, the items will wrap.

{% include side-by-side.html demo="grid2" %}

If you are using the grid field modifier, it is recommended that you use [labels above fields] in [features] or the [label above field] field modifier. The default labeling style, where the label is to the left of the field, can work with grid, but the label takes up a width of 4, which does not leave a lot of room for the field.

{% include side-by-side.html demo="grid3" %}

It is possible to use a different width for the label. To do this, use grid to specify a [YAML] dictionary instead of an integer.

{% include side-by-side.html demo="grid4" %}

Inside this dictionary, width is the width of the field itself, and label width is the width of the label. Note that writing:

{% highlight yaml %} grid: width: 3 {% endhighlight %}

is equivalent to writing:

{% highlight yaml %} grid: 3 {% endhighlight %}

By default, any adjacent fields that specify a grid will be joined together in the same "row." If you want adjacent fields to be in separate rows even though they both specify a grid, you can specify start: True or end: True to indicate that a "row" should start or end with the given field.

This interview uses start: True to indicate that the fields A, B, C, etc. should start on a new row:

{% include side-by-side.html demo="grid5" %}

The following interview has the same appearance, but uses end: True to indicate that the row of fields 1 through 10 should end at field 10.

{% include side-by-side.html demo="grid6" %}

[Bootstrap]'s [grid system] is [responsive]. On screens that are less than 768 pixels wide, grid fields are arranged vertically. The threshold of 768 pixels is based on [Bootstrap]'s "medium" [breakpoint], which uses the code md.

The breakpoint of 768 pixels can be changed globally for the server by setting the grid breakpoint setting of the [grid classes] directive in the [Configuration]. The available values are:

  • xs
  • sm
  • md
  • lg
  • xl
  • xxl

If xs is used as the grid breakpoint, that means that grid fields will be side-by-side no matter how small the screen is.

The breakpoint can also be configured on a field-by-field basis by setting the breakpoint option under grid:

{% include side-by-side.html demo="grid7" %}

In the above example, the fields for gathering the address will be side-by-side as long as the screen is at least 576 pixels wide (the sm threshold).

If you want to insert horizontal space before a field, set the offset under grid:

{% include side-by-side.html demo="grid9" %}

This will indent the field by the given amount of space.

You can specify the width, label width, offset, start, and end as Python expressions, and you can specify the breakpoint using [Mako].

{% include side-by-side.html demo="grid8" %}

If grid refers to a string, the string is expected to be a Python expression that evaluates to an integer between 1 and 12.

item grid

The item grid field modifier is similar to the grid field modifier, but it only applies to fields that contain a list of radio buttons or a list of checkboxes.

{% include side-by-side.html demo="item-grid" %}

The default breakpoint is the md screen size. You can change this globally for the server using the item grid breakpoint setting under the [grid classes] Configuration directive. You can change this for a particular field by setting item grid to a [YAML] dictionary with values width and breakpoint.

{% include side-by-side.html demo="item-grid-breakpoint" %}

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.

{% include side-by-side.html demo="label" %}

field metadata

The field metadata field modifier allows you to associate custom metadata with a field. You can use any format [YAML] will accept, and you can use [Mako] in text. The metadata will appear within the [JSON] representation of the [question].

{% highlight yaml %} question: | What is your favorite fruit? fields:

  • Fruit: favorite_fruit field metadata: importance: extreme accomplices: - vegetables - legumes description: | This is critical for national security. quota: ${ fruit_limit - 4 } {% endhighlight %}

Special features

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](#field with buttons), 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).

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.

{% include side-by-side.html demo="min" %}

  • minlength: require a minimum number of characters in a textbox, number of checkboxes checked, etc. This uses the jQuery Validation Plugin.
  • maxlength: require a maximum number of characters in a textbox, number of checkboxes checked, etc. This uses the jQuery Validation Plugin.

{% include side-by-side.html demo="minlength" %}

The min, max, minlength, and maxlength specifiers accept [Mako], so you can use [Mako] templating if you need computable validation limits.

{% highlight yaml %} fields:

  • Year insurance will expire: insurance_year_end min: | ${ today().year } {% endhighlight %}

You can customize the standard validation messages that users see. If you want to customize these messages on a server-wide basis, you can edit the [words] directive in the [Configuration]. If you want to customize these messages on an interview-wide basis, you can add a [default validation messages] block to your interview. For more information on how to do this, see the documentation for the [default validation messages] block.

You can also customize the messages for a particular field using the validation messages field modifier.

{% include side-by-side.html demo="validation-messages" %}

Each validation error message has a special code. In the example above, the codes were required and max. A full list of these codes is available in the documentation for the [default validation messages] block.

In a validation messages field modifier, you can use an abbreviated version of many of these codes. For example, instead of using the code combobox required, you can use required. Instead of using date min, you can use min. Either will work in the context of a validation messages field modifier. The last word in the code is sufficient.

You can also use [Python] code to validate an input field. To do so, add a validate field modifier 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.

{% include demo-side-by-side.html demo="validation-test" %}

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

{% highlight python %} def is_multiple_of_four(x): return x/4 == int(x/4) {% endhighlight %}

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.

{% include demo-side-by-side.html demo="validation-test-two" %}

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

{% highlight python %} 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 {% endhighlight %}

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 [raise]s 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.

Instead of creating a separate module file, you can also use an anonymous (Lambda) function as the value of the validate field modifier. This may be useful if your validate function is very simple. It is common to use x as the variable name in a Lambda expression, but note that this is a reserved name in docassemble, so you should use a different variable name, such as y.

{% highlight yaml %}

question: | Tell us some vital statistics fields:

  • Weight: weight validate: | lambda y: True if not y.isnumeric() else validation_error("Please include a unit. E.g., 180 pounds")
  • Height: height validate: | lambda y: True if not y.isnumeric() else validation_error("Please include a unit. E.g., 6 feet 1 inch") {% endhighlight %}

Note that the validate field modifier is not available for use with fields having datatype: checkboxes. (However, note that you can use [minlength] and [maxlength] to require a certain number of checkboxes to be checked when [none of the above] is disabled.)

A more general 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 validation code. Rather than showing an inline validation error, validation code will create a pop-up error for the question as a whole.

{% include demo-side-by-side.html demo="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 [raise]d.

In addition to validating user input by raising an exception if something is wrong, you can use validation code to transform values before they are saved in the interview answers.

For example, this validation code normalizes the formatting of a phone number.

{% include side-by-side.html demo="validation-code-phone" %}

The following validation code makes adjustments to object attributes if a user's income is less than zero.

{% highlight yaml %} validation code: | if user.income < 0: user.has_negative_income = True user.income = 0 {% endhighlight %}

By default, an error message raised by validation code is placed at the top of the screen. If you want the message to be placed next to a specific field on the screen, you can call validation_error() with the optional keyword argument field set to the name of the field.

{% include demo-side-by-side.html demo="phone-number-2" %}

Code under validation code is very different from code in a [code] block. It is not [interview logic] and cannot function as [interview logic]; it can only be used to validate or transform user input before the interview answers are updated. If you refer to an undefined variable in validation code, docassemble will not try to fetch the definition for you, as it normally does; instead, the user will see an error. You cannot use functions like force_ask() or command(), which operate by raising exceptions. If you try to make validation code do the work of [interview logic], you will be disappointed.

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.

{% include side-by-side.html demo="address-autocomplete" %}

You can set address autocomplete to True, False, or a Python expression that returns True or False.

For this feature to work, make sure that in your Google Cloud console, you have enabled the following APIs for your [google maps api key]:

  • Places API (New)
  • Maps JavaScript API

Also make sure that use places api new: True is present under google in your Configuration.

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].

{% include side-by-side.html demo="address-autocomplete-test" %}

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

Advanced usage

If you want to use additional features of the [Place Autocomplete] JavaScript API, you can set address autocomplete to a dictionary of options that will be passed directly to the [Place Autocomplete] API.

You will need to set the types and fields items within the dictionary to values that the [Place Autocomplete] API considers valid. Consult the API documentation for the list of valid [types] and [fields]. docassemble will pass the dictionary of options directly to the [.fetchFields()] method without checking if the options are valid. You need to monitor the JavaScript console and consult Google's documentation if there is an error.

The following example demonstrates conducting a query on "establishments" of all types.

{% include side-by-side.html demo="address-autocomplete-establishment" %}

The fields should be specified in lowercase underscore format (e.g., adr_format_address). These will be converted into camel case (e.g., adrFormatAddress) and passed directly to [.fetchFields()].

When [.fetchFields()] method returns information, attributes of the Address object will be populated, if the attribute name corresponds to the name of the field requested. However, there are some exceptions:

  • If you request address_components, the address_components field is broken out into its numerous components, including administrative_area_level_1, street_number, etc. Attributes of the Address object are populated as follows:
    • address is populated with street_number and route, separated by a space.
    • city is populated with locality, sublocality_level_1, neighborhood, administrative_area_level_3, or colloquial_area, whichever is first available.
    • state is popuated with the shortText version of administrative_area_level_1.
    • country is popuated with the shortText version of country.
    • sublocality is populated with sublocality_level_1, sublocality_level_2, sublocality_level_3, sublocality_level_4, sublocality_level_5, whichever is first available.
    • zip is populated with postal_code, and if there is a postal_code_suffix, it is appended to the zip, separated by a hyphen.
  • If you request address_components and your question populates attributes of the Address object that correspond with Google's address components, those fields will be populated with the longText version of the component. For example, you can capture Google's street_number and route fields by using street_number and route as attributes of your Address object in your question.
  • If any of the other fields you request is an object, the components of the object are broken out and the individual components of the object are populated.
    • If you request accessibility_options, the fields that are populated are has_wheelchair_accessible_entrance, has_wheelchair_accessible_parking, has_wheelchair_accessible_restroom, and has_wheelchair_accessible_seating.
    • If you request plus_code, the fields that are populated are compound_code and global_code.
    • If you request google_maps_links, the fields that are populated are directions_uri, photos_uri, place_uri, reviews_uri, and write_a_review_uri.
    • If you request location, the fields that are populated are latitude and longitude.

Some of the options may contain HTML, or may be a JSON array. You may wish to set datatype: raw to avoid input validation errors. You may also wish to set input type: hidden so that the user does not see the codes. You may also want to use the .geocode() method to retrieve this information using the server, rather than retrieving it through the user's browser.

Note that in the above example, address autocomplete is attached to the name attribute of the Address rather than the address attribute. You can attach address autocomplete to any text field, whether or not it corresponds to a field that is re-written by [JavaScript]. However, you must attach address autocomplete to an attribute of the object whose attributes you wish to populate (typically this is an Address object).

Setting address autocomplete to True passes the following options to the [Place Autocomplete] API:

{% highlight yaml %} types:

  • street_address fields:
  • address_components {% endhighlight %}

The following example demonstrates conducting a query on establishments of particular types.

{% include side-by-side.html demo="address-autocomplete-specific" %}

The following example demonstrates using the (cities) type.

{% include side-by-side.html demo="address-autocomplete-cities" %}

The following example demonstrates using the (regions) type.

{% include side-by-side.html demo="address-autocomplete-regions" %}

Instead of specifying the dictionary of options in YAML, you can set address autocomplete to a Python expression that returns a dictionary of options.

Note that API calls to the [Place Autocomplete] API are more expensive depending on the type of search done and the fields that are returned.

Setting a variable with the Continue button

Sometimes, it is useful for a question to set a single variable to True, along with the other variables it sets, much like the [simple "continue" button that sets a variable](#field continue) question does.

If you want your question to set a variable to True when the user presses "Continue," add a continue button field line to the question indicating the variable that should be set to True.

{% include side-by-side.html demo="continue-button-field" %}

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 side-by-side.html demo="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:

{% include side-by-side.html demo="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 side-by-side.html demo="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]. The [object_radio] data type allows the use of a [none of the above] option.

By default, the objects listed in the user interface are labeled by their textual representations. For example, if the object in a choices list is an [Individual], the label for the object will be the textual representation for an [Individual], which is the individual's name. To use an alternate label, provide a object labeler. The object labeler must be a [Python expression] that evaluates to a function.

For example:

{% highlight yaml %} question: Who is the villain? fields:

  • The villain is: villain datatype: object default: antagonist object labeler: | lambda y: y.nickname choices:
    • protagonist
    • antagonist {% endhighlight %}

In this case, the protagonist and the antagonist will be labeled using the nickname attribute. The object labeler in this example is a Python [lambda function], which is a shorthand way of creating a function. You could also used a named function, if you wrote one in a module. For example, suppose you had some code in a module that defined the function my_labeling_function:

{% highlight python %} def my_labeling_function(obj): return obj.nickname {% endhighlight %}

Suppose also that you imported this function into your interview using a [modules] block. Then, in your fields item you could simply write object labeler: my_labeling_function.

Here is an example that uses object labeler to label a datatype: object list of addresses.

{% include side-by-side.html demo="object-radio-address" %}

Instead of writing:

{% highlight yaml %} object labeler: | lambda y: y.on_one_line() {% endhighlight %}

you could instead write:

{% highlight yaml %} object labeler: Address.on_one_line {% endhighlight %}

Address.on_one_line (note the lack of parentheses at the end) is a reference to the .on_one_line() method of the Address class. In Python, a method is like a function where the first parameter is the object. So you can call Address.on_one_line like a function, passing it the object instance as a parameter.

Using a reference to a method in place of a lambda function only works if the method has no other required parameters. For example, if your objects were [Individual]s and you wanted the the choices to display as "John Smith's house," "Jane Doe's house," and "Harry Morgan's house," you could write:

{% highlight yaml %} object labeler: | lambda y: y.possessive('house') {% endhighlight %}

but there would be no way to call this method successfully using a mere method reference like

{% highlight yaml %} object labeler: Individual.possessive {% endhighlight %}

because there is no place to put the 'house' parameter.

Similar to the way object labeler works, you can specify a help generator lambda function that takes the object as its argument and returns help text associated with a choice. You can also specify an image generator lambda function that generates the image that should be associated with the choice.

{% include side-by-side.html demo="generators" %}

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].

{% include side-by-side.html demo="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 field 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:

{% highlight yaml %} question: | How many of each fruit? fields:

  • Apples: num_apples datatype: integer
  • Oranges: num_oranges datatype: integer {% endhighlight %}

you would write this:

{% highlight yaml %} question: | How many of each fruit? fields:

  • code: | [{'Apples': 'num_apples', 'datatype': 'integer'}, {'Oranges': 'num_oranges', 'datatype': 'integer'}] {% endhighlight %}

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

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="fields-code-embed" %}

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

{% highlight yaml %} 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'}) {% endhighlight %}

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:

{% highlight yaml %} 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 {% endhighlight %}

You could do something like the following:

{% highlight yaml %} 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"}) {% endhighlight %}

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.

A comprehensive example

Here is a lengthy example that illustrates many of the features of [fields].

{% include side-by-side.html demo="fields" %}

Questions that upload files

Storing files as variables

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

{% include side-by-side.html demo="upload" %}

Note that this question uses [fields], which is explained in more detail above. Specifically, it uses the file data type.

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

Note that after a file is uploaded, if you send the user back to the same question again, the user might expect to see the file that they had already uploaded. However, they will instead be required to upload a new file. This new upload will replace the [DAFileList] they had created early.

To see an example of providing the user with an interface for editing a list of files that were upload (including deleting specific files, reordering files, and adding additional files), see [this recipe]({{ site.baseurl }}/docs/recipes.html#upload exhibits).

Gathering the user's signature into a file variable

The signature block presents a special screen in which the user can sign his or her name with the trackpad or other pointing device. When the user presses "Continue," the signature image will be uploaded to the docassemble server as a transparent [PNG] file.

{% include side-by-side.html demo="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:

{% highlight yaml %}

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

{% endhighlight %}

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

{% highlight yaml %}

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

{% endhighlight %}

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 small smartphone.

By default, the signature screen will not let the user continue if the signature box is empty. If you want to allow users to submit blank signatures, set required to False:

{% highlight yaml %} question: Sign here signature: client.signature under: | ${ client } required: False {% endhighlight %}

By default, the color of the signature is black. To use a different color, set pen color to a valid [CSS color]. This example uses a blue color.

{% include side-by-side.html demo="signature-blue" %}

[Mako] can be used when specifying a pen color.

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 (and other blocks that set a variable) in a generalized way: the generic object modifier, and [index variables](#index variables).

The generic object modifier

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

{% include side-by-side.html demo="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].

The [generic object modifier] can be used with [question] blocks, [code] blocks, and any other blocks that set variables ([template], [table], [attachment], and [objects], [objects from file], [data], [data from code]).

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.

{% include side-by-side.html demo="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.

{% include side-by-side.html demo="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]({{ site.baseurl }}/docs/logic.html#variablesearching) section.

Index variables can be used with [question] blocks, [code] blocks, and any other blocks that set variables ([template], [table], [attachment], and [objects], [objects from file], [data], [data from code]).

Tips on using generalized questions

If you use generic object variable x, or index variables like i, j, k, etc., it is important that you do not use them in blocks that you have marked as mandatory.

Suppose you have a block that defines fruit[i].seeds. When docassemble needs a specific value, like fruit[2].seeds, it will find your block automatically, no matter where it is in the interview source file. docassemble will take care of setting i = 2 before "running" your block. Your block will only work correctly if i is set to the right value.

If you mark the block as mandatory in order to force it to be run, you will be forcing the running of [Python] code in a context where the value of i could be anything; it might be a number like 0 or 5, or it might be a string like 'income'. The variable i might not even be defined at all.

Thus, you should only use x, i, j, k, etc. when you are letting docassemble choose which block to use.

Catchall questions

By default, if a reference is made to a variable and no block that defines that variable is available, an error message will appear saying "Interview has an error. There was a reference to a variable 'variable_name' that could not be looked up in the question file."

Typically, you should always have a [question] or [code] block that defines any variable your interview might encounter. But if you want to have a fallback option, you can set use catchall: True in the [features].

{% include side-by-side.html demo="catchall" %}

This interview uses the variable names user_name and salary, but there are no blocks that define user_name or salary. However, use catchall: True is part of the [features]. This means that when the variable user_name is encountered, user_name is defined as a DACatchAll object. The DACatchAll class is a subclass of [DAObject]. The instanceName attribute of the object is set to 'user_name'. When the interview tries to place user_name into [Mako] text, this has the effect of calling str(user_name). Because of the way DACatchAll objects work, this results in a call to str(user_name.value); thus docassemble will seek the value of user_name.value. The interview provides a generic object block that sets x.value where x is a DACatchAll object.

Thus, with use catchall, you can have a single [question] in your interview that can define any single variable, no matter what its name is. In the example above, the first [question] uses the [.object_name()] method to present a user-friendly representation of the variable name based on the .instanceName attribute of the object.

One problem with such "catchall" questions is that the data type of the variable is not known. The DACatchAll object provides a hint about the data type where possible. If the variable user_name.value is sought because str() is called on user_name, then user_name.context is set to 'str'. This attribute is available to your [question] block.

In the above example, a second variable is salary. When the interview calls currency(salary), this has the effect of calling float(salary). This means that when salary.value is sought, salary.context will be 'float'. The second question block in the interview asks the question a different way based on this context, using an if specifier.

If user_name + '@example.com' or currency(salary + 10000.0) triggers the seeking of the value attribute, then the context attribute will be 'add'. This is ambiguous because the + operator can refer to string concatenation as well as numeric addition. Luckily, in the scenario where the catchall variable is followed by an operator like +, the operand attribute is set to the value on the other side of the operator. You can test for the data type on the other side of the operator and infer what the data type of the catchall variable should be.

{% highlight yaml %} if: | x.context == 'float' or (x.context == 'add' and isinstance(x.operand, float)) generic object: DACatchAll question: | How much is ${ x.object_name() }? fields:

  • Amount: x.value datatype: currency {% endhighlight %}

The .context values are based on whichever of the [Python special methods] was called on the variable. The possible values of .context are 'abs', 'add', 'and', 'bool', 'complex', 'contains', dir', 'div', 'divmod', 'eq', 'float', 'floordiv', 'ge', 'getitem', gt', 'hash', 'hex', 'index', 'int', 'invert', 'iter', 'le', 'len', 'long', 'lshift', 'lt', 'mod', 'mul', 'ne', 'neg', 'oct', 'or', 'pos', 'pow', 'radd', 'rand', 'rdiv', 'rdivmod', 'repr', 'reversed', 'rfloordiv', 'rlshift', 'rmod', 'rmul', 'ror', 'rpow', 'rrshift', 'rshift', 'rsub', 'rtruediv', 'str', 'sub', 'truediv', and 'xor'. Since dates are not a built-in Python data type, whether the variable is a date cannot be detected based on the context in which the variable was accessed. If the .context is 'bool', it is likely that the variable was used in the context of an if statement.

If you call .data_type_guess() on a DACatchAll object, it will return 'str', 'int', 'float', 'bool', or 'complex', based on what the .context is and what the .operand is (if applicable).

{% include side-by-side.html demo="catchall-guess" %}

The .data_type_guess() method will likely work correctly most of the time, but what it returns is just an opinion. In particular, whether a number should be an int or a float is highly debatable.

You may want to implement a convention of embedding the data type in the variable name, so that you can identify the data type in situations where the [Python special methods] do not provide a reliable answer. For example, you household_size_int instead of household_size, deadline_date instead of deadline, or salary_currency instead of salary.

Since it is better for variables to be set to their natural types rather than as the artificial object DACatchAll, you will probably want to use [validation code] to overwrite the DACatchAll object with a different value. The example above does this by using the [define()] function, obtaining the name of the variable from the instanceName. Thus, at the end of the interview, user_name is a string, salary is a floating-point number, and there are no DACatchAll objects.

It is possible to use validation code to try to transform data types once you know what input the user has provided. For example, if the user types a valid date into a text box, you can set the variable to a [DADateTime] object:

{% highlight yaml %} validation_code: | try: define(x.instanceName, as_datetime(x.value)) except: define(x.instanceName, x.value) {% endhighlight %}

This works because as_datetime() will raise an exception if it is given text that does not contain a valid date. [Python]'s try/except intercepts the error and sets the variable to the plain value of the date is not valid.

Note that the utility of the use catchall feature is very limited. They are not a replacement for interview [YAML].

Special screens

Performing special actions requested by the user

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 an event specifier.

An event specifier acts much like [sets]: it advertises that the question will potentially define a variable (although it actually doesn't).

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.

{% include side-by-side.html demo="dialog-box" %}

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

{% include side-by-side.html demo="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.

event 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].

However, event is not appropriate for questions that set variables (e.g., that use yesno, noyes, field, continue button field, fields, signature, etc.). If you want to take the user to a screen that sets a variable, refer to an undefined variable so that docassemble will seek out the definition of the variable and show the question that defines the variable. Or, if the variable is already defined, use force_ask(). The [interview logic] system in docassemble is not like a flow chart, where you "go to" question 1 and then "go to" question 2; it is based on seeking definitions of variables and satisfying prerequisites.

The event modifier can also be used on code blocks, where the meaning is similar, but the purpose is not necessarily to show a special screen.

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

The review specifier allows interview developers to create a review screen. A review screen is type of question that allows users to review and edit their answers, whether the user is part of the way through the interview or all the way through the interview. 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 screen that is launched from the menu:

{% include side-by-side.html demo="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 screen does not show a link for "Favorite fungus" because the variable fungi has not been defined yet. However, once fungi is defined, the review screen 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 screen, 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 question with the review specifier is tagged with event: review_answers. For more information about how [event]s 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 the interview session 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 list of answers the user has provided with buttons that the user can press to revisit an answer:

{% include side-by-side.html demo="review-2" %}

The review specifier, like the [fields] specifier, allows you to use note and html entries.

If these are modified with the optional show if field modifier, they will only be displayed if the variable referenced by the show if field 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.

{% include side-by-side.html demo="review-3" %}

By default, items in a review list have the [CSS] class of bg-secondary-subtle so that each item is distinguishable from its neighbors. However, note and html items do not have a class. Using the css class modifier on an item, you can change the [CSS] class of an item.

{% include side-by-side.html demo="review-10" %}

In this example, the favorite_vegetable item has been given a different background color, and the note, which by default is colorless, is given the color bg-secondary-subtle so that it matches the other items.

If you want an item to have no background color, set the css class to the name of a class that does not exist or that does not define a background color.

If you include note and html as modifiers of an item under the review specifier, the text will appear to the right of the item on wide screens. On small screens, the HTML will appear after the item.

{% include side-by-side.html demo="review-side-note" %}

You can add help text to an item, 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.

{% include side-by-side.html demo="review-4" %}

If you want the list of review items to be formatted as an HTML <table>, set the tabular modifier on the question to True.

{% include side-by-side.html demo="review-tabular" %}

The tabular modifier can also be used to specify a particular CSS class for the <table>.

{% include side-by-side.html demo="review-tabular-class" %}

Mako can be used with tabular.

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.

{% include side-by-side.html demo="review-5" %}

If there is a follow-up question that might need to come after the changing of a variable, you can list the follow-up variable in the fields under follow up.

{% include side-by-side.html demo="review-conditional" %}

You will need to tag the follow-up question with an [if] modifier; in order for the review screen to skip the field when it is not required, it needs to find no [question]s that will define the variable. If the follow-up question is set up in this way, you can list its variable under follow up, and docassemble will ask the question if the if condition is true, but will ignore the follow up variable if the if condition is false.

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

{% include side-by-side.html demo="review-6" %}

Some of the variables that you use in your interview might be computed by [code] based on answers to [question]s, 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 screen by including recompute in the list of variables to be re-asked.

{% include side-by-side.html demo="review-7" %}

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

{% highlight yaml %}

  • Edit:
    • fruit
    • vegetable
    • salad
    • fungi {% endhighlight %}

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 [question]s 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 three other special commands that you can use in a list of variables in a review item: set, undefine, and invalidate. The following example illustrates set:

{% include side-by-side.html demo="review-8a" %}

In this interview, the set command sets address.manually_edited to True after the user edits the address.

The undefine specifier causes the values to be undefined. The invalidate specifier works like undefine, except that the original values (if any) will be remembered and offered up as default values when a [question] defining the variable is asked again.

Placing a review screen within the interview logic

In the examples above, the question containing the review specifier is identified with an event specifier 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 continue button field instead of event.

{% include side-by-side.html demo="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 screen encounters and undefined variable, it does not seek out its definition. This is so you can have a single review screen that is used throughout an interview (or a section of an interview), where the user only sees the fields that have already been asked about.

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

{% highlight yaml %} skip undefined: False question: | Review your answers review: ... {% endhighlight %}

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

Customizing the Resume button

By default, the review screen 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.

{% include side-by-side.html demo="resume-button-label" %}

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

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

Why can't review screens be automatically generated?

The list of variables to display to the user in a review screen needs to be specified by the interview developer. 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 developer control what the user can edit.
  2. A list of answers already provided might not be user-friendly unless the interview developer 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.

[configuration]: {{ site.baseurl }}/docs/config.html [select]: https://www.w3schools.com/tags/tag_select.asp [placeholder]: https://www.w3schools.com/tags/att_input_placeholder.asp [code blocks]: {{ site.baseurl }}/docs/code.html [Mako]: https://www.makotemplates.org/ [Markdown]: https://daringfireball.net/projects/markdown/ [YAML]: https://en.wikipedia.org/wiki/YAML [object]: {{ site.baseurl }}/docs/objects.html [objects]: {{ site.baseurl }}/docs/objects.html [Individual]: {{ site.baseurl }}/docs/objects.html#Individual [DAObject]: {{ site.baseurl }}/docs/objects.html#DAObject [DAList]: {{ site.baseurl }}/docs/objects.html#DAList [DADict]: {{ site.baseurl }}/docs/objects.html#DADict [Python identifiers]: https://docs.python.org/3/reference/lexical_analysis.html#identifiers [reserved variable names]: {{ site.baseurl }}/docs/special.html#reserved [Python]: https://en.wikipedia.org/wiki/Python_%28programming_language%29 [question]: {{ site.baseurl }}/docs/questions.html [function]: {{ site.baseurl }}/docs/functions.html [functions]: {{ site.baseurl }}/docs/functions.html [special variables]: {{ site.baseurl }}/docs/special.html [legal applications]: {{ site.baseurl }}/docs/legal.html [interview logic]: {{ site.baseurl }}/docs/logic.html [mandatory]: {{ site.baseurl }}/docs/logic.html#mandatory [code]: {{ site.baseurl }}/docs/code.html#code [DAObject]: {{ site.baseurl }}/docs/objects.html#DAObject [url_action()]: {{ site.baseurl }}/docs/functions.html#url_action [process_action()]: {{ site.baseurl }}/docs/functions.html#process_action [action_menu_item()]: {{ site.baseurl }}/docs/functions.html#action_menu_item [menu_items]: {{ site.baseurl }}/docs/special.html#menu_items [question]: {{ site.baseurl }}/docs/questions.html#question [subquestion]: {{ site.baseurl }}/docs/questions.html#subquestion [code]: {{ site.baseurl }}/docs/code.html [DAFile]: {{ site.baseurl }}/docs/objects.html#DAFile [DAFileList]: {{ site.baseurl }}/docs/objects.html#DAFileList [DAList]: {{ site.baseurl }}/docs/objects.html#DAList [need()]: {{ site.baseurl }}/docs/functions.html#need [basic-questions.yml]: {{ site.github.repository_url }}/blob/master/docassemble_base/docassemble/base/data/questions/basic-questions.yml [validationfuncs.py]: {{ site.github.repository_url }}/blob/master/docassemble_demo/docassemble/demo/validationfuncs.py [validationfuncstwo.py]: {{ site.github.repository_url }}/blob/master/docassemble_demo/docassemble/demo/validationfuncstwo.py [yesno]: #yesno [group]: {{ site.baseurl }}/docs/groups.html [groups]: {{ site.baseurl }}/docs/groups.html [groups section]: {{ site.baseurl }}/docs/groups.html [Python constant]: https://docs.python.org/3/library/constants.html [inserting images]: {{ site.baseurl }}/docs/markup.html#inserting uploaded images [fields]: #fields [Python dictionaries]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [HTML5]: https://en.wikipedia.org/wiki/HTML5 [selections() function]: {{ site.baseurl }}/docs/functions.html#selections [exclude]: #exclude [event]: #event [object]: #object [object_radio]: #object_radio [object_checkboxes]: #object_checkboxes [datatype]: #datatype [roles]: {{ site.baseurl }}/docs/roles.html [task_not_yet_performed()]: {{ site.baseurl }}/docs/functions.html#task_not_yet_performed [css]: {{ site.baseurl }}/docs/modifiers.html#css [script]: {{ site.baseurl }}/docs/modifiers.html#script [tooltip]: https://www.w3schools.com/tags/att_title.asp [list]: https://docs.python.org/3/tutorial/datastructures.html [dictionary]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [dictionaries]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [DOCX fill-in forms]: {{ site.baseurl }}/docs/documents.html#signature docx [PDF fill-in forms]: {{ site.baseurl }}/docs/documents.html#signature [documents]: {{ site.baseurl }}/docs/markup.html#inserting uploaded images [generic object modifier]: {{ site.baseurl }}/docs/modifiers.html#generic object [section on question modifiers]: {{ site.baseurl }}/docs/modifiers.html#generic object [objects section]: {{ site.baseurl }}/docs/objects.html [currency()]: {{ site.baseurl }}/docs/functions.html#currency [currency_symbol()]: {{ site.baseurl }}/docs/functions.html#currency_symbol [DADict]: {{ site.baseurl }}/docs/objects.html#DADict [date functions]: {{ site.baseurl }}/docs/functions.html#date functions [machine learning section]: {{ site.baseurl }}/docs/ml.html#howtouse [raise an exception]: https://en.wikibooks.org/wiki/Python_Programming/Exceptions [exception is raised]: https://en.wikibooks.org/wiki/Python_Programming/Exceptions [Exception]: https://docs.python.org/3/library/exceptions.html#exceptions.Exception [force_ask()]: {{ site.baseurl }}/docs/functions.html#force_ask [action]: {{ site.baseurl }}/docs/functions.html#actions [HTML]: https://en.wikipedia.org/wiki/HTML [disable others]: #disable others [.append()]: {{ site.baseurl }}/docs/objects.html#DAList.append [default]: #default [Mozilla's documentation]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date [Firefox]: https://www.mozilla.org/en-US/firefox/ [maximum image size configuration directive]: {{ site.baseurl }}/docs/config.html#maximum image size [maximum image size interview feature]: {{ site.baseurl }}/docs/initial.html#maximum image size [combobox]: https://github.com/danielfarrell/bootstrap-combobox [datalist]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/datalist [multiple-choice dropdown]: #select [combobox]: #combobox [datalist]: #datalist [images]: {{ site.baseurl }}/docs/initial.html#images [image sets]: {{ site.baseurl }}/docs/initial.html#image sets [sets]: {{ site.baseurl }}/docs/modifiers.html#sets [CSS]: https://en.wikipedia.org/wiki/Cascading_Style_Sheets [PNG]: https://en.wikipedia.org/wiki/Portable_Network_Graphics [JPEG]: https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format [BMP]: https://en.wikipedia.org/wiki/BMP_file_format [embeds]: #embed [label and field]: #label [str()]: https://docs.python.org/3/library/functions.html#str [repr()]: https://docs.python.org/3/library/functions.html#repr [all_true()]: {{ site.baseurl }}/docs/objects.html#DADict.all_true [all_false()]: {{ site.baseurl }}/docs/objects.html#DADict.all_false [any_true()]: {{ site.baseurl }}/docs/objects.html#DADict.any_true [any_false()]: {{ site.baseurl }}/docs/objects.html#DADict.any_false [true_values()]: {{ site.baseurl }}/docs/objects.html#DADict.true_values [false_values()]: {{ site.baseurl }}/docs/objects.html#DADict.false_values [Address]: {{ site.baseurl }}/docs/objects.html#address autocomplete [google maps api key]: {{ site.baseurl }}/docs/config.html#google [Place Autocomplete]: https://developers.google.com/maps/documentation/places/web-service/place-autocomplete [Google Places API]: https://developers.google.com/maps/documentation/places/web-service/op-overview [datetime.datetime]: https://docs.python.org/3/library/datetime.html#datetime-objects [datetime.time]: https://docs.python.org/3/library/datetime.html#datetime.time [DADateTime]: {{ site.baseurl }}/docs/objects.html#DADateTime [as_datetime()]: {{ site.baseurl }}/docs/functions.html#as_datetime [dateutil.parser.parse]: https://dateutil.readthedocs.io/en/stable/parser.html#dateutil.parser.parse [.replace_time()]: {{ site.baseurl }}/docs/objects.html#DADateTime.replace_time [.replace()]: {{ site.baseurl }}/docs/objects.html#DADateTime.replace [.format_date()]: {{ site.baseurl }}/docs/objects.html#DADateTime.format_date [.format_time()]: {{ site.baseurl }}/docs/objects.html#DADateTime.format_time [.format_datetime()]: {{ site.baseurl }}/docs/objects.html#DADateTime.format_datetime [format_date()]: {{ site.baseurl }}/docs/functions.html#format_date [format_time()]: {{ site.baseurl }}/docs/functions.html#format_time [format_datetime()]: {{ site.baseurl }}/docs/functions.html#format_datetime [.strftime()]: https://docs.python.org/3/library/datetime.html#datetime.time.strftime [continue button label]: {{ site.baseurl }}/docs/modifiers.html#continue button label [validation_error()]: {{ site.baseurl }}/docs/functions.html#validation_error [raise]: https://docs.python.org/3.12/tutorial/errors.html#raising-exceptions [date]: #date [number]: #number [.geocode()]: {{ site.baseurl }}/docs/objects.html#Address.geocode [JavaScript]: https://en.wikipedia.org/wiki/JavaScript [val()]: {{ site.baseurl }}/docs/functions.html#js_val [input type]: #input type [minlength]: #minlength [maxlength]: #maxlength [none of the above]: #none of the above [forget_result_of()]: {{ site.baseurl }}/docs/functions.html#forget_result_of [id]: {{ site.baseurl }}/docs/modifiers.html#id [if]: {{ site.baseurl }}/docs/modifiers.html#if [template]: {{ site.baseurl }}/docs/initial.html#template [table]: {{ site.baseurl }}/docs/initial.html#table [attachment]: {{ site.baseurl }}/docs/documents.html#attachment [objects]: {{ site.baseurl }}/docs/initial.html#objects [objects from file]: {{ site.baseurl }}/docs/initial.html#objects from file [data]: {{ site.baseurl }}/docs/initial.html#data [data from code]: {{ site.baseurl }}/docs/initial.html#data from code [lambda function]: https://docs.python.org/3.12/tutorial/controlflow.html#lambda-expressions [modules]: {{ site.baseurl }}/docs/initial.html#modules [imports]: {{ site.baseurl }}/docs/initial.html#imports [accept]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-accept [words]: {{ site.baseurl }}/docs/config.html#words [default validation messages]: {{ site.baseurl }}/docs/initial.html#default validation messages [screen parts]: {{ site.baseurl }}/docs/questions.html#screen parts [Python expression]: https://stackoverflow.com/questions/4782590/what-is-an-expression-in-python [list comprehension]: https://docs.python.org/3.12/tutorial/datastructures.html#list-comprehensions [continue button field]: #continue button field [tuples]: https://docs.python.org/3.12/tutorial/datastructures.html#tuples-and-sequences [Ajax]: https://en.wikipedia.org/wiki/Ajax_(programming) [url_action_call()]: {{ site.baseurl }}/docs/functions.html#js_url_action_call [JSON]: https://en.wikipedia.org/wiki/JSON [json_response()]: {{ site.baseurl }}/docs/functions.html#json_response [words file]: https://en.wikipedia.org/wiki/Words_(Unix) [wamerican]: https://packages.debian.org/buster/wamerican [locale]: {{ site.baseurl }}/docs/config.html#locale [set_locale()]: {{ site.baseurl }}/docs/functions.html#set_locale [initial]: {{ site.baseurl }}/docs/logic.html#initial [multi-user interview]: {{ site.baseurl }}/docs/roles.html [multi-user interviews]: {{ site.baseurl }}/docs/roles.html [privileges]: {{ site.baseurl }}/docs/users.html [.set_attributes()]: {{ site.baseurl }}/docs/objects.html#DAFile.set_attributes [.user_access()]: {{ site.baseurl }}/docs/objects.html#DAFile.user_access [.privilege_access()]: {{ site.baseurl }}/docs/objects.html#DAFile.privilege_access [word()]: {{ site.baseurl }}/docs/functions.html#word [define()]: {{ site.baseurl }}/docs/functions.html#define [defined()]: {{ site.baseurl }}/docs/functions.html#defined [jQuery Validation Plugin]: https://jqueryvalidation.org [jQuery.validator.addMethod()]: https://jqueryvalidation.org/jQuery.validator.addMethod/ [validation messages]: #validation messages [Python object]: https://docs.python.org/3.12/tutorial/classes.html [features]: {{ site.baseurl }}/docs/initial.html#features [.object_name()]: {{ site.baseurl }}/docs/objects.html#DAObject.object_name [Python special methods]: https://docs.python.org/3.12/reference/datamodel.html#special-method-names [depends on]: {{ site.baseurl }}/docs/logic.html#depends on [labels above fields]: {{ site.baseurl }}/docs/initial.html#labels above fields [floating labels]: {{ site.baseurl }}/docs/initial.html#floating labels [label above field]: #label above field [Social Security number]: https://en.wikipedia.org/wiki/Social_Security_number [custom datatypes to load]: {{ site.baseurl }}/docs/initial.html#custom datatypes to load [floating labels]: https://getbootstrap.com/docs/5.2/forms/floating-labels/ [fields]: https://developers.google.com/maps/documentation/places/web-service/data-fields [types]: https://developers.google.com/maps/documentation/places/web-service/place-types [address autocomplete]: #address autocomplete [logic system]: {{ site.baseurl }}/docs/logic.html [validation code]: #validation code [Bootstrap colors]: https://getbootstrap.com/docs/5.2/customize/color/ [Bootstrap]: https://getbootstrap.com/ [accept method]: https://jqueryvalidation.org/accept-method/ [grid system]: https://getbootstrap.com/docs/5.3/layout/grid/ [flexbox]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox [responsive]: https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design [breakpoint]: https://getbootstrap.com/docs/5.3/layout/breakpoints/#available-breakpoints [breakpoints]: https://getbootstrap.com/docs/5.3/layout/breakpoints/#available-breakpoints [grid classes]: {{ site.baseurl }}/docs/config.html#grid classes [Bootstrap File Input]: https://plugins.krajee.com/file-input [CSS color]: https://developer.mozilla.org/en-US/docs/Web/CSS/color [disable if]: #disable if [js disable if]: #js disable if [disabled]: #disabled [accessibility]: {{ site.baseurl }}/docs/accessibility.html [.fetchFields()]: https://developers.google.com/maps/documentation/javascript/place-details