When talking about data in Appian Interfaces, we need to consider three important topics
- Rule Inputs and Local Variables
- Updating Variables
- Passing Data to Components
But the most important thing is that variables in Appian are immutable. This is due to the fact that Appian uses a functional programming language. In terms of interfaces, this statement refers to a single re-evaluation as a result of a user interaction.
See the Appian documentation for details: https://docs.appian.com/suite/help/22.2/SAIL_Performance.html
Rule Inputs and Local Variables
The execution of code in Interfaces or Expressions happens in a context of variables. Rule Inputs define the top-level context, and a!localVariables can define nested contexts of additional local variables.
Rule Inputs themselves do not store any data, but are backed by outside memory passed into the Interface. This memory can be Activity Class Parameters created in User Input Tasks, or variables passed from a parent Interface.
Local Variables are created using the function a!localVariables(). This function takes any number of parameters, where the first parameters declare new variables defining the execution context for the last parameter, the code being executed.
Look at the diagram and try to determine the available variables for each context:
- Only the Rule Inputs A & B are available.
- The Rule Inputs A & B, plus the local variables C & D are available.
- The Rule Inputs A & B, plus the local variable C are available. The local variable D of context 2 is overwritten by the local variable D in context 3.
- The Rule Inputs A & B, plus the local variables C & D & E are available.
Rule Inputs and local variables in an Appian Interface can only be updated as a direct consequence of a user interaction. Certain Interface Components like buttons or links provide the parameter saveInto. Any code assigned to this parameter is evaluated as the user interacts with the component.
The function a!save(target, value) is the only way to update variables in a saveInto. The value is stored in the target. The target accepts a list of variables to allow you to store a value to multiple variables. To update multiple variables with individual values, create a list of a!save(). The value to be stored is available as the special variable save!value.
A shortcut: When you omit the a!save() and just pass a variable, Appian will handle that for you and store save!value into the passed variable.
The value can be a simple assignment, a calculation, a query to a database or a web service call. A list of a!save() is evaluated in-order. This way you can do multistep data manipulations. You can even create an expression which returns a list of a!save() to separate complex logic from the Interface.
Just like the execution context, a!save() can be nested. The value entered by the user is provided in save!value. You can manipulate that value and assign it to a!save()’s value parameter. When you now pass an a!save() to the target parameter, the modified value becomes the new save!value for the inner a!save().
Think of a custom text field component which stores the entered value in UPPER case. It uses a simple a!textField() as the inner component. That custom text field has a parameter saveInto of type “List of Save”. The code will then look like this:
a!textField( label: ri!label, value: ri!value, saveInto: a!save( target: ri!saveInto, value: upper(save!value) ) )
This way, you define the saving behaviour of your component but allow customisation from the outside.
Passing Data to Components
An interface component is just a normal interface, but called in a parent interface. The Rule Inputs defined in the component define the inner context and, passing data to them, defines the values.
There is one important difference to nested execution contexts, we discussed above. A component has its own isolated context and does not inherit the context from its parent, and variables of the same name will not be overwritten.
So far, so good, but what does “passing data” actually mean? Above, we discussed that Rule Inputs do not represent actual memory. This means that the data itself is always stored “outside” of the component and then passed as a reference. In the context of nested Interface components, “outside” is the parent Interface which calls the component.
As we only pass references, we can also pass a reference to the same data to two or more components. This way, we can establish a communication channel between several nested components. Updating the data from one component, will also update the data available to the components.
Keep in mind that you can pass data to a Rule Input in two ways. As a reference, as discussed above. Pass references using the following syntax:
- The value itself: local!data, ri!value
- Using dot-notation: local!data.name, ri!value.id
- Using square brackets: local!data[“name”], ri!value[“id”]
The other way is, to pass a copy of the data using this syntax:
- index(local!data, “name”, null)
- property(ri!value, “id”, null)
These are very common examples, but basically any function returns a copy of the data.
Now, why is this important? You cannot write to the copy of the data! Whenever the subcomponent changes the passed data, make sure to pass it by reference.