Uniform Grid and Freeform Grid: Logic and Referencing Syntax

Overview

The Uniform Grid and Freeform Grid components support nesting logic components inside the grid. These nested logic components can reference inputs inside or outside of the grid. You can also configure logic components positioned outside of the grid to reference inputs and outputs that are nested inside the grid. The Freeform Grid even supports nesting other grid components inside the Freeform Grid, which themselves can have nested logic components that reference fields that are now inside two grids, and so on. With so much configuration flexibility available, it's important to know when and how to use grid referencing syntax with the logic components in your module.

NOTE  Grid referencing syntax is currently supported in the following components, collectively referred to as logic components: Calculator, Data Workflow, Decisions, Initializer, and Plug-In.

TIP  This article focuses on using grid referencing syntax with the Uniform Grid and Freeform Grid components. To learn about referencing syntax with the Dynamic Grid component, see the Dynamic Grid: Targeted Logic and Referencing Syntax article.

What You'll Learn

In this article, you'll learn:

Structure of a Uniform Grid or Freeform Grid Component's Data

Data entered in the Uniform Grid or Freeform Grid component stores as an array of objects under a single key. The top-level key is the grid component's Property ID. Each row added to the grid component in Express View becomes a new object in the array of row objects. In each row object, row-specific values store as explicit key/value pairs, where the keys are the Property IDs of the components nested in the Uniform Grid or Freeform Grid. The values are those entered by the end-user in that row. Depending on the component type, values store as different data types and data structures.

The following image shows an example of how two rows of data entered in a Uniform Grid component looks in the DevTools Console:

The following image shows an example of how two rows of data entered in a Freeform Grid component looks in the DevTools Console. As you can see, both components use the array of row objects data structure:

When to Use Grid Referencing Syntax

Before learning how to use grid referencing syntax, it helps to understand when you'll use it in your configurations. The basic rules are:

1. A logic component accessing data nested below its level of the data structure needs to use grid referencing syntax. This rule applies when the logic component is positioned outside of the grid component.
2. A logic component accessing data at its same level of the data structure, as well as data at a higher level of the data structure, doesn't need to use grid referencing syntax. This rule applies when the logic component is nested inside of the grid component.

TIP  If you're confused about what level of the data structure an input or output is, relative to the logic component, try examining the submission data object. After populating some sample data in your module in Express View, use the Angular command to take a snapshot of the submission data object. You can then expand and explore the object to identify which keys (Property IDs) are at the same level, higher level, or lower level of the data structure.

To understand the first rule, consider the following diagram. It shows the data access flow for a logic component positioned outside of the grid component:

Relative to the grid component's data, the logic component is at a higher level of the data structure. It also has access to every row object of the grid array. So, when referencing an input or output nested inside the grid, the logic component needs to use grid referencing syntax. The logic component needs to be given a specific path to follow through the submission data object to find the target in the grid array.

To understand the second rule, consider the following diagram. It shows the data access flow for a logic component nested inside of the grid component:

NOTE  Only the Freeform Grid component syncs data to the grid cache. To learn how this affects the data access flow of logic components, see the Freeform Grid: The Grid Cache and Logic Behavior section below.

Relative to the grid component's data, the logic component is at the same level of the data structure. The logic component is already present in the same row object as its input or output, so there's no need to use grid referencing syntax. The logic component doesn't need to follow a specific path through the submission data object to find its target.

A key term in the above data access flow diagram is row-scoped. Logic components nested inside a Uniform Grid or Freeform Grid have row-scoped access to the row object (whether in the grid cache or the submission data). But what does row-scoped access mean?

Row-Scoped Access

When configuring your Uniform Grid or Freeform Grid in the Module Builder, you add the components you want to appear in the grid once. Then, in Express View, for each row added to the grid, a new instance of each nested component is created. The data from each instance (row) of the grid stores to its own row object of the submission data object (and grid cache, in the case of the Freefrom Grid). Because of this, logic components nested inside the grid have row-scoped access to data. That is, logic components from one instance (row) can't access data from another instance.

For example, a logic component nested inside row 1 (index [0]) of the grid can only access data from:

  • The index [0] row object of the grid array in the grid cache (Freeform Grid) or submission data object (Uniform Grid).

  • The entire submission data object.

Another way to think of this restriction is that a logic component can't go up a level in the data structure, then back down a level. Logic components nested inside the grid start their data access flow at the row object, then move to the whole submission data object. They can't move back down a level to a separate row object. A nested logic component's data access flow is linear and restricted to its row object.

