Grid with Selection Pattern

This pattern provides useful save behavior and is an example of good UX design for a grid that allows users to select items and easily view their selections when there are multiple pages of data. This page explains how you can use the grid with selection pattern in your interface, and walks you through the design structure of the pattern in detail.

The saves determine whether a paging action was taken, and if there was, the interface will request the next page of data. This efficiently loads the next page of data when users interact with the grid. The saveInto() also compares the current and previous selections to determine if an item was added or removed. The added or removed item is then appended or removed from the selection array. This is more efficient than updating the selection each time the user interacts with the grid.

This pattern is also an example of good UX design for a grid that allows users to select items and easily view their selections when there are multiple pages of data.

grid_with_selection_pattern_orig.png

Design Structure

The purpose of this section is to break down this expression so you can better understand how to adapt this pattern to your own data.

The main components in this pattern are a rich text header, grid field, and column layout. The image below displays how the pattern looks on a blank interface with callouts of the main components. You can examine the entire expression or jump down to the subsections below with referenced line numbers to see a detailed breakdown of the main components.

grid_with_selection_pattern.png

Pattern Expression

When you drag and drop the grid with selection pattern onto your interface, 146 lines of expressions will be added to the section where you dragged it.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
{
  load(
    local!selectionInfo: a!gridSelection(
      pagingInfo: a!pagingInfo(
        startIndex: 1,
        batchSize: 3
      ),
      selected: {}
    ),
    local!oldSelectionInfo: local!selectionInfo,
    local!employees: {
      {id: 11, name: "Elizabeth Ward",  dept: "Engineering",     role: "Senior Engineer",      startDate: today()-500},
      {id: 22, name: "Michael Johnson", dept: "Finance",         role: "Payroll Manager",      startDate: today()-1000},
      {id: 33, name: "John Smith",      dept: "Engineering",     role: "Quality Engineer",     startDate: today()-100},
      {id: 44, name: "Diana Lopez",     dept: "Engineering",     role: "UX Designer",          startDate: today()-1200},
      {id: 55, name: "Francois Morin",  dept: "Sales",           role: "Account Executive",    startDate: today()-700},
      {id: 66, name: "Maya Kapoor",     dept: "Sales",           role: "Regional Director",    startDate: today()-1400},
      {id: 77, name: "Anthony Wu",      dept: "Human Resources", role: "Benefits Coordinator", startDate: today()-300}
    },
    /* This variable would normally be retrieved with a rule that returns a datasubset, like rule!getEmployees(pagingInfo: local!selectionInfo.pagingInfo). */
    local!employeeDatasubset: todatasubset(local!employees, local!selectionInfo.pagingInfo),
    /* This variable would be used to pass the selected items out of this interface, such as to a process model. */
    local!selectedEmployees,
    {
      a!richTextDisplayField(
        label: "",
        labelPosition: "COLLAPSED",
        value: {
          a!richTextHeader(
            text: "Performance Review Portal"
          )
        }
      ),
      a!columnsLayout(
        columns:{
          a!columnLayout(
            contents:{
              a!gridField(
                label: "Employee Directory",
                totalCount: local!employeeDatasubset.totalCount,
                columns: {
                  a!gridTextColumn(
                    label: "Name",
                    data: index(local!employeeDatasubset.data, "name", {})
                  ),
                  a!gridTextColumn(
                    label: "Department",
                    data: index(local!employeeDatasubset.data, "dept", {})
                  ),
                  a!gridTextColumn(
                    label: "Start Date",
                    data: index(local!employeeDatasubset.data, "startDate", {}),
                    alignment: "RIGHT"
                  )
                },
                identifiers: index(local!employeeDatasubset.data, "id", null),
                value: local!selectionInfo,
                saveInto: {
                  local!selectionInfo,
                    /* This save updates the grid to get the next page of data. */
                  a!save(
                    local!employeeDatasubset,
                    todatasubset(local!employees, local!selectionInfo.pagingInfo)
                  ),
                  /* This save adds any selected items to the selectedEmployees variable by comparing the current and previous selection. */
                  a!save(
                    local!selectedEmployees,
                    append(
                      local!selectedEmployees,
                      index(
                        local!employeeDatasubset.data,
                        wherecontains(
                          difference(
                            local!selectionInfo.selected,
                            cast(
                              runtimetypeof(local!selectionInfo.selected),
                              local!oldSelectionInfo.selected
                            )
                          ),
                          index(local!employeeDatasubset.data, "id", {})
                        ),
                      ""
                      )
                    )
                  ),
                  /* This save removes any unselected items from the selectedEmployees variable by comparing the current and previous selection. */
                  a!save(
                    local!selectedEmployees,
                    remove(
                      local!selectedEmployees,
                      wherecontains(
                        difference(
                          cast(
                            runtimetypeof(local!selectionInfo.selected),
                            local!oldSelectionInfo.selected
                          ),
                          local!selectionInfo.selected
                        ),
                        index(local!selectedEmployees, "id", {})
                      )
                    )
                  ),
                  /* Once all other saves are executed, this variable is used to cache the current value of selection for comparison in the next save evaluation. */
                  local!oldSelectionInfo
                },
                selection: true,
                shadeAlternateRows: true
              )
            }
          ),
          a!columnLayout(
            contents:{
              a!richTextDisplayField(
                label: "Selected Employees",
                value: {
                  if(
                    or(isnull(local!selectedEmployees), length(local!selectedEmployees) = 0),
                    a!richTextItem(
                      text: "None",
                      style: "EMPHASIS"
                    ),
                    a!forEach(
                      local!selectedEmployees,
                      {
                        a!richTextIcon(
                          icon: "USER-CIRCLE",
                          color: "ACCENT"
                        ),
                        "  ",
                        a!richTextItem(
                          text: fv!item.name
                        ),
                        char(10)
                      }
                    )
                  )
                }
              )
            },
            width: "NARROW"
          )
        }
      )
    }
  )
}

