Interface Variables and Inputs

This page is an overview of how to configure components when a user needs to interact with them.

Overview

Some interface components allow users to interact with them. Interactions are filling out form inputs, like typing in a text field or making a choice in a dropdown. Clicking links or buttons are also interactions.

When a user interacts with an interface, the interface expression is reevaluated and the resulting interface displayed. This means the interface can dynamically respond to user interactions, such as changing the options in a dropdown based on an earlier dropdown, showing a section after a link is clicked, sorting columns in a grid, and so on.

Every interface component that supports user interaction has a parameter called saveInto that defines what changes to make when the user interacts with the component. The only way for a user to cause changes is through a component's saveInto parameter.

There are three ways to respond to user interactions with the saveInto parameter:

  • Save the user's input to a variable, e.g. local!name or ri!amountPaid
  • Save a modified or alternative value into a variable, e.g. a!save(ri!username, lower(save!value))
  • Execute a smart service, e.g. a!deleteDocument(document: ri!requestForm)

Saving Input into Variables

When a user interacts with a component and that component's saveInto parameter is configured with a variable, the component's updated value will be saved to that variable.

Simple Example

The most common way to configure a component is to set its value and saveInto to the same variable. To see how this works, copy and paste the following expression into the INTERFACE DEFINITION in EXPRESSION MODE:

=load(
  local!name,
  a!textField(
    label: "Name",
    instructions: "Your name has " & len(local!name) & " characters",
    value: local!name,
    saveInto: local!name
  )
)

When the user types a name into the text field, the text they type will be saved to local!name. This in turn will be displayed in the text field because the same variable is passed to the value parameter.

Notice the local!name variable is used in the expression for the instructions parameter, as well as the value and saveInto parameters.

In the live view, type into the text field then press the tab key. Notice how the character count in the field instructions updates when you are no longer focused on the text input. This is because the expression was evaluated again and this time local!name had the new value.

The value of the variable configured in the saveInto parameter does not automatically show up as the display value of the component. The value input must be set separately for the change to be displayed by the component. To see what happens when this is not done, try updating that expression so that the value parameter is null:

=load(
  local!name,
  a!textField(
    label: "Name",
    instructions: "Your name has " & len(local!name) & " characters",
!    value: null,
    saveInto: local!name
  )
)

Now, when you click away from the text input, the text input becomes blank. This is because the value of the component is hard-coded to null. Notice, though, the character count was updated. That's because the variable has the correct data.

Local Variables

When a variable is defined in an expression instead of made available to the expression by the framework, it is called a local variable. Rule inputs, process variables, and record fields are examples of non-local variables that are provided to the expression because of where it is being evaluated.

There are two functions available to define local variables in expressions: load() and with().

load()

The load() function allows you to define one or more local variables, use the variables to evaluate the expression defined in its last parameter, and return the result. The load() function only sets its local variables to their configured values when it is first rendered, often when the user first navigates to the interface or refreshes the browser window.

The fact that load() only sets its local variables when the interface is first rendered has two key consequences:

  • an interface component can save its value into a local variable defined by load() just as it can save to process variables and node inputs.
  • When external data is queried and stored in a local variable, either as the variable's initial value or in a component saveInto, the data is retained during later reevaluations of the interface without being queried every time.

See also: load()

with()

The with() function also creates local variables, but whereas load() sets its variables only when the interface first loads, with() sets its variables every time the expression is reevaluated.

with() is useful when you need to reuse the result of a function in multiple places in your expression and you need the value to be recalculated on every reevaluation.

NOTE: The value of with() variables cannot be updated in component saveInto inputs because their value is overwritten whenever the expression reevaluates.

See also: with()

with() and load()

To better understand the difference between with() and load(), copy and paste the following expression into the INTERFACE DEFINITION in EXPRESSION MODE:

load(
  local!loadVariable: rand(),
  local!typedText,
  with(
    local!withVariable: rand(),
    {
      a!textField(label: "load() Variable", readOnly: true, value: local!loadVariable),
      a!textField(label: "with() Variable", readOnly: true, value: local!withVariable),
      a!textField(label: "Try typing here", value: local!typedText, saveInto: local!typedText),
      a!buttonLayout(
        secondaryButtons: {
          a!buttonWidget(
            label: "Increment load() variable",
            value: local!loadVariable+1,
            saveInto: local!loadVariable
          )
        }
      )
    }
  )
)

