| 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.
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_fish1NyanCatnyancat(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:NyanminusCat)fried.fish1(this is valid code, but [Python] will think you are referring to the attributefish1of the objectfried)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)fororlicense.for(attributes cannot share names with built-in Python syntax. Names likefor,while, orinwill 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.
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.
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.
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" %}
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).
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.
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" %}
To provide a multiple choice question with a dropdown selector, use
field with a dropdown list:
{% include side-by-side.html demo="choices-dropdown" %}
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.
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" %}
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.
{% 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.
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.
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:
usercameraenvironmentcamcorder- [
checkboxes](#fields checkboxes) - [
multiselect](#fields multiselect) currencydatedatetimeemailfilefilesintegermicrophonemlmlarea- [
noyes](#fields noyes) - [
noyesmaybe](#fields noyesmaybe) - [
noyesradio](#fields noyesradio) - [
noyeswide](#fields noyeswide) numberobjectobject_checkboxesobject_multiselectpasswordrangetext(the default)time- [
yesno](#fields yesno) - [
yesnomaybe](#fields yesnomaybe) - [
yesnoradio](#fields yesnoradio) - [
yesnowide](#fields yesno)
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].
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.
datatype: password provides an input box
suitable for passwords.
{% include side-by-side.html demo="password-field" %}
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" %}
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.
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.
datatype: email provides an e-mail address input box.
{% include side-by-side.html demo="email-field" %}
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" %}
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.
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" %}
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:
- Your file: file_variable
datatype: file
allow users:
- peter@abc.com
- daniel@abc.com {% endhighlight %}
{% 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.
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" %}
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" %}
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" %}
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" %}
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.
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.
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:
- A
listof pieces of text; - A
dictin 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 - A
listoflists, 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 %}
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.
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" %}
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" %}
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].
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" %}
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" %}
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 = """
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) - thedatatypename. 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 toda-field-container-datatype-followed by thename.input_class- a [CSS] class for the<input>element. By default, this will be set todafollowed by thename.input_type- thetypefor the<input>element. By default, this will be set totext.javascript- [JavaScript] code related to the data type. By default, this will beNone. If notNone, 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, thejavascriptdefines a new validation rule. For more information on how to extend the [jQuery Validation Plugin], see the documentation for [jQuery.validator.addMethod()]. Thejavascriptcode can be used to initialize any fields on the screen that have the custom datatype. For example, if yourcontainer_classisda-ssn, yourjavascriptcould be:
{% highlight javascript %}
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 thejavascriptattribute. 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 thejq_ruleis not satisfied. The message will pass through the [word()] function to support multiple languages, and it can be overridden with [validation messages]. Instead of settingjq_messageto 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 setjq_messageto something likejq_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. Ifjq_ruleis a list of rules,jq_messagemust be expressed in dictionary format.skip_if_empty- the default isTrue. This is rarely used, so you can probably ignore it. This is relevant when thedatatypeis used on a multiple-choice question and there are zero choices to present to the user. Ifskip_if_emptyisTrue, then the variable is not set to any value. Ifskip_if_emptyisFalse, then the variable will be set to the output of the.empty()class method.is_object- the default isFalse. If you have atransform()class method that returns something that cannot be defined withrepr(), set this toTrue.parameters- the default is[]. If you want to pass parameters from the YAML todataattributes of the resulting<input>, you can list the parameter names here. For example, ifparametersis set to['kind'], and you includekind: basicas a field modifier in the YAML of the field, then the [HTML] of the<input>element will containdata-kind="basic". You can extract these data values using [JavaScript].code_parameters- the default is[]. This is just likeparameters, except the values in the YAML are treated as a [Python expression], and thedatavalue 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 likeparameters, except the values in the YAML are treated as [Mako], and thedatavalue is set to the rendered text.
The available class methods are:
validate()- thevalidate()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 (seeparameters, etc., above). The method should returnTrueif the value is valid. If the value is invalid, the method should raise aDAValidationErrorexception with a message. The message given toDAValidationErrorwill 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 avalidate()class method is not provided, no input validation will be performed.
transform- thetransform()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 (seeparameters, etc., above). The method should return the transformed value. In the example above, thetransform()class method ensures that Social Security numbers that are entered without hyphens (which are accepted) will contain hyphens when the variableuser.ssnis actually defined. If atransform()class method is not provided, the variable will be set to the raw value from the POST request (a string).default_for- thedefault_for()method is used when the user visits aquestionwhen the variable is already defined. If the output of yourtransformmethod is not suitable for placing into the field as the default value, you can define adefault_for()class method that returns the text that should be inserted into the field. The first positional parameter for thedefault_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 (seeparameters, etc., above). If yourtransform()method returns a [Python object], you can write adefault_for()class method that takes the object as the first positional parameter and returns plain text (anstr) that is a suitable default value to use for the HTML field. If you do not define adefault_for()class method, an attempt will be made to obtain a default value by runningstr()on the variable.empty- this is rarely used, so you can probably ignore it. Theemptyclass method is used whenskip_if_emptyisFalse. Instead of not defining the variable, docassemble will set the value of the variable to the output of this method. If noempty()method is provided, the valueNoneis 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.
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.
The following are the keys that have special meaning within a list
item under [fields].
datatype affects how the data will be collected, validated and
stored. For a full explanation of how this is used, see
[above](#data types).
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 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.
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.
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" %}
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.
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" %}
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" %}
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.
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" %}
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.
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" %}
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 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" %}
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.
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.
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.
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,FalseandNone, JavaScript hastrue,false, andnullas well asundefined
{% 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 %}
This works just like [js show if](#js show if), except that it hides
the field instead of showing it.
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.
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" %}
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 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 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.
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].
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.
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.
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.
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:
xssmmdlgxlxxl
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.
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" %}
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" %}
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 %}
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).
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: forcurrencyandnumberdata types, require a minimum value. This is passed directly to the jQuery Validation Plugin.max: forcurrencyandnumberdata 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.
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.
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.
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, theaddress_componentsfield is broken out into its numerous components, includingadministrative_area_level_1,street_number, etc. Attributes of theAddressobject are populated as follows:addressis populated withstreet_numberandroute, separated by a space.cityis populated withlocality,sublocality_level_1,neighborhood,administrative_area_level_3, orcolloquial_area, whichever is first available.stateis popuated with theshortTextversion ofadministrative_area_level_1.countryis popuated with theshortTextversion ofcountry.sublocalityis populated withsublocality_level_1,sublocality_level_2,sublocality_level_3,sublocality_level_4,sublocality_level_5, whichever is first available.zipis populated withpostal_code, and if there is apostal_code_suffix, it is appended to thezip, separated by a hyphen.
- If you request
address_componentsand yourquestionpopulates attributes of theAddressobject that correspond with Google's address components, those fields will be populated with thelongTextversion of the component. For example, you can capture Google'sstreet_numberandroutefields by usingstreet_numberandrouteas attributes of yourAddressobject in yourquestion. - 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 arehas_wheelchair_accessible_entrance,has_wheelchair_accessible_parking,has_wheelchair_accessible_restroom, andhas_wheelchair_accessible_seating. - If you request
plus_code, the fields that are populated arecompound_codeandglobal_code. - If you request
google_maps_links, the fields that are populated aredirections_uri,photos_uri,place_uri,reviews_uri, andwrite_a_review_uri. - If you request
location, the fields that are populated arelatitudeandlongitude.
- If you request
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.
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" %}
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" %}
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.
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.
Here is a lengthy example that illustrates many of the features of
[fields].
{% include side-by-side.html demo="fields" %}
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).
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:
question: | Is this your signature? subquestion: | ${ user_signature } yesno: user_signature_verified
{% endhighlight %}
or, if you want to control the width of the image:
question: | Is this your signature? subquestion: | ${ user_signature.show(width='1in') } yesno: user_signature_verified
{% 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.
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.birthdatemother.birthdatefather.birthdate
or:
plaintiff[0].servedplaintiff[1].servedplaintiff[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] 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]).
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]).
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.
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].
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.
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.
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.
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.
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.
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.
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:
- 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 ofeligible_for_medicare. Therefore, it is important that the interview developer control what the user can edit. - 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