Conditional Logic and Dynamic Texts

Revision as of 22:42, 16 December 2024 by Admin (talk | contribs) (Created page with "Conditional Logic and Dynamic Texts This help topic describes how to implement custom conditional logic and add dynamic texts to your  form. == Dynamic Texts == Survey UI texts support placeholders whose values are computed at runtime to make the texts dynamic. Placeholders can be used in the following places: * Titles and descriptions of  forms, pages, panels, and questions * Properties that accept HTML markup (completedHtml, loadingHtml, etc.) * Expressions You c...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Conditional Logic and Dynamic Texts

This help topic describes how to implement custom conditional logic and add dynamic texts to your  form.

Dynamic Texts

Survey UI texts support placeholders whose values are computed at runtime to make the texts dynamic. Placeholders can be used in the following places:

  • Titles and descriptions of  forms, pages, panels, and questions
  • Properties that accept HTML markup (completedHtml, loadingHtml, etc.)
  • Expressions

You can use the following values as placeholders:

  • Question Values
  • Variables
  • Calculated Values

Question Values

To use a question value as a placeholder, specify the question's name in curly brackets. The name will be replaced with the question value. For instance, the following example defines two Single-Line Input questions: First Name and Last Name. An HTML question uses their name property to reference them and display their values:

const  formJson = {

 "elements": [

   { "name": "firstName", "type": "text", "title": "First Name", "defaultValue": "John" },

   { "name": "lastName", "type": "text", "title": "Last Name", "defaultValue": "Smith" },

   {

     "name": "greetings",

     "type": "html",

     "html": "<p>Hello, {firstName} {lastName}!</p>"

   }

 ]

};

For questions with a specified valueName property, use its value instead of the name value.

In question types whose value is an array, you can use zero-based indexes to access a specific item, question, or cell:

Question Type Syntax
Checkboxes,

Image Picker

{questionname[index]}
Child Object Panel {dynamicpanelname[index].questionname}

You can also use prefixes, such as row, panel, and parentPanel, to access a specific question or cell relative to the question you configure:

Question Type Syntax Action
Child Object Panel {panel.questionname} Accesses a question within the same panel.
{parentPanel.questionname} Accesses a question within a parent Child Object Panel.

Applies when one Child Object Panel question is nested in another.

Calculated Values

Calculated values allow you to register an expression under a required name. If the expression includes questions, variables, or functions, it is recalculated each time their values are changed.

To configure a calculated value, define the calculatedValues array in the  form JSON schema. Each object in this array should contain the following fields:

  • name - A name that identifies the calculated value.
  • expression - An expression that returns the calculated value.
  • includeIntoResult - A Boolean property that specifies whether to include the calculated value in  form results.

The following code shows how to calculate a full name value based on the first and last names:

const  formJson = {

 "elements": [

   { "name": "firstName", "type": "text", "title": "First Name", "defaultValue": "John" },

   { "name": "lastName", "type": "text", "title": "Last Name", "defaultValue": "Smith" },

   {

     "name": "greetings",

     "type": "html",

     "html": "<p>Hello, {fullname}!</p>"

   }

 ],

 "calculatedValues": [{

   "name": "fullname",

   "expression": "{firstName} +  ' ' + {lastName}"

 }]

};

Variables vs Calculated Values

Variables and calculated values are both used to perform custom calculations within a  form. However, they also have a number of important differences. The following table compares variables with calculated values across multiple criteria:

Criteria Variables Calculated values
Configuration Configured using JavaScript code Configured using an expression in the  form JSON schema
Evaluation / Re-evaluation Evaluated only once—when set Evaluated when the  form model is instantiated and re-evaluated each time dynamic values within the expression are changed
Inclusion in  form results Aren't saved in  form results but can be (see below) Saved in  form results if the includeIntoResult property is enabled

If you need to save a variable in  form results, create an intermediary calculated value that references the variable.

Expressions

Expressions allow you to add logic to your  form and perform calculations right in the  form JSON schema. Expressions are evaluated and re-evaluated at runtime.

Forms supports the following expression types:

String expression

An expression that evaluates to a string value. The following string expression evaluates to "Adult" if the age function returns a value of 21 or higher; otherwise, the expression evaluates to "Minor":

"expression": "iif(age({birthdate}) >= 21, 'Adult', 'Minor')"

Numeric expression

An expression that evaluates to a number. The following numeric expression evaluates to the sum of the total1 and total2 question values:

"expression": "sum({total1}, {total2})"

Boolean expression

An expression that evaluates to true or false. Boolean expressions are widely used to implement conditional logic. Refer to the following help topic for more information: Conditional Visibility.

Expressions can include question names, variables, and calculated values (described in the Dynamic Texts section).

Supported Operators

The Forms expression engine is built upon the PEG.js parser generator. The following table gives a brief overview of operators that you can use within expressions. For a detailed look at the grammar rules used by the expression parser, refer to the  form-library GitHub repository.

Operator Description Expression example
empty Returns true if the value is undefined or null. "{q1} empty"
notempty Returns true if the value is different from undefined and null. "{q1} notempty"
|| / or Combines two or more conditions and returns true if any of them is true. "{q1} empty or {q2} empty"
&&" / and Combines two or more conditions and returns true if all of them are true. "{q1} empty and {q2} empty"
! / negate Returns true if the condition returns false, and vice versa. !{q1}
<= / lessorequal Compares two values and returns true if the first is less or equal to the second. "{q1} <= 10"
>= / greaterorequal Compares two values and returns true if the first is greater or equal to the second. "{q1} >= 10"
= / == / equal Compares two values and returns true if they are loosely equal (that is, their type is disregarded). "{q1} = 10"
!= / <> / notequal Compares two values and returns true if they are not loosely equal. "{q1} != 10"
< / less Compares two values and returns true if the first is less than the second. "{q1} < 10"
> / greater Compares two values and returns true if the first is greater than the second. "{q1} > 10"
+ Adds up two values. "{q1} + {q2}"
- Subtracts the second value from the first. "{q1} - {q2}"
* Multiplies two values. "{q1} * {q2}"
/ Divides the first value by the second. "{q1} / {q2}"
% Returns the remainder of the division of the first value by the second. "{q1} % {q2}"
^ / power Raises the first value to the power of the second. "{q1} ^ {q2}"
*= / contains / contain Compares two values and returns true if the first value contains the second value within it. "{q1} contains 'abc'"
notcontains / notcontain Compares two values and returns true if the first value doesn't contain the second value within it. "{q1} notcontains 'abc'"
anyof Compares a value with an array of values and returns true if the value is present in the array. "{q1} anyof [ 'value1', 'value2', 'value3' ]"
allof Compares two arrays and returns true if the first array includes all values from the second. "{q1} allof [ 'value1', 'value2', 'value3' ]"
# Disables type conversion for a referenced value (e.g., string values "true", "false", "123" won't be converted to corresponding Boolean and numeric values). "{#q1}"

Built-In Functions

Functions allow you to perform additional calculations within an expression. One expression can contain multiple function calls.

Functions can accept arguments. For example, the following expression shows the built-in age and iif functions. age accepts the value of a birthdate question. iif accepts three arguments: a condition, a value to return when the condition is truthy, and a value to return when the condition is falsy.

"expression": "iif(age({birthdate}) >= 21, 'Adult', 'Minor')"

The following built-in functions are available:

  • iif
  • isContainerReady
  • isDisplayMode
  • age
  • currentDate
  • today
  • year
  • month
  • day
  • weekday
  • getDate
  • diffDays
  • sum
  • max
  • min
  • avg
  • sumInArray
  • maxInArray
  • minInArray
  • avgInArray
  • countInArray
  • displayValue
  • propertyValue

iif

Definition: iif(condition: expression, valueIfTrue: any, valueIfFalse: any): any

Returns the valueIfTrue value if the condition is truthy or the valueIfFalse value if the condition is falsy.

Example: "expression": "iif({question1} + {question2} > 20, 'High', 'Low')"


isContainerReady

Definition: isContainerReady(nameOfPanelOrPage: string): boolean

Returns true if all questions in a given panel or page have valid input; otherwise, returns false. An empty question value is considered valid if neither validators nor required status is defined for it.

Example: "expression": "isContainerReady('page1')"


isDisplayMode

Definition: isDisplayMode(): boolean

Returns true if the  form is in display or preview mode.

Example: "expression": "isDisplayMode()"


age

Definition: age(birthdate: any): number

Returns age according to a given birthdate. The date argument (which is typically taken from a question) should be defined as a valid JavaScript Date.

Example: "expression": "age({birthdate})"


currentDate

Definition: currentDate(): Date

Returns the current date and time.

Example: "expression": "currentDate()"


today

Definition: today(daysToAdd?: number): Date

Returns the current date or a date shifted from the current by a given number of days. For example, today() returns the current date, 0 hours, 0 minutes, 0 seconds; today(-1) returns yesterday's date, same time; today(1) returns tomorrow's date, same time.

Examples:

  • "expression": "today()"
  • "expression": "today(2)"

year

Definition: year(date?: Date): number

Returns the year of a given date.

Example: "expression": "year({birthdate})"


month

Definition: month(date?: Date): number

Returns the month of a given date as a value from 1 (January) to 12 (December).

Example: "expression": "month({birthdate})"


day

Definition: day(date?: Date): number

Returns the day of the month for a given date as a value from 1 to 31.

Example: "expression": "day({birthdate})"


weekday

Definition: weekday(date?: Date): number

Returns the day of the week for a given date as a value from 0 (Sunday) to 6 (Saturday).

Example: "expression": "weekday({birthdate})"


getDate

Definition: getDate(questionName: expression): Date

Returns a Date value converted from a given question's value.

Example: "expression": "getDate({birthdate})"


dateDiff

Definition: dateDiff(fromDate: any, toDate: any, "days" | "months" | "years"): number

Returns a difference between two given dates in full days (default), months, or years.

Example: "expression": "dateDiff({birthdate}, today(), "months")"


sum

Definition: sum(param1: number, param2: number, ...): number

Returns the sum of passed numbers.

Example: "expression": "sum({total1}, {total2})"


max

Definition: max(param1: number, param2: number, ...): number

Returns the maximum of passed numbers.

Example: "expression": "max({total1}, {total2})"


min

Definition: min(par1: number, par2: number, ...): number

Returns the minimum of passed numbers.

Example: "expression": "min({total1}, {total2})"


avg

Definition: avg(par1: number, par2: number, ...): number

Returns the average of passed numbers.

Example: "expression": "avg({total1}, {total2}, {total3})"


sumInArray

Definition: sumInArray(questionName: expression, dataFieldName: string, filter?: expression): number

Returns the sum of numbers taken from a specified data field. This data field is searched in an array that contains a user response to a Child Object Panel question type. The optional filter parameter defines a rule according to which values are included in the calculation.

The following code sums up values from a "total" matrix column but includes only the rows where a "categoryId" column equals 1:

Example: "expression": "sumInArray({matrixdynamic}, 'total', {categoryId} = 1)"


maxInArray

Definition: maxInArray(questionName: expression, dataFieldName: string, filter?: expression): number

Returns the maximum of numbers taken from a specified data field. This data field is searched in an array that contains a user response to a Child Object Panel question. The optional filter parameter defines a rule according to which values are included in the calculation.

The following code finds a maximum value within a "quantity" matrix column, but the value should be under 100:

Example: "expression": "maxInArray({matrixdynamic}, 'quantity', {quantity} < 100)"


minInArray

Definition: minInArray(questionName: expression, dataFieldName: string, filter?: expression): number

Returns the minimum of numbers taken from a specified data field. This data field is searched in an array that contains a user response to a  Child Object Panel question. The optional filter parameter defines a rule according to which values are included in the calculation.

The following code finds a minimum value within a "quantity" matrix column but searches for it only in the rows where a "categoryId" column equals 1 and the values are positive:

Example: "expression": "minInArray({matrixdynamic}, 'quantity', {quantity} > 0 and {categoryId} = 1)"


avgInArray

Definition: avgInArray(questionName: expression, dataFieldName: string, filter?: expression): number

Returns the average of numbers taken from a specified data field. This data field is searched in an array that contains a user response to a  Child Object Panel question. The optional filter parameter defines a rule according to which values are included in the calculation.

The following code finds an average of values within a "quantity" matrix column, excluding zeroes:

Example: "expression": "avgInArray({matrixdynamic}, 'quantity', {quantity} > 0)"


countInArray

Definition: countInArray(questionName: expression, dataFieldName: string, filter?: expression): number

Returns the total number of array items in which a specified data field has a value other than null or undefined. This data field is searched in an array that contains a user response to a  Child Object Panel question.

The following code finds the total number of matrix rows with a "quantity" column value greater than zero but includes only the rows where a "categoryId" column equals 1.:

Example: "expression": "countInArray({matrixdynamic}, 'quantity', {quantity} > 0 and {categoryId} = 1)"


displayValue

Definition: displayValue(questionName: string, value?: any): any

Returns a question's display text. Supports questions nested within panels or matrices.

The second parameter allows you to get a display text associated with a specific value rather than with the current question value. For instance, the following expression references a display text that corresponds to value 5 in a Dropdown question. If you don't pass the second parameter, the displayValue function returns a display text for the currently selected question value.

Example: "expression": "displayValue('my-dropdown-question', 5)"


propertyValue

Definition: propertyValue(questionName: string, propertyName: string): any

Returns the value of a property specified for a given question. Supports questions nested within panels or matrices.

Example: "expression": "propertyValue('question1', 'visible')"

Custom Functions

In addition to built-in functions, expressions can use custom functions. They allow you to extend the functionality of your  form.

Conditional Visibility

You can specify whether an individual  form element is visible, read-only, or required based on a condition. This functionality is built upon Boolean expressions. Such expressions evaluate to true or false.

A  form parses and runs all expressions on startup. If a Boolean expression evaluates to false, the corresponding element becomes invisible (or read-only, or optional); if it evaluates to true, the element becomes visible (or enabled, or required). After any value used in an expression changes, the  form re-evaluates this expression.

The table below shows examples of Boolean expressions.

Expression Description
"{age} >= 21" Evaluates to true if the "age" question has a value of 21 or higher.
"({rank1} + {rank2} + {rank3}) > 21 and {isLoyal} = 'yes'" The or and and operators combine two or more conditions.
"{name} notempty" Evaluates to true if the "name" question has any value.
"{name} empty" Evaluates to true if the "name" question has no value.
"{speakinglanguages} = ['English', 'Spanish']" Evaluates to true if strictly English and Spanish are selected in the "speakinglanguages" question. If one of the languages is not selected or other languages are selected too, the expression evaluates to false.
"{speakinglanguages} contains 'Spanish'" Evaluates to true if Spanish is selected. Other languages may or may not be selected.
"age({birthdate}) >= 21" Evaluates to true if the age function returns 21 or higher.

You should use different properties to specify the visibility of questions and items (choices, rows, columns).

Question Visibility

Assign Boolean expressions to the visibleIf, enableIf, and requiredIf properties of questions, panels, and pages. In the following example, the visibleIf property is used to hide the drivers-license question for respondents under 16 years old:

const  formJson = {

 "elements": [{

   "name": "birthdate"

 }, {

   "name": "drivers-license",

   "title": "Have you got a driver's license?",

   "visibleIf": "age({birthdate}) >= 16"

 }]

}

If you do not specify the visibleIf, enableIf, and requiredIf properties, an element's state depends on the isVisible, isReadOnly, and isRequired properties. You can specify them at design time or use them to get or set the current state at runtime. If you set one of these properties for a panel or page, all nested questions inherit the setting.

Item Visibility (Choices, Columns, Rows)

Forms allows you to control available choices, columns, and rows based on previous answers.

Specify Visibility Conditions for Individual Items

Individual items (choices, columns, rows) can be configured with objects. Each object can have a visibleIf property that accepts an expression. When the expression evaluates to true, the associated item becomes visible.

In the following code, the SMS and WhatsApp choices are visible only if a user has entered their phone number:

const  formJson = {

 "elements": [{

   "name": "Contacts"

   "choices": [

     "Email",

     { "value": "SMS", "visibleIf": "{phone} notempty" },

     { "value": "WhatsApp", "visibleIf": "{phone} notempty" }

   ]

 },

 // ...

 ]

}


This technique has one drawback: if a question contains many items, you have to copy the same expression into every item that should have dynamic visibility. If that is your case, use the technique described in the next topic.

Combine Visibility Conditions

You can specify one expression that will run against every item (choice, row, column). If the expression evaluates to true, the item becomes visible. Assign your expression to the choicesVisibleIf, rowsVisibleIf, or columnsVisibleIf property. To access the current item, use the {item} operand.

The following code shows how to specify the choicesVisibleIf property. The "default" question includes selected choices from the "installed" question. The "secondChoice" question also includes selected choices from the "installed" question but uses the choiceVisibleIf property to filter out the choice selected in the "default" question.

const  formJson = {

 "elements": [{

   "name": "installed",

   "choices": ["Google Chrome", "Microsoft Edge", "Firefox", "Internet Explorer", "Safari", "Opera"],

   // ...

 }, {

   "name": "default",

   "choicesFromQuestion": "installed",

   "choicesFromQuestionMode": "selected"

   // ...

 }, {

   "name": "secondChoice",

   "choicesFromQuestion": "installed",

   "choicesFromQuestionMode": "selected",

   "choicesVisibleIf": "{item} != {default}",

   // ...

 }]

}

Conditional Survey Logic (Triggers)

Triggers allow you to implement additional logic that isn't related to read-only or required state or visibility. Each trigger is associated with an expression and an action. A  form re-evaluates this expression each time values used in it are changed. If the expression returns true, the  form performs the associated action.

The following triggers are available:

  • complete
  • setvalue
  • copyvalue
  • runexpression
  • skip

complete

Completes the  form. The expression is evaluated only when a user switches to the next page.

In the following code, a trigger completes the  form if the "age" question on this page has a value under 18:

const  formJson = {

 "elements": [{

   "name": "age",

   // ...

 }],

 "triggers": [

   { "type": "complete", "expression": "{age} < 18" }

 ]

}


setvalue

Sets a specified value to a given question. The setValue property specifies the value; the setToName property specifies the question name.

In the following code, triggers are used to set the "ageType" value to "minor" or "adult" based on the "age" question value:

const  formJson = {

 "elements": [{

   "name": "age",

   // ...

 }, {

   "name": "ageType",

   // ...

 }],

 "triggers": [{

   "type": "setvalue",

   "expression": "{age} < 18",

   "setToName": "ageType",

   "setValue": "minor"

 }, {

   "type": "setvalue",

   "expression": "{age} >= 18",

   "setToName": "ageType",

   "setValue": "adult"

 }]

}

You can use the setValueExpression and setValueIf properties as an alternative.


copyvalue

Copies a value from one question to another. The fromName property specifies the source question; the setToName property specifies the target question.

In the following code, a trigger copies the "billingAddress" question value into the "shippingAddress" question if the "sameAsBilling" question is Yes:

const  formJson = {

 "elements": [{

   "name": "billingAddress",

   // ...

 }, {

   "name": "shippingAddress",

   // ...

 }, {

   "name": "sameAsBilling",

   "choices": [ "Yes", "No" ]

   // ...

 }],

 "triggers": [{

   "type": "copyvalue",

   "expression": "{sameAsBilling} = 'Yes'",

   "fromName": "billingAddress",

   "setToName": "shippingAddress"

 }]

}

You can use the setValueExpression and setValueIf properties as an alternative.


runexpression

If the expression is true, the trigger runs another expression specified by the runExpression property. You can also save the result of runExpression as a question value. For this, assign the question's name to the setToName property.


skip

Switches the  form to a target question's page and focuses the question. The gotoName property specifies the target question.

In the following code, a trigger navigates to the "additionalInfoPage" page and focuses the "additionalInfo" question if the "sameAsBilling" question is Yes:

const  formJson = {

"pages": [{

  "name": "billingAddressPage",

  "elements": [{

    "name": "billingAddress",

    // ...

   }]

 }, {

  "name": "shippingAddressPage",

  "elements": [{

     "name": "sameAsBilling",

     "choices": [ "Yes", "No" ]

     // ...

 }, {    

    "name": "shippingAddress",

    "visibleIf": "{sameAsBilling} = 'No'",

    // ...

   }]

 }, {

  "name": "additionalInfoPage",

  "elements": [{

    "name": "additionalInfo",

    // ...

   }]

 }],

"triggers": [{

  "type": "skip",

  "expression": "{sameAsBilling} = 'Yes'",

  "gotoName": "additionalInfo"

 }]

}