# Form Logic¶

ODK Collect supports a wide range of dynamic form behavior. This document covers how to specify this behavior in your XLSForm definition.

XLSForm

## Form logic building blocks¶

### Variables¶

Variables reference the value of previously answered questions. To use a variable, put the question's name in curly brackets preceded by a dollar sign:

${question-name} Variables can be used in label, hint, and repeat_count columns, as well as any column that accepts an expression. XLSForm survey type name label text your_name What is your name? note hello_name Hello,${your_name}.

### Expressions¶

An expression, sometimes called a formula, is evaluated dynamically as a form is filled out. It can include XPath functions, operators, values from previous responses, and (in some cases) the value of the current response.

Example expressions

${bill_amount} * 0.18 Multiplies the previous value bill_amount by 18%, to calculate a suitable tip. concat(${first_name}, ' ', ${last_name}) Concatenates two previous responses with a space between them into a single string.${age} >= 18
Evaluates to True or False, depending on the value of age.
round(${bill_amount} *${tip_percent} * 0.01, 2)
Calculates a tip amount based on two previously entered values, and then rounds the result to two decimal places.

Expressions are used in:

### Calculations¶

To evaluate complex expressions, use a calculate row. Put the expression to be evaluated in the calculation column. Then, you can refer to the calculated value using the calculate row's name.

Expressions cannot be used in label and hint columns, so if you want to display calculated values to the user, you must first use a calculate row and then a variable.

XLSForm

survey
type name label calculation
decimal bill_amount Bill amount:
calculate tip_18   round((${bill_amount} * 0.18),2) calculate tip_18_total${bill_amount} + ${tip_18} note tip_18_note Bill: $${bill_amount} Tip (18%):$${tip_18} Total:$${tip_18_total} ### Form logic gotchas¶ #### When expressions are evaluated¶ Every expression is constantly re-evaluated as an enumerator progresses through a form. This is an important mental model to have and can explain sometimes unexpected behavior. More specifically, expressions are re-evaluated when: • a form is opened • the value of any question in the form changes • a repeat group is added or deleted • a form is saved or finalized A common misconception is that expressions are only evaluated when a question that uses it is reached. This faulty mental model leads form designers to include functions such as random() or now() and to expect them to be evaluated exactly once. In fact, they will be re-evaluated over and over again until the form is finalized for the last time. For example, the following calculate will keep track of the last time the form was saved: survey type name label calculation calculate datetime_last_saved now() The once() function prevents multiple evaluation by only evaluating the expression passed into it if the node has no value. That means the expression will be evaluated once either on form open or when any values the expression depends on are set. Every call on now() in the form will have the same value unless the once() function is used. For example, the following calculate will keep track of the first time the form was opened: survey type name label calculation calculate datetime_first_opened once(now()) The following calculate will keep track of the first time the enumerator set a value for the age question: survey type name label calculation integer age What is your age? calculate age_timestamp if(age = '', '', once(now())) #### Empty values in math¶ Unanswered number questions are nil. That is, they have no value. When a variable referencing an empty value is used in a math operator or function, it is treated as Not a Number (NaN). The empty value will not be converted to zero. The result of a calculation including NaN will also be NaN, which may not be the behavior you want or expect. To convert empty values to zero, use either the coalesce() function or the if() function. coalesce(${potentially_empty_value}, 0)

if(${potentially_empty_value}="", 0,${potentially_empty_value})


## Requiring responses¶

By default, users are able to skip questions in a form. To make a question required, put yes in the required column.

Required questions are marked with a small asterisk to the left of the question label. You can optionally include a required_message which will be displayed to the user who tries to advance the form without answering the question.

XLSForm

survey
type name label required required_message

## Setting default responses¶

To provide a default response to a question, put the response value in the default column.

Default values must be static values, not expressions or variables.

Note

The content of the default row in a question is taken literally as the default value. Quotes should not be used to wrap string values, unless you actually want those quote marks to appear in the default response value.

XLSForm

survey
type name label default
select_one contacts contact_method How should we contact you? phone_call
choices
list_name name label
contacts phone_call Phone call
contacts text_message Text message
contacts email Email

Tip

You may want to use a previously entered value as a default, but the default column does not accept dynamic values.

To work around this, use the calculation column instead, and wrap your default value expression in a once() function.

XLSForm

survey
type name label calculation
text name Child's name
integer current_age Child's age
select_one gndr gender Gender
decimal total_income Total income yes ${income_sum} ## Conditionally showing questions¶ The relevant column can be used to show or hide questions and groups of questions based on previous responses. If the expression in the relevant column evaluates to True, the question or group is shown. If False, the question is skipped. Often, comparison operators are used in relevance expressions. For example:${age} <= 5
True if age is five or less.
${has_children} = 'yes' True if the answer to has_children was yes. Relevance expressions can also use functions. For example: selected(${allergies}, 'peanut')
True if peanut was selected in the Multi select widget named allergies.
contains(${haystack}, 'needle') True if the exact string needle is contained anywhere inside the response to haystack. count-selected(${toppings}) > 5
True if more than five options were selected in the Multi select widget named toppings.

### Simple example¶

XLSForm

