Data Records and Grids

Overview

The Curl® data access API provides RecordSet and related classes to organize data into fields and records. The GUI toolkit provides specialized controls to aid in displaying and manipulating RecordSet data. The abstract base class RecordSetDisplay defines much of the basic functionality for displaying a RecordSet. Both RecordGrid, which provides a tabular display, and RecordForm, which provides a form-type display, inherit RecordSetDisplay. The following list discusses some of the most important functions defined by RecordSetDisplay.
In short, while both are subclasses of RecordSetDisplay, RecordGrid is is a Control designed to show many records at once while RecordForm 2is a Dialog that contains controls and is designed to show one record at a time. See Displaying Data in Forms for more information on RecordForm.

RecordGrid and Related Classes

RecordGrid is a specific implementation of RecordSetDisplay that provides a tabular display of RecordSet data built-in functionality for managing the display and the data in it. See Managing Data for more information on working with data in a RecordSet.
The following figure shows the major components of the RecordGrid display.
Figure: Record Grid
The rows and columns correspond to records and fields in the source RecordSet. You can create columns explicitly with RecordGridColumn, which enables you to set properties that control column appearance and function. Columns can also be created automatically and you only need to add a column yourself it you want to customize the column.
RecordGrid enables end users to select RecordSet data using the SelectionContext and Selection interface. RecordGrid inherits SelectionContext, and RecordGridSelection is a subclass of Selection. The property RecordGrid.selection is a RecordGridSelection that holds the current selection. The property RecordGrid.select-current-record? brings together the concepts of selection and current record. When this property is true, the current record is also selected.
The class RecordGridOptions defines a number of important local options. Options defined in RecordGridOptions are passed from RecordGrid to RecordGridColumn to RecordGridCell, unless set explicitly.
RecordGridCell defines a specialized cell. You can specify cells at the RecordGrid, RecordGridColumn, or RecordGridCell level. RecordGridOptions.cell-spec enables you to specify a cell, using either a procedure that returns a RecordGridCell, or a subclass of RecordGridCell.
RecordGrid also inherits a number of basic GUI toolkit options from ControlFrame and Graphic.

The RecordGrid Display

RecordGrid supplies substantial functionality by default. Use the next example to explore the capabilities listed here.
In addition, right-click on a grid cell brings up a context menu that offers the following choices:
The Add Filter menu is available only when the cursor is positioned over a cell in the RecordGrid. The commands Sort Ascending and Sort Descending are available when the cursor is positioned over a column. You can access the commands Remove Filters, Commit Changes, and Revert Changes when the cursor is anywhere in the RecordGrid display.

Example: Default RecordGrid Behavior
{include "../../default/support/data-long.scurl"}
{RecordGrid
    record-source = records,
    height = 10cm,
    width = 13cm
}

Modifying the RecordGrid Appearance

RecordGrid provides a number of options that enable you to modify the appearance of the grid display. These options include:
The following example changes the appearance of the RecordGrid display by setting several of these properties to values other then their default.

Example: Modifying RecordGrid Appearance
{let people:RecordSet =
    {RecordSet
        {RecordFields
            {RecordField "First", domain = String},
            {RecordField "Last", domain = String},
            {RecordField "Age", domain = int}
        },
        {RecordData First = "John", Last = "Smith", Age = 25},
        {RecordData First = "Jane", Last = "Smith", Age = 29},
        {RecordData First = "Jane", Last = "Jones", Age = 28}
    }
}
{value
    {RecordGrid
        record-source = people,
        height = 3cm,
        display-column-headers? = false,
        display-filler-column? = true,
        alternate-row-background = "#cceecc",
        grid-line-color = "#ccccff",
        horizontal-grid-line-width = 2px,
        vertical-grid-line-width = 4px
    }
}

Computed Row Background

The property RecordGrid.row-background-spec lets you supply a procedure that calculates the background color of each row. The procedure is passed the record grid, the record to be displayed, and the index of the record in RecordGrid.records. The procedure calculates the background based on this information. Remember that the procedure may be called frequently, so lengthy computation in this procedure could have an impact on performance.
The following example calculates the row background based on data in the supplied record. The background is silver for records where the value of the field Notified? is true. In this case, the background color is independent of the record's position in the grid.

