App Navigation Features
Navigation in CommCare is oriented around form entry. The goal of each CommCare session is to complete a form. Menus and case lists are tools for gathering the necessary data required to enter a particular form. App manager gives app builders direct control over many parts of their app’s UI, but the exact sequence of screens a user sees is indirectly configured.
Based on app configuration, HQ builds a
suite.xml file that acts as a blueprint for the app. It outlines what
forms are available to fill out, where they fit, and what data (like cases) they will need to function. CommCare
interprets this configuration to decide which screens to show to the user and in what order.
Much of the complexity of app manager code, and of building apps, comes from inferences HQ makes while building the suite file, especially around determining the set of data required for each form. These features that influence, but don’t directly control, the suite also influence each other, in ways that may not be obvious. The following features are particularly prone to interact unexpectedly and should be tested together when any significant change is made to any of them:
Display Only Forms
Select Parent First
End of Form Navigation and Form Linking
Several of these features are simple from an app builder’s perspective, but they require HQ to “translate” UI concepts into suite concepts. Other features force the app builder to understand suite concepts, so they may be challenging for app builders to learn but are less prone to interacting poorly with other features:
Case search, which maps fairly cleanly to the
<remote-request>element (except when using the
Display Only Forms
Display only forms is deceptively simple. This setting causes a module’s forms to be displayed directly in the parent menu (either the parent module’s menu or the root CommCare menu), instead of the user needing to explicitly select the menu’s name. This can be a UX efficiency gain.
However, quite a lot of suite generation is structured around modules, and using display only forms means that
modules no longer map cleanly to
<menu> elements. This means that modules using display only forms can’t be
“destinations” in their own right, so they don’t work with end of form navigation, form linking, or smart links.
It also complicates menu construction, raising issues like how to deal with module display conditions when the
module doesn’t have a dedicated
Select Parent First
When the “select parent first” setting is turned on for a module, the user is presented with a case list for the parent case type. The user selects a case from this list and is then given another case list limited to children of that parent. The user can select any other module in the app that uses the parent case type to use as the configuration for this parent case list.
This setting is controlled by
ModuleBase.parent_select and has a dedicated model,
The suite implementation is small: HQ adds a
parent_id datum to the module’s
<entry> blocks and a filter to the
case_id datum’s nodeset to filter it to children of the parent:
This is easy to confuse with parent/child modules (see below), which affect the suite’s
<menu> elements and can
affect datum generation.
The feature flag
NON_PARENT_MENU_SELECTION allows the user to use any module as the “parent” selection, and it
does not use the additional nodeset filter. This allows for more generic two-case-list workflows.
End of Form Navigation and Form Linking
These features allow the user to select a destination for the user to be automatically navigated to after filling out a particular form. To support this, HQ needs to figure out how to get to the requested destination, both the actions taken (user selecting a form or menu) and the data needed (which needs to be pulled from somewhere, typically the session, in order to automatically navigate the user instead of asking them to provide it).
End of form navigation (“EOF nav”) allows for a couple of specific locations, such as going back to the form’s module or its parent module. EOF nav also has a “previous screen” option this is particularly fragile, since it requires HQ to replicate CommCare’s UI logic.
Form linking, which is behind the
FORM_LINK_WORKFLOW flag, allows the user to select a form as the destination.
Form linking allows the user to link to multiple forms, depending on the value of an XPath expression.
Most forms can be linked “automatically”, meaning that it’s easy for HQ to determine what datums are needed. See the auto_link logic for implementation. For other forms, HQ pushes the burden of figuring out datums towards the user, requiring them to provide an XPath expression for each datum.
EOF nav and form linking config is stored in
FormBase.post_form_workflow. In the suite, it’s implemented as a
stack in the form’s
For details, see docs on
In principle child modules is very simple. Making one module a child of another
simply changes the
menu elements in the suite.xml file. For example in the
XML below module
m1 is a child of module
m0 and so it has its
attribute set to the ID of its parent.
<menu id="m0"> <text> <locale id="modules.m0"/> </text> <command id="m0-f0"/> </menu> <menu id="m1" root="m0"> <text> <locale id="modules.m1"/> </text> <command id="m1-f0"/> </menu>
HQ’s app manager only allows users to configure one level of nesting; that is, it does not allow for “grandchild” modules. Although CommCare mobile supports multiple levels of nesting, beyond two levels it quickly gets prohibitively complex for the user to understand the implications of their app design and for for HQ to determine a logical set of session variables for every case. The modules could have all different case types, all the same, or a mix, and for modules that use the same case type, that case type may have a different meanings (e.g., a “person” case type that is sometimes a mother and sometimes a child), which all makes it difficult for HQ to determine the user’s intended application design. See below for more on how session variables are generated with child modules.
As described above the basic menu structure is quite simple however there is one property in particular that affects the menu structure: module.put_in_root
This property determines whether the forms in a module should be shown under the module’s own menu item or under the parent menu item:
id=”<parent menu id>”
id=”<module menu id>” root=”<parent menu id>”
If the module has no parent then the parent is root.
root=”root” is equivalent to excluding the root attribute altogether.
This is all good and well until we take into account the way the Session works on the mobile which “prioritizes the most relevant piece of information to be determined by the user at any given time”.
This means that if all the forms in a module require the same case (actually just the same session IDs) then the user will be asked to select the case before selecting the form. This is why when you build a module where all forms require a case the case selection happens before the form selection.
From here on we will assume that all forms in a module have the same case management and hence require the same session variables.
When we add a child module into the mix we need to make sure that the session variables for the child module forms match
those of the parent in two ways, matching session variable names and adding in any missing variables.
HQ will also update the references in expressions to match the changes in variable names.
corehq.apps.app_manager.suite_xml.sections.entries.EntriesHelper.add_parent_datums for implementation.
Matching session variable names
For example, consider the session variables for these two modules:
case_id: load mother case
module B child of module A:
case_id_mother: load mother case case_id_child: load child case
You can see that they are both loading a mother case but are using different session variable names.
To fix this we need to adjust the variable name in the child module forms otherwise the user will be asked to select the mother case again:
case_id_mother -> case_id
module B final:
case_id: load mother case case_id_child: load child case
If you have a case_id in both module A and module B, and you wish to access the ID of the case selected in
parent module within an expression like the case list filter, then you should use
Inserting missing variables
In this case imagine our two modules look like this:
case_id: load patient case case_id_new_visit: id for new visit case ( uuid() )
module B child of module A:
case_id: load patient case case_id_child: load child case
Here we can see that both modules load the patient case and that the session IDs match so we don’t have to change anything there.
The problem here is that forms in the parent module also add a
case_id_new_visit variable to the session
which the child module forms do not. So we need to add it in:
module B final:
case_id: load patient case case_id_new_visit: id for new visit case ( uuid() ) case_id_child: load child case
Note that we can only do this for session variables that are automatically computed and hence does not require user input.
A shadow module is a module that piggybacks on another module’s commands (the “source” module). The shadow module has its own name, case list configuration, and case detail configuration, but it uses the same forms as its source module.
This is primarily for clinical workflows, where the case detail is a list of patients and the clinic wishes to be able to view differently-filtered queues of patients that ultimately use the same set of forms.
Shadow modules are behind the feature flag Shadow Modules.
The shadow module has its own independent:
Menu mode (display module & forms, or forms only)
Media (icon, audio)
Case list configuration (including sorting and filtering)
Case detail configuration
The shadow module inherits from its source:
commands (which forms the module leads to)
end of form behavior
A shadow module can neither be a parent module nor have a parent module
A shadow module’s source can be a parent module. The shadow will automatically create a shadow version of any child modules as required.
A shadow module’s source can have a parent module. The shadow will appear as a child of that same parent.
Shadow modules are designed to be used with case modules. They may behave unpredictably if given an advanced module or reporting module as a source.
Shadow modules do not necessarily behave well when the source module uses custom case tiles. If you experience problems, make the shadow module’s case tile configuration exactly matches the source module’s.
A shadow module duplicates all of its parent’s entries. In the example below, m1 is a shadow of m0, which has one form. This results in two unique entries, one for each module, which share several properties.
<entry> <form> http://openrosa.org/formdesigner/86A707AF-3A76-4B36-95AD-FF1EBFDD58D8 </form> <command id="m0-f0"> <text> <locale id="forms.m0f0"/> </text> </command> </entry> <entry> <form> http://openrosa.org/formdesigner/86A707AF-3A76-4B36-95AD-FF1EBFDD58D8 </form> <command id="m1-f0"> <text> <locale id="forms.m0f0"/> </text> </command> </entry>
In the simplest case, shadow module menus look exactly like other module menus. In the example below, m1 is a shadow of m0. The two modules have their own, unique menu elements.
<menu id="m0"> <text> <locale id="modules.m0"/> </text> <command id="m0-f0"/> </menu> <menu id="m1"> <text> <locale id="modules.m1"/> </text> <command id="m1-f0"/> </menu>
Menus get more complex when shadow modules are mixed with parent/child modules. In the following example, m0 is a basic module, m1 is a child of m0, and m2 is a shadow of m0. All three modules have put_in_root=false (see Child Modules > Menu structure above). The shadow module has its own menu and also a copy of the child module’s menu. This copy of the child module’s menu is given the id m1.m2 to distinguish it from m1, the original child module menu.
<menu id="m0"> <text> <locale id="modules.m0"/> </text> <command id="m0-f0"/> </menu> <menu root="m0" id="m1"> <text> <locale id="modules.m1"/> </text> <command id="m1-f0"/> </menu> <menu root="m2" id="m1.m2"> <text> <locale id="modules.m1"/> </text> <command id="m1-f0"/> </menu> <menu id="m2"> <text> <locale id="modules.m2"/> </text> <command id="m2-f0"/> </menu>
Legacy Child Shadow Behaviour
Prior to August 2020 shadow modules whose source was a parent had inconsistent behaviour.
The child-shadows were not treated in the same manner as other shadows - they inherited everything from their source, which meant they could never have their own case list filter, and were not shown in the UI. This was confusing. A side-effect of this was that display-only forms were not correctly interpreted by the phone. The ordering of child shadow modules also used to be somewhat arbitrary, and so some app builders had to find workarounds to get the ordering they wanted. Now in V2, what you see is what you get.
Legacy (V1) style shadow modules that have children can be updated to the new behaviour by clicking “Upgrade” on the settings page. This will create any real new shadow-children, as required. This will potentially rename the identifier for all subsequent modules (i.e. m3 might become m4 if a child module is added above it), which could lead to issues if you have very custom XML references to these modules anywhere. It might also change the ordering of your child shadow modules since prior to V2, ordering was inconsistent. All of these things should be easily testable once you upgrade. You can undo this action by reverting to a previous build.
If the old behaviour is desired for any reason, there is a feature flag “V1 Shadow Modules” that allows you to make old-style modules.