NOTE  This row-scoped access rule also applies to grids nested inside a Freeform Grid. Imagine you have a Freeform Grid that contains another, nested Freeform Grid. Inside the nested Freeform Grid is a logic component. The logic component can't reference inputs in another row of the nested Freeform Grid, or another row of the parent Freeform Grid. Twice-nested logic components still can't reference outside of their own row (or parent row).

Syntax for Referencing a Uniform Grid or Freeform Grid Component

Now that you know when to use grid referencing syntax, look at how to use it. You can reference and create logic targeting a Uniform Grid or Freeform Grid component at the component, column, row, or cell level. This lets you reference various levels of your grid in the Inputs or Outputs tables of logic components. For example, outputting the same value to a component in every row of the grid. Or, replacing the value of a component in a single, specific row of the grid.

The following table shows the syntax you'll use to reference each level of your Uniform Grid or Freeform Grid component:

Level Syntax Notation Example

Component

{gridPropertyID}

myGrid

Column

{gridPropertyID}.col({columnPropertyID})

myGrid.col(firstName)

Row

{gridPropertyID}.row({rowIndex})

myGrid.row(0)

Cell

{gridPropertyID}.col({columnPropertyID}).row({rowIndex})

myGrid.col(firstName).row(0)

NOTE  In the context of a Uniform Grid or Freeform Grid, a column is a component nested in the grid. And a cell is a row-specific instance of a component nested in the grid. For example, say your Uniform Grid contains a component called firstName. Every new row of the Uniform Grid now has an instance of the firstName component. The syntax myGrid.col(firstName) targets every instance of the firstName component in every row of the grid. This is a column-level effect. The syntax myGrid.col(firstName).row(0) targets only the instance of the firstName component in row 1 of the grid. This is a cell-level effect.

Syntax Usage Notes

