Delete Rows in a Grid

SAIL Recipes give you an opportunity to explore different interface design patterns. To learn how to directly use SAIL recipes within your interfaces, see Adapt a SAIL Recipe to Work with My Applications.


Delete one or more rows of data in a read-only paging grid.


This design pattern is not recommended for offline interfaces because reflecting immediate changes in an interface based on user interaction requires a connection to the server.

This scenario demonstrates:

  • How to to use a!gridSelection for a selectable grid
  • How to use update a grid by removing a row from that grid


  local!removeFailure: false,
  local!removedIDs: {},
  local!employeeData: {
    { 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 },
    { id: 4, firstName: "Angela" , lastName: "Cooper" , department: "Sales" , title: "Manager" , phoneNumber: "555-123-4567" , startDate: today()-240 },
    { id: 5, firstName: "Elizabeth" , lastName: "Ward" , department: "Sales" , title: "Sales Associate" , phoneNumber: "555-987-6543" , startDate: today()-240 },
    { id: 6, firstName: "Daniel", lastName: "Lewis" , department: "HR" , title: "Manager" , phoneNumber: "555-876-5432" , startDate: today()-180 },
    { id: 7, firstName: "Paul" , lastName: "Martin" , department: "Finance" , title: "Analyst" , phoneNumber: "555-609-3691" , startDate: today()-150 },
    { id: 8, firstName: "Jessica" , lastName: "Peterson" , department: "Finance" , title: "Analyst" , phoneNumber: "555-987-6543" , startDate: today()-150 },
    { id: 9, firstName: "Mark" , lastName: "Hall" , department: "Professional Services" , title: "Director" , phoneNumber: "555-012-3456" , startDate: today()-150 },

  /* Set the default paging and sorting config `*/
  local!gridSelection: a!gridSelection(
    pagingInfo: a!pagingInfo(
      startIndex: 1,
      batchSize: 3,
      sort: a!sortInfo(
        field: "id",
        ascending: true
    /* Replace the value of local!datasubset with `a!queryEntity()`, or */
    /* `queryrecord()`, or use your own CDT array in todatasubset()     */
    local!datasubset: todatasubset(local!employeeData, local!gridSelection.pagingInfo),
      label: "SAIL Example: Delete from Grid",
          secondaryButtons: {
              label: "Remove",
              value: true,
              saveInto: {
                a!save(local!employeeData, remove(local!employeeData, wherecontains(local!gridSelection.selected, local!,
                  a!save(local!removedIDs, append(local!removedIDs, local!gridSelection.selected))
                /*`  This sets the start index back by one page when  *
                 *  all of the results on the final page are deleted.  */
                    local!gridSelection.pagingInfo.startIndex <= length(local!employeeData),
                    /* If at least as many items as the previous start index exist, use it `*/
                    /* Otherwise, create a new start index to avoid an error */
                      /* If the last item in the grid was deleted or there are only enough items for one page, use 1 */
                      /*` If there are multiple pages' worth remaining, adjust the start index so that the last page */
                      /* of items is shown                                                                           */
                a!save(local!gridSelection.selected, null)
          instructions: if(local!removeFailure, "Select one or more items to remove.", ""),
          totalCount: local!datasubset.totalCount,
          columns: {
            a!gridTextColumn(label: "First", field: "firstName", data: index(local!, "firstName" , {})),
            a!gridTextColumn(label: "Last",  field: "lastName",  data: index(local!, "lastName" , {})),
            a!gridTextColumn(label: "Department", field: "department", data: index(local!, "department" , {})),
            a!gridTextColumn(label: "title", field: "title", data: index(local!, "title" , {}))
          identifiers: index(local!, "id" , {}),
          value: local!gridSelection,
          saveInto: {
                or(isnull(local!gridSelection.selected), length(local!gridSelection.selected) < 1),
          selection: true
           label: "Employee Data",
           value: local!employeeData,
           readOnly: true
          label: "Removed IDs",
          readOnly: true,
          value: local!removedIDs
      buttons: a!buttonLayout(
        primaryButtons: a!buttonWidgetSubmit(
          label: "Submit"

Test it out

  1. Click Remove and note that instructions appear above the grid because you have not selected any rows
  2. Select a checkbox or two, click Remove, and note that the rows are removed from the grid
    • The Employee Data text field displays the value of the entire test data object
    • The Removed IDs text field tracks the IDs of rows deleted from the grid. This allows the user to verify that the data has been modified by the delete action.

Notable implementation details

  • The value of the grid is GridSelection rather than PagingInfo, and the value that the grid returns when the user interacts with it is also a GridSelection. The PagingInfo information is embedded in GridSelection. GridSelection must be used when a grid is configured for selection.
  • When deleting rows in a paging grid, we need to tell the grid what to display when all the results on a page are deleted. Updating the startIndex of the local!gridSelection.pagingInfo makes sure that the grid does not break when all the entries on the last page are deleted, resetting the view to the new last page of results. Since we're now manually setting our start index when the last page is deleted, we now have to catch when the last result overall is deleted, to correctly set the index to 1.
  • In this example, we stored the ids of the removed items. This is useful when you want to remove items from an external source, such as a database or another process. If the data on the form is the authoritative version, then you may wish to return the remaining items, rather than what was removed. For that, follow the process below for local!employeeData instead of local!removedIDs.
  • The wherecontains() function operates on parameters of the same type, so once you switch to your own data, you may have to wrap cast() around local!gridSelection.selected to make it match the type of your identifiers. For example: wherecontains(tointeger(local!gridSelection.selected), local! would work successfully if local!employeeData is a CDT array where the id field is of type Number (Integer).