Example: Data-driven row background
{include "../../default/support/data-long.scurl"}

{define-proc public {notified rg:RecordGrid, r:Record, i:int}:#Background
    {if {r.compare-field "Notified?", true} == 0 then
        {return  {Background.from-string "silver"}}
     else
        {return null}
    }
}

{RecordGrid
    record-source = records,
    height = 10cm,
    width = 13cm,
    row-background-spec = notified
}
The next example uses the record index to return a colored background for every fifth and tenth record displayed in the grid. The background depends entirely on position, and is independent of the data in the record.

Example: Position-based row background
{include "../../default/support/data-long.scurl"}

{define-proc public {notified rg:RecordGrid, r:Record, i:int}:#Background
    {if ((i + 1) mod 10) == 0 then
        {return  {Background.from-string "#ccccff"}}
     elseif ((i + 1) mod 5) == 0 then
        {return  {Background.from-string "#eeeeff"}}
     else
        {return null}
    }
}

{RecordGrid
    record-source = records,
    height = 10cm,
    width = 13cm,
    row-background-spec = notified
}

Creating a Custom Header Row

The property RecordGrid.header-options enables you to set several options governing the appearance of the header row in a single RecordGridRowOptions object.
The custom header in the following example sets control-actuator-color, which controls the color of the arrow in the header cell that indicates the current sort direction.
This example also sets the key-spec property on the record grid to the unique field id. Setting key-spec enables the record grid to maintain the current record through bulk changes, such as sorting the records. Select a record, then sort the records by clicking on one of the column headers. Note that the selection indicator at the left of the grid retains its association with the record after you have sorted the records. Try commenting this line and executing the example.
key-spec = "id",
Note that the selection indicator goes to the first record displayed in the grid when you perform a search.

Example: Customizing the header row
{let staff:RecordSet =
    {evaluate
        {url "../../default/support/data.scurl"}
    }
}
{let gradient-header:RecordGridRowOptions = 
    {RecordGridRowOptions
        background = 
            {LinearGradientFillPattern
                {Fraction2d 0, 1}, 
                {Fraction2d 0, 0}, 
                {Spectrum.from-endpoints
                    {FillPattern.get-silver},
                    {FillPattern.get-white}
                }
            },
        valign = "origin",
        halign = "left",
        control-actuator-color = "gray",
        color = "gray",
        font-size = 14pt,
        font-family = "serif"
    }
}
{let rg:RecordGrid = 
    {RecordGrid height = 10cm,
        record-source = staff,
        column-movable?= false,
        header-options = gradient-header,
        key-spec = "id",
        width = 16cm
    }
}
{value rg}

Creating Custom Columns

RecordGrid.automatic-columns? controls whether the grid automatically generates a column for each field in the record source. The default value is true, which generates columns. Each generated column sets the property RecordGridColumn.automatic? to true. You can take over management of the column by setting automatic? to false.
The following example creates a custom column to display data from two fields in the underlying data in a single column. It sets automatic-columns? to false to suppress automatic column generation, and sets RecordGrid.format-spec to a procedure that returns a single string containing both first and last name from the data set. The example must also explicitly create a column for age.

Example: Displaying Two Fields in a Single Column
{let people:RecordSet =
    {RecordSet
        {RecordFields
            {RecordField 
                "First", caption = "First Name", domain = String
            },
            {RecordField 
                "Last", caption = "Last Name", domain = String
            },
            {RecordField "Age", domain = int}
        },
        {RecordData First = "John", Last = "Smith", Age = 25},
        {RecordData First = "Jane", Last = "Smith", Age = 29},
        {RecordData First = "Jane", Last = "Jones", Age = 28}
    }
}
{let rg:RecordGrid = 
    {RecordGrid
        record-source = people,
        height = 3cm,
        automatic-columns? = false,
        {RecordGridColumn 
            "First",
            editable? = false,
            format-spec = 
                {proc {data:any, r:Record}:String
                    {return r["First"] & " "&
                        r["Last"]}
                },
            header-spec = "Full Name"
        },
        {RecordGridColumn "Age"}
    }
}
{let rg-real:RecordGrid = 
    {RecordGrid
        record-source = people,
        height = 3cm
    }
}
{value rg}
The previous example sets RecordGridOptions.header-spec to the string "Full Name", The next example sets RecordGridOptions.header-spec to a procedure that creates a more sophisticated custom header.
It also sets the background of alternating columns. Note also that the RecordGrid sets the option column-movable? to false, so you cannot reorder columns by dragging.

Example: Creating a custom column header
{let staff:RecordSet =
    {evaluate
        {url "../../default/support/data.scurl"}
    }
}
{define-proc public {make-header rgc:RecordGridColumn}:Graphic
    {return
        {TextFlowBox
            font-weight = "bold",
            font-size = 12pt,
            color = "green",
            rgc.field.caption
        }
    }
}   
{let rg:RecordGrid = 
    {RecordGrid height = 10cm,
        record-source = staff,
        width = 16cm,
        column-movable? = false,
        header-spec = make-header
    }
}
{for i:int = 0 to rg.columns.size - 1 do
    {if (i mod 2 == 0) then
        set rg.columns[i].background = "#cceecc"
    }
}
{value rg}

Grouping Columns

You can use RecordGridColumnGroup to group columns in the RecordGrid display. You supply a caption for the group, and list the columns in the order you want them to appear initially in the group. The option RecordGridOptions.enclose-header-label? determines whether the display should include a visual separator between the group caption and the column caption.
You can drag-and-drop columns in a group, but you cannot drag a column into or out of a group. You also cannot drag-and-drop the groups themselves.
Groups affect the action of the freeze frame operation. Freeze frame can take effect only at group boundaries. If you apply freeze frame anywhere in a group the freeze boundary is at the right edge of the group.

Example: Grouping RecordGrid Columns
{RecordGrid
    record-source = 
        {evaluate
            {url "../../default/support/data.scurl"}
        },
    height = 10cm,
    width = 13cm,
    {RecordGridColumn "id"},
    {RecordGridColumnGroup
        "Name",
        || remove comment to remove header separator
||--        enclose-header-label? = false,
        {RecordGridColumn "First"},
        {RecordGridColumn "Last"}
    },
    {RecordGridColumnGroup
        "Address",
        || remove comment to remove header separator
||--        enclose-header-label? = false,
        {RecordGridColumn "City"},
        {RecordGridColumn "State"}
    }
}

Modifying the RecordGrid UI Interaction

Options that modify the way RecordGrid interacts with end users include:

Setting Display Properties

The following example sets several of these options to values other than their default. The result is a grid that is very unresponsive to user gestures. Try executing the example with various combinations of these property settings and observe how the grid behaves.

Example: Modifying UI Interaction
{let people:RecordSet =
    {RecordSet
        {RecordFields
            {RecordField "First", domain = String},
            {RecordField "Last", domain = String},
            {RecordField "Age", domain = int}
        },
        {RecordData First = "John", Last = "Smith", Age = 25},
        {RecordData First = "Jane", Last = "Smith", Age = 29},
        {RecordData First = "Jane", Last = "Jones", Age = 28}
    }
}
{value
    {RecordGrid
        cells-take-focus? = false,
        column-movable? = false,
        column-resizable? = false,
        editable? = false,
        display-record-selectors? = false,
        display-navigation-panel? = false,
        record-source = people,
        height = 3cm

    }
}

Parsing User Input

The option RecordGridOptions.parse-spec enables you to control how RecordGrid converts text entered by an end user into a value in the RecordSet. The following example lets the user input a simple domain name. The parse-spec adds http://www. to form a complete url.

Example: Using parse-spec
{let urls:RecordSet =
    {RecordSet
        {RecordFields
            {RecordField 
                "Company", domain = String
            },
            {RecordField 
                "URL", domain = String
            }
        },
        {RecordData Company = "Curl", URL = "http://www.curl.com"},
        {RecordData Company = "Google", URL = "http://www.google.com"},
        {RecordData Company = "EBay", URL = "http://www.ebay.com"}
    }
}

