Function orditems_dialog
This is the most important function of the program. It implements the multiple dialog instruction to control order and items input simultaneously.
The function uses the opflag variable to determine the state of the operations
            for items:
N- no current operationT- temporary row was createdI- row insertion was done in the listM- row in the list was modified
Function orditems_dialog (orders.4gl)
001 FUNCTION orditems_dialog()
002   DEFINE query_ok SMALLINT,
003          id INTEGER,
004          name LIKE customer.store_name,
005          opflag CHAR(1),
006          curr_pa INTEGER
007 
008   DIALOG ATTRIBUTES(UNBUFFERED)
009 
010    INPUT BY NAME order_rec.*, order_total 
011      ATTRIBUTES(WITHOUT DEFAULTS, NAME="order")
012 
013     ON ACTION find 
014        IF NOT order_update(DIALOG) THEN NEXT FIELD CURRENT END IF
015        CALL order_query()
016 
017     ON ACTION new 
018        IF NOT order_update(DIALOG) THEN NEXT FIELD CURRENT END IF
019       IF NOT order_new() THEN
020          EXIT PROGRAM
021       END IF
022 
023     ON ACTION save 
024        IF NOT order_update(DIALOG) THEN NEXT FIELD CURRENT END IF
025 
026     ON CHANGE store_num 
027        IF NOT order_check_store_num() THEN NEXT FIELD CURRENT END IF
028 
029     ON ACTION zoom1
030        CALL display_custlist() RETURNING id, name 
031        IF id > 0 THEN
032           LET order_rec.store_num = id 
033           LET order_rec.store_name = name 
034           CALL DIALOG.setFieldTouched("store_num", TRUE)
035        END IF
036 
037     AFTER INPUT
038        IF NOT order_update(DIALOG) THEN NEXT FIELD CURRENT END IF
039 
040     ON ACTION first 
041        IF NOT order_update(DIALOG) THEN NEXT FIELD CURRENT END IF
042        CALL order_move(move_first)
043     ON ACTION previous 
044        IF NOT order_update(DIALOG) THEN NEXT FIELD CURRENT END IF
045        CALL order_move(move_prev)
046     ON ACTION next 
047        IF NOT order_update(DIALOG) THEN NEXT FIELD CURRENT END IF
048        CALL order_move(move_next)
049     ON ACTION last 
050        IF NOT order_update(DIALOG) THEN NEXT FIELD CURRENT END IF
051        CALL order_move(move_last)
052 
053   END INPUT
054 
055   INPUT ARRAY arr_items FROM sa_items.*
056     ATTRIBUTES (WITHOUT DEFAULTS, INSERT ROW =FALSE)
057 
058     BEFORE INPUT
059       MESSAGE msg19
060 
061     BEFORE ROW
062       LET opflag = "N"
063       LET curr_pa = DIALOG.getCurrentRow("sa_items")
064       CALL DIALOG.setFieldActive("stock_num", FALSE)
065 
066     BEFORE INSERT
067       LET opflag = "T"
068       LET arr_items[curr_pa].quantity = 1
069       CALL DIALOG.setFieldActive("stock_num", TRUE)
070 
071     AFTER INSERT
072       LET opflag = "I"
073 
074     BEFORE DELETE
075       IF opflag="N" THEN
076          IF NOT item_delete(curr_pa) THEN
077             CANCEL DELETE
078          END IF
079       END IF
080 
081     AFTER DELETE
082       LET opflag="N"
083 
084     ON ROW CHANGE 
085       IF opflag != "I" THEN LET opflag = "M" END IF
086 
087     AFTER ROW
088       IF opflag == "I" THEN
089          IF NOT item_insert(curr_pa) THEN
090             NEXT FIELD CURRENT
091          END IF
092          CALL items_line_total(curr_pa)
093       END IF
094       IF opflag == "M" THEN
095          IF NOT item_update(curr_pa) THEN
096             NEXT FIELD CURRENT
097          END IF
098          CALL items_line_total(curr_pa)
099       END IF
100 
101     ON ACTION zoom2
102        LET id = display_stocklist()
103        IF id > 0 THEN
104           IF NOT get_stock_info(curr_pa,id) THEN
105              LET arr_items[curr_pa].stock_num = NULL
106           ELSE
107              LET arr_items[curr_pa].stock_num = id 
108           END IF
109           CALL DIALOG.setFieldTouched("stock_num", TRUE)
110        END IF
111 
112     ON CHANGE stock_num 
113        IF NOT get_stock_info(curr_pa,
114                   arr_items[curr_pa].stock_num) THEN
115           LET arr_items[curr_pa].stock_num = NULL
116           CALL __mbox_ok(title2,msg07,"stop")
117           NEXT FIELD stock_num 
118        ELSE
119           CALL items_line_total(curr_pa)
120        END IF
121 
122     ON CHANGE quantity 
123        IF arr_items[curr_pa].quantity <= 0 THEN
124           CALL __mbox_ok(title2,msg13,"stop")
125           NEXT FIELD quantity 
126        ELSE
127           CALL items_line_total(curr_pa)
128        END IF
129 
130   END INPUT
131 
132   BEFORE DIALOG
133      IF NOT order_select("1=1") THEN
134        CALL order_query()
135      END IF
136 
137   ON ACTION about 
138      CALL __mbox_ok(title1,msg18,"information")
139 
140   ON ACTION quit 
141      EXIT DIALOG
142 
143   END DIALOG
144 
145 END FUNCTION
Note: 
- Lines 
002thru006define the variables used by this function. - Lines 
008thru143define aDIALOGinstruction implementing the controller of the form.- Lines 
010thru053implement theINPUT BY NAMEsub-dialog, controlling theorder_recrecord input. All actions triggers declared inside theINPUT BY NAMEsub-dialog will only be activated if the focus is in this sub-dialog. Data validation will occur when focus is lost by this sub-dialog, or when the user presses the Save button.- Lines 
013thru015implement thefindON ACTIONtrigger, to execute a Query By Example with theorder_query()function. Before calling the query function, we must validate and save current modifications in the order record with theorder_update()function. If the validation/save fails, the cursor remains in the current field (when the user clicks an action view, such as a Toolbar icon, the focus does not change.) - Lines 
017thru021implement thenewON ACTIONtrigger, to create a new order record. Before calling the new function, we must validate and save current modifications in the order record with theorder_update()function. - Lines 
023thru024implement thesaveON ACTIONtrigger, to validate and save current modifications in the order record with theorder_update()function. - Lines 
026thru027declare theON CHANGEtrigger for thestore_numfield, to check if the number is a valid store identifier with theorder_check_store_num()function. If the function returnsFALSE, we execute aNEXT FIELDto stay in the field. - Lines 
029thru035implement thezoom1ON ACTIONtrigger for thef01field, to open a typical "zoom" window with thedisplay_custlist()function. If the user selects a customer from the list, we mark the field as touched with theDIALOG.setFieldTouched()method. This simulates a real user input. - Lines 
037thru038implement theAFTER INPUTtrigger, to validate and save current modifications with theorder_update()function when the focus is lost by the order header sub-dialog. - Lines 
040thru051implement theON ACTIONtriggers for the four navigation actions to move in the order list with theorder_move()function. Before calling the query function, we must validate and save current modifications with theorder_update()function. 
 - Lines 
 - Lines 