1. The Uniform Grid or Freeform Grid component's Property ID should exist in the same module before referencing the grid's Property ID in logic components. In other words, add your grid component before configuring a logic component that references the grid.
2. The order of expressions for the column and row properties doesn't matter.
a. For example, both myGrid.col(firstName).row(0) and myGrid.row(0).col(firstName) are valid.
3. Expressions are case-sensitive.
a. For example, myGrid.ROW(0) is invalid.
b. For example, myGrid.COL(firstName) is invalid.
4. When referencing a specific level of the grid, you must include a parameter value (such as a row index or nested component's Property ID) in your expression.
a. For example, myGrid.row() and myGrid.col() are invalid.
5. There are constraints on what parameter values the expression supports.
a. For example, when referencing .col({columnPropertyID}), the {columnPropertyID} parameter value can't contain special characters or spaces.
b. For example, when referencing .row({rowIndex}), the {rowIndex} parameter value must be a valid, zero-based row index. Or, # when referencing Dynamic Index.
6. You can't use multiple parameter values in a single expression.
a. For example, myGrid.col(firstName, lastName).row(0) is invalid.
b. For example, myGrid.col(firstName).row(0, 1) is invalid.
7. You can't use spaces in your expression.
a. For example, myGrid. row (0) . col (first Name) is invalid.
8. Usage not meeting the above syntax converts to the legacy square brackets convention.

NOTE  This concession exists to support the Sheet component, prior to its deprecation.

Dynamic Index

The grid referencing syntax used by the Dynamic Grid, Uniform Grid, and Freeform Grid components supports referencing Dynamic Index. Dynamic Index can stand in as a row index value and lets you set up logic that watches for a trigger event (editing a row, adding a row) to occur in any row index. To reference Dynamic Index, you can use # anywhere you use the rowIndex parameter. For example: myGrid.col(firstName).row(#).

When creating triggers or rules that reference a row index value, you'll rarely hard-code that row index value in your configuration. It's often more useful to define the index value at run time, based on end-user interaction. For example, referencing the row index value of the row the end-user adds or edits. Dynamic Index is a way to set a variable row index (#) in your configuration. Then, when your trigger or rule runs, the associated row index passes along to the triggered logic component based on the end-user's interaction. The logic component in turn can use the dynamically-populated row index value.

Say your configuration includes a Plug-In component that, whenever a row in the grid is updated, sends that data to an API. The Plug-In can reference Dynamic Index to receive run-time information about which row is edited, letting the Plug-In know which row of data to send to the API.

Dynamic Index Usage Notes

  • Dynamic Index is only supported for the Manual trigger type in the triggering logic component.
  • For Dynamic Index to work, the trigger mechanism needs to be able to pass along the row index value. This is possible with the following configuration set-ups:
    • The triggering component is nested within the grid. Since the component is inside the grid, it can pass its row index when triggering the logic component.
    • Using the grid component's native triggers, such as Add Row and Edit Row. The grid can pass the row index when the trigger is fired.

Supported Logic Input and Output Types

Because the Uniform Grid and Freeform Grid components support component nesting, there are many possible input/output combinations that logic components referencing a grid can use.

With so much flexibility, it's key that you're mindful of the data types and data structures of the inputs and outputs you're referencing. For example, if outputting a value to a specific cell in your grid, the data type and data structure must match the data type and data structure of the nested component. You can't output a string value to a Number component. Similarly, if the logic component is watching for an exact Input Type from a given cell of the grid, the data type and data structure of the defined exact value must match what data from that cell looks like.

It's also important to think critically about if the Output Type your logic component is using makes sense when applied to the target, both in terms of the level of targeting and the type of component being targeted. For example, it doesn't make sense to apply an Output Type of minimum at the component level. The entire Uniform Grid or Freeform Grid can't have a minimum numeric value.

TIP  Understanding what an Output Type does can help understand if it makes sense to apply that output to a given component. The Decisions Component article in our In-Product Help has a comprehensive overview of Input and Output Types.

For column-level and cell-level targeting, the supported Output Types depend on what the nested component is that you're targeting. For example, a Number component supports the currency output type, whereas a Text Field component doesn't. Component-level and row-level targeting have more defined limitations:

Component Level

The following Output Types are supported at the component level:

  • clear
  • customClass
  • disabled
  • label/title

NOTE  Both target the grid's Title.

  • readOnlyView
  • required
  • requiredAndVisible
  • reset
  • visible

Row Level

The following Output Types are supported at the row level:

  • clear
  • customClass
  • disabled
  • required
  • requiredAndVisible
  • reset
  • visible

Freeform Grid: The Grid Cache and Logic Behavior

When working with the Freeform Grid component, logic components have a more nuanced data access flow than when working with the Uniform Grid component. This is due to how data entered in a Freeform Grid saves and stores. There are two locations data from the Freeform Grid can be written to and read from:

  • Grid cache: Any data entered in the Freeform Grid is written to the cache. Cached data is temporarily saved in the browser, but the data only writes to the submission data object after a grid Save event occurs.
  • Submission data object (submission.data): The submission data object houses data for all components in the module. The Freeform Grid's data is accessible via the submission.data.[gridPropertyId] path. Data from the Freeform Grid component writes to the submission data object after a grid Save event.

TIP  To learn more about the grid cache, see the Freeform Grid Component article in our In-Product Help.

Understanding when data is available in the grid cache or the submission data object is critical to your configuration. This is because the location of logic components in your module affects whether the logic component accesses data from the grid cache or the submission data object:

  • A logic component nested inside the Freeform Grid can read from and write to both the module's submission data object and the grid cache.
  • A logic component outside of the Freeform Grid can only read from and write to the module's submission data object.

However, the logic component's location in the module is only one part of the puzzle. The location of the logic component's inputs and outputs (whether they're inside or outside the Freeform Grid) also affects where the logic component reads the input's value from or writes the output's value to.

The following flow-chart summarizes where a logic component nested inside a Freeform Grid reads input values from and writes output values to. Note that when a logic component that's inside the grid reads from or writes to the grid cache, this is a row-scoped operation.

The following flow-chart summarizes where a logic component positioned outside of a Freeform Grid reads input values from and writes output values to:

NOTE  The exact path the logic component reads input values from and writes output values to varies based on your configuration. The key concept is that logic components positioned outside the grid, with inputs or outputs nested inside the grid, don't have access to the grid cache. Instead, they read from and write to the grid array within the submission data object (submission.data.{gridPropertyID}).

This second flow-chart is fairly intuitive, however, there are some considerations to keep in mind:

  • When the logic component's input is nested inside the Freeform Grid, the logic component reads the input from the submission data object. So, the logic component can't read changes to the input value until a grid Save event occurs.

  • When the logic component's output is nested inside the Freeform Grid, the logic component writes the output to the submission data object. That value then automatically syncs to the grid cache without needing a grid Save event.