How to Adapt a Pattern for Your Application

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:

  • Hard-coded: typically seen on recipes that editable form fields have data hard-coded. These values are stored as a local variable.
  • Queried: typically seen on recipes that display charts or Read-Only Grids. These values are brought in via a!queryEntity() and stored in a local variable

We 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.

Swap Out Hard-Coded Data

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:

  • id (number (integer)) - primary key value to persist data to a relational database
  • summary (text) - Describes the purchase request, for example, the item name
  • qty (number (integer)) - how many of that particular item
  • unitPrice (number (decimal)) - how much each item costs
  • dept (text) - which department needs the particular item
  • due (date) - when this item needs to be received by

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.

Making Sure the Interface Components Work with My Data

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:

  • Changing the editable grid's addRowlink parameter
  • Updating the form and submit button's label
  • Removing the a!localVariables() function since there were no remaining local variables

Again, 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.

Swap Out Queried Data

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.

Making Sure the Interface Components Work with My Data

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:

  • Changing the pie chart's chartSeries to refer to item data
  • Changing the grid's text columns to refer to item data
  • Formatting the link and pie chart labels

From this, we end up with a chart that filters our purchase requests:

Dealing with Nested Values

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 Read-Only 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) 
)
FEEDBACK