{let rg:RecordGrid = 
    {RecordGrid
        record-source = urls,
        height = 3cm,
        automatic-columns? = false,
        {RecordGridColumn "Company"},
        {RecordGridColumn "URL",
            width = {make-elastic},
            parse-spec = 
                {proc {v:String, r:Record}:any
                    let nv:String = 
                        {if {v.prefix? "http://www."} then v
                         else "http://www." & v
                        }
                    {return nv}
                }
        }
    }
}
{value
    {VBox
        rg,
        {HBox
            {CommandButton 
                width = {make-elastic}, label = "append record",
                {on Action do
                    {urls.append {RecordData}}
                }
            },
            {CommandButton 
                width = {make-elastic}, label = "commit records",
                bound-command = {rg.get-command "commit"}                
            },
            {CommandButton 
                width = {make-elastic}, label = "revert records",
                bound-command = {rg.get-command "revert"}                
            }
        }
    }        
}

Freezing a Region of the Display

RecordGrid provides a freeze frame capability that enables you to set a specified number of rows and columns to be non-scrolling. The following RecordGrid properties and methods manage the number of non-scrolling rows and columns.
The offset values enable you to specify a frozen region with rows and columns scrolled out of view at the top and left of the display.
RecordGrid specifies the non-scrolling region, but the RecordGridUI implements the way RecordGrid displays frozen columns and rows. If you need to create a subclass of RecordGridUI, you can use the method RecordGridUI.note-frozen-count-changed to enable your subclass to respond to changes in the non-scrolling region.
The following example illustrates the default behavior, implemented by StandardRecordGridUI. Note that the first data record contains header information. The example uses RecordGrid.set-frozen-region to freeze the header record and the id column.

Example: Freezing a Header Row
{let staff:CsvRecordSet =
    {CsvRecordSet
        {url "../../default/support/data.csv"}
    }
}
{let rg:RecordGrid = 
    {RecordGrid
        record-source = staff,
        display-column-headers? = false,
        display-record-selectors? = false
    }
}
{rg.set-frozen-region 1, 1}
{value rg}

Modifying the Data Displayed in a RecordGrid

Sorting and Filtering Records

You can impose filtering on the source RecordSet through the RecordGrid. You can filter based on a cell value through a context menu on the grid. You can also set the RecordGrid.filter property. The value you set is used to set the filter property of the RecordGrid.records RecordView.
You can view sorted records in a similar way. You can sort a single column through a context menu on the grid. You can also set the RecordGrid.sort property. The value you set is used to set the sort property of the RecordGrid.records RecordView.
To change the sorting and filtering interactively, right-click a cell or column heading and make a choice from the context menu that pops up. The property RecordGrid.filter-menu-proc allows you to customize this menu. This example includes a simple filter-menu-proc, which reverses the order of items in the menu.

Example: Using RecordGrid.filter and RecordGrid.sort
{let staff:RecordSet =
    {evaluate
        {url "../../default/support/data.scurl"}
    }
}
{let rg:RecordGrid = 
    {RecordGrid
        record-source = staff, height = 5cm, width = 16cm,
        column-selection-enabled? = true,
        filter-menu-proc =
            {proc {array:{Array-of MenuItem}, cell:RecordGridCell}:{Array-of MenuItem}
                {array.reverse}
                {return array}
            } 
    }
}
{VBox
    rg,
    {HBox
        {CommandButton label = "Sort by City, State", 
            width = {make-elastic},
                {on Action do
                    set rg.sort = "City, State"
                }
        },
        {CommandButton 
            width = {make-elastic},
            label = "Filter for Cambridge, MA",
            {on Action do
                set rg.filter = 
                    {RecordData City = "Cambridge", State = "MA"}
            }
        }
    }
}

Removing Columns from a RecordGrid Display