Each time you type in the text field and click out from it, you can see the value of the with() variable changes. That's because rand() returns a different value every time it is evaluated. The load() variable does not change when you type, however, because load() set the variable's value once, when it was first evaluated. After that, the value only changes if you save into it. Click the increment button to see this happen. Finally, click the Test button to reset the interface and notice that then, and only then, the load() variable's value gets set to a new random number. Clicking Test has the same effect as refreshing the interface as if you'd closed it and opened it again.

Arrays and Custom Data Types

In addition to saving to a variable, you can save into an array at a specific index using square brackets. For example:

saveInto: local!names[10]

or

saveInto: local!names[local!index]

This is especially useful when generating components based on a list of data, such as in the Add Multiple Text Components Dynamically recipe.

You can also save into a field of a custom data type using the dot operator. For example:

saveInto: local!person.firstName

This is useful when you want to populate a custom data type via user input, since you can display an appropriate component for each field.

The dot and bracket notation can also be combined:

saveInto: local!persons.firstNames[local!index]

For a more extensive example, see the Add and Populate Sections Dynamically recipe.

NOTE: You must use square brackets or the dot operator to index in a saveInto variable. You cannot use the index() function to save to a specific index.

Rule Inputs

When saving sections of your expression into rules, you can pass a load() local variable, a process variable, or a node input to the rule and save into the rule input. In such a scenario, the variable must always be passed to the rule input as is. The saveInto parameter will not work if the variable has been modified with a function or operator, nor will it work if something besides a valid variable is passed, like "hello", 3, or a with() variable.

For example, let’s say you have a local variable called local!name and a rule that returns a Text component. In the rule definition, you want to save into local!name. You would create a rule input of type Text, and map it to the local variable by passing the local variable to the rule.

=load(
  local!name,
  returnTextField(local!name)
)

Where the definition of returnTextField is the following:

=a!textField(
  label: "Name",
  instructions: "Your name has " & len(ri!name) & " characters",
  value: ri!name,
  saveInto: ri!name
)

Saving Modified or Alternative Values

Instead of saving the user's exact input, you can also modify the component's updated value before saving it into a variable. To do so, use the a!save() function. The first parameter of this function is the variable to be updated. The second parameter is the value to set. This parameter can be configured with an expression that can either modify the component's value or return an alternative value completely unrelated to that of the component. The component's new value can be accessed in the second parameter using the special variable save!value.

For example, if you want to remove leading and trailing spaces from the user's input before saving it, you can use the a!save() function along with the trim() function. Update your interface with the following expression:

=load(
  local!name,
  a!textField(
    label: "Name",
    instructions: "Your name has " & len(local!name) & " characters",
    value: local!name,
!    saveInto: a!save(local!name, trim(save!value))
  )
)

Enter leading and trailing spaces into the text field and click away. Notice how the character count in the instructions does not count the spaces you entered.

You can modify the user’s input with as many functions or operators as you like. For example, update your interface with the following expression:

=load(
  local!name,
  a!textField(
    label: "Name",
    instructions: local!name,
    value: local!name,
    saveInto: a!save(local!name, `fn!append("Hello ", trim(save!value)))`
  )
)

To save into multiple variables, you can pass an array containing both variables to update and a!save() functions. This expression saves the user's input into one variable while updating a second variable the input prefixed with "Hello ":

=load(
  local!name,
!  local!greeting,
  a!textField(
    label: "Name",
!    instructions: local!greeting,
    value: local!name,
    saveInto: {
      local!name,
!      a!save(local!greeting, append("Hello ", save!value))
    }
  )
)

You can also use multiple a!save() functions. This expression trims the user's input before saving it into the first variable, then updates the second by prefixing the first variable:

=load(
  local!name,
!  local!greeting,
  a!textField(
    label: "Name",
!    instructions: local!greeting,
    value: local!name,
!    saveInto: {
!      a!save(local!name, trim(save!value)),
!      a!save(local!greeting, append("Hello ", local!name))
!    }
  )
)

Try typing leading and trailing spaces into the field and notice how local!greeting is getting updated with the trimmed name, not the original user input.

The expression in the saveInto parameter evaluates when the user interacts with the component. Each item in the saveInto array evaluates one at a time. Therefore, if an a!save() parameter uses a variable that was updated higher in the list, a!save() evaluates with the variable's updated value.

FEEDBACK