My docassemble Notes
- Generally
- Blocks
- Buttons
- Disable Input Field
- Functions
- Give Users Information
- Groups
- If/Then
- Interview
- Objects
- Variables
- Word Template
- Example: Collecting Signatures
- Resources on docassemble
Generally
docassemble is a web app for document assembly.
docassemble uses:
Blocks
Generally
docassemble has several blocks:
- attachment
- code
- data
- data from code
- objects
- objects from file
- metadata
- question
- reset
- table
- template
Question Block
Generally on Question Blocks
Every interview screen that a docassemble user sees is generated either by a question block or by the message() function.1
See Question blocks, docassemble.
Example of a Question Block
Here is an example of a question
block from Hello world example, docassemble:
---
question: Hello, world!
buttons:
- Exit: exit
mandatory: True
---
Buttons
Custom Button
In getField() JavaScript function, docassemble’s documentation shows an example of how you can use JavaScript to create an “Eat it” button on the screen:
question: |
What do you want for dessert<span id="the_dessert"></span>?
fields:
- Dessert: dessert
- html: |
<a href="#" id="show_me" class="btn btn-primary">Eat it</a>
script: |
<script>
$("#show_me").click(function(){
$("#the_dessert").html(" besides " + getField('dessert').value);
return false;
});
</script>
Exit Button
Example of an exit button from Hello world example, docassemble:
---
question: Hello, world!
buttons:
- Exit: exit
mandatory: True
---
Disable Input Field
Scenario
Scenario: You want to display a field with a prefilled value but prevent the user from modifying the field.
Two Ways
There are two ways to disable an input field:
disable if
- Use JavaScript
disable if
See disable if
, docassemble.
N.B. You cannot use code
inside disable if
. So, for example, the following will not work:2
question: |
Survey Title
subquestion: |
fields:
- Please provide a ticket ID: q146005986
required: False
default: |
${ url_argument_variable }
disable if:
code: |
2 + 2 == 4
JavaScript
Jonathan Pyle gives the following example of how you can use JavaScript to disable a field:
question: |
Survey Title
subquestion: |
fields:
- Please provide a ticket ID: q146005986
required: False
default: |
${ url_argument_variable }
script: |
% if url_argument_variable == `unknown`:
<script>
$(getField(`q1460005986`)).prop('disabled', true);
</script>
% endif
For more information on getField()
, see getField() JavaScript function, docassemble.
Functions
Generally
See Functions, docassemble.
indefinite_article()
You use this function in a question block for question
or subquestion
in Mako.
Example:
question: |
Would you like to have ${ indefinite_article(fruit) }?
yesno: like_fruit
See indefinite_article(), docassemble.
See DAList, docassemble, for an example of indefinite_article
with objects.
message()
Every interview screen that a docassemble user sees is generated either by a question block or by the message() function.1
See message(), docassemble.
Give Users Information
Display User’s Answer with Exit Button
Scenario: The user is in an interview. The user answered the variable. In another screen, you want to display that variable back to the user.
To display the user’s answer, use ${ varname }
.
Example:
---
question: Hello, ${ planet }!
buttons:
- Exit: exit
mandatory: True
---
question: |
What is your planet's name?
fields:
- Your Planet: planet
---
Help
You can provide users help. See Providing help text to users, docassemble.
Progressive Disclosure
*See Progressive disclosure, docassemble.
note
You can have a note as its own field or as a field modifier. See note
, docassemble.
Groups
Generally
A group is a way to gather information that is repeated. A group allows you to avoid typing the same question over and over.
Types of Groups in docassemble
docassemble has three types of groups: Lists, dictionaries, and sets. They correspond to their Python counterparts.
docassemble | Python |
---|---|
list | list |
dictionary | dict |
set | set |
Lists
Generally on Lists
A list is a collection of variables that has a numerically defined order. The index for the elements starts at 0. You retrieve an element by referencing its number.
Lists in Python
(1) Create the list by defining its elements:
fruit = ['apple', 'orange', 'pear']
Note:
fruit[0]
returnsapple
fruit[1]
returnsorange
fruit[2]
returnspear
(2) Add an element to the list by using append
:
fruit.append('grape')
Note:
- In Python, adding an element to a list is called “appending.”
- The result of print(fruit) is now:
['apple', 'orange', 'pear', 'grape']
(3) Arrange the list in order by using sorted
function:
print(sorted(fruit))
Note:
- The result is now
['apple', 'grape', 'orange', 'pear']
In docassemble: DAList
class
A list is an object. In docassemble, lists are part of the DAList
class. A list can also contain objects.
How to Initialize a List
The following shows how to initialize a list in docassemble and how to tell what type of object the list will hold.
---
objects:
- my_list: DAList
- my_peeps: DAList.using(object_type=Individual)
Dictionaries
A dictionary is a collection of variables in a lookup table and every element has a key and value. You retrieve the element by referencing its key.
If/Then
If/Then Using Mako
% if my_var:
Here's the optional text. It could have an optional ${variable} too.
% endif
(This code example is from GBLS/docassemble-workinggroup, GitHub.)
If/Then Using Jinja2 in Word
Example:
{% if test == 'My text' %}
Optional text. It can have an optional {{ variable }}.
{% endif %}
This example would leave an empty line. To avoid an empty line:
{%p if x >= 2 %}
Optional text. It can have an optional {{ variable }}.
{%p endif %}
(These code examples are from GBLS/docassemble-workinggroup, GitHub.)
If/Then Using Python in Code Block
---
question: What is your favorite number?
fields:
- Number: favorite_number
datatype: number
---
code: |
if favorite_number == 42:
inhabitant_count = 2
else:
inhabitant_count = 2000 + favorite_number * 45
---
Interview
Set Title of Interview
You can set the title of an interview by using the metadata
block.
metadata:
title: |
Residential Lease Docs
Share Interview
You can share your interview with others in several ways:
- Share URL for Playground version – The “bleed edge” version of your interview that reflects what is on Playground. So, changes you save are immediately visible to users. The URL link ends with something like
?i=docassemble.playground1:hello.yml
. - Share URL for package version – A final, stable version of the interview. The link will end with something like
?i=docassemble.helloworld:data/questions/hello.yml
. - Use different development server and production server, which is recommended.
- Store on PYPL.
- Store on Github.
Reset Questions and Restart Interview
Use the Restart
special button:
restart
resets the user’s variable store, except that any parameters that were originally passed through as URL parameters will be used again. The user is redirected to the first question of the interview.
Objects
Generally
DAObject, Generic Object, Individual, Organization, Person
Initializing an Object
There are three ways to create (“initialize”) an object in docassemble: (1) objects
block, (2) code block, or (3) importing (by using objects from file
).
Creating objects
explains that you can initialize an object using the objects block or a code block, but “[w]henever possible, you should use objects
blocks rather than code to initialize your objects because objects
blocks are clean and readable.”
Generic Object
Generally
See Reusable questions: generic object
, docassemble.
See Index variables, docassemble.
Using Two DAList
objects:
- fruit: DAList
- vegetable: DAList
---
generic object: DAList
code: |
x.there_are_any = True
---
generic object: DAList
question: |
What is your ${ ordinal(i) } favorite ${ x.object_name() }?
fields:
- label: |
${ x.object_name(capitalize=True) }
field: x[i]
---
generic object: DAList
question: |
Do you have any other favorite ${ noun_plural(x.object_name()) }?
yesno: x.there_is_another
---
mandatory: True
question: |
Your favorites
subquestion: |
Your favorite fruits are:
% for item in fruit:
* ${ item }
% endfor
Your favorite vegetables are:
% for item in vegetable:
* ${ item }
% endfor
DAList.using
, x.
√objects:
- grantor: DAList.using(object_type=Individual, there_are_any=True)
---
generic object: Individual
question: |
What is ${ x.object_possessive('name') }?
fields:
- First Name: x.name.first
default: ${ x.first_name_hint() }
- Middle Name: x.name.middle
required: False
- Last Name: x.name.last
default: ${ x.last_name_hint() }
- Suffix: x.name.suffix
required: False
code: |
name_suffix()
---
generic object: DAList
question: |
Are there are any more ${ noun_plural(x.object_name()) }?
yesno: x.there_is_another
---
question: |
Hello, ${ grantor }!
mandatory: True
DAList.using
, x[i]
objects:
- grantor: DAList.using(object_type=Individual, there_are_any=True)
---
generic object: DAList
question: |
What is the name of the ${ ordinal(i) } ${ x.object_name() }?
fields:
- First Name: x[i].name.first
default: ${ x[i].first_name_hint() }
- Middle Name: x[i].name.middle
required: False
- Last Name: x[i].name.last
default: ${ x[i].last_name_hint() }
- Suffix: x[i].name.suffix
required: False
code: |
name_suffix()
---
generic object: DAList
question: |
Are there are any more ${ noun_plural(x.object_name()) }?
yesno: x.there_is_another
---
question: |
Hello, ${ grantor }!
mandatory: True
Variables
Address
Generally
An Address
is a built-in class in docassemble. So, you have to state in the YAML file that you are using the Address
object.
objects:
address: Address
Text Attributes
The Address
object has several text attributes, which you ask for under fields
:
attribute | example |
---|---|
address |
“123 Main Street” |
unit |
“Suite 100” |
city |
“Springfield” |
state |
“MA” |
zip (or postal_code ) |
“01199” |
country |
“US” |
List States
You can provide the user with a list of states:
fields:
- State: address.state
code: |
states_list()
required: False
List Countries
You can provide the user with a list of countries:
fields:
- Country: address.country
code: |
countries_list()
required: False
Return Address in Interview Using Mako
The following example shows several ways you can return the address in Mako.
mandatory: True
question: Your address
subquestion: |
### Address Block
Default:
${ address }
Showing the country:
${ address.block(show_country=True) }
International:
${ address.block(international=True) }
### State
You live in
${ state_name(address.state) },
which is abbreviated
${ address.state }.
Use the .block()
method to return a formatted address.
Additional Resources on Address
See
- Address, docassemble.
states_list()
andstate_name()
, docassemble.
Ask User for More than One Variables
You can collect more than one variable on a screen.
Checkbox
Generally
See Checkboxes, docassemble.
Checkbox in Word
See Checkbox, below.
Require Checkbox to Be Checked
See Require a checkbox to be checked, documentation.
Date Variable
Dates, docassemble
datatype: date
dateutil.parser.parse, dateutil – “This module offers a generic date/time string parser which is able to parse most known formats to represent a date and/or time.”
Special date/time class DADateTime
, docassemble
Functions for working with dates and times
Multiple Choice Variable
The following example is from How to Use Datatypes in Docassemble, YouTube.
question: |
What nuts do you like more - peanuts or pecans?
fields:
- Answer here: peanuts_or_pecans
choices:
- Peanuts
- Pecans
The following examples shows how you can use code to access a multiple choice answer and set other variables:
---
question: |
Who is the landlord?
fields:
- Landlord: landlord
choices:
- Big Realty LLC
- John Doe and Jane Doe
- Other
- Landlord's Name: landlord_name
show if:
variable: landlord
is: Other
---
code: |
if landlord == "Big Realty LLC":
landlord_is_br = True
landlord_is_jd = False
landlord_is_other = False
if landlord == "John Doe and Jane Doe":
landlord_is_br = False
landlord_is_jd = True
landlord_is_other = False
if landlord == "Other":
landlord_is_br = False
landlord_is_jd = False
landlord_is_other = True
Name
Generally on Asking for a Name
There are two ways to ask for an person’s name: (1) Use separate variables or (2) use an object.
Separate variables
You can ask for each variable separately:
question: |
Grantor
fields:
- Full name: grantor_name_full
- Last name: grantor_name_last
- aka: grantor_name_aka
Object for Name
This example is from How docassemble uses objects:
objects:
- grantor: Individual
- grantee: Individual
- trustee: Individual
---
generic object: Individual
question: |
What is
${ x.object_possessive('name') }?
fields:
- First Name: x.name.first
default: ${ x.first_name_hint() }
- Middle Name: x.name.middle
required: False
- Last Name: x.name.last
default: ${ x.last_name_hint() }
- Suffix: x.name.suffix
required: False
code: |
name_suffix()
Generic Object for Name Using x[i]
objects:
- grantor: DAList.using(object_type=Individual, there_are_any=True)
---
generic object: DAList
question: |
What is the name of the ${ ordinal(i) } ${ x.object_name() }?
fields:
- First Name: x[i].name.first
default: ${ x[i].first_name_hint() }
- Middle Name: x[i].name.middle
required: False
- Last Name: x[i].name.last
default: ${ x[i].last_name_hint() }
- Suffix: x[i].name.suffix
required: False
code: |
name_suffix()
---
generic object: DAList
question: |
Are there are any more ${ noun_plural(x.object_name()) }?
yesno: x.there_is_another
---
question: |
Hello, ${ grantor }!
mandatory: True
Number Variable
Ask User for Number Variable
---
question: What is your favorite number?
fields:
- Number: favorite_number
datatype: number
---
Manipulate User’s Number Variable
---
question: What is your favorite number?
fields:
- Number: favorite_number
datatype: number
---
code: |
if favorite_number == 42:
inhabitant_count = 2
else:
inhabitant_count = 2000 + favorite_number * 45
---
Reset Variable
You can set variables using code and reset them using a reset
block.
Set Variable
There are two ways to set a variable:
- Use Python in a
Code
block - Use the
setField()
JavaScript function
See setField() JavaScript function, docassemble documentation.
Text Variable
Ask User for Text Variable
---
question: |
What is your planet's name?
fields:
- Your Planet: planet
---
Yes/No
Yes/No in Playground
Yes/no fields, docassemble.
datatype: yesno
datatype: yesnowide
uncheck others: True
– causes the field to act as a “none of the above” field for all the other yes/no checkbox fields on the pageuncheck others:
– when followed by list of variables, it will uncheck only those variablesdatatype: yesnoradio
– shows “Yes” and “No” choicesdatatype: yesnomaybe
– shows “Yes,” “No,” and “I don’t know.”help
Yes/No in Word
See Yes/No in Word, above.
Word Template
Generally
Variables use Jinja2.
Inline Variable
Insert inline variable:
Here is an inline variable,{{ var_name }}, which is part of a paragraph.
- Syntax: Two curly open brackets (
{{
), space (}}
). - A variable name cannot have spaces.
Capitalize, Lower Case, Title Case
See https://www.documate.org/automation/docassemble-word-document-templates-part-v-formatting-words/
Checkbox
Use the following if
statement:
{% if var_name['cb_answer'] %}Some conditional text.{% endif %}
Format Numbers
See https://www.documate.org/automation/docassemble-word-document-templates-part-iv-formatting-numbers/
Multiple Choice
Use the following if
statement:
{% if var_name=='mc_answer' %}Some conditional text.{% endif %}
Perform Math
{{ num_var1 + num_var2 | float }}
{{ num_var1 - num_var2 | float }}
{{ num_var1 * num_var2 | float }}
{{ num_var1 / num_var2 | float }}
{{ 100 + num_var2 | float }}
Singular/Plural
See https://www.documate.org/automation/docassemble-word-document-templates-part-v-formatting-words/
Use a/an
See https://www.documate.org/automation/docassemble-word-document-templates-part-v-formatting-words/
Yes/No in Word
Use the following if
statement:
{% if var_name %}Some conditional text.{% endif %}
Resources on Jinja
Jinja built-in statements/tags and functions (like Django template tags), Web Forefront.
Example: Collecting Signatures
The Problem
On 8/28/2021, in docassemble’s Slack channel, Jonathan Pyle shared with sample code to gather signatures for landlords, landlord’s representatives, and tenants. The problem that I posed is that there can be more than one landlord, landlords who are entities or individuals, or landlords who have one address (such as a married couple). I will examine the code block-by-block.
The Advice
Pyle gave the following advice:
Keep in mind that the point of
generic object
is simply to avoid having to repeat yourself. It only makes sense to usegeneric object
if you have so many different objects in your interview that it would be boring and repetitive to use a different block for each object. In the case of an interview involving landlords and tenants, I would just write separate blocks for questions about the tenant(s) and questions about the landlord(s). It’s up to you, though.Anyway, I would recommend using a data structure that is general enough to encompass all the scenarios, so you don’t have to use
if/else
statements.
The Code
objects:
- tenant: DAList.using(object_type=Individual, there_are_any=True)
- landlord: DAList.using(object_type=(Individual if mom_and_pop else Person), there_are_any=True)
- landlord[i].signers: DAList.using(object_type=Individual, auto_gather=(not mom_and_pop))
---
question: |
Who is the ${ ordinal(i) } landlord?
fields:
- First name: landlord[i].name.first
- Last name: landlord[i].name.last
---
question: |
Who is your landlord?
fields:
- Name: landlord[i].name.text
---
question: |
Is your landlord a company, or one or more individuals?
field: mom_and_pop
buttons:
- Company: false
- One or more individuals: True
---
question: |
Is there another landlord besides ${ landlord }?
yesno: landlord.there_is_another
---
code: |
if not mom_and_pop:
landlord.there_is_another = False
---
only sets: landlord[i].signers.gathered
code: |
log("auto gather is " + repr(landlord[i].signers.auto_gather))
landlord[i].signers.clear()
landlord[i].signers.append(landlord[i])
landlord[i].signers[0].title = 'owner'
landlord[i].signers.gathered = True
---
code: |
landlord[i].signers.there_are_any = True
---
question: |
Who is the ${ ordinal(j) } person signing for ${ landlord[i] }?
fields:
- First name: landlord[i].signers[j].name.first
- Last name: landlord[i].signers[j].name.last
- Title: landlord[i].signers[j].title
---
question: |
Do any more people need to sign for ${ landlord[i] }?
yesno: landlord[i].signers.there_is_another
---
need: x.signature_notification
generic object: Individual
signature: x.signature
question: Sign your name
under: ${ x }
---
generic object: Individual
question: |
${ x } needs to sign now.
subquestion: |
Press Continue when ready.
continue button field: x.signature_notification
---
mandatory: True
question: |
Signature page
attachment:
content: |
% for ll in landlord:
For landlord: ${ ll }
% for signer in ll.signers:
${ signer.signature } [BR] [BLANK] [BR]
${ signer }, ${ signer.title }
% endfor
% endfor
Comments on the Code
Object Block
The code opens with the following object block:
objects:
- tenant: DAList.using(object_type=Individual, there_are_any=True)
- landlord: DAList.using(object_type=(Individual if mom_and_pop else Person), there_are_any=True)
- landlord[i].signers: DAList.using(object_type=Individual, auto_gather=(not mom_and_pop))
1) This block defines three objects: tenant
, landlord
, and landlord[i].signers
.
- Why is the third one so complicated?
2) The first object definition is tenant: DAList.using(object_type=Individual, there_are_any=True)
a) “A DAList
acts like an ordinary Python list, except that docassemble can ask questions to define items of the list.”3
Resources on docassemble
GBLS/docassemble-workinggroup, GitHub.
-
Question blocks, docassemble. ↩ ↩2
-
This example is from a question that Ronald Ko asked on docasssemble’s Slack channel on August 31, 2021. ↩