Example: custlistinp.4gl (Function custarr_input)

The custlistinp.4gl module implements the INPUT ARRAY dialog in the custarr_input function.

The custarr_input function in custlistinp.4gl:
  1 PRIVATE FUNCTION custarr_input() RETURNS ()
  2   DEFINE x INTEGER
  3   DEFINE op CHAR(1)
  4 
  5   LET int_flag = FALSE
  6 
  7   INPUT ARRAY custarr FROM sa_cust.*
  8           ATTRIBUTES(UNBUFFERED,
  9                      CANCEL = FALSE,
 10                      WITHOUT DEFAULTS)
 11 
 12      BEFORE DELETE
 13         DISPLAY "BEFORE DELETE: op=",op
 14         IF op == "N" THEN
 15            LET x = arr_curr()
 16            IF NOT comutils.mbox_yn("Customers",
 17                 "Are you sure you want to delete this record?")
 18            THEN
 19               CANCEL DELETE
 20            END IF
 21            TRY
 22               DELETE FROM customer
 23                   WHERE cust_num = custarr[x].cust_num
 24            CATCH
 25               ERROR SQLERRMESSAGE
 26               CANCEL DELETE
 27            END TRY
 28         END IF
 29 
 30      AFTER DELETE
 31         DISPLAY "AFTER DELETE: op=",op
 32         IF op == "N" THEN
 33            MESSAGE "Record has been deleted successfully"
 34         ELSE
 35            LET op = "N"
 36         END IF
 37 
 38      AFTER FIELD cust_name
 39         DISPLAY "AFTER FIELD cust_name: op=",op
 40         LET x = arr_curr()
 41         IF custarr[x].cust_name MATCHES "*@#$%^&()*" THEN
 42            ERROR "This field contains invalid characters"
 43            NEXT FIELD CURRENT
 44         END IF
 45 
 46      ON ROW CHANGE
 47         DISPLAY "ON ROW CHANGE: op=",op
 48         IF op != "I" THEN LET op = "M" END IF
 49 
 50      BEFORE INSERT
 51         DISPLAY "BEFORE INSERT: op=",op
 52         LET op = "T"
 53         LET x = arr_curr()
 54         LET custarr[x].cust_num = 1000 + arr_curr()
 55         LET custarr[x].cust_name = "<undefined>"
 56 
 57      AFTER INSERT
 58         DISPLAY "AFTER INSERT: op=",op
 59         LET op = "I"
 60 
 61      BEFORE ROW
 62         DISPLAY "BEFORE ROW: op=",op
 63         LET op = "N"
 64 
 65      AFTER ROW
 66         DISPLAY "AFTER ROW: op=",op
 67         IF int_flag THEN EXIT INPUT END IF
 68         LET x = arr_curr()
 69         IF op == "M" OR op == "I" THEN
 70            IF custarr[x].cust_name MATCHES "[0-9]*" THEN
 71               ERROR "Customer name seems invalid"
 72               NEXT FIELD cust_name
 73            END IF
 74         END IF
 75         IF op == "I" THEN
 76            TRY
 77               INSERT INTO customer VALUES ( custarr[x].* )
 78               MESSAGE "Record has been inserted successfully"
 79            CATCH
 80               ERROR SQLERRMESSAGE
 81               NEXT FIELD CURRENT
 82            END TRY
 83         END IF
 84         IF op == "M" THEN
 85            TRY
 86               LET x = arr_curr()
 87               UPDATE customer SET customer.* = custarr[x].*
 88                   WHERE cust_num = custarr[x].cust_num
 89               MESSAGE "Record has been updated successfully"
 90            CATCH
 91               ERROR "Could not update the record in database!"
 92               NEXT FIELD CURRENT
 93            END TRY
 94         END IF
 95 
 96   END INPUT
 97 
 98 END FUNCTION
Note:
  • Line 1 defines the function, taking no parameters and returning no values.
  • Line 3 defines the op variable as CHAR(1), to hold the current state of the INPUT ARRAY (the current operation):
    • N: in default, browsing state, current row already exists and is untouched.
    • T: temporary state, after starting the creation of a new row (insert or append action)
    • M: the current row has been modified: An SQL UPDATE should be performed.
    • I: a new row has been created: An SQL INSERT must be performed.
  • Lines 7 thru 10 define the INPUT ARRAY instruction binding and options.
  • Lines 12 thru 28 implement the BEFORE DELETE control block. This block is used to ask the user to confirm the row deletion, and we perform the SQL DELETE statement to remove the row from the database table. If the row deletion needs to be canceled, use the CANCEL DELETE instruction in BEFORE DELETE.
  • Lines 30 thry 36 implement the AFTER DELETE control block. This trigger is executed after the row was removed from the record list. This trigger may also be fired, if a new row was inserted, and the user decides to delete the new temporary row. In this case, the op variable is set to "T" and we don't want to show a deletion confirmation message to the user.
  • Lines 38 thru 44 define the AFTER FIELD control block for the cust_name field. Here we make sure that the customer name does not contain unexpected characters, and force the focus to stay in the field with a NEXT FIELD CURRENT instruction, if needed.
  • Lines 46 thru 48: The ON ROW CHANGE control block is used to detect if some fields were modified, before leaving the current row. We must check the value of the op state variable, and only set to "M" (for modified row), if it has not a new inserted row. The UPDATE SQL statement will be performed in ATFER ROW.
  • Lines 50 thru 55: The BEFORE INSERT control block is used to detect the creation of a new row. In this block you typically set default values for the new row. Starting from here, the user can still delete the new created row, that's why we use the op state "T" for "temporary".
  • Lines 59 thru 59: When moving to a another row after inserting a new row, the AFTER INSERT control block is fired, to validate the new row creation. Here we set the op state to "I" for "inserted". The INSERT SQL statement will be performed in ATFER ROW.
  • Line 61 thru 63: The BEFORE ROW control block is executed after all other triggers, when the user goes to a different row in the record list. Here we can reset the op state to "N", indicating that we are in "browsing mode".
  • Lines 65 thru 94: This is the AFTER ROW control block where the code to update the database table must be implemented. With INPUT ARRAY, the only way to force the focus to stay in a row is to issue a NEXT FIELD in the context of the AFTER ROW control block. Therefore, the INSERT and UPDATE SQL statements are performed in the AFTER ROW block, and in case of SQL error, we force the focus to stay in the invalid row.

To better understand the INPUT ARRAY control block execution sequence, add DISPLAY statements below each of the block markers, with the name of the block, and display the value of the "op" state variable: