Run standard validation procedures on update
- Last Updated: February 11, 2026
- 4 minute read
- OpenEdge
- Version 13.0
- Documentation
There are no standard event hooks for SAVE-ROW-CHANGES as
there are for FILL because, generally, validation
logic will be executed before or possibly after the SAVE-ROW-CHANGES method,
so there is nowhere for the AVM to execute standard events. Instead, you
can execute whatever validation logic or other business logic you
need to before or after running SAVE-ROW-CHANGES.
Because commitChanges.p simulates what
a SAVE-CHANGES method on the whole ProDataSet would do,
there are places within this procedure where standard event hooks can go. This section shows
you how to add event hooks of this kind to your commitChanges procedure.
To
add event hooks to commitChanges.p:
-
Add a
HANDLEvariablehSourceProcand assign it to theSOURCE-PROCEDURE, as shown:DEFINE VARIABLE hSourceProc AS HANDLE NO-UNDO. hSourceProc = SOURCE-PROCEDURE.Validation procedures will be executed in the calling procedure from the internal procedure
saveBuffer. At the time commitChanges.p is run, the calling procedure's handle is theSOURCE-PROCEDURE. By running validation in the caller, you can code validation logic directly into the entity procedure or into another procedure that you run as a super procedure of the entity. The problem is that from within an internal procedure likesaveBuffer, called from the main block, the value ofSOURCE-PROCEDUREiscommitChanges.pitself. For this reason the new code saves off the value ofSOURCE-PROCEDUREin the main block so that it can be referenced insaveBuffer.The rest of the changes are to the internal procedure
saveBuffer. -
Add a variable definition to
saveBufferfor a character string to hold a procedure name:DEFINE VARIABLE cLogicProc S CHARACTER NO-UNDO.The new code assumes a naming convention of the temp-table name plusDelete,Create, orModifyfor validation logic procedures. For example, a procedure to execute when attOlinerecord is modified is calledttOlineModify. You will code just such a procedure. Add this statement to generate the procedure name at the beginning of theDOblock that walks through the dynamic query:DO WHILE NOT hBeforeQry:QUERY-OFF-END: cLogicProc = phBuffer:TABLE-HANDLE:NAME + IF hBeforeBuff:ROW-STATE = ROW-DELETED THEN "Delete" ELSE IF hBeforeBuff:ROW-STATE = ROW-CREATED THEN "Create" ELSE "Modify".Before the code runs the logic procedure it needs to find the right after-table row so that the validation procedure can see its values. Remember that the dynamic queryhBeforeQryis navigating the rows of the before-table. The after-table rows are not automatically found, so this statement is required to bring the corresponding after-table row into its own buffer:phBuffer:FIND-BY-ROWID(hBeforeBuff:AFTER-ROWID).Now you can run the logic procedure if it exists. The code passes the ProDataSet asINPUT BY-REFERENCEjust as the AVM does forFILLevent procedures. Remember that anINPUTparameter passedBY-REFERENCEto a local procedure is effectively the same as anINPUT-OUTPUTparameter, because any changes made are visible to the caller. Nonetheless, we pass the ProDataSet asINPUTfor consistency with the calling sequence ofFILLevents, as shown:RUN VALUE(cLogicProc) IN hSourceProc (INPUT DATASET-HANDLE hDataSet BY-REFERENCE) NO-ERROR.The validation procedure can set the
ERRORattribute for the row if it fails validation. If this is not the case then it runsSAVE-ROW-CHANGESon the row.After this, if the
ERRORattribute has been set either by the validation procedure or bySAVE-ROW-CHANGESitself, the code setsERRORon the ProDataSet itself. This is because when your code setsERRORon a row programmatically, the AVM does not set it on the ProDataSet. This happens only when the AVM detects an error internally and setsERRORon both the row and the ProDataSet.For example:IF NOT hBeforeBuff:ERROR THEN hBeforeBuff:SAVE-ROW-CHANGES(). /* If there was an error signal that this row did not make it into the database. */ IF hBeforeBuff:ERROR THEN ASSIGN hDataSet:ERROR = TRUE hBeforeBuff:REJECTED = TRUE. hBeforeQry:GET-NEXT().As before, the code also sets the
REJECTEDflag.Now you have a general-purpose save procedure that also runs general-purpose business logic at key points during the update process.
-
Open the procedure OrderEntity.p, where
Order-specific business logic goes, and create an internal procedurettOlineModifythat compares some values in the before- and after-tables for the currentttOlinerow.If theQuantityorder has been doubled or more, then the procedure rewards the customer by increasing the discount by 20%. On the other hand, if theQuantityhas been cut by half or more, this is considered an error and the procedure sets theERRORflag and also anERROR-STRINGmessage. For example:PROCEDURE ttOlineModify: DEFINE INPUT PARAMETER DATASET FOR dsOrder. /* If the customer doubled the quantity ordered, then increase the discount by 20%. */ IF ttOline.Qty >= (ttOlineBefore.Qty * 2) AND ttOline.Discount = ttOlineBefore.Discount THEN ttOline.Discount = ttOlineBefore.Discount * 1.2. ELSE IF ttOline.Qty <= (ttOlineBefore.Qty * .5) THEN ASSIGN BUFFER ttOline:ERROR = TRUE BUFFER ttOline:ERROR-STRING = "Line " + STRING(ttOline.LineNum) + ": You can't drop the Qty that much!". RETURN. END PROCEDURE. /* ttOlineModify */Even though
saveBufferpasses the ProDataSet as a dynamicDATASET-HANDLE(because it has only a dynamic reference to the ProDataSet),ttOlineModifycan receive it as a staticDATASET, matching its local definition, so that you can code static ABL statements for your business logic.Now if you re-run the window you can see the effects.
-
Run dsOrderWinAdv.w. Select an
Order, increase oneQtyby more than double, and cut another by more than half:
In this case, we doubled the
QtyforLine Number 2, and the discount was increased accordingly. We decreased theQtyforLine Number 3by more than half, and that change was rejected and the message displayed. This is an example of why you might not want to runMERGE-CHANGESon the whole ProDataSet when not all changes were successful. The original values for the rejected row are redisplayed and the user's changes erased. It would probably be better to runMERGE-ROW-CHANGESjust on successful updates and leave the incorrect values for other rows so that they can be seen and more easily corrected.