The next example enables the end user choose to view a subset of the columns in a RecordGrid.
Note that this example uses two properties called "columns." One is RecordGrid.columns, the columns currently displayed. The other is RecordGridSelection.columns, the columns currently selected. The example deletes the RecordGridSelection.columns from the RecordGrid.columns.
This example uses the RecordGrid.selection property to delete the selected columns from the display. The columns property of RecordGridSelection is an Iterator-of RecordGridColumn, which enables you to loop through the selected columns, processing each one in turn. You must set RecordGrid.automatic-columns? to false, and generate the initial set of columns explicitly. If automatic-columns? is true, columns are automatically regenerated as soon as they are deleted.

Example: Remove and Restore Columns
{let staff:RecordSet =
    {evaluate
        {url "../../default/support/data.scurl"}
    }
}
{let rg:RecordGrid = 
    {RecordGrid
        record-source = staff, height = 5cm, width = 16cm,
        column-selection-enabled? = true
    }
}
{let original-columns:{Array-of RecordGridColumn} = {rg.columns.clone}}
{VBox
    rg,
    {HBox
        {CommandButton label = "Remove Selected Column(s)", width = 8cm,
            {on Action do
                {let new-columns:{Array-of RecordGridColumn} = {rg.columns.clone}}
                {for col:RecordGridColumn in rg.selection.columns do
                    {new-columns.remove {new-columns.find col}}
                }
                set rg.columns = new-columns
            }
        },
        {CommandButton label = "Restore Original Columns", width = 8cm,
            {on Action do
                set rg.columns = original-columns
            }
        }
    }
}

Selecting Data with RecordGrid

The following properties enable different types of selection.
RecordGrid provides a number of methods that select data in a RecordGrid. These methods include:
RecordGrid.selection holds the current selection. RecordGrid provides a number of methods that act on the selection. These methods include:

Selecting Rows and Columns

When the user selects rows and columns in a RecordGrid, the RecordGrid object records the selection in the property RecordGrid.selection. RecordGrid.selection is a RecordGridSelection, and provides properties that supply the number of columns or records selected, and enable you to iterate over the selected columns and records.
The following example uses RecordGridSelection.record-count and RecordGridSelection.records to report on selected records, and RecordGridSelection.column-count and RecordGridSelection.columns to report on selected columns. Try selecting columns and/or records and then pressing the supplied command buttons. Add to a selection by typing ctrl+left-click.
Note also that this example sets RecordGrid.select-current-record? to true, which enables you to select a record by clicking on a cell in the record. It is useful to set RecordGridOptions.cells-take-focus? to false when using select-current-record?, so that clicking on the cell causes selection rather than shifting focus to the cell.

Example: Using RecordGrid to Select Columns and Records
{let staff:RecordSet =
    {evaluate
        {url "../../default/support/data.scurl"}
    }
}
{let rg:RecordGrid = 
    {RecordGrid height = 10cm,
        column-selection-enabled? = true,
        select-current-record? = true,
        cells-take-focus? = false,
        record-source = staff,
        width = 16cm
    }
}
{let msg:VBox = {VBox}}
{VBox
    rg,
    {HBox
        {CommandButton 
            width = 8cm,
            label = "Process records",
            {on Action do
                {if rg.selection.record-count == 0 then
                    {popup-message "No records selected"}
                 else
                    {for i:int in rg.selection.records do
                        let r:Record = rg.records[i]
                        {msg.add {format "%s %s", 
                                     r["First"],
                                     r["Last"]
                                 }
                        }
                    }
                    {popup-message
                        msg
                    }
                    {msg.clear}
                }
            }
        },
        {CommandButton 
            width = 8cm,
            label = "Process columns",
            {on Action do
                {if rg.selection.column-count == 0 then
                    {popup-message "No columns selected"}
                 else
                    {for c:RecordGridColumn in rg.selection.columns do
                        {msg.add c.field.name}
                    }
                    {popup-message
                        msg
                    }
                    {msg.clear}
                }
            }
        }
    }
}

Selecting Regions

You can also click and drag to select rectangular regions made up of columns and rows. Use the ctrl key to select more than one region. RecordGrid maintains information about selected regions in RecordGrid.selection.
The following example uses RecordGridSelection.region-count and RecordGridSelection.regions to report on selected regions. Try selecting one or more regions and then pressing the command button.

