The first of these procedures, called clientChanges.p, runs on the client side of the application. (As with earlier examples, these simplified procedures do not actually use the AppServer, but can very easily be extended to do so.) This can be run from a client procedure such as the BtnSave trigger in the Order update window, and later you will do that.
So, let us create procedure clientChanges.p based on code from the CHOOSE trigger for BtnSave in dsOrderWinUpd.w.
ClientChanges.p takes the ProDataSet handle as input, along with the supporting entity procedure handle. As we noted, this procedure handle parameter would be unnecessary if there were a single service procedure on the server to handle all updates. It returns any status messages as OUTPUT, as shown:
/* clientChanges.p -- client side of generic commitChanges support */
DEFINE INPUT PARAMETER phDataSet AS HANDLE NO-UNDO.
DEFINE INPUT PARAMETER phSupportProc AS HANDLE NO-UNDO.
DEFINE OUTPUT PARAMETER pcStatus AS CHARACTER NO-UNDO.
It defines variables for the handles of the change ProDataSet, query, buffer, and a buffer counter:
DEFINE VARIABLE hBuffer AS HANDLE NO-UNDO.
DEFINE VARIABLE hDSChanges AS HANDLE NO-UNDO.
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
DEFINE VARIABLE iBuffer AS INTEGER NO-UNDO.
It creates a dynamic ProDataSet, makes it like the INPUT ProDataSet, and extracts changes from the input ProDataSet:
It then runs this generic saveChanges procedure in the support procedure handle, passing the change ProDataSet as INPUT-OUTPUT. This is done BY-REFERENCE so that in the case where this procedure and the supporting entity procedure are in the same session, the change ProDataSet does not need to be copied:
RUN saveChanges IN phSupportProc
(INPUT-OUTPUT DATASET-HANDLE hDSChanges BY-REFERENCE).
If any errors were logged, it places them into the status parameter for return to the caller. For example:
/* Check the ERROR status that might have been returned. */
IF hDSChanges:ERROR THEN
DO iBuffer = 1 TO phDataSet:NUM-BUFFERS:
CREATE QUERY hQuery.
hBuffer = hDSChanges:GET-BUFFER-HANDLE(iBuffer).
hQuery:ADD-BUFFER(hBuffer).
hQuery:QUERY-PREPARE("FOR EACH " + hBuffer:NAME).
hQuery:QUERY-OPEN().
hQuery:GET-FIRST().
DO WHILE NOT hQuery:QUERY-OFF-END:
IF hBuffer:ERROR THEN
pcStatus = pcStatus + hBuffer:ERROR-STRING + CHR(10).
hQuery:GET-NEXT().
END.
hQuery:QUERY-CLOSE().
DELETE OBJECT hQuery.
END.
And finally, it merges the final change records back into the ProDataSet passed in, deletes the dynamic change ProDataSet, and does a synchronize on behalf of the user interface to make sure it redisplays any parts of the ProDataSet that are on the screen. For example:
It is worth noting here that you need to decide when to use MERGE-CHANGES in your own applications. An additional parameter to clientChanges.p could provide some alternatives, or the generic procedure could leave the merge up to the caller entirely. If there were any errors, you might not want to do a MERGE-CHANGES because this rejects any change that has been marked with the ERROR or REJECTED attribute on the record, which restores the original record values. This overwrites whatever changes the user has made. Typically, you are more likely to want to leave the changes in the user interface alone, point them out to the user using the Status return messages, and give the user the opportunity to correct the errors without having to re-enter all changes. In this case, you should run MERGE-CHANGES only when the updates all succeeded. This is entirely up to you. In some cases the SYNCHRONIZE method might also better be left up to the caller.
So now you have a general client-side change handler that you can call from any procedure that has accumulated changes in a ProDataSet. Now, move on to the server side of the update.