Tutorial Chapter 8: Array Input
The program in this chapter allows the user to view and change a list of records displayed on a form. As each record in the program array is added, updated, or deleted, the program logic makes corresponding changes in the rows of the corresponding database table.
This program uses a form and a screen array to allow the user to view and change multiple records
of a program array at once. The INPUT ARRAY statement and its control blocks are
used by the program to control and monitor the changes made by the user to the records. As each
record in the program array is Added, Updated, or Deleted, the program logic makes corresponding
changes in the rows of the customer database table.

The INPUT ARRAY statement
The INPUT ARRAY statement supports data entry by users into a
screen array, and stores the entered data in a program array of records. During the
INPUT ARRAY execution, the user can edit or delete existing
records, insert new records, and move inside the list of records. The program can then
use the INSERT, DELETE or UPDATE SQL
statements to modify the appropriate database tables. The INPUT ARRAY
statement does not terminate until the user validates or cancels the dialog.
INPUT ARRAY cust_arr WITHOUT DEFAULTS FROM sa_cust.*
ATTRIBUTES (UNBUFFERED)
The example INPUT ARRAY statement binds the screen array fields in
sa_cust to the member records of the program array
cust_arr. The number of variables in each record of the
program array must be the same as the number of fields in each screen record (that
is, in a single row of the screen array). Each mapped variable must have the same
data type or a compatible data type as the corresponding field.
WITHOUT DEFAULTS clause
The WITHOUT DEFAULTS clause instructs the INPUT
ARRAY statement to use and display the rows currently stored in the program
array. Without this clause, the INPUT ARRAY would start with an empty
list.
When creating a new row with the insert or append action, the
REQUIRED attribute is always taken into account by INPUT
ARRAY, even if the WITHOUT DEFAULTS clause is used.
The WITHOUT DEFAULTS clause prevents BDL from displaying any default values that
have been defined for form fields. You must use this clause if you want to see the
values of the program array.
The UNBUFFERED attribute
As in the INPUT statement, when the UNBUFFERED
attribute is used, the INPUT ARRAY statement is sensitive to program
variable changes.
If you need to display new data during the execution, use the UNBUFFERED
attribute and assign the values to the program array row; the runtime system will
automatically display the values to the screen. This sensitivity applies to ON
ACTION control blocks, as well: Before executing the code corresponding to
the invoked action, the content of the field is converted and assigned to the
corresponding program variable.
COUNT and MAXCOUNT attributes
INPUT ARRAY supports the COUNT and
MAXCOUNT attributes to manage program arrays.
- The
COUNTattribute ofINPUT ARRAYdefines the number of valid rows in the program array to be displayed as default rows.- When using a static array, if you do not use the
COUNTattribute, the runtime system cannot determine how much data to display, so the screen array remains empty. - When using a dynamic array, the
COUNTattribute is ignored: The number of elements in the dynamic array is used.
- When using a static array, if you do not use the
- The
MAXCOUNTattribute defines the maximum number of data rows that can be entered in the program array. In a dynamic array, the user can enter an infinite number of rows if theMAXCOUNTattribute is not set.
Control Blocks
Your program can control and monitor the changes made by the user by using control
blocks with the INPUT ARRAY statement.
The control blocks that are used in the example program are:
- The
BEFORE INPUTblock - executed one time, before the runtime system gives control to the user. You can implement initialization in this block. - The
BEFORE ROWblock - executed each time the user moves to another row, after the destination row is made the current one. - The
ON ROW CHANGEblock - executed when the user moves to another row after modifications have been made to the current row. - The
ON CHANGE<fieldname> block - executed when the cursor leaves a specified field and the value was changed by the user after the field got the focus. - The
BEFORE INSERTblock - executed each time the user inserts a new row in the array, before the new row is created and made the current one. - The
AFTER INSERTblock - executed each time the user inserts a new row in the array, after the new row is created. You can cancel the insert operation with theCANCEL INSERTkeywords. - The
BEFORE DELETEblock - executed each time the user deletes a row from the array, before the row is removed from the list. You can cancel the delete operation with theCANCEL DELETEkeywords. - The
AFTER ROWblock - executed each time the user moves to another row, before the current row is left. This trigger can also be executed in other situations, such as when you delete a row, or when the user inserts a new row.
For a more detailed explanation of the priority of control blocks see INPUT control blocks in the Genero Business Development Language User Guide.
Built-in Functions - ARR_CURR
The language provides several built-in functions to use in an INPUT
ARRAY statement. The example program uses the ARR_CURR
function to tell which array element is being changed. This function returns the row number
within the program array that is displayed in the current line of a screen
array.
Predefined actions
There are some predefined actions that are specific to the INPUT
ARRAY statement, to handle the insertion and deletion of rows in the screen
array automatically.
- The
insertaction inserts a new row before current row. When the user has filled this record, BDL inserts the data into the program array. - The
deleteaction deletes the current record from the display of the screen array and from the program array, and redraws the screen array so that the deleted record is no longer shown. - The
appendaction adds a new row at the end of the list. When the user has filled this record, BDL inserts the data into the program array.
As with the predefined actions accept and cancel actions
discussed in Chapter 4, if your form specification does not contain action views for
these actions, default action views (buttons on the form) are automatically created.
Control attributes of the INPUT ARRAY statement allow you to prevent
the creation of these actions and their accompanying buttons.
Example: Using a Screen Array to modify Data
The arrayinput program in chapter 8 uses the INPUT
ARRAY statement with a Screen Array to allow the user to modify data in the
customer table.
The Form Specification File
The custallform.per form specification file displays
multiple records at once, and is similar to the form used in chapter 7. The
item type of field f6, containing the
state values, has been changed to
COMBOBOX to provide the user with a dropdown
list when data is being entered.
01 SCHEMA custdemo
02
03 LAYOUT
04 TABLE
05 {
06 Id Name .. zip_code Contact Phone
07 [f01][f02 ] [f07 ][f08 ][f09 ]
08 [f01][f02 ] [f07 ][f08 ][f09 ]
09 [f01][f02 ] [f07 ][f08 ][f09 ]
10 [f01][f02 ] [f07 ][f08 ][f09 ]
11 [f01][f02 ] [f07 ][f08 ][f09 ]
12 [f01][f02 ] [f07 ][f08 ][f09 ]
13 }
14 END
15 END
16
17 TABLES
18 customer
19 END
20
21 ATTRIBUTES
22 EDIT f01 = customer.store_num, REQUIRED;
23 EDIT f02 = customer.store_name, REQUIRED;
24 EDIT f03 = customer.addr;
25 EDIT f04 = customer.addr2;
26 EDIT f05 = customer.city;
27 COMBOBOX f6 = customer.state, ITEMS = ("IA", "IL", "WI");
28 EDIT f07 = customer.zip_code;
29 EDIT f08 = customer.contact_name;
30 EDIT f09 = customer.phone;
31 END
32
33 INSTRUCTIONS
34 SCREEN RECORD sa_cust (customer.*);
35 ENDThe Main block
The single module program custall.4gl allows the user to update
the customer table using a form that displays multiple records at
once.
01 SCHEMA custdemo
02
03 DEFINE cust_arr DYNAMIC ARRAY OF RECORD
04 store_num LIKE customer.store_num,
05 store_name LIKE customer.store_name,
06 addr LIKE customer.addr,
07 addr2 LIKE customer.addr2,
08 city LIKE customer.city,
09 state LIKE customer.state,
10 zip_code LIKE customer.zip_code,
11 contact_name LIKE customer.contact_name,
12 phone LIKE customer.phone
13 END RECORD
14
15
16MAIN
17 DEFINE idx SMALLINT
18
19 DEFER INTERRUPT
20 CONNECT TO "custdemo"
21 CLOSE WINDOW SCREEN
22 OPEN WINDOW w3 WITH FORM "custallform"
23
24 CALL load_custall() RETURNING idx
25 IF idx > 0 THEN
26 CALL inparr_custall()
27 END IF
28
29 CLOSE WINDOW w3
30 DISCONNECT CURRENT
31
32 END MAIN- Lines
03thru13define a dynamic arraycust_arrhaving the same structure as thecustomertable. The array is modular is scope. - Line
17defines a local variableidx, to hold the returned value from theload_custallfunction. - Line
20connects to thecustdemodatabase. - Line
22opens a window with the formmanycust. This form contains a screen arraysa_custwhich is referenced in the program. - Line
24thru27call the functionload_custallto load the array, which returns the index of the array. If the load was successful (the returned index is greater than 0) the functioninparr_custallis called. This function contains the logic for the Input/Update/Delete of rows. - Line
29closes the window. - Line
30disconnects from the database.
Function load_custall
This function loads the program array with rows from the customer
database table.
The logic to load the rows is identical to that in Chapter 7. Although this program loads all the
rows from the customer table, the program could be written to allow the
user to query first, for a subset of the rows. A query-by-example, as illustrated in
chapter 4, can also
be implemented using a form containing a screen array such as
manycust.
load_custall
(custall.4gl):01 FUNCTION load_custall()
02 DEFINE cust_rec RECORD LIKE customer.*
03
04
05 DECLARE custlist_curs CURSOR FOR
06 SELECT store_num,
07 store_name,
08 addr,
09 addr2,
10 city,
11 state,
12 zip_code,
13 contact_name,
14 phone
15 FROM customer
16 ORDER BY store_num
17
18
19 CALL cust_arr.clear()
20 FOREACH custlist_curs INTO cust_rec.*
21 CALL cust_arr.appendElement()
22 LET cust_arr[cust_arr.getLength()].* = cust_rec.*
23 END FOREACH
24
25 IF (cust_arr.getLength() == 0) THEN
26 DISPLAY "No rows loaded."
27 END IF
28
29 RETURN cust_arr.getLength()
30
31END FUNCTION- Line
02defines a local record variable,cust_rec, to hold the rows fetched inFOREACH. - Lines
05thru16declare the cursorcustlist_cursto retrieve the rows from thecustomertable. - Lines
20thru23retrieve the rows from the result set into the program array. - Lines
25thru27If the array is empty, we display a warning message. - Line
29returns the number of rows to theMAINfunction.
Function inparr_custall
This is the primary function of the program, driving the logic for inserting, deleting,
and changing rows in the customer database table.
Each time a row in the array on the form is added, deleted, or changed, the values from the
corresponding row in the program array are used to update the customer database
table. The variable opflag is used by the program to indicate the status of the
current operation.
- N - no action; set in the
BEFORE ROWcontrol block; this will subsequently be changed if an insert or update of a row in the array is performed. - T - temporary; set in the
BEFORE INSERTcontrol block; indicates that an insert of a new row has been started. - I - insert; set in the
AFTER INSERTcontrol block; indicates that the insert of the new row was completed. - U - update; set in the
ON ROW CHANGEcontrol block; indicates that a change has been made to an existing row.
The value of opflag is tested in an AFTER ROW control block to
determine whether an SQL INSERT or SQL UPDATE of the database
table is performed.
This example illustrates how the order of execution of the control blocks is used by the program
to set the opflag variable appropriately:
inparr_custall
(custall.4gl):01 FUNCTION inparr_custall(idx)
02
03 DEFINE curr_pa SMALLINT,
04 opflag CHAR(1)
05
06 INPUT ARRAY cust_arr WITHOUT DEFAULTS
07 FROM sa_cust.*
08 ATTRIBUTES (UNBUFFERED)
09
10 BEFORE INPUT
11 MESSAGE "OK exits/" ||
12 "Cancel exits & cancels current operation"
13
14 BEFORE ROW
15 LET curr_pa = ARR_CURR()
16 LET opflag = "N"
17
18 BEFORE INSERT
19 LET opflag = "T"
20
21 AFTER INSERT
22 LET opflag = "I"
23
24 BEFORE DELETE
25 IF NOT (delete_cust(curr_pa)) THEN
26 CANCEL DELETE
27 END IF
28
29 ON ROW CHANGE
30 IF (opflag <> "I") THEN
31 LET opflag = "U"
32 END IF
33
34 BEFORE FIELD store_num
35 IF (opflag <> "T") THEN
36 NEXT FIELD store_name
37 END IF
38
39 ON CHANGE store_num
40 IF (opflag = "T") THEN
41 IF NOT store_num_ok(curr_pa) THEN
42 MESSAGE "Store already exists"
43 LET cust_arr[curr_pa].store_num = NULL
44 NEXT FIELD store_num
45 END IF
46 END IF
47
48 AFTER ROW
49 IF (INT_FLAG) THEN EXIT INPUT END IF
50 CASE
51 WHEN opflag = "I"
52 CALL insert_cust(curr_pa)
53 WHEN opflag = "U"
54 CALL update_cust(curr_pa)
55 END CASE
56
57 END INPUT
58
59 IF (INT_FLAG) THEN
60 LET INT_FLAG = FALSE
61 END IF
62
63 END FUNCTION -- inparr_custall- Line
03defines the variablecurr_pa, to hold the index number of the current record in the program array. - Line
04defines the variableopflag, to indicate whether the operation being performed on a record is an Insert ("I") or an Update ("U"). - Lines
06thru57contain theINPUT ARRAYstatement, associating the program arraycust_arrwith thesa_custscreen array on the form. The attributeWITHOUT DEFAULTSis used to use and display existing records of the program array. TheUNBUFFEREDattribute insures that the program array the screen array of the form are automatically synchronized for input and output. - Lines
10thru12BEFORE INPUTcontrol block: before theINPUT ARRAYstatement is executed aMESSAGEis displayed to the user. - Lines
14thru16BEFORE ROWcontrol block: when called in this block, theARR_CURRfunction returns the index of the record that the user is moving into (which will become the current record). This is stored in a variablecurr_pa, so the index can be passed to other control blocks. We also initialize theopflagto "N": This will be its value unless an update or insert is performed. - Lines
18and19BEFORE INSERTcontrol block: just before the user is allowed to enter the values for a new record, the variableopflagis set to "T", indicating an Insert operation is in progress. - Lines
21and22AFTER INSERTcontrol block sets theopflagto "I" after the insert operation has been completed. - Lines
24thru27BEFORE DELETEcontrol block: Before the record is removed from the program array, the functiondelete_custis called, which verifies that the user wants to delete the current record. In this function, when the user verifies the delete, the index of the record is used to remove the corresponding row from the database. Unless thedelete_custfunction returnsTRUE, the record is not removed from the program array. - Lines
29thru32ON ROW CHANGEcontrol block: After row modification, the program checks whether the modification was an insert of a new row. If not, theopflagis set to "U" indicating an update of an existing row. - Lines
34thru37BEFORE FIELDstore_numcontrol block: thestore_numfield should not be entered by the user unless the operation is an Insert of a new row, indicated by the "T" value ofopflag. Thestore_numcolumn in thecustomerdatabase table is a primary key and cannot be updated. If the operation is not an insert, theNEXT FIELDstatement is used to move the cursor to the next field in the program array,store_name, allowing the user to change all the fields in the record of the program array exceptstore_num. - Lines
39thru46ON CHANGEstore_numcontrol block: if the operation is an Insert, thestore_num_okfunction is called to verify that the value that the user has just entered into the fieldstore_numof the current program array does not already exist in thecustomerdatabase table. If thestore numberdoes exist, the value entered by the user is nulled out, and the cursor is returned to thestore_numfield. - Lines
48thru55AFTER ROWcontrol block: First, the program checksINT_FLAGto see whether the user wants to interrupt theINPUToperation. If not, theopflagis checked in aCASEstatement, and theinsert_custorupdate_custfunction is called based on theopflagvalue. The index of the current record is passed to the function so the database table can be modified. - Line
57indicates the end of theINPUTstatement. - Lines
59thru61check the value of the interrupt flagINT_FLAGand reset it toFALSEif necessary.
Function store_num_ok
When a new record is being inserted into the program array, this function verifies
that the store number does not already exist in the customer database
table. The logic in this function is virtually identical to that used in Chapter
5.
store_num_ok
(custall.4gl):01 FUNCTION store_num_ok(idx)
02 DEFINE idx SMALLINT,
03 checknum LIKE customer.store_num,
04 cont_ok SMALLINT
05
06 LET cont_ok= FALSE
07 WHENEVER ERROR CONTINUE
08 SELECT store_num INTO checknum
09 FROM customer
10 WHERE store_num =
11 cust_arr[idx].store_num
12 WHENEVER ERROR STOP
13 IF (SQLCA.SQLCODE = NOTFOUND) THEN
14 LET cont_ok = TRUE
15 ELSE
16 LET cont_ok = FALSE
17 IF (SQLCA.SQLCODE = 0) THEN
18 MESSAGE "Store Number already exists."
19 ELSE
20 ERROR SQLERRMESSAGE
21 END IF
22 END IF
23
24 RETURN cont_ok
25
26 END FUNCTION- Line
02The index of the current record in the program array is stored in the variableidx, passed to this function from theINPUT ARRAYcontrol blockON CHANGEstore_num. - Line
03The variablechecknumis defined to hold thestore_numreturned by theSELECTstatement. - Line
06sets the variablecont_okto an initial value ofFALSE. This variable is used to indicate whether thestore numberis unique. - Lines
07thru12use an embedded SQLSELECTstatement to check whether thestore_numalready exists in thecustomertable. The index passed to this function is used to obtain the value that was entered into thestore_numfield on the form. The entire database row is not retrieved by theSELECTstatement since the only information required by this program is whether thestore numberalready exists in the table. TheSELECTis surrounded byWHENEVER ERRORstatements. - Lines
13thru22testSQLCA.SQLCODEto determine the success of theSELECTstatement. The variablecont_okis set to indicate whether thestore numberentered by the user is unique. - Line
24returns the value ofcont_okto the calling function.
Function insert_cust
This function inserts a new row into the customer database
table.
insert_cust
(custall.4gl):01 FUNCTION insert_cust(idx)
02 DEFINE idx SMALLINT
03
04 WHENEVER ERROR CONTINUE
05 INSERT INTO customer
06 (store_num,
07 store_name,
08 addr,
09 addr2,
10 city,
11 state,
12 zip_code,
13 contact_name,
14 phone)
15 VALUES (cust_arr[idx].* )
16 WHENEVER ERROR STOP
17
18 IF (SQLCA.SQLCODE = 0) THEN
19 MESSAGE "Store added"
20 ELSE
21 ERROR SQLERRMESSAGE
22 END IF
23
24 END FUNCTION- Line
02This function is called from theAFTER INSERTcontrol block of theINPUT ARRAYstatement. The index of the record that was inserted into thecust_arrprogram array is passed to the function and stored in the variableidx. - Lines
04thru16uses an embedded SQLINSERTstatement to insert a row into thecustomerdatabase table. The values to be inserted into thecustomertable are obtained from the record just inserted into the program array. TheINSERTis surrounded byWHENEVER ERRORstatements. - Lines
18thru22test theSQLCA.SQLCODEto see if the insert into the database was successful, and return an appropriate message to the user.
Function update_cust
This function updates a row in the customer database table. The
functionality is very simple for illustration purposes, but it could be enhanced with
additional error checking routines similar to the example in chapter 6.
update_cust
(custall.4gl):01 FUNCTION update_cust(idx)
02 DEFINE idx SMALLINT
03
04 WHENEVER ERROR CONTINUE
05 UPDATE customer
06 SET
07 store_name = cust_arr[idx].store_name,
08 addr = cust_arr[idx].addr,
09 addr2 = cust_arr[idx].addr2,
10 city = cust_arr[idx].city,
11 state = cust_arr[idx].state,
12 zip_code = cust_arr[idx].zip_code,
13 contact_name = cust_arr[idx].contact_name,
14 phone = cust_arr[idx].phone
15 WHERE store_num = cust_arr[idx].store_num
16 WHENEVER ERROR STOP
17
18 IF (SQLCA.SQLCODE = 0) THEN
19 MESSAGE "Dealer updated."
20 ELSE
21 ERROR SQLERRMESSAGE
22 END IF
23
24 END FUNCTION- Line
02The index of the current record in thecust_arrprogram array is passed asidxfrom theON ROW CHANGEcontrol block. - Lines
04thru16use an embedded SQLUPDATEstatement to update a row in thecustomerdatabase table. The index of the current record in the program array is used to obtain the value ofstore_numthat is to be matched in thecustomertable. Thecustomerrow is updated with the values stored in the current record of the program array. TheUPDATEis surrounded byWHENEVER ERRORstatements. - Lines
18thru22test theSQLCA.SQLCODEto see if the update of the row in the database was successful, and return an appropriate message to the user.
Function delete_cust
This function deletes a row from the customer database table. A
modal Menu similar to that illustrated in Chapter 6 is used to verify that the user wants to
delete the row.
delete_cust
(custall.4gl):01 FUNCTION delete_cust(idx)
02 DEFINE idx SMALLINT,
03 del_ok SMALLINT
04
05 LET del_ok = FALSE
06
07 MENU "Delete" ATTRIBUTES (STYLE="dialog",
08 COMMENT="Delete this row?")
09 COMMAND "OK"
10 LET del_ok = TRUE
11 EXIT MENU
12 COMMAND "Cancel"
13 LET del_ok = FALSE
14 EXIT MENU
15 END MENU
16
17 IF del_ok = TRUE THEN
18 WHENEVER ERROR CONTINUE
20 DELETE FROM customer
21 WHERE store_num = cust_arr[idx].store_num
22 WHENEVER ERROR STOP
23
24 IF (SQLCA.SQLCODE = 0) THEN
25 LET del_ok = TRUE
26 MESSAGE "Dealer deleted."
27 ELSE
28 LET del_ok = FALSE
29 ERROR SQLERRMESSAGE
30 END IF
31 END IF
32
33 RETURN del_ok
34
35 END FUNCTION- Line
02The index of the current record in thecust_arrprogram array is passed from theBEFORE DELETEcontrol block ofINPUT ARRAY, and stored in the variableidx. TheBEFORE DELETEcontrol block is executed immediately before the record is deleted from the program array, allowing the logic in this function to be executed before the record is removed from the program array. - Line
05sets the initial value ofdel_oktoFALSE. - Lines
07thru15display the modal Menu to the user for confirmation of the Delete. - Lines
18thru22use an embedded SQLDELETEstatement to delete the row from thecustomerdatabase table. The variableidxis used to determine the value ofstore_numin the program array record that is to be used as criteria in theDELETEstatement. This record in the program array has not yet been removed, since thisdelete_custfunction was called in aBEFORE DELETEcontrol block. TheDELETEis surrounded byWHENEVER ERRORstatements. - Lines
24thru30test theSQLCA.SQLCODEto see if the update of the row in the database was successful, and return an appropriate message to the user. The valuedel_okis set based on the success of the SQLDELETEstatement. - Line
33returns the variabledel_okto theBEFORE DELETEcontrol block, indicating whether the Delete of thecustomerrow was successful.