Edit this page on GitHub

Initial blocks

Interview title and other metadata

---
metadata:
  title: |
    Advice on Divorce
  short title: |
    Divorce
  description: |
    A divorce advice interview
  authors:
    - name: John Doe
      organization: Example, Inc.
  revision_date: 2015-09-28
---

A metadata block contains information about the interview, such as the name of the author. It must be a YAML dictionary, but each the dictionary items can contain any arbitrary YAML structure.

If a title is defined, it will be displayed in the navigation bar in the web app. If a short title is provided, it will be displayed in place of the title when the size of the screen is small.

If a logo is defined, it will be displayed in the navigation bar in the web app in place of the title and short title. The content of the logo should be raw HTML. If you include an image, you should size it to be about 20 pixels in height.

If a tab title is provided, it will be displayed as the title of the browser tab. Otherwise, the title will be used.

If a subtitle is provided, it will be displayed as the subtitle of the interview in the “Interviews” list available to a logged-in user at /interviews.

These titles can be overridden using the set_title() function.

The metadata block and the set_title() function can be used to modify other aspects of the navigation bar.

If an exit link is provided, the behavior of the “Exit” link can be modified. (The “Exit” menu option is displayed when the show login configuration directive is set to False or the show login metadata specifier in an interview is set to False.) The value can be either exit or leave. If it is exit, then when the user clicks the link, they will be logged out (if they are logged in) and their interview answers will be deleted from the server. If it is leave, the user will be logged out (if they are logged in), but the interview answers will not be deleted from the server. It can be important to keep the interview answers on the server if background tasks are still running.

If exit label is provided, the given text will be used in place of the word “Exit” on the “Exit” menu option. This text is passed through the word() function, so that it can be translated into different languages.

If you set unlisted: True for an interview that has an entry in the dispatch list in your configuration, the interview will be exempted from display in the list of interviews available at /list. For more information about this, see the documentation for the dispatch configuration directive.

The metadata block also accepts the specifiers pre, submit, and post. You can use these to provide raw HTML that will be inserted into the page before the question heading, before the buttons, and after the buttons, respectively. You can also set server-wide defaults for these values using the main page pre, main page submit, and main page post directives in the Configuration. You can also customize these values with the set_title() function.

The metadata block also accepts the specifier error help. This is Markdown-formatted text that will be included on any error screen that appears to the user during the interview. You can also provide this text on a server-wide basis using the error help directive in the Configuration.

metadata:
  error help: |
    We are sorry.
    An error has occurred.
    Please contact
    [the administrator](mailto:[email protected]).
error-help

The metadata block also accepts the specifier show login, which can be true or false. This controls whether the user sees a “Sign in or sign up to save answers” link in the upper right-hand corner during the interview. If show login is not specified in the metadata, the Configuration directive show login determines whether this link is available.

metadata:
  show login: False
show-login

Creating objects

---
objects:
  - spouse: Individual
  - user.case: Case
---

An objects block creates objects that may be referenced in your interview. See objects for more information about objects in docassemble.

If your interview references the variable spouse, docassemble will find the above objects block and process it. It will define spouse as an instance of the object class Individual and define user.case as an instance of the object class Case.

The use of objects in docassemble interviews is highly encouraged. However, the objects you use as variables need to inherit from the class DAObject. Otherwise, docassemble might not be able to find the appopriate code blocks or questions necessary to define them. This is because of the way docassemble keeps track of the names of variables.

A code block like this would effectively do the same thing as the objects block above:

---
code: |
  spouse = Individual('spouse')
  user.initializeAttribute('case', Case)
---

This code is more complicated than normal Python code for object initialization because the full name of the variable needs to be supplied to the function that creates and initializes the object. The base class DAObject keeps track of variable names.

In some situations, running spouse = Individual() will correctly detect the variable name spouse, but in other situations, the name cannot be detected. Running spouse = Individual('spouse') will always set the name correctly.

Whenever possible, you should use objects blocks rather than code to initialize your objects because objects blocks are clean and readable.

You can also use objects blocks to initialize attributes of the objects you create. For information on how to do this, see the documentation for the using() method.

Importing objects from file

---
objects from file:
  - claims: claim_list.yml
---

An objects from file block imports objects or other data elements that you define in a separate YAML data file located in the sources folder of the current package. If the interview file containing the objects from file block is data/questions/manage_claims.yml, docassemble will expect the data file to be located at data/sources/claim_list.yml.

For more information about how this works, and about how to format the data file, see the documentation for the objects_from_file() function. The example above is equivalent to running claims = objects_from_file('claim_list.yml', name='claims').

Incorporation by reference: include

---
include:
  - basic-questions.yml
  - docassemble.helloworld:questions.yml
---

The include block incorporates the questions in another YAML file, almost as if the contents of the other YAML file appeared in place of the include block. When the included file is parsed, files referenced within it will be assumed to be located in the included file’s package.

When a filename is provided without a package name, docassemble will look first in the data/questions directory of the current package (i.e., the package within which the YAML file being read is located), and then in the data/questions directory of docassemble.base.

You can include question files from other packages by explicitly referring to their package names. E.g., docassemble.helloworld:questions.yml refers to the file questions.yml in the docassemble/helloworld/data/questions directory of that package.

Images

With attribution: image sets

