Try OpenEdge Now
skip to main content
ProDataSets
Batching Data with ProDataSets : Setting up an event handler for the FIND-FAILED buffer event
 

Setting up an event handler for the FIND-FAILED buffer event

We can extend the example even further to show how to use the FIND-FAILED event. This ProDataSet buffer event is similar to the OFF-END event on a query in that it gives your application the opportunity to retrieve missing data without the rest of the application even being aware that it was not in the ProDataSet in the first place. You can use the event to retrieve needed rows one at a time, or in batches, depending on the situation.
To show how the FIND-FAILED event works:
1. Add a callback for a FIND-FAILED event handler to the LEAVE trigger for SalesRep in PickOrderBatch.w, right after the callback for the OFF-END event. For example:
/* Set up an OFF-END event handler for the Order buffer to do batching. */
QUERY OrderBrowse:SET-CALLBACK-PROCEDURE("OFF-END","OffEndOrder",
                                         THIS-PROCEDURE).
/* Also a FIND-FAILED event handler for the Item table. */
BUFFER ttItem:SET-CALLBACK-PROCEDURE("FIND-FAILED","FindFailedItem",
                                     THIS-PROCEDURE).
Note that the OFF-END event must be attached to a query, and the FIND-FAILED event to a ProDataSet buffer, in this case the ttItem buffer, which will allow the procedure to retrieve Items to add to the browse the first time they are referenced.
2. Change the call to fetchOrderDetail in the MOUSE-SELECT-DBLCLICK trigger for the Order browse by removing the second parameter that tells fetchOrderDetail whether items have been retrieved or not, as shown:
/* Don't get all Items */
RUN fetchOrderDetail IN hOrderProc
  (iOrderNum, OUTPUT DATASET dsOrder APPEND).
In this case, fetchOrderDetail will never return any Items. They will be retrieved one at a time as they are needed.
3. Edit the fetchOrderDetail internal procedure in OrderSupportBatch.p to remove the second parameter. In addition, set the FILL-MODE for ttItem to be NO-FILL, unconditionally. This means that fetchOrderDetail will return only OrderLines for the current Order, and no Items. For example:
PROCEDURE fetchOrderDetail:
  DEFINE INPUT PARAMETER piOrderNum AS INTEGER NO-UNDO.
  /* Removed lFillItems parameter -- never return all items */
  DEFINE OUTPUT PARAMETER DATASET    FOR dsOrder BY-VALUE.

  hDataSet:EMPTY-DATASET.
  cSelection = "OrderNum = " + STRING(piOrderNum).
  hDataSet:GET-BUFFER-HANDLE(2):FILL-MODE = "APPEND". /* ttOline */
  hDataSet:GET-BUFFER-HANDLE(3):FILL-MODE = "NO-FILL". /* ttItem */
  hDataSet:FILL().
END PROCEDURE. /* fetchOrderDetail */
4. Now code the FindFailedItem internal procedure in PickOrderBatch.w, as shown:
PROCEDURE FindFailedItem:
  DEFINE INPUT PARAMETER DATASET FOR dsOrder.

  DEFINE VARIABLE cItemName AS CHARACTER NO-UNDO.

  RUN fetchItem IN hOrderProc (ttOline.ItemNum, OUTPUT cItemName).
  CREATE ttItem.
  ASSIGN
    ttItem.ItemNum = ttOline.ItemNum
    ttItem.ItemName = cItemName.
  RETURN NO-APPLY.
END PROCEDURE.
As with all callbacks, this one gets the ProDataSet as INPUT. It runs a new procedure called fetchItem, which you will write in a moment, which accepts the needed Item number and returns its ItemName. As always with callbacks, the buffer in the appropriate temp-table in the ProDataSet parameter holds the row that was current when the event occurred. In this case the AVM is attempting to reposition the ttItem browse each time you select a ttOline row in its browse. To do this, the AVM is doing the equivalent of a FIND statement internally. When the Item is not already there, the FIND-FAILED event occurs, giving your event handler the opportunity to add the needed Item to the client.
Since the browse shows only the ItemNum and ItemName fields, fetchItem only needs to return the name. You then create a new ttItem temp-table row for that item, and RETURN NO-APPLY. This tells the AVM to ignore the failure and try to locate the row again, transparent to wherever the find failed. This now succeeds, and the new Item appears in its browse and becomes the currently selected row.
5. Finally, write the new fetchItem procedure in OrderSupportBatch.p, as shown:
PROCEDURE fetchItem:
  DEFINE INPUT PARAMETER piItemNum AS INTEGER NO-UNDO.
  DEFINE OUTPUT PARAMETER pcItemName AS CHARACTER NO-UNDO.

  FIND Item WHERE Item.ItemNum = piItemNum.
  pcItemName = Item.ItemName.
END.
This accepts the current ItemNum, which the window procedure discovered that it did not have when it tried to reposition the Item browse when you select an OrderLine. It finds the database record for that Item and returns its ItemName. Since you are just returning a single field, there is no need to do a FILL. That would be overkill in this case.
6. Save all of this and rerun PickOrderBatch.w. Enter some selection criteria for Orders, such as Customer 1, tab through the fill-ins, and then double-click on an Order to retrieve its OrderLines.
When you do this, your modified version of fetchOrderDetail is not returning any Items. Only when you select an OrderLine does the AVM detect that its Item is missing, because there is no row to which it can reposition the Item browse. This fires your FIND-FAILED event handler, which retrieves that one Item, and adds it to the temp-table. By executing a RETURN NO-APPLY, the handler tells the AVM to ignore the failure and retry the reposition.
Now the Item is retrieved as expected, as shown:
As you continue to select OrderLines, the AVM continues to retrieve each Item the first time it is needed, adding it to the ttItem temp-table and to the browse, as shown:
Note that because you have created a general-purpose event handler for any FIND on the ttItem table that fails, any other action your client might take that tries to find a ttItem row unsuccessfully will automatically trigger the same handler, and will transparently add the row to the temp-table and make the row available to the statement that needed it, without that statement or its surrounding code needing to do anything special to allow for the possibility that the row is not yet there when it is first referenced. This is the value of the event handlers, that they execute whenever they are needed within the session.
Retrieving rows to cache on the client one at a time may not be a good strategy in many cases. Your FIND-FAILED handler could also retrieve batches of rows up to the one that is needed, or batches of related rows that are likely to be needed by the client. In any case, the FIND-FAILED event is just one more tool you can use to make batching and caching data in your ProDataSets as transparent as possible.