055thru130implement theINPUT ARRAYsub-dialog, controlling thearr_itemsarray input. All actions triggers declared inside theINPUT ARRAYsub-dialog will only be activated if the focus is in this sub-dialog. The sub-dialog uses theopflagtechnique to implement SQL instructions inside the dialog code and update the database on the fly.- Lines 
058thru059implement theBEFORE INPUTtrigger, to display information message to the user, indicating that item row data will be validated and saved in the database when the user moves to another row or when the focus is lost by the item list. - Lines 
061thru064implement theBEFORE ROWtrigger, initialize theopflagoperation flag to "N" (no current operation), save the current row index incurr_pavariable and disable thestock_numfield (only editable when creating a new line). - Lines 
066thru069implement theBEFORE INSERTtrigger, to set theopflagto "T" (meaning a temporary row was created). A row will be fully validated and ready for SQLINSERTwhen we reach theAFTER INSERTtrigger, there we will setopflagto "I". The code initializes the quantity to 1 and enables thestock_numfield for user input. - Lines 
071thru072implement theAFTER INSERTtrigger, to set theopflagto "I" (row insertion done in list). Data is now ready to be inserted in the database. This is done in theAFTER ROWtrigger, according toopflag. - Lines 
074thru079implement theBEFORE DELETEtrigger. We execute the SQLDELETEonly ifopflagequals "N", indicating that we are in a normal browse mode (and not inserting a new temporary row, which can be deleted from the list without any associated SQL instruction). - Lines 
081thru082implement theAFTER DELETEtrigger, to reset theopflagto "N" (no current operation). This is done to clean the flag after deleting a new inserted row, when data validation or SQL insert failed inAFTER ROW. In that case,opflagequals "I" in the nextAFTER DELETE/AFTER ROWsequence and would invoke validation rules again. - Lines 
084thru085implement theON ROW CHANGEtrigger, to set theopflagto "M" (row was modified), but only if we are not currently doing a row insertion: Row insertion can have failed inAFTER ROWandAFTER INSERTwould not be executed again, butON ROW CHANGEwould. The real SQLUPDATEwill be done later inAFTER ROW. - Lines 
087thru099implement theAFTER ROWtrigger, executingINSERTorUPDATESQL instructions according to theopflagflag. If the SQL statement fails (for example, because a constraint is violated), we set the focus back to the current field withNEXT FIELD CURRENTand keep theopflagvalue as is. If the SQL instruction succeeds,opflagwill be reset to "N" in the nextBEFORE ROW. - Lines 
101thru103implement thezoom2ON ACTIONtrigger for thef08field, to open a typical "zoom" window with thedisplay_stocklist()function. If the user selects a stock from the list, we mark the field as touched with theDIALOG.setFieldTouched()method. This simulates a real user input. - Lines 
112thru120declare theON CHANGEtrigger for thestock_numfield, to check if the number is a valid stock identifier with theget_stock_info()lookup function. If the function returnsFALSE, we execute aNEXT FIELDto stay in the field, otherwise we recalculate the line total withitems_line_total(). - Lines 
122thru128declare theON CHANGEtrigger for thequantityfield, to check if the value is greater than zero. If the value is invalid, we execute aNEXT FIELDto stay in the field, otherwise we recalculate the line total withitems_line_total(). 
 - Lines 
 - Lines 
132thru134implement theBEFORE DIALOGtrigger, to fill the list of orders with an initial result set. - Lines 
137thru138implement theaboutON ACTIONtrigger, to display a message box with the version of the program. - Lines 
140thru141implement thequitON ACTIONtrigger, to leave the dialog (and quit the program). 
 - Lines