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.
Grids are how we display data in a tabular format. The read-only grid component can be used to display the following types of data:
The read-only grid component is a modern, interface component that is designed to handle your data intelligently. The grid can display data in various formats, provide advanced filtering, paging, and sorting options, allow row selection, and pass record data to other components in the interface. In addition, when you use a record type as the grid's data source, you can bring in record actions. The best part is that it's easy to setup, modify, and configure for user interaction.
The read-only grid is a new design paradigm compared to the old, paging grid, and has a number of assistive features that you can access and configure in Design Mode. These features 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.
You can quickly create a grid in Design Mode by selecting a record type as the data source.
The basic workflow for creating a a quick and easy read-only grid is:
If you have not already created a record type or a query in a rule, embedding the query is a great way to get started with your grid configuration. The grid can reflect a lot of the changes you make to an embedded 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.
The grid's Data (data) parameter accepts the following data types: RecordType
, Datasubset
, PortalReportDataSubset
, List of Map
and List of Dictionary
.
You can easily pull record data into the grid by choosing the record type as the grid's data source. This also allows you to leverage the record list view in your grid along with other configurations on the record type, such as the search box, user filters, record actions, and the Export to Excel button.
If you don't have a record type, 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.
In Expression Mode, you can configure the grid's data source using a!recordData()
, a!queryEntity()
, a!queryProcessAnalytics()
, a Datasubset, a list of dictionary, or an array of data. Note that when using a query that returns a datasubset, you must pass the total count fetchTotalCount
parameter as true
on the query.
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.
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.
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, which you can access with a record type field reference. For example, fv!row[recordType!Record.fields.firstName]
allows you to reference and display an employee's first name.
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. Note that when using a record type as your data source, Appian automatically pulls in the data from the fields used in your expression.
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.
There are several ways to filter the data in your grid. If you're using a record type as the grid's data source, you can configure a record filter on the grid itself. Based on the filter value you configured, record filters selectively remove part of the data returned by the record type's record list. When the user interacts with a grid that has a record filter applied, they will only have access to view and interact with the datasubset you want them to see. Note that end users do not have visibility to any record filters you configured on the grid. Therefore, they do not have permission to modify or remove them.
You can also configure user filters on the record type and bring one or more of them into the grid. Unlike record filters, user filters give users more control over the record data they want to see. Based on the user filters you configured and applied to the grid, users can select one or more filter values to return a different datasubset. Users that interact with a grid often, may want save their filter combinations for reuse. These are called user-saved filters.
Alternatively, if you're using a!queryEntity()
to query a data store entity to return the grid data, you can use a!queryFilter()
or a!queryLogicalExpression()
to configure an expression with a list of filters. See a!queryEntity, a!queryFilter, and a!queryLogicalExpression for more information.
The following sections describe how to configure filters on a grid in Design Mode.
When you select a record type as the grid's data source, the Filter Records button will be enabled by default in the Data section. You can use this feature to return a different subset of data on the grid than the datasubset returned by the record list.
Clicking the Filter Records button opens the Filter Records dialog. From here you can create, save, and manage unique record filters to display the data for each particular use case that you expect end users to need when interacting with the grid.
Each filter allows you to filter down the grid data to only the data you want to users to see.
Record filters are specific to each interface component that pulls in the record data. This means that you can configure different record filters for different grids that use the same record data.
The Add User Filter button in the Record List section allows you to add one or more user filters that are already configured on the record type and apply them to the grid. This makes the user filters available to users when they interact with the grid so they can filter down the grid data to display only the records that match the selected filter values.
To add a user filter to the grid, click on the Add User Filter button to expose the user filter drop-down menu and select a filter.
Note that if there are no user filters to select in the drop-down menu, it means there are no user filters configured on the record type that you selected as the grid's data source. You will need to open the record type object and create the user filters you want to apply to the record list and grid before you can add them to the grid.
In the example shown below, the Department user filter, configured on the Employee record type, is selected.
Saved filters are based on the combination of filters applied to the current grid, so some saved filters may display on multiple grids if you apply the same combination of filters.
In Design mode, the Add User Filter button is automatically disabled once you have added all of the available user filters configured on the record type to the referenced grid.
Finally, users can create and save their own user-saved filters on the grid by choosing values from the existing user filters and selecting Save filters as… from the Filters menu.
The Filters menu allows each user to name their filters and choose which filter they want to load by default when they navigate to the record list or the grid that uses the record type as a data source. The Filters menu also allows users to access and view all of their saved filters by selecting Manage my filters…. From here, users can remove or rename existing filters.
Each saved filter will display with a shortcut at the top of the page next to My Filters.
All saved filters are also visible by selecting Manage my filters… from the filters menu. Here users can remove or rename existing filters. Each user can save up to 10 filters on each record list. If saving filters on a grid that pulls in the record data, users can also save 10 additional filters each time a different combination of filters is used.
Users can save values for any filters visible on the record list. However, some changes to the record type may affect user-saved filters. If you deploy any of the following changes to the record type, the corresponding user-saved filter is affected:
In all of these cases, users may need to update their user-saved filter values. A warning message also displays that describes what has happened.
Configuration changes on the grid can also affect user-saved filters. If a developer adds or removes a user filter from the grid, all previously user-saved filters will no longer be visible on this grid. Users must recreate these saved filter values in order to reuse them.
The refresh component allows you to enable or disable the data refresh feature on the grid for all data source types.
You can also configure what event triggers a data refresh, including a variable change or the completion of a specific time interval, a user interaction, or a record action.
"Paging" refers to how many rows of data you want to display at a time, and "sorting" refers to the order in which you want to display the data. 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. You can also define what order the data displays when the grid initially loads with the Initial Sorts (initialSorts) parameter. Collectively, these are referred to as "paging information."
Queries also consider paging and sorting; from the query()
, function and the a!queryRecordType()
function, the a!pagingInfo()
function is used to determine how many records to retrieve at a time (batchSize), from which initial position (startIndex). The a!sortInfo()
function is used to determine in what order (sortInfo) to sort the data in the grid.
Regardless of whether you use a record type as the grid's data source, give the grid an array of data, or give the grid a query, the grid can manage the paging information for you.
In this section, we'll walk through how to configure these settings and discuss the default sort attributes Appian automatically applies to the grid data.
Design Mode makes it easy to configure the page size and sort order for the data in your grid in the PAGING & SORTING section of the COMPONENT CONFIGURATION panel.
In Rows to Display Per Page*, simply enter the number of rows you want the grid to display on each page.
In the example shown, the grid has a total of 20 rows of record data and displays 10 rows on each page.
When the grid's data source is record type or an array of data, the grid can automatically manage paging for you. Alternatively, if the grid uses a query as the data source to return a Datasubset or a List of Dictionary, 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
. Use a!queryEntity()
or a!queryProcessAnalytics()
with pagingInfo set as 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 or a record type, 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 following subsections provide examples for all data-source scenarios.
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
),
...
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).
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:
a!pagingInfo()
function with that rule input.In the grid, from the COMPONENT CONFIGURATION of the grid, for DATA, select Choose rule, and enter your rule name.
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.
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`
),
...
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:
.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:
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.
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.
Regardless of whether you give the grid an array of data, or a query, the grid can manage the paging information for you.
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 it, 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 is for the very rare cases where you need to control a query's paging information. You may need to configure manual paging if you have a query that's too large to return all results at once and it has to be in a local variable. If you need to query at a start index other than 1
, which fv!pagingInfo
doesn't do, you'll need to configure manual paging.
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:
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`
)
)
Applying a sort to the grid arranges the data so it is ordered by a particular column in ascending or descending order. By default, Appian automatically applies a default sort on the primary key and sorts the data in ascending order. A deterministic sort is also applied to grid data, which ensures the record data is returned in the same order each time the sort parameters are applied.
Deterministic sort is not supported for record types that use a process, expression, or web service.
The read-only grid also allows you to define two sort parameters that determine how the data is ordered when the record list initially loads in the grid and another sort that controls the data order after the user interactions with the grid.
In the grid shown, the Initial Sorts parameter is configured so the grid sorts the record data by the employee Start Date, in ascending order. Appian applies this initial sort to the record list when it initially loads in the grid.
The secondary sort defines how the grid data is ordered after the user clicks a column in the grid. In this grid shown, a secondary sort is configured and applied to the Name column. When the user clicks on the Title column, the secondary sort is applied to the data in the grid. Notice how the data is reordered in the grid so that the records are sorted in alphabetical order according to the Title column (user interaction) and ascending last name (secondary sort).
When you use a record type as your data source, you can easily add initial sorts and secondary sorts to the grid in Design Mode. Simply click ADD SORT under Initial Sorts from the PAGING & SORTING section of the Component Configuration panel.
This will open the Sort Info , which allows you to define the Sort Field and Sort Order.
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
.
You can define secondary sorts to 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.
Note that you configure the grid to sort on multiple fields by defining multiple initial and secondary sort parameters for the read-only grid.
You can also configure these grid settings in Expression Mode. Use the Page Size (pageSize) parameter to set the number of rows to display at a time, and use the Initial Sorts (initialSorts) and Secondary Sorts (secondarySorts) parameters to define the order. 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).
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)
.
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.
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 (with 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.
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)
.
Selection for the grid can be enabled/disabled entirely, or on a per-row basis.
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.
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. For example, if a record type is used as the grid's data source, you would disable row selection when the transaction hasn't been rejected: disablerowselectionwhen: fv!row[recordType!MyRecord.fields.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.