---
image sets:
  freepik:
    attribution: |
      Icon made by [Freepik](http://www.flaticon.com/authors/freepik)
    images:
      baby: crawling.svg
      people: users6.svg
      injury: accident3.svg
---

An image sets block defines the names of icons that you can use to decorate your questions.

The file names refer to files located in the data/static directory of the package in which the YAML file is located.

Since most free icons available on the internet require attribution, the image sets block allows you to specify what attribution text to use for particular icons. The web app shows the appropriate attribution text at the bottom of any page that uses one of the icons. The example above is for a collection of icons obtained from the web site Freepik, which offers free icons under an attribution-only license.

The image sets block must be in the form of a YAML dictionary, where the names are the names of collections of icons. The collection itself is also a dictionary containing terms images and (optionally) an attribution. The images collection is a dictionary that assigns names to icon files, so that you can refer to icons by a name of your choosing rather than by the name of the image file.

For information on how to use the icons you have defined in an image sets block, see decoration in the question modifiers section, buttons in the setting variables section, and “Inserting inline icons” in the markup section.

Without attribution: images

---
images:
  bills: money146.svg
  children: children2.svg
---

An images block is just like an image sets block, except that it does not set any attribution information. It is simpler because you do not need to give a name to a “set” of images.

The above images block is essentially equivalent to writing:

---
image sets:
  unspecified:
    images:
      bills: money146.svg
      children: children2.svg
---

Python modules

Importing the module itself: imports

---
imports:
  - datetime
  - us
---

imports loads a Python module name into the namespace in which your code and question templates are evaluated. The example above is equivalent to running the following Python code:

import datetime
import us

Importing all names in a module: modules

---
modules:
  - datetime
---

Like imports, modules loads Python modules into the namespace in which your code and question templates are evaluated, except that it imports all of the names that the module exports. The example above is equivalent to running the following Python code:

from datetime import *

Storing structured data in a variable

The data block allows you to specify a data structure in YAML in a block and have it available as a Python data structure.

For example, in this interview we create a Python list and then re-use it in two questions to offer a multiple-choice list.

variable name: fruits
data:
  - Apple
  - Orange
  - Peach
  - Pear
---
question: |
  What is your favorite fruit?
field: user_favorite_fruit
dropdown:
  code: fruits
data-simple

In Python, the variable fruits is this:

[u'Apple', u'Orange', u'Peach', u'Pear']

You can also use the data block to create more complex data structures. You can also use Mako in the data structure.

variable name: fruits
data:
  Apple:
    description: |
      The apple is a tasty red fruit.

      Everyone on ${ planet } loves
      to eat apples.
    seeds: 5
  Orange:
    description: |
      The orange is, surprisingly,
      orange-colored.  Most people
      on ${ planet } dislike
      eating oranges.
    seeds: 10
  Peach:
    description: |
      The peach is a fragile fruit.

      There are 165,323 peach
      orchards on ${ planet }.
    seeds: 1
  Pear:
    description: |
      The pear is variously yellow,
      green, or brown.

      The planet ${ planet } is
      shaped like a pear.
    seeds: 0
---
question: |
  On what planet were you born?
fields:
  Planet: planet
---
question: |
  What is your favorite fruit?
field: user_favorite_fruit
choices:
  code: fruits.keys()
---
mandatory: True
question: |
  Summary of ${ user_favorite_fruit }
subquestion: |
  ${ fruits[user_favorite_fruit]['description'] }

  The ${ user_favorite_fruit } has
  ${ nice_number(fruits[user_favorite_fruit]['seeds']) }
  seeds.
data

You can also import data from YAML files using the objects_from_file() function.

Storing structured data in a variable using code

The data from code block works just like the data block, except that Python code is used instead of text or Mako markup.

variable name: fruits
data from code:
  Apple:
    description: |
      ', '.join(['red', 'shiny', 'for teachers'])
    seeds: 10/2
  Orange:
    description: |
      capitalize('round') + " and orange"
    seeds: seeds_in_orange
  Peach:
    description: peach_description
    seeds: 10**6
  Pear:
    description: |
      "Like an apple, but not like an apple."
    seeds: 0
---
question: |
  How many seeds in an orange?
fields:
  - no label: seeds_in_orange
    datatype: range
    min: 0
    max: 100
---
question: |
  How would you describe a peach?
fields:
  - no label: peach_description
---
question: |
  What is your favorite fruit?
field: user_favorite_fruit
choices:
  code: fruits.keys()
---
mandatory: True
question: |
  Summary of ${ user_favorite_fruit }
subquestion: |
  ${ fruits[user_favorite_fruit]['description'] }

  The ${ user_favorite_fruit } has
  ${ nice_number(fruits[user_favorite_fruit]['seeds']) }
  seeds.
data-code

Keeping variables fresh: reset

The reset block will cause variables to be undefined every time a screen loads.

This can be helpful in a situation where a variable is set by a code block and the value of the variable ought to be considered afresh based on the user’s latest input.

---
reset:
  - client_is_guilty
  - opposing_party_is_guilty
---

Effectively, this causes variables to act like functions.

Another way to use this feature is to set the reconsider modifier on a code block. This will have the same effect as reset, but it will apply automatically to all of the variables that are capable of being assigned by the code block.

The reset block and the reconsider modifier are computationally inefficient because they cause extra code to be run every time a new screen loads. For a more computationally efficient alternative, see the reconsider() function

Changing order of precedence

As explained in how docassemble finds questions for variables, if there is more than one question or code block that offers to define a particular variable, blocks that are later in the YAML file will be tried first.

If you would like to specify the order of precedence of blocks in a more explicit way, so that you can order the blocks in the YAML file in whatever way you want, you can tag two or more blocks with ids and insert an order block indicating the order of precedence of the blocks.

For example, suppose you have an interview with two blocks that could define the variable favorite_fruit. Normally, docassemble will try the the second block first because it appears later in the YAML file; the second block will “override” the first.

question: |
  What the heck is your favorite fruit?
fields:
  Fruit: favorite_fruit
---
question: |
  What is your favorite fruit?
fields:
  Fruit: favorite_fruit
---
mandatory: True
question: |
  Your favorite fruit is
  ${ favorite_fruit }.
supersede-regular

However, if you actually want the first block to be tried first, you can manually specify the order of blocks:

order:
  - informal favorite fruit question
  - regular favorite fruit question
---
id: informal favorite fruit question
question: |
  What the heck is your favorite fruit?
fields:
  Fruit: favorite_fruit
---
id: regular favorite fruit question
question: |
  What is your favorite fruit?
fields:
  Fruit: favorite_fruit
---
mandatory: True
question: |
  Your favorite fruit is
  ${ favorite_fruit }.
supersede-order

Another way to override the order in which blocks will be tried is by using the id and supersedes question modifiers.

Vocabulary terms and auto terms

---
terms:
  enderman: |
    A slender fellow from The End who carries enderpearls and picks up
    blocks.
  fusilli: |
    A pasta shape that looks like a corkscrew.
---

Sometimes you will use vocabulary that the user may or may not know. Instead of interrupting the flow of your questions to define every term, you can define certain vocabulary words, and docassemble will turn them into hyperlinks wherever they appear in curly brackets. When the user clicks on the hyperlink, a popup appears with the word’s definition.

terms:
  creeper: |
    A tall green creature that explodes if
    you get too close.
  zombie pigman: |
    A harmless creature who carries a gold
    sword.
---
question: Have you ever met a {creeper}?
subquestion: |
  If you have met a {zombie pigman}, you
  have almost certainly met a creeper.
yesno: met_a_creeper
terms

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

auto terms:
  creeper: |
    A tall green creature that explodes if
    you get too close.
  zombie pigman: |
    A harmless creature who carries a gold
    sword.
---
question: Have you ever met a creeper?
subquestion: |
  If you have met a zombie pigman, you
  have almost certainly met a creeper.
yesno: met_a_creeper
auto-terms

You can also use terms and auto terms as question modifiers, in which case the terms will apply only to the question, not to the interview as a whole.

Defining the sections for the navigation bar

You can add use the navigation bar feature or the nav.show_sections() function to show your users the “sections” of the interview and what the current section of the interview is.

Here is a complete example.

sections:
  - Introduction
  - About you:
    - Contact info
    - Demographics
  - Preferences
  - Conclusion
---
features:
  navigation: True
  progress bar: True
---
modules:
  docassemble.base.util
---
mandatory: True
code: |
  menu_items = [ action_menu_item('Roadmap', 'road_map') ]
---
initial: True
code: |
  if returning_user(minutes=0.5):
    welcome_back
---
mandatory: True
question: |
  Welcome to the interview
subquestion: |
  If you are not on a
  smartphone-sized device,
  you should see a navigation
  bar to the left.
field: sees_nav_bar
---
mandatory: True
question: |
  I am going to ask you some
  questions about yourself.
field: intro_to_about_you
section: About you
---
mandatory: True
question: |
  What is your name?
fields:
  - First Name: first_name
  - Last Name: last_name
section: Contact info
---
mandatory: True
question: |
  What is your e-mail address?
fields:
  - E-mail: email_address
    datatype: email
---
mandatory: True
question: |
  What is your gender?
field: gender
choices:
  - Male
  - Female
  - Something else
section: Demographics
---
mandatory: True
question: |
  What kind of belly button
  do you have?
subquestion: |
  To see what a user would
  see after returning to
  the interview after a period
  of absence, try waiting
  thirty seconds, then
  [click into the
  interview](${ interview_url(local=True) }).

  In addition, there is a similar
  screen available on the Menu in the
  upper-right, under "Roadmap."
field: belly_button
choices:
  - Innie
  - Outie
---
mandatory: True
question: |
  What is your favorite fruit?
fields:
  - Favorite fruit: favorite_fruit
section: Preferences
---
mandatory: True
question: |
  What is your favorite vegetable?
fields:
  - Favorite vegetable: favorite_vegetable
---
progress: 100
mandatory: True
question: Thank you.
subquestion: |
  ${ first_name },

  Your answers mean a lot to me.
  
  I am going to go eat some
  ${ favorite_vegetable }
  now.
section: Conclusion
---
event: welcome_back
question: |
  Welcome back!
subquestion: |
  You are currently in the
  **${ nav.get_section(display=True) }**
  section.

  ${ nav }

  Press "Continue" to pick up
  where you left off.
buttons:
  Continue: continue
---
event: road_map
question: |
  Roadmap
subquestion: |
  You are currently in the
  **${ nav.get_section(display=True) }**
  section.

  ${ nav }

  Press "Continue" to resume the
  interview.
buttons:
  Continue: continue
sections

Subsections are supported, but only one level of nesting is allowed.

If your interview uses multiple languages, you can specify more than one sections block and modify each one with a language modifier:

---
language: en
sections:
  - Introduction
  - Fruit
  - Vegetables
  - Conclusion
---
language: es
sections:
  - Introducción
  - Fruta
  - Vegetales
  - Conclusión
---

If no language is specified, the fallback language * is used.

In the example above, the section modifier referred to sections using the same text that is displayed to the user. However, in some circumstances, you might want to use a shorthand to refer to a section, and update the actual section names displayed to the user without having to make changes in numerous places in your interview. You can do this by using key/value pairs in your sections block, and using the special key subsections to indicate subsections:

sections:
  - intro: Introduction
  - about: About you
    subsections:
      - contact: Contact info
      - demographic: Demographics
  - prefs: Preferences
  - conclusion: Conclusion
---
features:
  navigation: True
---
mandatory: True
question: |
  Welcome to the interview
subquestion: |
  If you are not on a
  smartphone-sized device,
  you should see a navigation
  bar to the left.
field: sees_nav_bar
---
mandatory: True
question: |
  I am going to ask you some
  questions about yourself.
field: intro_to_about_you
section: about
---
mandatory: True
question: |
  What is your name?
fields:
  - First Name: first_name
  - Last Name: last_name
section: contact
---
mandatory: True
question: |
  What is your e-mail address?
fields:
  - E-mail: email_address
    datatype: email
---
mandatory: True
question: |
  What is your gender?
field: gender
choices:
  - Male
  - Female
  - Something else
section: demographic
---
mandatory: True
question: |
  What kind of belly button
  do you have?
field: belly_button
choices:
  - Innie
  - Outie
---
mandatory: True
question: |
  What is your favorite fruit?
fields:
  - Favorite fruit: favorite_fruit
section: prefs
---
mandatory: True
question: |
  What is your favorite vegetable?
fields:
  - Favorite vegetable: favorite_vegetable
---
mandatory: True
question: Thank you.
subquestion: |
  ${ first_name },

  Your answers mean a lot to me.
  
  I am going to go eat some
  ${ favorite_vegetable }
  now.
section: conclusion
sections-keywords

The keywords for section names need to be valid Python names. When choosing keywords, make sure not to use the names of variables that already exist in your interview.

This is because the keywords can be used to make the left-hand navigation bar clickable. If a keyword for a section is a variable that exists in the interview, clicking on the section will cause an action to be launched that seeks a definition of that variable.

The recommended way to use this feature is to set up review blocks that have event set to the keyword of each section that you want to be clickable.

sections:
  - intro: Introduction
  - about: About you
    subsections:
      - contact: Contact info
      - demographic: Demographics
  - prefs: Preferences
  - conclusion: Conclusion
---
event: contact
section: contact
question: |
  Review contact information
review:
  - Edit name: first_name
    button: |
      Name: ${ first_name } ${ last_name }
  - Edit e-mail: email_address
    button: |
      E-mail: ${ email_address }
---
event: demographic
section: demographic
question: |
  Review demographic information
review:
  - Edit gender: gender
    button: |
      Gender: ${ gender }
  - Edit belly button: belly_button
    button: |
      Belly button: ${ belly_button }
---
event: prefs
section: prefs
question: |
  Preferences
review:
  - Edit fruit: favorite_fruit
    button: |
      Favorite fruit: ${ favorite_fruit }
  - Edit vegetable: favorite_vegetable
    button: |
      Favorite vegetable: ${ favorite_vegetable }
sections-keywords-review

Note that if you use review blocks in an interview with sections, every question should have a section defined. Otherwise, when your users jump around the interview, their section may not be appropriate for the question they are currently answering. Alternatively, you could use code blocks and the nav.set_section() function to make sure that the section is set appropriately.

By default, users are only able to click on sections that they have visited. If you want users to be able to click on any section at any time, set progressive to False:

sections:
  - intro: Introduction
  - about: About you
    subsections:
      - contact: Contact info
      - demographic: Demographics
  - prefs: Preferences
  - conclusion: Conclusion
progressive: False
---
event: intro
code: |
  force_ask('sees_nav_bar')
---
event: about
code: |
  force_ask('intro_to_about_you')
---
event: contact
code: |
  force_ask('first_name', 'email_address')
---
event: demographic
code: |
  force_ask('gender', 'belly_button')
---
event: prefs
code: |
  force_ask('favorite_fruit', 'favorite_vegetable')
---
event: conclusion
code: |
  force_ask('final_screen')
---
features:
  navigation: True
sections-non-progressive

Assisting users with interview help

---
interview help:
  heading: How to use this web site
  content: |
    Answer each question.  At the end, you will get a prize.
---

An interview help block adds text to the “Help” page of every question in the interview. If the question has help text of its own, the interview help will appear after the question-specific help.

You can also add audio to your interview help:

---
interview help:
  heading: How to use this web site
  audio: answer_each_question.mp3
  content: |
    Answer each question.  At the end, you will get a prize.
---

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

See the question modifiers section for an explanation of how audio and video file references work.

You can also provide a label as part of the interview help. This label will be used instead of the word “Help” in the navigation bar as a label for the “Help” tab.

---
interview help:
  label: More info
  heading: More information about this web site
  content: |
    If you are not sure what the right answer is, provide
    your best guess.
    
    You are answering these questions under the pains and
    penalties of perjury.  Your answers will be 
    shared with the special prosecutor.
---

Note that if you provide question-specific help, and you include a label as part of that help, that label will override the default label provided in the interview help (except if question help button is enabled).

Mako functions: def

def: adorability
mako: |
  <%def name="describe_as_adorable(person)"> \
  ${ person } is adorable. \
  </%def>

A def block allows you to define Makodef” functions that you can re-use later in your question or document templates. You can use the above function by doing:

---
question: |
  ${ describe_as_adorable(spouse) } Am I right?
yesno: user_agrees_spouse_is_adorable
usedefs:
 - adorability
---

Due to the way docassemble parses interviews, the def block needs to be defined before it is used.

Note the \ marks at the end of the lines in the mako definition. Without these marks, there would be an extra newline inserted. You may or may not want this extra newline.

Setting the default role

---
modules:
  - docassemble.base.util
---
default role: client
code: |
  if user_logged_in() and user_has_privilege('advocate'):
    user = advocate
    role = 'advocate'
  else:
    user = client
    role = 'client'
  set_info(user=user, role=role)
---

If your interview uses the roles feature for multi-user interviews, the default role specifier will define what role or roles will be required for any question that does not contain an explicit role specifier.

When you use the roles feature, you need to have some way of telling your interview logic what the role of the interviewee is.

If you include code within the same block as your default role specifier, that code will be executed every time the interview logic is processed, as if it was marked as initial. For this reason, any default role specifier that contains code should be placed earlier in the interview file than and mandatory questions or code blocks.

In the example above, the interview has two roles: “client” and “advocate”. The special variables user and role are set in the code block, which is executed every time the interview logic is processed.

In addition, the set_info() function from docassemble.base.util is called. This lets the linguistic functions in docassemble.base.util know who the user is, so that questions can ask “What is your date of birth?” or “What is John Smith’s date of birth” depending on whether the current user is John Smith or not.

Setting the default language

---
default language: es
---

This sets the language to use for all of the remaining questions in the file for which the language modifier is not specified. The purpose of this is to save typing; otherwise you would have to set the language modifier for each question. Note that this does not extend to questions in included files.

If your interview only uses one language, it is not necessary to (and probably not a good idea to) set a default language.

See language support for more information about how to create multi-lingual interviews. See question modifiers for information about the language setting of a question.

Machine learning training data

If you use machine learning in your interviews, then by default, docassemble will use training data associated with the particular interview in the particular package in which the interview resides.

If you would like your interview to share training data with another interview, you can use the machine learning storage specifier to point to the training data of another interview.

For example, suppose you have developed an interview called child_custody.yml that uses machine learning, and you have built rich training sets for variables within this interview. Then you decide to develop another interview, in the same package, called child_support.yml, which uses many of the same variables. It would be a lot of work to maintain two identical training sets in two places.

In this scenario, you can add the following block to the child_support.yml interview:

---
machine learning storage: ml-child_custody.json
---

ml-child_custody.json is the name of a file in the data/sources directory of the package. This file contains the training data for the child-custody.yml interview. The naming convention for these data files is to start with the name of the interview YAML file, add ml- to the beginning, and replace .yml with .json.

Now, both the child-custody.yml and child-support.yml interviews will use ml-child_custody.json as “storage” area for training data. In the Training interface, you will find this data set under the name child_custody.

If you had run the child-support.yml interview before adding machine learning storage, you may still see a data set called child-support in the Training interface. If you are using the Playground, you may see a file called ml-child-support.json in the Sources folder. To get rid of this, go into the Playground and delete the ml-child-support.json file from the Sources folder. Then go into the Training interface and delete any “items” that exist within the child-support interview.

If you want, you can set machine learning storage to a name that does not correspond with an actual interview. For example, you could include machine learning storage: ml-family-law.json in both the child-custody.yml and child-support.yml interviews. Even though there is no interview called family-law.yml, this will still work. If you are using the Playground, a file called ml-family-law.json will automatically be created in the Sources folder.

You can also share “storage” areas across packages. Suppose you are working within a package called docassemble.missourifamilylaw, but you want to take advantage of training sets in a package called docassemble.generalfamilylaw. You can write:

---
machine learning storage: docassemble.generalfamilylaw:data/sources/ml-family.json
---

For more information about managing training data, see the machine learning section on packaging your training sets

Optional features

The features block sets some optional features of the interview.

Whether debugging features are available

If the debug directive in the Configuration is True, then by default, the navigation bar will contain a “Source” link that shows information about how the interview arrived at the question being shown. If the debug directive is False, then this will not be shown.

This can be overridden in the features by setting debug to True or False depending on the behavior you want.

The following example demonstrates turning the debug feature off.

features:
  debug: False
debug-mode

On the server that hosts the demonstration interviews, the debug directive is True, so the “Source” link is normally shown. Setting debug: False makes the “Source” link disappear.

Whether interview is centered

If you do not want your interview questions to be centered on the screen, set centered to False.

features:
  centered: False
centered

Progress bar

The progress bar feature controls whether a progress bar is shown during the interview. You can use the progress modifier or the set_progress() function to indicate the setting of the progress bar.

features:
  progress bar: True
progress-features

If you want the progress bar to display the percentage, include show progress bar percentage: True:

features:
  progress bar: True
  show progress bar percentage: True
progress-features-percentage

The navigation feature controls whether a navigation bar is shown during the interview. You can use the sections initial block or the nav.set_sections() function to define the sections of your interview. The section modifier or the nav.set_section() function can be used to change the current section.

sections:
  - Introduction
  - About you:
    - Contact info
    - Demographics
  - Preferences
  - Conclusion
---
features:
  navigation: True
  progress bar: True
---
modules:
  docassemble.base.util
---
mandatory: True
code: |
  menu_items = [ action_menu_item('Roadmap', 'road_map') ]
---
initial: True
code: |
  if returning_user(minutes=0.5):
    welcome_back
---
mandatory: True
question: |
  Welcome to the interview
subquestion: |
  If you are not on a
  smartphone-sized device,
  you should see a navigation
  bar to the left.
field: sees_nav_bar
---
mandatory: True
question: |
  I am going to ask you some
  questions about yourself.
field: intro_to_about_you
section: About you
---
mandatory: True
question: |
  What is your name?
fields:
  - First Name: first_name
  - Last Name: last_name
section: Contact info
---
mandatory: True
question: |
  What is your e-mail address?
fields:
  - E-mail: email_address
    datatype: email
---
mandatory: True
question: |
  What is your gender?
field: gender
choices:
  - Male
  - Female
  - Something else
section: Demographics
---
mandatory: True
question: |
  What kind of belly button
  do you have?
subquestion: |
  To see what a user would
  see after returning to
  the interview after a period
  of absence, try waiting
  thirty seconds, then
  [click into the
  interview](${ interview_url(local=True) }).

  In addition, there is a similar
  screen available on the Menu in the
  upper-right, under "Roadmap."
field: belly_button
choices:
  - Innie
  - Outie
---
mandatory: True
question: |
  What is your favorite fruit?
fields:
  - Favorite fruit: favorite_fruit
section: Preferences
---
mandatory: True
question: |
  What is your favorite vegetable?
fields:
  - Favorite vegetable: favorite_vegetable
---
progress: 100
mandatory: True
question: Thank you.
subquestion: |
  ${ first_name },

  Your answers mean a lot to me.
  
  I am going to go eat some
  ${ favorite_vegetable }
  now.
section: Conclusion
---
event: welcome_back
question: |
  Welcome back!
subquestion: |
  You are currently in the
  **${ nav.get_section(display=True) }**
  section.

  ${ nav }

  Press "Continue" to pick up
  where you left off.
buttons:
  Continue: continue
---
event: road_map
question: |
  Roadmap
subquestion: |
  You are currently in the
  **${ nav.get_section(display=True) }**
  section.

  ${ nav }

  Press "Continue" to resume the
  interview.
buttons:
  Continue: continue
sections

Note that the section list is not shown on small devices, such as smartphones. To show a smartphone user a list of sections, you can use the nav.show_sections() function.

If you want the navigation bar to be horizontal across the top of the page, set navigation to horizontal:

features:
  navigation: horizontal
  progress bar: True
sections-horizontal

Back button style

By default, there is a “Back” button located in the upper-left corner of the page. (However, the “Back” button is not present when the user is on the first page of an interview, or the prevent_going_back() function has been used, or the prevent going back modifier is in use.)

Whether this back button is present can be controlled using the navigation back button feature. This will hide the “Back” button:

features:
  navigation back button: False

You can also place a “Back” button inside the body of a question, next to the other buttons on the screen, by setting the question back button feature to True (the default is False).

features:
  question back button: True
  navigation back button: False
question-back-button

You can also place a “Back” button inside the body of a question on some questions but not others, using the back button modifier.

Help tab style

When interview help is available, or the help modifier is present on a question, the “Help” tab will be present in the navigation bar. When the help modifier is present, the “Help” tab is highlighted yellow and marked with a yellow star. When the user presses the help tab, the help screen will be shown.

If you set the question help button to True, users will be able to access the help screen by pressing a “Help” button located within the body of the question, to the right of the other buttons on the page. When question help button is True, the “Help” tab will not be highlighted yellow.

Here is an interview in which the question help button is not enabled (which is the default).

features:
  question help button: False
---
interview help:
  label: More info
  heading: About this interview
  content: |
    This is an interview about fruit.
---
question: |
  What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
help:
  label: Huh?
  heading: What is a fruit?
  content: |
    A fruit is a fleshy edible
    part of a plant that has
    seeds.
---
question: |
  What is your favorite color?
fields:
  - Color: favorite_color
help:
  heading: What is a color?
  content: |
    Every photon has a frequency, which
    determines its color.
---
question: |
  What is your favorite vegetable?
fields:
  - Vegetable: favorite_vegetable
question-help-button-off

Here is the same interview, with the question help button feature enabled:

features:
  question help button: True
---
interview help:
  label: More info
  heading: About this interview
  content: |
    This is an interview about fruit.
---
question: |
  What is your favorite fruit?
fields:
  - Fruit: favorite_fruit
help:
  label: Huh?
  heading: What is a fruit?
  content: |
    A fruit is a fleshy edible
    part of a plant that has
    seeds.
---
question: |
  What is your favorite color?
fields:
  - Color: favorite_color
help:
  heading: What is a color?
  content: |
    Every photon has a frequency, which
    determines its color.
---
question: |
  What is your favorite vegetable?
fields:
  - Vegetable: favorite_vegetable
question-help-button

Note that when question help button is enabled, the label for the help tab in the navigation bar always defaults to “Help” or to the label of the interview help, and it is not highlighted yellow when question-specific help is available.

Hiding the standard menu items

By default, the menu in the corner provides logged-in users with the ability to edit their “Profile” and the ability to go to “My Interviews,” which is a list of interview sessions they have started. If you want to disable these links, you can use the hide standard menu specifier:

features:
  hide standard menu: True

If you want to add any of these links manually, or add them with different names, you can do so with the menu_items special variable and the url_of() function.

mandatory: True
code: |
  menu_items = [
    {'label': 'Edit my Profile', 'url': url_of('profile')},
    {'label': 'Saved Sessions', 'url': url_of('interviews')}
  ]

Javascript and CSS files

If you are a web developer and you know how to write HTML, Javascript, and CSS, you can embed HTML in your interview text. You can also bring Javascript and CSS files into the user’s browser.

For example, the following interview brings in a Javascript file, my-functions.js, and a CSS file, my-styles.css, into the user’s browser. These files are located in the data/static folder of the same package in which the interview is located.

features:
  javascript: my-functions.js
  css: my-styles.css
---
mandatory: True
question: |
  A test of Javascript and CSS
subquestion: |
  <span class="groovy"></span>
external_files

The contents of my-functions.js are:

$(document).on('daPageLoad', function(){
  $(".groovy").html("I am purple");
});

The contents of my-styles.css are:

.groovy {
  color: purple;
}

You can write whatever you want in these files; they will simply be loaded by the user’s browser. Note that your Javascript files will be loaded after jQuery is loaded, so your code can use jQuery, as this example does.

If you have Javascript code that you want to run after each screen of the interview is loaded, attach a jQuery event handler to document for the event daPageLoad, which is a docassemble-specific event that is triggered after each screen loads. (Since docassemble uses Ajax to load each new screen, if you attach code using jQuery’s ready() method, the code will run when the browser first loads, but not every time the user sees a new screen.) The example above demonstrates this; every time the page loads, the code will replace the contents of any element with the class groovy.

This example demonstrates bringing in CSS and Javascript files that are located in the data/static directory of the same package as the interview. You can also refer to files in other packages:

features:
  css: docassemble.demo:data/static/my.css

or on the internet at a URL:

features:
  javascript: https://example.com/js/my-functions.js

Also, if you want to bring in multiple files, specify them with a YAML list:

features:
  css:
    - my-styles.css
    - https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css
  javascript:
    - http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js
    - https://cdnjs.cloudflare.com/ajax/libs/offline-js/0.7.18/offline.min.js

If you want to include CSS or Javascript code in a specific question, rather than in all questions of your interview you can use the script and css modifiers.

Example use of JavaScript: charting

Here is an example interview that uses a javascript feature and a script modifier to draw a doughnut chart using chart.js.

features:
  javascript: https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.js
---
mandatory: True
question: Your stuff
subquestion: |
  <div class="chart-container" style="position: relative; height:450px; width:100%">
    <canvas id="myChart" width="600" height="400"></canvas>
  </div>
script: |
  <script>
    var ctx = $("#myChart");
    var myDoughnutChart = new Chart(ctx, {
     type: 'doughnut',
     data: ${ json.dumps(data) }
    });
  </script>
---
code: |
  data = {'datasets': [{'data': [how_many[y] for y in things],
                        'backgroundColor': [color[y] for y in range(len(things))]}],
          'labels': things}
---
variable name: color
data:
  - '#3366CC'
  - '#DC3912'
  - '#FF9900'
  - '#109618'
  - '#990099'
  - '#3B3EAC'
  - '#0099C6'
  - '#DD4477'
  - '#66AA00'
  - '#B82E2E'
  - '#316395'
  - '#994499'
  - '#22AA99'
  - '#AAAA11'
  - '#6633CC'
  - '#E67300'
  - '#8B0707'
  - '#329262'
  - '#5574A6'
  - '#3B3EAC'
chart

Here is an example interview that draws a pie chart using Google Charts.

features:
  javascript: https://www.gstatic.com/charts/loader.js
---
mandatory: True
question: Your stuff
subquestion: |
  <div id="piechart" style="width: 100%; min-height: 450px;"></div>
script: |
  <script type="text/javascript">
    google.charts.load('current', {'packages':['corechart']});
    google.charts.setOnLoadCallback(drawChart);

    function drawChart() {
      var chartwidth = $('#piechart').width();
      var data = google.visualization.arrayToDataTable(${ json.dumps(data) });
      var options = {
        title: ${ json.dumps(title) },
        width: chartwidth,
        chartArea: {width: chartwidth, left: 20, top: 20, height: chartwidth*0.75}
      };

      var chart = new google.visualization.PieChart(document.getElementById('piechart'));

      chart.draw(data, options);
    }
  </script>
---
code: |
  title = "Household stuff"
  data = [['Thing', 'How many']] + [[y, how_many[y]] for y in things]
googlechart

Bootstrap theme

Using the bootstrap theme feature, you can change the look and feel of your interview’s web interface by instructing your interview to use a non-standard CSS file in place of the standard CSS file used by Bootstrap.

features:
  bootstrap theme: lumen.min.css
bootstrap-theme

The file can be referenced in a number of ways:

  • lumen.min.css: the file lumen.min.css in the “static” folder of the current package.
  • docassemble.demo:lumen.min.css: the file lumen.min.css in the “static” folder (data/static/) of the docassemble.demo package.
  • docassemble.demo:data/static/lumen.min.css: the same.
  • https://bootswatch.com/lumen/bootstrap.min.css: a file on the internet.

For more information about using custom Bootstrap themes, and for information about applying themese on a global level, see the documentation for the bootstrap theme configuration directive.

Inverted Bootstrap navbar

By default, docassemble uses Bootstrap’s “dark” (formerly known as “inverted”) style of navigation bar so that the navigation bar stands out from the white background. If you do not want to use the inverted navbar, set the inverse navbar feature to False.

features:
  inverse navbar: False
inverse-navbar

To make this change at a global level, see the inverse navbar configuration directive.

Width of tables in attachments

As explained more fully in the tables section, if you include a table in an attachment and the table is too wide, or not wide enough, you can change the default character width of tables from 65 to some other value using the table width specifier within the features block.

features:
  table width: 75

Disabling document caching

By default, docassemble caches assembled documents for performance reasons. To disable the document caching feature for a given interview, set cache documents to False.

features:
  cache documents: False

Producing PDF/A files

If you want the PDF files produced by your interview to be in PDF/A format, you can set this as a default:

features:
  pdf/a: True

The default is determined by the pdf/a configuration directive. The setting can also be made on a per-attachment basis by setting the pdf/a attachment setting.

Limiting size of uploaded images

If your users upload digital photos into your interviews, the uploads may take a long time. Images can be reduced in size before they are uploaded. To require by default for all uploads in your interview, set maximum image size in the features block of your interview.

features:
  maximum image size: 100
upload-max-image-size-features

In this example, images will be reduced in size so that they are no taller than 100 pixels and no wider than 100 pixels.

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

This is just a default value; you can override it by setting the maximum image size in a field definition.

If you have an interview-wide default, but you want to override it for a particular field to allow full-resolution camera uploads, you can set the maximum image size field modifier to None.

If you want to use a site-side default value, set the maximum image size in the configuration.

Going full screen when interview is embedded

It is possible to embed a docassemble interview in a web page using an iframe. However, the user experience on mobile is degraded when an interview is embedded.

If you want the interview to switch to “full screen” after the user moves to the next screen in the embedded interview, you can do so. Within a features block, include go full screen: True.

features:
  go full screen: True
---
question: |
  Let's go on a quest!
subquestion: |
  How exciting would you
  like your quest to be?
field: excitement_level
choices:
  - Thrilling
  - Interesting
  - Soporific
---
question: |
  We are nearing the end of the
  quest.
field: quest_almost_over
---
question: |
  We have finished the quest.
buttons:
  - Return: exit
    url: |
      ${ referring_url() }
need:
  - excitement_level
  - quest_almost_over
mandatory: True
exit-url-referer-fullscreen

For more information about implementing an embedded interview like this, see the HTML source of the web page used in this example.

Note that in this example, the user is provided with an exit button at the end of the interview that directs the user back to the page that originally embedded the interview. This is accomplished by setting the url of the exit button to the result of the referring_url() function.

If you only want the interview to go full screen if the user is using a mobile device, use go full screen: mobile.

features:
  go full screen: mobile
---
code: |
  if device().is_mobile or device().is_tablet:
    on_mobile = True
  else:
    on_mobile = False
---
mandatory: True
code: |
  excitement_level
  quest_almost_over
  if on_mobile:
    final_screen_mobile
  else:
    final_screen_desktop
---
question: |
  Let's go on a quest!
subquestion: |
  % if on_mobile:
  I see you are using a mobile
  device.
  % else:
  I see that you are not using
  a mobile device.
  % endif
  
  How exciting would you
  like your quest to be?
field: excitement_level
choices:
  - Thrilling
  - Interesting
  - Soporific
---
question: |
  We are nearing the end of the
  quest.
field: quest_almost_over
---
event: final_screen_mobile
question: |
  We have finished the quest.
buttons:
  - Return: exit
    url: |
      ${ referring_url() }
---
event: final_screen_desktop
question: |
  We have finished the quest.
exit-url-referer-fullscreen-mobile

Note that this example provides a different ending screen depending on whether the user is on a desktop or a mobile device. If a desktop user is viewing the interview in an iframe on a web site, the interview should not provide an exit button that takes the user to a web site, because then the user will see a web site embedded in a web site. The interview in this example uses the device() function to detect whether the user is using a mobile device. Note that the interview logic looks both at device().is_mobile as well as device().is_tablet. This corresponds with the functionality of go full screen: mobile, which will make the interview go full screen if the user has either a mobile phone or a tablet.

Infinite loop protection

The infinite loop protection section of the configuration documentation explains how you can change the default limits on recursion and looping for all interviews on the server.

You can also set these limits on a per-interview basis using the loop limit and recursion limit features.

features:
  loop limit: 600
  recursion limit: 600