Example: Using RecordGrid to select Regions
{let staff:RecordSet =
    {evaluate
        {url "../../default/support/data.scurl"}
    }
}
{let rg:RecordGrid = 
    {RecordGrid height = 10cm,
        region-selection-enabled? = true,
        record-selection-enabled? = false,
        record-source = staff,
        width = 16cm
    }
}
{let msg:VBox = {VBox}}
{VBox
    rg,
    {HBox
        {CommandButton 
            width = {make-elastic},
            label = "Process regions",
            {on Action do
                {if rg.selection.region-count == 0 then
                    {popup-message "No regions selected"}
                 else
                    {msg.add "regions: " & rg.selection.region-count}
                    {for r:RecordGridRegion in rg.selection.regions do
                        {msg.add "columns: " & r.column-count 
                            & " First column index: " & r.first-column}
                        {msg.add "rows: " & r.row-count 
                            & " First row index: " & r.first-row}
                        {msg.add "---"}
                    }
                    {popup-message
                        msg
                    }
                    {msg.clear}
                }
            }
        }
    }
}

Copying and Pasting Data

RecordGrid supports copy and paste operations on selected rows, columns, and regions through the following Commands:
The standard UI makes these commands accessible through the standard keyboard accelerators, such as ctrl + c for copy, and through a right-click context menu.
The selection, copy, and paste commands operate on the rows and columns displayed in the grid. If you apply filters, or remove columns from the display, and act on only the fields and records that remain visible.
The following example illustrates a number of these commands. Note the following points:

Example: Select, copy and paste
{let staff:RecordSet =
    {evaluate
        {url "../../default/support/data.scurl"}
    }
}
{let rg:RecordGrid = 
    {RecordGrid height = 10cm,
        region-selection-enabled? = true,
        record-selection-enabled? = false,
        select-current-record? = true,
        cells-take-focus? = false,
        record-source = staff,
        width = 16cm
    }
}
{let msg:VBox = {VBox}}
{let fixed-region:RecordGridRegion = 
    {RecordGridRegion
        1, 1, 2, 2
    }
}
{VBox
    rg,
    {HBox
        {CommandButton 
            width = {make-elastic}, 
            label = "Select All",
            bound-command = {rg.get-command "select-all"}                
        },
        {CommandButton 
            width = {make-elastic}, 
            label = "Select Nothing",
            bound-command = {rg.get-command "select-nothing"}                
        },
        {CommandButton
            width = {make-elastic}, 
            label = "Select Region",
            {on Action do
                {rg.select-region fixed-region, additive? = true}
            }
        },
        {CommandButton
            width = {make-elastic}, 
            label = "Deselect Region",
            {on Action do
                {rg.deselect-region fixed-region}
            }
        },
        {CommandButton 
            width = {make-elastic}, 
            label = "Copy Selection",
            bound-command = {rg.get-command "copy"}                
        },
        {CommandButton 
            width = {make-elastic}, 
            label = "Paste",
            bound-command = {rg.get-command "paste"}                
        }
    }
}

Creating a Custom Cell

In the following example, the RecordSet maritime-signal-flags contains a field called flag that contains strings that identify graphics files. Displaying these files as a graphic image in a RecordGrid requires a specialized RecordGridCell.
The example creates a subclass of StandardRecordGridCell called FlagCell, which overrides the method refresh-data, in order to display an image of the flag. The cell also uses RecordGridCell.can-update? to set RecordGridCell.cells-take-focus?, which prevents the cell from taking focus if it cannot be updated.
The example also uses RecordGridColumn to create columns in the grid. The column that displays the flag data uses FlagCell as its cell-spec. The column also sets the width to a value appropriate for the width of the images, and sets column-resizable? to false, so the user cannot change the column width and distort the images.
Note also that the RecordGrid in this example sets the option editable? to false, which makes the entire grid display-only.