survey
type name label relevant
select_one yes_no watch_sports Do you watch sports?
text favorite_team What is your favorite team? ${watch_sports} = 'yes' choices list_name name label yes_no yes Yes yes_no no No ### Complex example¶ XLSForm survey type name label hint relevant constraint select_multiple medical_issues what_issues Have you experienced any of the following? Select all that apply. select_multiple cancer_types what_cancer What type of cancer have you experienced? Select all that apply. selected(${what_issues}, 'cancer')
select_multiple diabetes_types what_diabetes What type of diabetes do you have? Select all that apply. selected(${what_issues}, 'diabetes') begin_group blood_pressure Blood pressure reading selected(${what_issues}, 'hypertension')
integer systolic_bp Systolic     . > 40 and . < 400
integer diastolic_bp Diastolic     . >= 20 and . <= 200
end_group
text other_health List other issues.   selected(${what_issues}, 'other') note after_health_note This note is after all health questions. choices list_name name label medical_issues cancer Cancer medical_issues diabetes Diabetes medical_issues hypertension Hypertension medical_issues other Other cancer_types lung Lung cancer cancer_types skin Skin cancer cancer_types prostate Prostate cancer cancer_types breast Breast cancer cancer_types other Other diabetes_types type_1 Type 1 (Insulin dependent) diabetes_types type_2 Type 2 (Insulin resistant) Warning Calculations are evaluated regardless of their relevance. For example, if you have a calculate widget that adds together two previous responses, you cannot use relevant to skip in the case of missing values. (Missing values will cause an error.) Instead, use the if() function to check for the existence of a value, and put your calculation inside the then argument. For example, when adding together fields a and b: if(${a} != '' and ${b} != '',${a} + ${b}, '')  In context: type name label calculation integer a a = integer b b = calculate a_plus_b if(${a} != '' and ${b} != '',${a} + ${b}, '') note display_sum a + b =${a_plus_b}

## Repeating questions and groups of questions¶

Note

Using repetition in a form is very powerful but can also make training and data analysis more time-consuming. Aggregate does not export repeats so Briefcase or one of the data publishers will be needed to transfer data from Aggregate. Repeats will be in their own documents and will need to be joined with their parent records for analysis.

• if the number of repetitions is small and known ahead of time, consider "unrolling" the repeat by copying the same questions several times.
• if the number of repetitions is large and includes many questions, consider building a separate form that enumerators fill out multiple times and link the forms with some parent key (e.g., a household ID).

If repeats are needed, consider adding some summary calculations at the end so that analysis will not require joining the repeats with their parent records. For example, if you are gathering household information and would like to compute the total number of households visited across all enumerators, add a calculation after the repeats that counts the repetitions in each submission.

To repeat questions or groups of questions use the begin_repeat…end_repeat syntax.

XLSForm (Single question repeat)

survey
type name label
begin_repeat my_repeat_group Repeat group label
text repeated_question This question will be repeated.
end_repeat

XLSForm (Multi-question repeat)

survey
type name label
begin_repeat my_repeat Repeat group label
note repeated_note These questions will be repeated as an entire group.
text name What is your name?
text quest What is your quest?
text fave_color What is your favorite color?
end_repeat

### Controlling the number of repetitions¶

#### User-controlled repeats¶

By default, the user controls how many times the questions are repeated.

Before each repetition, the user is asked if they want to add another repeat group.

Note

The label in the begin_repeat row is shown in the Add New Group? message.

A meaningful label will help enumerators and participants navigate the form as intended.

XLSForm

survey
type name label
begin_repeat repeat_example repeat group label
text repeat_test Question label
end_repeat

Note

This interaction may be confusing to users the first time they see it. If enumerators know the number of repetitions ahead of time, consider using dynamically defined repeats.

#### Statically defined repeats¶

Use the repeat_count column to define the number of times a group will repeat.

XLSForm

survey
type name label repeat_count
begin_repeat my_repeat Repeat group label 3
note repeated_note These questions will be repeated as an entire group.
text name What is your name?
text quest What is your quest?
text fave_color What is your favorite color?
end_repeat

#### Dynamically defined repeats¶

The repeat_count column can reference previous responses and calculations.

XLSForm

survey
type name label repeat_count
integer number_of_children How many children do you have?
begin_repeat child_questions Questions about child ${number_of_children} text child_name Child's name integer child_age Child's age end_repeat ## Filtering options in select questions¶ To limit the options in a select question based on the answer to a previous question, use a choice_filter row in the survey sheet, and filter key columns in the choices sheet. For example, you might ask the user to select a state first, and then only display cities within that state. This is called a cascading select, and can be extended to any depth. This example form shows a three-tiered cascade: state, county, city. XLSForm survey type name label choice_filter select_one job_categories job_category Job category select_one job_titles job_title Job title job_category=${job_category}
choices
list_name name label job_category
job_categories finance Finance
job_categories hr Human Resources
job_categories marketing Marketing
job_titles ar Accounts Receivable finance
job_titles ap Account Payable finance
job_titles bk Bookkeeping finance
job_titles pay Payroll finance
job_titles recruiting Recruiting hr
job_titles training Training hr
job_titles retention Retention hr