Configuring the Read-Only Grid

Eager to get started with the new grid? Check out the Grid Tutorial for the fastest and easiest way to create a read-only grid.

This page explains the main elements of the read-only grid, and how to configure them.

Overview

Grids are how we display data in a tabular format, whether it be an array of data, or records returned from a query. The read-only grid component is a modern, interface element that has been engineered to handle data intelligently to make the grid easy to setup, modify, and interact with. The grid can display data in various formats, take a query as its data source, provide advanced paging and sorting options, allow row selection, and pass row data to other components in the interface.

The read-only grid is a new design paradigm compared to the old, paging grid, and has a number of assistive features from Design Mode that help ensure you have your grid configured correctly. If you want to know more about the differences between the new grid and the old grid, see Common Questions.

Create a Quick Grid

The basic workflow for creating a read-only grid is:

  1. Drag the grid from the component palette onto your interface.
  2. Choose a data source (or launch the query editor to quickly configure one for the grid).
  3. Adjust your columns display options and set your preferred paging and sorting (if you didn't already in the query editor).

Embedding the query is a great way to get started if you don't have the query in a rule already, since the grid can reflect a lot of the changes you make to the query when working with the query editor from Design Mode. Once you have your query setup correctly, you can move the expression to an expression rule.

Data

data_section

The grid's Data (data) parameter accepts the following data types: datasubset, PortalReportDataSubset, and List of Dictionary.

The grid is also able to work directly with the query that provides the datasubset. When using a query, the grid can manage the paging information for you; how you configure the data for your grid depends primarily on how you want to handle your paging. See the Paging & Sorting section for more information, and example configurations for all data scenarios.

Columns

columns_section

Columns are configured at two levels: (1) column selection and order, and (2) column width and data display options.

For most data sources, when you populate the Data parameter for the grid from Design Mode, the grid automatically generates an initial a set of columns for you based on the fields in the data or query results. The grid will convert camel case field names to title casing, and, depending on the underling data type of those fields, the grid will also format some of the display values. See Design Mode Notes for more information.

Arranging Columns

columns_hover

The Columns (columns) parameter is where you can add, remove, and re-order columns from the Read-only Grid. In order to make changes, hover over the option menu ( ). From any column in the list, the options menu allows you to move your columns, add a column above or below, or remove a column.

To make other changes to a particular column, click on it to open its configuration properties.

Display Value

display_value

Click on any of the Columns to access the Grid Column component configuration.

Column data maps to the corresponding field in the grid's data, and you choose which field(s) you want to display in that column from the Display Value (value) parameter.

If it's a new column, there won't be anything there. You can choose any of the fields in the data to display from the dropdown. This value is available from the function variable fv!row. This variable contains all the data for the entire record in the data, which you can access with dot notation. For example, fv!row.firstName.

You cannot access fields that are not returned by the query. For example, if we queried the employee entity, and did not select all fields, the fields that weren't selected will not be available to the grid.

Display Options

display_value

Under Display Value, you'll see the DISPLAY OPTIONS button, which will let you choose a compatible interface component to display the cell data with.

Paging & Sorting

paging_sorting

"Paging" refers to how many rows you want to display at a time, and "sorting" refers to the order in which you want to display them. When we display data in a read-only grid, you can set the number of rows to display at a time with the Page Size (pageSize) parameter, and in what order with the Initial Sorts (initialSorts) parameter. Collectively, these are referred to as "paging information."

Queries also consider paging and sorting; from the query() function, the a!pagingInfo() function is used to determine how many records to retrieve at a time (batchSize), from which initial position (startIndex), and in what order (sortInfo).

Regardless of whether you give the grid an array of data, or a query, the grid can manage the paging information for you.

If you use the query editor from the grid, and choose Rows Per Page, or choose fields to Sort By, these values will automatically populate the corresponding grid parameters (pageSize and initialSorts).

Grid-Managed Paging

When the grid's data source is just an array of data, the grid manages your paging information. If the data source a query, you don't need to use the a!pagingInfo() function; the grid can manage the query's paging information with its own function variable, fv!pagingInfo. In this case, the grid can use the pageSize value as the batchSize value in the query, fetching that number of records on every user interaction with the paging controls.

While fv!pagingInfo can manage the batchSize and sortInfo for you, it always uses a startIndex of 1. If you need a different startIndex, or for other rare cases, you will have to manage the query's paging information yourself. See the manual paging section.

How you enable the grid to manage your paging depends on the data source for the grid. When your data source is just an array of data, you don't need to do anything special. When your data source is a query, you can use the grid's paging function variable, fv!pagingInfo, instead of a!pagingInfo(). The followng sub-sections provide examples for all data-source scenarios.

Direct Query

Entering a query directly into the Data parameter of the grid is useful in interfaces where you need a separate query, or version of a query, for a particular scenario, like displaying chart information in a grid. For an example, see the Configure a Chart Drilldown to a Grid pattern.

To setup a query directly from the grid, in the Data parameter of the grid, select Use query editor, then click CREATE QUERY. The resulting expression will look something like this:

1
2
3
4
5
6
7
8
9
10
11
a!gridField(
  label: "Read-only Grid",
  labelPosition: "ABOVE",
  data: a!queryEntity(
    entity: cons!EMPLOYEE_ENTITY,
    query: a!query(
      pagingInfo: `fv!pagingInfo`
    ),
    fetchTotalCount: true
  ),
...

Query in Expression Rule

When you need to populate a grid with data from a query that's used in multiple places, you can simply reference the expression rule that has the query in it and pass fv!pagingInfo to that rule through an input. To do so:

  1. In the expression rule, create a rule input of type PagingInfo, and replace your a!pagingInfo() function with that rule input.
  2. In the grid, from the COMPONENT CONFIGURATION of the grid, for DATA select Use expression, then click Edit.
  3. In the expression editor, enter your rule reference, and pass fv!pagingInfo to the paging info parameter.

    choose_rule

For example, if you have this rule employeeEntityQuery, and you follow the steps above to create the rule input pagingInfo (type: PagingInfo), the resulting expression will look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
a!queryEntity(
  entity: cons!EMPLOYEE_ENTITY,
  query: a!query(
    selection: a!querySelection(columns: {
      a!queryColumn(field: "firstName"),
      a!queryColumn(field: "lastName"),
      a!queryColumn(field: "department"),
      a!queryColumn(field: "title"),
    }),
    pagingInfo: `ri!pagingInfo`
  ),
  fetchTotalCount: true
)

When you select your expression rule from the grid, the grid will automatically map fv!pagingInfo to that input when you select it.

paging_input

The resulting expression looks like this:

1
2
3
4
5
6
7
a!gridField(
    label: "Read-only Grid",
    labelPosition: "ABOVE",
    data: rule!employeeEntityQuery(
      paginginfo: `fv!pagingInfo`
    ),
...

Query in a Local Variable

Having your query outside the grid, in a local variable, may be useful when you have other interface components interacting with that data, or need that exact same data to populate other interface components (when you aren't passing selected-row data). However, you cannot pass a query to the grid from a local variable, as you can with an expression rule, and still have the grid manage your paging for you. This means if you are querying a large set of data into the local variable, and only want to pull records from that entity in batches, you should use manual paging, or use a second query for the grid; you can see an example of this pattern here: Configure a Chart Drilldown to a Grid.

If you don't need to fetch results in batches, you can still pass the data (.data) to the grid. To do this:

  1. In the query's paging info, change batchSize to the total number of records you want to return.
    • Note: Because the grid is only getting the data, it can't requery in batches.
  2. From the COMPONENT CONFIGURATION of the grid, for DATA, select Use expression, and enter the name of the local variable with your query data.
    • You can place the .data suffix on the query (local!employeeData: a!queryEntity(...).data) or on the local variable reference (data: local!employeeData.data,).

The resulting expression will look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
a!localVariables(
  `local!employeeData`: a!queryEntity(
    entity: cons!EMPLOYEE_ENTITY,
    query: a!query(
      selection: a!querySelection(columns: {
        a!queryColumn(field: "firstName"),
        a!queryColumn(field: "lastName"),
        a!queryColumn(field: "department"),
        a!queryColumn(field: "title"),
      }),
      pagingInfo: a!pagingInfo(startIndex: 1, `batchSize: -1`)
    ),
    fetchTotalCount: true
  ),
  a!gridField(
    label: "Read-only Grid",
    labelPosition: "ABOVE",
    data: `local!employeeData.data`,
    columns: {
...

More examples of this pattern:

Query and Paging Info in Local Variables

When using the read-only grid, you do not need a local variable with the paging information for the grid. The only reasons to do this would be if you needed to use manual paging.

Just Data

When you pass the grid an array of data (List of Dictionary), it's no different than passing just the data returned from the query (for example, local!employeeData.data), as seen is the Query in Local Variable example. You don't need to do anything to let the grid manage your paging in this case; that's the default behavior.

Sorting

You can add your initial sorts to the grid directly in the Initial Sorts (initialSorts) parameter.

When you use a query as your data source, and you're letting the grid manage the paging with fv!pagingInfo, the grid will pass your initial sorts to the query as the sortInfo for the initial rendering of the grid.

Since the grid passes your initialSorts value to the query through fv!pagingInfo, you can provide initial sorts on fields that are present in the source entity, even if those fields aren't returned by the query. For example, if you query the EMPLOYEE_ENTITY, and only select firstName, lastName, and startDate as your query fields, you can still pass an initial sort on id.

The grid also offers Secondary Sorts (secondarySorts), which run after the user interacts with the grid. For example, if you have an aggregate query with dates in them, and the user wants to sort on a particular column, it may be helpful to have the YEAR and MONTH columns as secondary sorts so they remain linear. You can see an example of secondary sorts used here: Aggregate Data from a Data Store Entity on a Date or Date and Time Field.

Manual Paging

Manual paging is for the very rare cases where you need to control a query's paging information because you have a query that's too large to return all results at once and it has to be in a local variable, or you need to query at a start index other than 1, which fv!pagingInfo doesn't do.

When you use manual paging, you will need the paging info defined in a local variable that the grid has access to so you can provide it to the grid in the Paging Save Into (pagingSaveInto) parameter of the grid. Note that with manual paging, you cannot set the Page Size (pageSize) or Initial Sorts (initialSorts) parameters of the grid.

To setup manual paging:

  1. Place your paging info in a local variable in the same interface as the grid.
  2. Set the grid's DATA to your datasubset.
  3. In Paging Save Into, select your paging variable.

The resulting expression will look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
a!localVariables(
  `local!pagingInfo: a!pagingInfo(startIndex: 1, batchSize: 5)`,
  local!employeeData: a!queryEntity(
    entity: cons!EMPLOYEE_ENTITY,
    query: a!query(
      selection: a!querySelection(columns: {
        a!queryColumn(field: "firstName"),
        a!queryColumn(field: "lastName")
      }),
      pagingInfo: local!pagingInfo
    ),
    fetchTotalCount: true
  ),
  a!gridField(
    label: "Read-only Grid",
    labelPosition: "ABOVE",
    data: local!employeeData,
    columns: {
      a!gridColumn(
        label: "First Name",
        sortField: "firstName",
        value: fv!row.firstName
      ),
      a!gridColumn(
        label: "Last Name",
        sortField: "lastName",
        value: fv!row.lastName
      )
    },
    pagingsaveinto: `local!pagingInfo`
  )
)

Selection

Selection is an inherent feature of the read-only grid. You can enable it by selecting the Selectable checkbox. You will need a local variable to store the selection indices. If the data source is an array of data, the stored value is the index of the row. If the data source is a datasubset, the stored value is the datasubset's identifiers.

Once you've created the local variable, enter it into the Selection Value (selectionValue) and Selection Save Into (selectionSaveInto) parameters of the grid. For example, with the selection variable local!selection:

1
2
3
4
5
6
a!localVariables(
  `local!selection`,
...
    `selectionValue`: local!selection,
    `selectionSaveInto`: local!selection,
...

The save!value here is the array of selection indices, so selectionValue: local!selection is the same as selectionValue: a!save(local!selection, a!save).

Selected Row Data

All row data in a selectable grid is available from the Selection Save Into (selectionSaveInto) parameter, in the function variables fv!selectedRows and fv!deselectedRows, which store the row data from the most recent user selections or deselections. More precisely, everytime the user selects or deselects a row, the grid populates the associated function variables with that row's data. This means when the user selects a row, the grid stores that row's data in fv!selectedRows, and when the users selects a new row, the grid replaces the row data in fv!selectedRows with newly-selected row's data. You can see how this works from the Test Data section of the Delete Rows in a Grid pattern.

Capture and Pass Selected Rows

Typically, you want to capture all the data from the currently selected rows (not just the last user interaction) into a single variable (either a local variable or a rule input). If capturing to a local variable (most common), you will end up with two selection variables in your interface: (1) to store the selection indices, and (2) to store the selected row data.

Since the data is based on user interactions, create a local variable to store the row data, then append the row data to that variable when a user selects a row, and remove row data from that variable when a user deselects a row. We recommend the following expression (ith local!selection to store the selection indices, and local!selectedRows to store the selected-row data):

1
2
3
4
5
6
7
8
9
10
11
12
13
a!localVariables(
  `local!selection`,
  `local!selectedRows`,
...
selectionSaveInto: {
  /* This save stores the selection indices */
  local!selection,
  /* This save adds the full rows of data for items selected in the most recent user interaction to local!selectedRows. */
  a!save(local!selectedRows, append(local!selectedRows, `fv!selectedRows`)),
  /* This save removes the full rows of data for items deselected in the most recent user interaction to local!selectedRows. */
  a!save(local!selectedRows, difference(local!selectedRows, `fv!deselectedRows`))
}
...

You can see working in the Grid with Selection pattern.

Only Pass Some of the Row Data

Since fv!selectedRow and fv!deselectedRow contain all the data for the fields that are defined in the data source, just as fv!row does, you are passing all the fields, even if you aren't displaying that field in a column. If you don't need all of that data, you can limit it to only the fields that are relevant with dot notation. For example: append(local!selectedRows, fv!selectedRows.lastName).

Disabling Selection

Selection for the grid can be enabled/disabled entirely, or on a per-row basis.

Disabled Until

There are cases where you want to show that the grid is selectable, but have the grid selection disabled until a condition is met. For example, you may want to prevent selection until the user clicks a button. In this case, enter an expression in the Disable Row Selection (disablerowselectionwhen) parameter that evaluates to true or false. When true, the grid will show disabled checkboxes.

Disable Specific Rows

You may want to only disable certain rows. For example, if you have a grid that displays a list of transactions, and their resulting statuses (APPROVED, PENDING, or REJECTED), you may want a button that will allow the user to RE-SUBMIT selected transactions. In this case, you would disable row selection when the transaction hasn't been rejected: disablerowselectionwhen: fv!row.status <> "REJECTED"; when the status for a row is not REJECTED, the expression evaluates to true, which will disable that row.

Whether the row is disabled or not is evaluated on a per-row basis. If rows are disabled as a result of user interaction, the user may still be able to select one of those rows before the interface has finished evaluating the disable condition. For this reason, it's best to use row disabling where the user can't accidentally select a row that should be disabled.

Before using this option, consider whether filtering the rows wouldn't make more sense.

FEEDBACK