Example: Display a RecordSet with RecordGrid
{let maritime-signal-flags:RecordSet =
    {evaluate
        {url "../../default/support/flag-data.scurl"}
    }
}
{define-class public open FlagCell
  {inherits StandardRecordGridCell}
  
  field private flag:Frame = 
      {Frame width={make-elastic}, height={make-elastic}}

  {constructor public {default}
    {construct-super}
    set self.height = 42px
    {self.add-internal self.flag}
    set self.cells-take-focus? = self.can-update?
  }
  
  {method public open {refresh-data}:void
    let (data:String, valid?:bool) = {self.get-formatted-data}
    {if valid? and data != ""  then
        set self.flag.background = {url data}
     else
        {unset self.flag.background}
    }
  }
}
{let rg:RecordGrid = 
        {RecordGrid 
            width = 14cm,
            height = 14cm,
            record-source = maritime-signal-flags,
            editable? = false,
            automatic-columns? = false,
            {RecordGridColumn  width = 1.3cm, "letter"},
            {RecordGridColumn  width = 2cm, "phonetic"},
            {RecordGridColumn  width = 1.3cm, "colors"},
            {RecordGridColumn  width = 1.3cm, "red?"},
            {RecordGridColumn  width = 1.3cm, "white?"},
            {RecordGridColumn  width = 1.3cm, "blue?"},
            {RecordGridColumn  width = 1.3cm, "yellow?"},
            {RecordGridColumn  width = 1.3cm, "black?"},
            {RecordGridColumn  "flag", width = 53px, 
                column-resizable? = false, cell-spec = FlagCell}
        }
}
{value rg}

RecordSetDisplay Commands

The class RecordSetDisplay is a CommandContext and supports a number of commands for manipulating underlying data. Both RecordGrid and RecordForm inherit these commands, as do any subclasses of RecordSetDisplay you create. You can use these commands to provide functionality for menu items and controls. The following example uses the commit and revert commands for the buttons commit records and revert records.

Example: Data from User
{let people:RecordSet =
    {RecordSet
        {RecordFields
            {RecordField 
                "First", caption = "first name", domain = String
            },
            {RecordField 
                "Last", caption = "last name", domain = String
            },
            {RecordField 
                "Age", caption = "age", domain = int
            }
        },
        {RecordData First = "John", Last = "Smith", Age = 25},
        {RecordData First = "Jane", Last = "Smith", Age = 29},
        {RecordData First = "Jane", Last = "Jones", Age = 28}
    }
}
{let rg:RecordGrid = 
    {RecordGrid
        height = 3cm, width = 9.75cm, record-source = people
    }
}
{value
    {VBox
        rg,
        {HBox
            {CommandButton 
                width = {make-elastic}, label = "append record",
                {on Action do
                    {people.append {RecordData}}
                }
            },
            {CommandButton 
                width = {make-elastic}, label = "commit records",
                bound-command = {rg.get-command "commit"}                
            },
            {CommandButton 
                width = {make-elastic}, label = "revert records",
                bound-command = {rg.get-command "revert"}                
            }
        }
    }        
}

Using Rich Text in a Record Grid

The RichTextArea control enables users to input and format rich text content. There are two ways you can incorporate the rich text provided by RichTextArea in a RecordGrid:
The following example adds messages to a RecordSet. Use the TextArea to input a person's name, and the RichTextArea to input a message that can include text formatting. Click Add Messages to add the record to a RecordSet. Because this example does not place the controls in a RecordForm, you do not have a navigation panel available to view records as you add them. Click Show Messages to view all the records you have added in a RecordGrid. Note the use of format-as-curl-source-fragment to store the message, and the special-purpose RecordGridCell, CodeCell, which displays it.

