Skip to content

Proposed Language Extensions

Phillip Mates edited this page Aug 2, 2016 · 10 revisions

We are looking to enable CommCare's JavaRosa XForm engine, as well as the CommCareHQ Form Builder that generates XForms, to more easily handle advanced workflows. Such workflows include extending the 'easy references' Form Builder feature to usercases, and additional cases loaded via advanced modules.

One way forward might be to introduce new binding structures to the JavaRosa engine. This document explores that idea. The nice side-effect is that XForms written using this language extension should have less duplicate code and hence be much more readable to developers and advanced users.

In order to back-port these extensions to existing CommCare versions, all extensions must be expandable to old syntax. Of course, newer CommCare engine versions can be adapted to handle the new syntax, potentially offering performance gains.

EVENT ::= "xforms-ready" | "xforms-revalidate" | "xforms-insert"
XPATH_REF ::= INSTANCE | XPATH_REF/STEP
INSTANCE ::= instance("STRING") | /data
FILTERED_XPATH_REF ::= INSTANCE | FILTERED_XPATH_REF/STEP 
                     | FILTERED_XPATH_REF[PRED]
STEP ::= String | @String
PRED ::= XPATH_EXPR :: Boolean | XPATH_EXPR :: Integer
XPATH_EXPR ::= FILTERED_XPATH_REF | FUNC_EXPR(XPATH_EXPR...)
             | NUMERIC | STRING | VARIABLE
             | UNARY_OP XPATH_EXPR | XPATH_EXPR BINARY_OP XPATH_EXPR
             | let VARIABLE in XPATH_EXPR
UNARY_OP ::= -
BINARY_OP ::= < | <= | > | >= | = | != 
            | and | or | mod | div | + | - | * 
VARIABLE ::= $STRING

VIRTUAL_INSTANCE ::= jr://instance/casedb
                   | jr://instance/session
                   | jr://instance/fixture/STRING
                   | jr://instance/fixture/ledgerdb
REFVAR ::= #STRING
VIRTUAL_DEF ::= jr://def/STRING
(resolves to a DEF_FILE)

INSTANCE_DEF ::= <instance id="STRING" src="VIRTUAL_INSTANCE"/>
IMPORT_DEF ::= <import VIRTUAL_DEF/>

Current

<bind
  nodeset="XPATH_REF"
  relevant="XPATH_EXPR"
  calculate="XPATH_EXPR"
  required="XPATH_EXPR :: Boolean"
  type=""
  constraint="XPATH_EXPR :: Boolean"
  jr:constraintMsg="" />

<setvalue
  event=EVENT
  ref=""
  value="" />

<instance
  id="STRING"
  src="VIRTUAL_INSTANCE" />

Extensions to existing constructs

<instance
  id="STRING"
  src="VIRTUAL_INSTANCE" />

====>

#STRING instead of instance("STRING")

New constructs

<import VIRTUAL_DEF/>

====>

inline contents of VIRTUAL_DEF into form


<letref
  var="REFVAR"
  ref="FILTERED_XPATH_REF"/>

====>

search & replace all instances of REFVAR in form with FILTERED_XPATH_REF
NOTE: What about references that change depending on the evaluation context
they are in? What is the formal definition for a reference that is 'closed' 
in respect to its context?


<letref
  var="REFVAR"
  ref="VIRTUAL_INSTANCE"/>

====>

<instance
  id="STRING"
  src="VIRTUAL_INSTANCE" />
and search & replace all instances of REFVAR in form with instance("STRING")

Importing definitions

DEF_FILE ::= - | DEF_FILE LETREF.
           | DEF_FILE MACRO | DEF_FILE INSTANCE_DEF
           | DEF_FILE IMPORT_DEF

constraints:
  - any letref 'ref' must be imported by an instance definition, these instance
    definitions will not be available in forms that import the enclosing
    definitions file.

Macros

MACRO ::= STRING REFVAR... XPATH_EXPR

(macro myMacro (REFVAR...) XPATH_EXPR)

===>.

inline myMacro where found in forms

constraints:
  - macro body can only reference reference variables passed in
considerations:
  - what if reference variables defined in same definitions file were allowed?

Model Iteration

Currently, to iterate over a model, one needs a handful of bind and setvalue blocks. This can be replaced with an iterate control structure:

<iterate
  model="FILTERED_XPATH_REF"
  template="XPATH_REF"
  relevant="XPATH_EXPR"
  refvar="REFVAR">

  <bind that can reference the refvar />
  ...
  <setvalue event="jr-insert" that can reference the refvar />
  ...

</iterate>

For example, given this data model

<child_questions ids="" count="" current_index="">
  <item id="" index="" jr:template="">
    <question4>
      <display_label/>
      <name/>
      <question7/>
    </question4>
  </item>
</child_questions>

the iterate extension:

<iterate
  model="instance('casedb')/casedb/case[filter = 'b']"
  template="/data/child_questions/item"
  refvar="#item">

  <bind nodeset="/data/child_questions/item/name" calculate="#item/case_name" />
</iterate>

can be expanded to

<setvalue event="xforms-ready" 
  ref="/data/child_questions/@ids"
  value="join(' ', instance('casedb')/casedb/case[filter = 'b']/@case_id)"/>
<setvalue event="xforms-ready"
  ref="/data/child_questions/@count"
  value="count-selected(/data/child_questions/@ids)"/>
<setvalue event="jr-insert"
  ref="/data/child_questions/item/@index"
  value="int(/data/child_questions/@current_index)"/>
<setvalue event="jr-insert"
  ref="/data/child_questions/item/@id"
  value="selected-at(/data/child_questions/@ids,../@index)"/>
<bind nodeset="/data/child_questions/item/name"
  calculate="instance('casedb')/casedb/case[@case_id=current()/../@id]/case_name" />

I'm unsure how the 'view' aspect of xforms should be extended. Currently there is:

<repeat 
  jr:count="/data/child_questions/@count"
  jr:noAddRemove="true()"
  nodeset="/data/child_questions/item">
  <group ref="/data/child_questions/item/question4" appearance="field-list">
    <trigger ref="/data/child_questions/item/question4/display_label" appearance="minimal">
      <label ref="jr:itext('child_questions/item/question4/display_label-label')"/>
    </trigger>
  </group>
</repeat>

This could be adapted or we could introduce a iterate-repeat block that binds refvars:

<iterate-repeat
  model="FILTERED_XPATH_REF"
  template="XPATH_REF"
  refvar="REFVAR"/>
  
  <question blocks/>
  ...
</iterate-repeat>

to be

<iterate-repeat
  model="instance('casedb')/casedb/case[filter = 'b']"
  template="/data/child_questions/item"
  refvar="#item"/>
  
  <question blocks/>
  ...
</iterate-repeat>