| Summary: | - RecordSet organizes data into records and fields.
- ConnectedRecordSet lets you get data from an
existing database.
- RecordGrid supports tabular display of data
in a RecordSet
- RecordForm supports form-based data display.
|
The Curl® API package
CURL.DATA-ACCESS.BASE contains a number of classes that enable
you to write applications that manage data organized in records
and fields. It enables the end user to perform many database-like
operations on structured data, such as filtering and sorting,
directly on the client machine without needing to invoke database
functions on the server. This package also includes support for
storing structured data on the client in the standard CSV (comma
separated value) format.
A second data access package,
CURL.DATA-ACCESS.CONNECTED provides access to data stored in a
database or in a collection of files on the client machine that
comprise a simple database.
The Curl RTE also provides specialized GUI toolkit objects
for data display.
RecordGrid displays record-based data
in a row and column format, and provides the ability to sort and
filter the data records, and view a subset of available data
fields.
RecordForm provides the ability to view data
records in a form-based format by binding the content of fields
in a record to controls in a form. See
Data Records and Grids and
Forms and Bound Data.
Use the
RecordField object to define fields in the record set.
RecordField enables you to specify a number of attributes of
the field.
- name specifies the name of the data
field. The data access package uses this name extensively to
access the data in this field.
- caption specifies a string to use
when displaying data in this field. For example, RecordGrid uses the caption as the column heading, or the
field name if no caption is specified.
- domain specifies the Domain
for data values in this field. RecordSet uses the
field domain to validate data for the field. See Validating Data
- modifiable? specifies whether you
can modify data in this field after you have appended the
record. This is a separate property from RecordGridOptions.editable?.
- nullable? specifies whether this
field can contain the value null.
- default-value specifies a default
value for this field, which is used when you create new
records.
- index-type specifies the RecordFieldIndexType for this field.
The following list summarizes the major data access classes and the
relationships between them.
You can incorporate data into a
RecordSet when you
construct it. The
RecordData class associates field names
with data values. The following example creates a
RecordSet and uses
RecordData to include three data
records in the constructor.
| Example:
Including Data in the RecordSet Constructor |
 |
{define-proc {show-recordset rs:RecordSet}:Table
let t:Table = {Table columns=rs.fields.size}
{for f in rs.fields do
{t.add {header-cell {value f.name}}}
}
{for r in rs do
{for f in rs.fields do
{t.add r[f.name]}
}
}
{return t}
}
{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}
}
}
{show-recordset people}
| |
The following example uses the method
RecordSet.append to
add data to the record set. The parameter passed to
append
is a
RecordData that contains no data values. When you
click the
append record button in the example, an empty
record appears in the
RecordGrid display. Fill in data
values for the appended record by typing an appropriate string in
each field. You can use the
tab key to move to the
next field. The
Domain specified for each field also
handles validation of data in the field. The
RecordGrid
that displays the record set in this example simply ignores
invalid input. See
Validating Data for more information on the role of
Domain in data validation.
This example supplies default data values in each
RecordField when creating the
RecordSet. You can also
supply data for the new record by adding it to the
RecordData argument to
RecordSet.append. Data supplied
in the call to
append overrides the default data. Try
changing this line of code:
{people.append {RecordData}}
to:
{people.append {RecordData First = "F", Last = "L", Age = 0}}
then click the Execute button at the bottom of the example
box, and click append record in the example popup.
| Example:
Data from User |
 |
{let people:RecordSet =
{RecordSet
{RecordFields
{RecordField
"First", domain = String, default-value = "FIRST"
},
{RecordField
"Last", domain = String, default-value = "LAST"
},
{RecordField
"Age", domain = int, default-value = 99
}
},
{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 = 10cm, 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",
{on Action do {people.commit}}
},
{CommandButton
width = {make-elastic}, label = "revert records",
{on Action do {people.revert}}
}
}
}
}
| |
To create a view that sorts records, set the
RecordView.sort property to a
RecordSort, which you can
create implicitly by supplying either a procedure that compares
records, or a string that lists fields to be sorted. The procedure
must have the same signature and semantics as
RecordSort.compare; see the
default constructor for
RecordSort. The string specifies a compound sort on the
supplied field names; see the
from-string constructor for
RecordSort.
The following example uses command buttons to sort records by
state, city, last name and first name. Each command button sets
the sort property of the record view to a string that
specifies the sort. The string DESC following a field name
indicates a descending sort. The default sort is ascending, which
you can specify explicitly with the string ASC.
In order to keep the code examples compact, this example, and
others in this section, create a
RecordSet by evaluating
code in a support file.
| Example:
Sorting Records |
 |
{let staff:RecordSet =
{evaluate
{url "../../default/support/data.scurl"}
}
}
{let rv:RecordView =
{RecordView staff}
}
{let rg:RecordGrid =
{RecordGrid
record-source = rv, height = 10cm, width = 14cm
}
}
{value
{VBox
rg,
{HBox
{CommandButton
width = {make-elastic},
tooltip = {Tooltip "Sort by State, City, Last, First"},
label = "Sort Ascending",
{on Action do
set rv.sort = "State, City, Last, First"
}
},
{CommandButton
width = {make-elastic},
tooltip = {Tooltip "Sort by State, City, Last, First"},
label = "Sort Descending",
{on Action do
set rv.sort = "State DESC, City DESC, Last DESC, First DESC"
}
}
}
}
}
| |
In the following example, command buttons apply filters to a
RecordView. The filters are derived from a
RecordData that specifies a value for the field
Last.
| Example:
Filtering Records |
 |
{let staff:RecordSet =
{evaluate
{url "../../default/support/data.scurl"}
}
}
{let rv:RecordView =
{RecordView staff}
}
{let rg:RecordGrid =
{RecordGrid
record-source = rv, height = 5cm, width = 14cm
}
}
{VBox
rg,
{HBox
{CommandButton
width = {make-elastic}, label = "Show Abrams",
{on Action do
set rv.filter = {RecordData Last = "Abrams"}
}
},
{CommandButton
width = {make-elastic}, label = "Show Frankel",
{on Action do
set rv.filter = {RecordData Last = "Frankel"}
}
}
}
}
| |
The Curl data access package enables you to modify data and commit
or revert those modifications. The classes
Record,
RecordView, and
RecordSet all implement both the
commit and
revert methods. As implemented by
Record, both methods act on the single associated
record. As implemented by both
RecordSet and
RecordView, the methods act on all modified records in the
underlying
RecordSet. Note that
RecordView.commit and
RecordView.revert act on all
records in the record set, not just those contained in the view.
- RecordState.original This record has not changed
since its source RecordSet was last loaded, committed, or
reverted.
- RecordState.modified This record has changed since
its source RecordSet was last loaded, committed, or
reverted.
- RecordState.appended This record was appended to its
source RecordSet after the RecordSet was last
loaded, committed, or reverted.
- RecordState.deleted This record was deleted from its
source RecordSet after the RecordSet was last
loaded, committed, or reverted. Note that such a record is still a
fully accessible member of the RecordSet and is only
removed by a subsequent commit.
- RecordState.new This record was created with the
RecordSet.new-record method and has not yet been appended
to the RecordSet with RecordSet.append.
- RecordState.detached This record is not associated
with a RecordSet and should not be accessed by
applications. Records which have been deleted and committed, as
well as records not created with RecordSet.new-record
method are examples of records that may be found in this state.
The following example creates a
RecordSet called
people and includes three records in the constructor. These
records have the state
RecordState.original. It then
appends one record but does not commit it. That record has the
state
RecordState.appended.
The column
RecordState uses the specialized record grid
cell
StateDisplayCell to display the record state. See
Creating a Custom Cell for more information on custom record grid
cells.
Command buttons supplied in the example let you delete, commit, or
revert the current record. The "Record State" field shows the
state of each record.
Use the example to experiment with modifying, committing, and
reverting records, and note the changes in state. If you revert or
delete the appended record, it disappears from view. This is
because these operations change the state from appended to
new. If you modify the data in the appended record, the
state remains appended.
| Example:
Changes in Record State |
 |
{let people:RecordSet =
{RecordSet
{RecordFields
{RecordField "ID", domain = int},
{RecordField "Name", domain = String},
{RecordField "Age", domain = int}
},
{RecordData ID = 1, Name = "John Smith", Age = 25},
{RecordData ID = 2, Name = "Jane Smith", Age = 29},
{RecordData ID = 3, Name = "Jane Jones", Age = 28}
}
}
{define-class public StateDisplayCell {inherits StandardStringCell}
{method public open {get-formatted-data}:(String, bool)
{if-non-null rec = self.record then
{return rec.state.name, true}
else
{return "", false}
}
}
}
{people.append
{RecordData ID = 4, Name = "John Jones", Age = 26}
}
{let rg:RecordGrid =
{RecordGrid
record-source = people,
sort = "ID",
width = 9cm,
height = 4cm,
{RecordGridColumn width = 1cm, "ID"},
{RecordGridColumn width = 1cm, "Age"},
{RecordGridColumn width = 3cm, "Name"},
{RecordGridColumn width = 3cm, "RecordState",
cell-spec = StateDisplayCell}
}
}
{value
set rg.records.include-deleted-records? = true
{VBox
rg,
{HBox width = 9cm,
{CommandButton
width = {make-elastic}, label = "Delete Current",
{on Action do
{if-non-null rec = rg.current-record then
{rec.delete}
}
}
},
{CommandButton
width = {make-elastic}, label = "Commit Current",
{on Action do
{if-non-null rec = rg.current-record then
{rec.commit}
}
}
},
{CommandButton
width = {make-elastic}, label = "Revert Current",
{on Action do
{if-non-null rec = rg.current-record then
{rec.revert}
}
}
}
}
}
}
| |
You can filter records by record state. The following example uses
command buttons to apply record state filters. The record view it
displays contains both original and appended records. You need to
edit one or more records to create modified records. An application
might use such filters to enable the end user to examine all the
changed records before committing the changes.
| Example:
Filtering for Record State |
 |
{let staff:RecordSet =
{RecordSet
{RecordFields
{RecordField
"id", caption = "User ID", domain = int,
index-type = RecordFieldIndexType.unique
},
{RecordField
"First", domain = String
},
{RecordField
"Last", domain = String
}
},
{RecordData id = 1, First = "Gene", Last = "Smith"},
{RecordData id = 2, First = "Fred", Last = "Smith"},
{RecordData id = 3, First = "Mike", Last = "Smith"},
{RecordData id = 4, First = "Ben", Last = "Smith"}
}
}
{staff.append
{RecordData id = 5, First = "Ben", Last = "Abrams"}}
{staff.append
{RecordData id = 6, First = "Sam", Last = "Jones"}}
{staff.append
{RecordData id = 7, First = "Nigel", Last = "Stevens"}}
{staff.append
{RecordData id = 8, First = "Bert", Last = "Stevens"}}
{let rv:RecordView = {RecordView staff}}
{let rg:RecordGrid =
{RecordGrid record-source = rv, width = 9cm, height = 5cm}
}
{VBox
rg,
{HBox
{CommandButton
width = {make-elastic}, label = "Original State",
{on Action do
set rv.filter = RecordState.original
}
},
{CommandButton
width = {make-elastic}, label = "Appended State",
{on Action do
set rv.filter = RecordState.appended
}
},
{CommandButton
width = {make-elastic}, label = "Modified State",
{on Action do
set rv.filter = RecordState.modified
}
}
}
}
| |
Domain is a base class for classes that describe a valid
set of data values for a
RecordField, and provide
procedures that validate, compare values, and convert them to and
from strings. Standard domains are provided for all basic Curl
data types.
The following example sets maximum, minimum, and default values
for integers allowed in the
Age field. It catches the
exception thrown when you try to enter an invalid value, and shows
the message returned in the exception. Note that the default value
set in the
Domain supplies a value for the
Age
field when you append a blank record.
| Example:
Using Domain to Validate Data |
 |
{let limit-age:StandardIntDomain =
{StandardIntDomain
default-value = 25,
max-allowable = 30,
min-allowable = 20
}
}
{let people:RecordSet =
{RecordSet
{RecordFields
{RecordField
"First", caption = "First Name", domain = String
},
{RecordField
"Last", caption = "Last Name", domain = String
},
{RecordField
"Age", domain = limit-age
}
},
{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,
record-source = people
}
}
{VBox
rg,
{HBox
{CommandButton
width = {make-elastic}, label = "append invalid record",
{on Action do
{try
{people.append
{RecordData First = "George", Last = "Jones", Age = 55}
}
catch e:ValidationException do
{popup-message e.message}
}
}
},
{CommandButton
width = {make-elastic}, label = "append blank record",
{on Action do
{try
{people.append
{RecordData}
}
catch e:ValidationException do
{popup-message e.message}
}
}
}
}
}
| |
The following example creates a
RecordSet containing
images of maritime signal flags, and the letters and words
associated with them. Then it uses the associated letters to
select from the
RecordSet the records for flags that
spell "CURL" and displays the images and words.
Note that this example does not use
RecordGrid in any
way.
| Example:
Selecting Records from a RecordSet |
 |
{let maritime-signal-flags:RecordSet =
{evaluate {url "../../default/support/flag-data.scurl"}}
}
{define-proc public {find-letter-flag l:char}:(word:String, flag:Frame)
let selected:{Array-of Record} =
{maritime-signal-flags.select filter =
{RecordData letter = l}
}
let r:Record = selected[0]
let word:String = r["phonetic"]
let flag:Frame =
{Frame
height = 42px,
width = 53px,
background = {Background.from-url {url r["flag"]}}
}
{return word, flag}
}
{let (word-c:String, letter-c:Frame) = {find-letter-flag 'C'}}
{let (word-u:String, letter-u:Frame) = {find-letter-flag 'U'}}
{let (word-r:String, letter-r:Frame) = {find-letter-flag 'R'}}
{let (word-l:String, letter-l:Frame) = {find-letter-flag 'L'}}
{HBox
font-size = 12pt,
spacing = 5px,
{VBox halign = "center", 'C', word-c, letter-c},
{VBox halign = "center", 'U', word-u, letter-u},
{VBox halign = "center", 'R', word-r, letter-r},
{VBox halign = "center", 'L', word-l, letter-l}
}
| |
RecordSet fires events at itself to signal changes in
data content or organization.
This list describes some of the important event classes:
In the following example, the
RecordSet plan
contains data about a planned task that involves three phases with
several steps of varying duration for each phase.
SummaryTable is a class that maintains a
_source record
set, and
_summarized. which summarizes information in the
source to give the total time for each phase. In this example, the
source record set is
plan. When the underlying data in
plan changes, the content of the
_summarized
record set needs to be recalculated. An event handler on
plan performs the recalculation when it receives a
RecordModified event.
| Example:
Using the RecordModified Event |
 |
{let public plan:RecordSet =
{RecordSet
{RecordFields
{RecordField "task",
domain = String
},
{RecordField "phase",
domain = String
},
{RecordField "weeks",
domain = int
}
},
{RecordData task = "step 1", phase = "I", weeks = 2},
{RecordData task = "step 2", phase = "I", weeks = 1},
{RecordData task = "step 3", phase = "I", weeks = 3},
{RecordData task = "step 1", phase = "II", weeks = 2},
{RecordData task = "step 2", phase = "II", weeks = 4},
{RecordData task = "step 1", phase = "III", weeks = 2},
{RecordData task = "step 2", phase = "III", weeks = 3},
{RecordData task = "step 3", phase = "III", weeks = 4},
{RecordData task = "step 4", phase = "III", weeks = 6}
}
}
{define-class public SummaryTable
let constant summary-fields:RecordFields =
{RecordFields
{RecordField "phase", domain = String},
{RecordField "weeks", domain = int}
}
field public _source:RecordSet
field public _summarized:RecordSet
field public tasks:StringArray = {StringArray}
{constructor public {default
source:RecordSet
}
set self._source = source
set self._summarized =
{RecordSet SummaryTable.summary-fields}
}
{method public {recalc}:void
{self.accumulate-by-phase "I"}
{self.accumulate-by-phase "II"}
{self.accumulate-by-phase "III"}
}
{method private {accumulate-by-phase phase:String}:void
let week-sum:int = 0
let rv:RecordView =
{RecordView
self._source,
filter = {RecordData phase = phase}
}
{for r:Record in rv do
set week-sum = week-sum + r["weeks"]
}
let rec:#Record =
{self._summarized.select-one filter = {RecordData phase = phase}}
{if rec == null then
{self._summarized.append {RecordData phase = phase, weeks = week-sum}}
else
set rec["weeks"] = week-sum
}
}
}
{let summary:SummaryTable = {SummaryTable plan}}
{summary.recalc}
{plan.add-event-handler
{on RecordModified at rs:RecordSet do
{summary.recalc}
}
}
{HBox
{RecordGrid
background = "white",
width = 6cm,
height = 6cm,
record-source = summary._source,
{RecordGridColumn "task", width = 2cm},
{RecordGridColumn "phase", width = 1.5cm},
{RecordGridColumn "weeks", width = 1.5cm}
},
{RecordGrid
background = "white",
width = 4cm,
height = 6cm,
record-source = summary._summarized,
{RecordGridColumn "phase", width = 1.5cm},
{RecordGridColumn "weeks", width = 1.5cm}
}
}
| |
Copyright © 1998-2007 Sumisho Computer Systems Corp.
All rights reserved.
Curl, the Curl logo, Surge, and the Surge logo are trademarks of Sumisho
Computer Systems Corp. that are registered in the United States. Surge
Lab, the Surge Lab logo, and the Surge Lab Visual Layout Editor (VLE)
logo are trademarks of Sumisho Computer Systems Corp.