You can find a wealth of example patterns for your interface that represent the best designs for functionality and user experience. Many patterns rely on interacting with a set of data. In order to ensure the patterns are immediately functional, we include sample data with them. This page explains how to make those patterns work with your data rather than the sample data.
The two types of sample data commonly found in patterns:
a!queryEntity()
and stored in a local variableWe will walkthrough both hard-coded and query recipe examples, converting the interface recipes to meet a purchase request use case. In both these examples, our purchase request use case has a flat data structure. In addition to these examples, we will also discuss modifying a recipe to fit a nested data structure.
The add, edit, and delete data in an inline editable grid pattern recipe shows an example expression where hard-coded data is used to provide pre-poulated fields in the editable grid. Because an editable grid is most likely to be found within a form interface, we are going to walk-through taking the hard-coded employee data and converting it to an purchase request interface that will eventually be integrated with a process model.
"Employee" data is represented in this recipe as a hard-coded array of dictionary data stored in a local variable:
1
2
3
4
5
local!employees: {
{ id: 1, firstName: "John" , lastName: "Smith" , department: "Engineering" , title: "Director" , phoneNumber: "555-123-4567" , startDate: today()-360 },
{ id: 2, firstName: "Michael" , lastName: "Johnson" , department: "Finance" , title: "Analyst" , phoneNumber: "555-987-6543" , startDate: today()-360 },
{ id: 3, firstName: "Mary", lastName: "Reed" , department: "Engineering" , title: "Software Engineer" , phoneNumber: "555-456-0123" , startDate: today()-240 },
},
This hard-coded data is similar to what would be found in a CDTs. For our purchase request CDT, let’s assume the following data structure:
Once we have a CDT, we can start modifying the recipe by adding a rule input, and making it a datatype of a CDT array. A rule input is used instead of a local variable because this editable grid will be passing data into a process.
Next, we'll remove all of the hard-coded data in local!employees
by deleting the entire local variable. Expectedly, we will get an error message:
This is ok, because it’s a way to prompt us that we need to replace all remaining uses of local!employees
in our expression with the newly created rule input. Ctrl+H/Cmd+H is an extremely useful keyboard shortcut that will do this in one step.
There are a number of keyboard shortcuts available in expression editors, these can be found by hovering over the question mark at the top right of an expression editor or on the expressions page of the docs.
After we replace all the references to local!employee
, we’ll see our form again, but it’s still configured to collect employee information. We'll still need to update the individual interface components to meet our use case.
Unlike going from a hard-coded local variable to a rule input, the steps required to modify the interface components are going to be unique for each recipe and each use case. Instead of a step-by-step guide explaining what needs to be done, we'll address the high level things that were changed.
Modifying the Grid Columns
The number of columns changed as well as the column header labels:
Change the Interface Components
You can think of an editable grid row as its own independent form. Because of these, the interface components used to populate the employee grid need to change
For example, the first name column in Employee:
1
2
3
4
5
6
a!textField(
label: "first name " & fv!index,
value: fv!item.firstName,
saveInto: fv!item.firstName,
required: true
)
changed to the summary column in our updated editable grid:
1
2
3
4
5
6
a!textField(
label: "Summary " & fv!index,
value: fv!item.summary,
saveInto: fv!item.summary,
required: true
)
Clean-up the Remaining Recipe
The last part of our update included final formatting and expression cleanup. This included:
load()
function since there were no remaining local variablesAgain, these steps will be unique for every recipe modification. But the high-level steps will be similar for any recipe you are trying to modify. We are left with an Interface that looks like this:
From this point, the recipe can be used in a process either through a start form, or in an attended task later in a workflow. While the configuration of other recipes to work with your process with differ, the top level steps of, (1) replacing hardcoded data with rule inputs, (2) making sure the interface components are still relevant for your need, and (3) final formatting and clean up will remain the same.
So what happens when we try to convert a recipe, but instead of dealing with hard-coded data, we’re dealing with data from a query, either via queryrecord() or a!queryEntity(). These types of examples are usually found in recipes that make up the dashboard of a report or are referenced in a view of a record.
Let's continue with the purchase request use case and assume that our requested items are being stored in a database table. Our users would like a report that shows them the number of requested items per department. Additionally, they would like to see details about each department’s requests.
The Filter the Data in a Grid Using a Chart recipe is perfect for this. When replacing hard-coded data, we simply removed the local variable recipe and replaced with a rule input. Here, we need to modify to the datasubset local variables to query our database table instead of the recipe's employee table.
There are four places we need to update local variable references: local!chartPagingInfo
, local!gridPagingInfo
, local!chartDataSubset
, and local!gridDatasubset
.
Changing Aggregated Query Data
For our datasubset for our chart, local!chartDatasubset was this:
1
2
3
4
5
6
7
8
9
10
local!chartDatasubset: a!queryEntity(
entity: cons!EMPLOYEE_ENTITY,
query: a!query(
aggregation: a!queryAggregation(aggregationColumns: {
a!queryAggregationColumn(field: "department", isGrouping: true),
a!queryAggregationColumn(field: "id", aggregationFunction: "COUNT"),
}),
pagingInfo: local!chartPagingInfo
)
)
We will modify it to look like this:
1
2
3
4
5
6
7
8
9
10
local!chartDatasubset: a!queryEntity(
entity: cons!PURCHASE_REQUEST_ITEMS_ENTITY,
query: a!query(
aggregation: a!queryAggregation(aggregationColumns: {
a!queryAggregationColumn(field: "dept", isGrouping: true),
a!queryAggregationColumn(field: "id", aggregationFunction: "COUNT"),
}),
pagingInfo: local!chartPagingInfo
)
)
a!chartPagingInfo
will also need updated. Let’s replace the sorting from department to id.
Changing Selected Query Data
Now it’s on to local!gridDatasubset where this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local!gridDatasubset: 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")
}
),
filter: if(
isnull( local!selectedDepartment ),
null,
a!queryFilter( field: "department", operator: "=", value: local!selectedDepartment )
),
pagingInfo: local!gridPagingInfo
),
fetchTotalCount: true
)
Will get modified to query our data properly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local!gridDatasubset: a!queryEntity(
entity: cons!PURCHASE_REQUEST_ITEMS_ENTITY,
query: a!query(
selection: a!querySelection(
columns: {
a!queryColumn( field: "summary"),
a!queryColumn( field: "qty"),
a!queryColumn( field: "unitPrice")
}
),
filter: if(
isnull( local!selectedDepartment),
null,
a!queryFilter( field: "dept", operator: "=", value: local!selectedDepartment
)
),
pagingInfo: local!gridPagingInfo
),
fetchTotalCount: true
)
Again, we’ll need to adjust paging info, so a!gridPagingInfo
sorting will change from title to id.
Once we’ve got these local variables querying our data, we'll need to change the interface components so they work with our new queried data. For our purchase request use case this included:
From this, we end up with a chart that filters our purchase requests:
In our purchase request example, we’ve gone from one flat relatively simple data type to another. This will not always be the case. What should you do if you if you want to adapt an interface recipe to a CDT value that’s nested?
Let’s say that our employee CDT was nested, with department and title living in a nested position CDT. When we go to query the data our expression would look something like this:
1
2
3
4
5
6
7
a!querySelection(columns: {
a!queryColumn(field: "firstName"),
a!queryColumn(field: "lastName"),
a!queryColumn(field: "position.department"),
a!queryColumn(field: "position.title")
}
)
The alias parameter in a!queryColumn()
can be used to make referring to nested values easier in an interface expression.
We would follow a similar convention when querying process a process backed record and wanted to reference a process property:
1
2
3
4
5
6
a!queryAggregation(
aggregationColumns: {
a!queryAggregationColumn(field: "department", isGrouping: true),
a!queryAggregationColumn(field: "pp.id", aggregationFunction: "COUNT"),
}
)
The tricky part when trying to adapt a CDT with nested values comes when we want to display that data. It’s important to understand that the query brings back both the value and the data structure. This means that when I’m referencing a nested query column, I need to account for my data structure when using index()
. For things like paging grids, my gridTextCoumn looks like this:
1
2
3
4
5
a!gridTextColumn(
label: "Department",
field: "position.department",
data: index(local!datasubset.data.position, "department", {})
)
For chart data, my chartSeries would look like this:
1
2
3
4
a!chartSeries(
label: index( local!datasubset.data, "department", null),
data: index( local!datasubset.data.pp, "id", null)
)