ODK XForms spec proposal: create and update Entities from repeats or groups

See Create and update Entities from repeats for the user-facing functionality and proposed XLSForm spec.

:warning: For an expanded version of this spec, see below.

The concept

We propose adding a well-known meta block to repeats that can include an inner entity block. This would match the top-level meta/entity block described in the Entities spec.

With this addition, saveto attributes could be declared within a repeat if that repeat has a corresponding meta/entity block.

See below for an example of a form that creates one Entity for each instance of the tree repeat that matches the create condition. Entity updates could also be specified in the meta/entity block for a repeat using the update attribute and other attributes specified by the spec.

<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:entities="http://www.opendatakit.org/xforms/entities" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:jr="http://openrosa.org/javarosa" xmlns:odk="http://www.opendatakit.org/xforms" xmlns:orx="http://openrosa.org/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <h:head>
   <h:title>Register many trees</h:title>
   <model odk:xforms-version="1.0.0" entities:entities-version="2025.1.0">
     <instance>
       <data id="trees_registration" version="2025040701">
         <tree>
           <location />
           <species />
           <meta>
             <entity dataset="trees" create_if="" id="">
               <label />
             </entity>
           </meta>
         </tree>
        
         <meta>
           <instanceID/>
         </meta>
       </data>
     </instance>
     <bind nodeset="/data/tree/location" type="geopoint" entities:saveto="geometry" />
     <bind nodeset="/data/tree/species" type="string" entities:saveto="species" />


     <bind nodeset="/data/tree/meta/entity/@id" type="string"/>
     <setvalue event="odk-instance-first-load odk-new-repeat" ref="/data/tree/meta/entity/@id" value="uuid()"/>


     <bind nodeset="/data/tree/meta/entity/@create_if" type="string" calculate="starts-with(../../../species, 'B')" />
    
     <bind nodeset="/data/tree/meta/entity/label" calculate="../../../species"  type="string"/>


     <bind jr:preload="uid" nodeset="/data/meta/instanceID" readonly="true()" type="string"/>
   </model>
 </h:head>
 <body>
   <group ref="/data/tree">
     <label>Tree</label>
     <repeat nodeset="/data/tree">
       <input ref="/data/tree/location">
           <label>Tree location</label>
       </input>
       <input ref="/data/tree/species">
           <label>Tree species</label>
       </input>
     </repeat>
   </group>
 </body>
</h:html>
1 Like

We are planning to expand this model to include groups. This will allow creating or updating multiple Entities with a single submission without necessarily using repeats. See also the updated XLSForm proposal.

Glossary

Container: the form definition root, a group, or a repeat.
Entity declaration: a meta/entity block in a container that declares how Entities are affected when the form is finalized.

XForms spec

Any container in a form definition MAY directly include at most one Entity declaration. Nested containers MAY also each directly include at most one Entity declaration.

An entities:saveto binding MUST be associated with the nearest ancestor container that has an Entity declaration. If no ancestor container has an Entity declaration, the form is invalid.

If an entities:saveto binding is declared within a repeat, the nearest ancestor container that has an Entity declaration MUST be that repeat or a descendant group of that repeat. If the nearest ancestor container with an Entity declaration is outside the repeat, the form is invalid.

Examples

  • If a saveto is declared within the group /data/tree and /data/tree has an Entity declaration, the saveto is associated with the Entity declared in /data/tree.
  • If a saveto is declared within the group /data/tree, /data/tree has no Entity declaration, and the root container has an Entity declaration, the saveto is associated with the root Entity declaration.
  • If a saveto is declared within the repeat /data/trees, /data/trees has no Entity declaration, and the root container has an Entity declaration, the form is invalid.
  • If a saveto is declared within the repeat /data/trees/seedlings, /data/trees/seedlings has an Entity declaration, then the saveto is associated with the Entity declared in /data/trees/seedlings, regardless of whether trees is a repeat or group and whether or not it has an Entity declaration.
  • If a saveto is declared within the repeat /data/trees/seedlings, /data/trees/seedlings does not have an Entity declaration, and /data/trees is a group or repeat with an Entity declaration, the form is invalid.
  • If a saveto is declared within the repeat /data/trees/seedlings, /data/trees/seedlings has an Entity declaration, then the saveto is associated with the Entity declaration in /data/trees/seedlings. If another saveto is declared within /data/trees and outside of /data/trees/seedlings and /data/trees has an Entity declaration, then that saveto is associated with the Entity declaration in /data/trees, regardless of whether trees is a repeat or group.
1 Like

I've read through this a few times, and I think I get it. It sounds very powerful and unlocks all the kinds of entity creation/update that I have been wishing to do.

Are these correct:

  • A repeat (data/r_household) that has a nested repeat (data/r_household/r_occupants) could each save multiple times to two entity lists in a single submission
    • r_household --> e_household
    • r_occupants --> e_occupants (and calc in householdID as link)
  • A form that updates a task as being in progress at the root level, with a repeat (/data/r_household) containing a series of groups (/data/r_household/g_humans | /data/r_household/g_pets | /data/r_household/g_bicycles) could, in a single submission, save once to one list and multiple times to 4x entity lists
    • root --> e_tasklist (once)
    • r_household --> e_household (and calc in tasklist ID as link)
    • g_humans --> e_human-occ (and calc in householdID as link)
    • g_pets --> e_animal-occ (and calc in householdID as link)
    • g_bicycles --> e_vehicles (and calc in householdID as link)

You got it!

g_humans, g_pets, g_bicycles

These could be repeats or groups depending on exactly what you want to represent.

I want to emphasize that some of this will have performance implications at scale but we'll be taking that on from a few different angles over the next months.

great!

re:

I suppose that if you wanted to divvy up the values across entity lists, you cluster the fields into groups per list, but if you rely on a certain question order that precludes this, instead include groups at the end of the form/repeat and calc in the values needed.

I think I'm out of questions until I start to try it in anger. Mostly full of ideas now.

1 Like