Dynamic navigation sections
By default, Assembly Line interviews have navigation turned on. In some cases, you may want navigation sections to show or hide depending on choices the user makes during the interview.
The Assembly Line framework adds an optional method to add dynamic navigation when the
enable_al_nav_sections
variable is set to True
. This feature is disabled by default.
Docassemble already has a way to define sections dynamically, with
nav.set_sections(). Use
the Assembly Line method when your navigation sections are relatively simple. If your needs
don't fit well with the Assembly Line's simple declarative approach, you may prefer to use
nav.set_sections()
directly.
The Assembly Line approach can be most helpful when:
- You know the full list of available sections at the beginning of the process. For example: one section for each optional document or dependent on the user's logged-in status.
- The total possible number of sections is also known in advance.
- You can evaluate a True/False expression to decide if a section should be visible.
Turning on the Assembly Line navigation systemβ
Add a code block to your interview that looks like this:
---
code: |
enable_al_nav_sections = True
Defining the list of available sectionsβ
When enable_al_nav_sections
is True
, the Assembly Line framework will look for the
definition of a list of dictionaries named al_nav_sections
on each screen load. The contents
of this list will be used to define the navigation sections.
The structure of each dictionary in the list should be the exact same format that is expected
by nav.set_sections(),
with the addition of one new key, hidden
. If hidden
is True
, the list item will
not be shown.
You can use docassemble's data from code
block as a convenient and easy
to read way to create this list:
---
reconsider: True
variable name: al_nav_sections
data from code:
- section_intro: |
"Introduction"
- section_about_you: |
"About you"
- section_about_spouse: |
"About your spouse"
hidden: not showifdef("is_married")
data from code
vs data
β
When you use data from code
, the labels need to be defined as literal Python strings (which is why they follow
the vertical pipe symbol, |
on their own lines).
In versions of Assembly Line > 2.28.0, you can use a data
block instead. If we chose to use data
instead, it would look like this:
reconsider: True
variable name: al_nav_sections
data:
- section_intro: Introduction
- section_about_you: About you
- section_about_spouse: About your spouse
hidden: ${ not showifdef("is_married") }
Avoiding triggering variables before they are definedβ
Navigation appears on every page of the interview, including the very first page. Therefore it's important that the conditional logic doesn't trigger any questions in itself.
Make sure to use some combination of:
hasattr
showifdef()
defined()
in your conditions, unless it's a variable that is guaranteed to be defined, such as whether the user is logged in, or the number of pages visited.
Example conditionsβ
Hide if the user is not logged inβ
...
data:
...
- section_how_to_return_later: How to return later
hidden: ${ not user_logged_in() }
Hide if you haven't gotten past the first page yetβ
- section_how_to_return_later: |
"How to return later"
hidden: ${ _internal.get('steps') < 2 }
Hide to anyone who is not an administrator or developerβ
- section_developer_only: |
"Developer and admin only"
hidden: ${ not (user_logged_in() and user_has_privilege(["developer", "admin"])) }
Using nested sectionsβ
You can define nested sections. If you would like to be able to hide the top level section, you'll need to use the special Docassemble "subsections" syntax (explained in the documentation).
Example:
reconsider: True
variable name: al_nav_sections
data:
- section_name: Section Name
hidden: True
- "top level item"
- about: About You
hidden: False
subsections:
- sub_item_1: Description 1
hidden: False
- sub_item_2: Description 2
hidden: True
Adding an unknown (dynamic) number of sectionsβ
If you want to add sections based on, for example, how many items are in a list,
you can't define those dynamically added sections in a data
or data from code
block.
But you may want to mix and match, with some dynamic sections and some pre-defined ones.
In that case, you could create a data
or data from code
block with a different
variable name
attached to it. And you would need to define al_nav_sections
itself
in a separate code block.
Here is an example:
question: |
How many rooms are in your house?
fields:
- Room count: room_count
- Do you want the optional section to show?: show_optional
datatype: yesno
---
variable name: my_always_defined_nav_sections
data:
- section_name: Section Name
hidden: False
- "top level item"
- section_about: About You
hidden: False
- section_opt: Optional section
hidden: ${ not showifdef("show_optional") }
---
reconsider: True
code: |
reconsider("my_always_defined_nav_sections")
if defined("room_count"):
tmp_sections = []
for n in range(room_count):
tmp_sections.append({f"room_{n+1}": f"Room {n+1}"})
al_nav_sections = my_always_defined_nav_sections + tmp_sections
del tmp_sections
else:
al_nav_sections = my_always_defined_nav_sections
Keeping the navigation menu fresh: warningsβ
A simple way to keep the list of sections fresh is to add the reconsider: True
modifier
to the block that defines the sections. We use this in the examples. It should be fine
for most real-world interviews. If your interview is already bit slow, note that using
reconsider: True
can add a small slowdown to every page load.
One alternative to avoid this slowdown is to list all of the variables that can
invalidate the current menu's accuracy with the depends on
modifier. This is a fine
tradeoff (adds a tiny bit of maintenance), and it works for most scenarios. As an example
of where it won't be sufficient: depends on
will not trigger an update when the items
in a list change.
If depends on
is not enough, you could still save a little page load time by explicitly
triggering the navigation to be rereshed at defined points in the interview flow. This
works well when you have a linear interview and don't allow editing the answer to questions
that could change the navigation. Even if you do have this issues, you could force reloading
the definition of al_navigation_sections
in the "edit" button.
To set up this more manual refreshing, you'll want to do this in a separate code block, not directly
in the interview order block. You need to reconsider the navigation before using nav.set_section()
.
For example:
---
code: |
reconsider("al_nav_sections")
reconsider_nav_once = True
---
code: |
reconsider("al_nav_sections")
reconsider_nav_twice = True
---
###################### Main order #######################
mandatory: True
code: |
nav.set_section("section_documents")
doc_list
reconsider_nav_once
...
reconsider_nav_twice
if some_condition:
nav.set_section("newly_visible_section")
A complete exampleβ
This example uses the data from code
method
---
include:
- docassemble.AssemblyLine:assembly_line.yml
---
code: |
enable_al_nav_sections = True
---
reconsider: True
variable name: al_nav_sections
data from code:
- section_intro: |
"Introduction"
- section_about_you: |
"About you"
- section_about_spouse: |
"About your spouse"
hidden: not showifdef("is_married")
---
mandatory: True
question: |
Are you married?
yesno: is_married
---
mandatory: True
question: |
Thanks for letting us know!