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.