Example: Storing Rich Text as a Code String
{let messages:RecordSet =
    {RecordSet
        {RecordFields
            {RecordField "Recipient", domain = String},
            {RecordField "Message", domain = String}
        }
    }
}
{define-class public open CodeCell
  {inherits StandardRecordGridCell}
  {constructor public {default}
    {construct-super}
    {self.add-internal {TextFlowBox}}
  }
  {method public open {refresh-data}:void
    {super.refresh-data}
    let (data:String, valid?:bool) = {self.get-formatted-data}
    {self.child.graphic.clear}
    {if valid? then
        {self.child.graphic.add
            {evaluate data}
        }
    }
  }
}
{let rg:RecordGrid = 
    {RecordGrid
        record-source = messages,
        automatic-columns? = false,
        {RecordGridColumn "Recipient"},
        {RecordGridColumn "Message", cell-spec = CodeCell}
    }
}
{let rcp:String = ""}
{let msgstr:String = ""}
{let ta:TextArea = {TextArea height = 1cm}}
{let rta:RichTextArea = {RichTextArea height = 2cm}}
{VBox
    {Table
        {row-prototype "Message recipient: ", ta},
        {row-prototype "Message text: ", rta}
    },
    {HBox
        {CommandButton label = "Add Message",
            {on e:Action do
                set rcp = ta.value
                set msgstr = {rta.format-as-curl-source-fragment}
                {messages.append {RecordData Recipient = rcp, Message = msgstr}}
                {e.consume}
            }
        },
        {CommandButton label = "Show Messages",
            {on e:Action do
                {{View {value rg}}.show}
                {e.consume}
            }
        }
    }
}
This example uses a RecordForm to store a RichTextString in a RecordSet. For more information on using rich text with RecordForm, see Using Rich Text in a Record Form. When you click show grid, the special-purpose RecordGridCell, RichTextAreaCell, displays the RichTextString in the grid. Because this example uses the RichTextArea for display only, it sets display-formatting-panel? to false to suppress display of the formatting controls. RichTextAreaCell also must establish a new ActiveTraversalContainer.

Example: Storing Rich Text as a RichTextString
{let messages:RecordSet =
    {RecordSet
        {RecordFields
            {RecordField "Recipient", domain = String},
            {RecordField "Message", domain = #any}
        },
        {RecordData Recipient = "Recipient", 
            Message = {RichTextString.from-string "Message"}}

    }
}
{define-class public open RichTextAreaCell
  {inherits StandardRecordGridCell}
  {doc-next {purpose Construct this object.}}
  {constructor public {default}   
    {construct-super}
    
    {self.add-internal 
        {RichTextArea height = 2cm,
            active-traversal-container = null,
            display-formatting-panel? = false}
    }
  }
  {method public open {refresh-data}:void
    {super.refresh-data}
    let rta:RichTextArea = self.child.graphic asa RichTextArea
    let (val:any, valid?:bool) = {self.get-data}
    {if valid? then
        set rta.value = val
     else
        set rta.ui-object.control-content-background = self.control-color 
    }
  }
}
{let rg:RecordGrid = 
    {RecordGrid
        record-source = messages,
        editable? = false,
        automatic-columns? = false,
        {RecordGridColumn "Recipient"},
        {RecordGridColumn "Message", cell-spec = RichTextAreaCell}
    }
}
{let rf:RecordForm = 
    {RecordForm
        record-source = messages,
        {VBox
            {TextArea {bind value to "Recipient"}},
            {RichTextArea 
                height = 2cm,
                {bind value to "Message"}
            }
        }
    }
}
{value
    {VBox
        rf,
        {HBox
            {CommandButton 
                label = "append record",
                {on Action do
                    {messages.append 
                        {RecordData Recipient = "Recipient", 
                            Message = {RichTextString.from-string "Message"}}}
                }
            },
            {CommandButton
                label = "update record",
                {on ac:Action do
                    {if-non-null {rf.update} then
                        {if {popup-question 
                                {message
                                    There were one or more errors on this form.
                                    Continue?
                                }
                            } == Dialog.no
                         then
                            {ac.consume}
                        }
                    }
                }
            },
            {CommandButton 
                label = "commit changes",
                bound-command = {rf.get-command "commit"}                
            },
            {CommandButton 
                label = "revert records",
                bound-command = {rf.get-command "revert"}
            }
            ,
            {CommandButton 
                label = "show grid",
                {on ac:Action do
                    {{View
                         {value
                             rg
                         }
                     }.show}
                    {ac.consume}
                }
            }
        }
    }
}