[Line 1-23] Define Paging Info, Employee Data, and Selected Employees

At the top of the pattern, local variables set up the grid paging info, employee data, and selected employees.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  load(
    local!selectionInfo: a!gridSelection(
      pagingInfo: a!pagingInfo(
        startIndex: 1,
        batchSize: 3
      ),
      selected: {}
    ),
    local!oldSelectionInfo: local!selectionInfo,
    local!employees: {
      {id: 11, name: "Elizabeth Ward",  dept: "Engineering",     role: "Senior Engineer",      startDate: today()-500},
      {id: 22, name: "Michael Johnson", dept: "Finance",         role: "Payroll Manager",      startDate: today()-1000},
      {id: 33, name: "John Smith",      dept: "Engineering",     role: "Quality Engineer",     startDate: today()-100},
      {id: 44, name: "Diana Lopez",     dept: "Engineering",     role: "UX Designer",          startDate: today()-1200},
      {id: 55, name: "Francois Morin",  dept: "Sales",           role: "Account Executive",    startDate: today()-700},
      {id: 66, name: "Maya Kapoor",     dept: "Sales",           role: "Regional Director",    startDate: today()-1400},
      {id: 77, name: "Anthony Wu",      dept: "Human Resources", role: "Benefits Coordinator", startDate: today()-300}
    },
    /* This variable would normally be retrieved with a rule that returns a datasubset, like rule!getEmployees(pagingInfo: local!selectionInfo.pagingInfo). */
    local!employeeDatasubset: todatasubset(local!employees, local!selectionInfo.pagingInfo),
    /* This variable would be used to pass the selected items out of this interface, such as to a process model. */
    local!selectedEmployees,

[Line 24-33] Display a Rich Text Header

The first visible component is the rich text header that appears above the grid.

24
25
26
27
28
29
30
31
32
33
   {
      a!richTextDisplayField(
        label: "",
        labelPosition: "COLLAPSED",
        value: {
          a!richTextHeader(
            text: "Performance Review Portal"
          )
        }
      ),

[Line 34-59] Use a Grid Field Component and Index Your Data

We use a grid field component and index data in each a!gridTextColumn().

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
a!columnsLayout(
        columns:{
          a!columnLayout(
            contents:{
              a!gridField(
                label: "Employee Directory",
                totalCount: local!employeeDatasubset.totalCount,
                columns: {
                  a!gridTextColumn(
                    label: "Name",
                    data: index(local!employeeDatasubset.data, "name", {})
                  ),
                  a!gridTextColumn(
                    label: "Department",
                    data: index(local!employeeDatasubset.data, "dept", {})
                  ),
                  a!gridTextColumn(
                    label: "Start Date",
                    data: index(local!employeeDatasubset.data, "startDate", {}),
                    alignment: "RIGHT"
                  )
                },
                identifiers: index(local!employeeDatasubset.data, "id", null),
                value: local!selectionInfo,
                saveInto: {
                  local!selectionInfo,

[Line 60-85] Use saveInto() to Update Data in Your Grid

The first save updates the data in the grid when the user pages or sorts. The second save adds any newly selected items into the selectedEmployees local variable. This save also compares the current and previous selections.

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
 /* This save updates the grid to get the next page of data. */
                  a!save(
                    local!employeeDatasubset,
                    todatasubset(local!employees, local!selectionInfo.pagingInfo)
                  ),
                  /* This save adds any selected items to the selectedEmployees variable by comparing the current and previous selection. */
                  a!save(
                    local!selectedEmployees,
                    append(
                      local!selectedEmployees,
                      index(
                        local!employeeDatasubset.data,
                        wherecontains(
                          difference(
                            local!selectionInfo.selected,
                            cast(
                              runtimetypeof(local!selectionInfo.selected),
                              local!oldSelectionInfo.selected
                            )
                          ),
                          index(local!employeeDatasubset.data, "id", {})
                        ),
                      ""
                      )
                    )
                  ),

[Line 86-102] Use saveInto() to Remove Unselected Employees

This save removes any unselected employees from the selectedEmployees array after comparing the current and previous selections.

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
 /* This save removes any unselected items from the selectedEmployees variable by comparing the current and previous selection. */
                  a!save(
                    local!selectedEmployees,
                    remove(
                      local!selectedEmployees,
                      wherecontains(
                        difference(
                          cast(
                            runtimetypeof(local!selectionInfo.selected),
                            local!oldSelectionInfo.selected
                          ),
                          local!selectionInfo.selected
                        ),
                        index(local!selectedEmployees, "id", {})
                      )
                    )
                  ),

[Line 103-110] Use local!oldSelectionInfo to Save the Currect Value

Once all saves are exceuted, local!oldSelectionInfo is used to save the current value that is selected so that you can compare this local variable with all other save evaluations.

103
104
105
106
107
108
109
110
 /* Once all other saves are executed, this variable is used to cache the current value of selection for comparison in the next save evaluation. */
                  local!oldSelectionInfo
                },
                selection: true,
                shadeAlternateRows: true
              )
            }
          ),

[Line 111-146] Use a Column Layout to Display the Selected Employees

In a separate column layout, we display each of the selected employees from the grid in a rich text display field using a!forEach() to loop through the selectedEmployees array and display a user icon next to each name. This section allows the user to review all their selections easily even when they are on a different page.

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
 a!columnLayout(
            contents:{
              a!richTextDisplayField(
                label: "Selected Employees",
                value: {
                  if(
                    or(isnull(local!selectedEmployees), length(local!selectedEmployees) = 0),
                    a!richTextItem(
                      text: "None",
                      style: "EMPHASIS"
                    ),
                    a!forEach(
                      local!selectedEmployees,
                      {
                        a!richTextIcon(
                          icon: "USER-CIRCLE",
                          color: "ACCENT"
                        ),
                        "  ",
                        a!richTextItem(
                          text: fv!item.name
                        ),
                        char(10)
                      }
                    )
                  )
                }
              )
            },
            width: "NARROW"
          )
        }
      )
    }
  )
}
FEEDBACK