Release 10.1A: OpenEdge Development:
Progress Dynamics Advanced Development

> ## Running a PLIP from client logic

> A Progress Dynamics Persistent Library of Internal Procedures (PLIP) is simply a Progress 4GL procedure that is designed to hold business logic and run on the server side of a distributed application, to be invoked from other code either on the server or on the client side. Naturally, you want to avoid making calls from client code to the server whenever possible in order to maximize application performance. But sometimes it is necessary to make extra calls beyond those that the framework uses to populate the client-side objects and return data to the server. This section illustrates how you can use the Progress Dynamics Session Manager to invoke server-side logic that accesses the application database.

> In this example, you want to allow the user to make a call back to the server to obtain the latest Credit information for a `Customer` during `OrderLine` maintenance. Again, whenever possible, this kind of information should always be brought over along with other information the framework objects are already handling for you, using a device such as calculated fields in an SDO. But for the sake of the example, we presume that this must indeed be an immediate call back to the server.

> | To run a Progress Dynamics PLIP:

> 1. Build the business logic procedure itself. Select the New command in the AppBuilder menu, and select Structured PLIP as the object type. See OpenEdge Development: Progress Dynamics Basic Development for more information on the structure of the PLIP. In the Section Editor, add a new internal procedure called `AvailCredit`, which looks like this:

>     PROCEDURE AvailCredit:
>     /*---------------------------------------------------------------------
>       Purpose:
>       Parameters:  <none>
>       Notes:
>     ---------------------------------------------------------------------*/
>     DEFINE INPUT  PARAMETER pcKeyType     AS CHARACTER  NO-UNDO.
>     DEFINE INPUT  PARAMETER piKeyNum      AS INTEGER    NO-UNDO.
>     DEFINE OUTPUT PARAMETER pdAvailCredit AS DECIMAL    NO-UNDO INIT ?.
>     IF pcKeyType = "OrderNum" THEN
>     DO:
>         FIND Order WHERE Order.OrderNum = piKeyNum NO-LOCK NO-ERROR.
>         IF AVAILABLE (Order) THEN
>             FIND Customer WHERE Customer.CustNum = Order.CustNum NO-LOCK.
>     END.
>     ELSE IF pcKeyType = "CustNum" THEN
>         FIND Customer WHERE Customer.CustNum = piKeyNum NO-LOCK NO-ERROR.
>     IF AVAILABLE (Customer) THEN
>         pdAvailCredit = Customer.CreditLimit.
>     END PROCEDURE.

> This takes a `pcKeyType` parameter to tell it whether the key being passed in is a `CustNum` or an `OrderNum`, and then uses the key value itself to retrieve the appropriate `Customer` record. It returns `Customer.CreditLimit` as the output parameter. Again, real-life PLIPs will normally do more serious work that could not be returned in a simple field as part of an SDO.

> 2. Save this PLIP in the `oe` directory (or wherever you are organizing your objects) as `customerplip.p`.

> Now you must build a window where you support `Order` and `OrderLine` maintenance. Any combination of objects that manages these tables will do, but the step describes the combination used for the present example. The example presumes that you have generated SDOs and dynamic viewers and browsers for the Sports2000 tables.

> 3. Build an `Order` browse window as an independent window. (For more information on building a browse window, see OpenEdge Development: Progress Dynamics Basic Development .) Use the layout called rywinObjCont as the Container Template, substitute the SDO orderfullo for the template SDO, and substitute the dynamic browser orderfullb for the template browser. Select the dynamic browser in the layout grid, right- click to bring up the property sheet, and type orderfoldwin as the FolderWindowToLaunch property. This is the maintenance window that starts when the user double-clicks on a row in the browse or selects one of the update-related buttons in the toolbar:

> 4. Save this container as orderbrowsewin.

> 5. Create the Order Maintenance window. Create a new dependent window, which gets the OrderNum as input from the `Order` browse window. Use the folder window rywinFolder as the existing container to create this from.

> 6. Select Page 1 in the Container Builder, which is part of the container template. This page layout is designed for a single static viewer. If you have a dynamic `Order` viewer you would like to use instead, you can unsubstitute the page layout rypagDynView as the template. Substitute the `Order` viewer (the example static viewer is `orderstviewv.w`, previously built in the AppBuilder) for the template viewer.

> 7. Add a Page 2 to display and maintain `OrderLine records`. Add the dynamic `OrderLine` SDO orderlinfullo to this page, along with the dynamic browser orderlinfullb, and the dynamic viewer orderlinviewv.

> 8. Select the orderlinfullo SDO in the grid, open the property sheet for it, and type ordernum,ordernum as the Foreign Fields. This will filter the `OrderLine` SDO for `OrderLine records` of the currently selected `Order`.

> 9. Fill out the necessary data and update links between the `OrderLine` objects on Page 2 so that records are passed back and forth properly.

> 10. Create a `TableIO` `link` from the `StandardToolbar` to the static `Order` viewer `orderstviewv`. This allows the toolbar on Page 0 to maintain the `Order` displayed on Page 1. Create another `TableIO` `link` from it to the `OrderLine` viewer `orderlinviewv` as well.

> 11. Create a `Data` link from `THIS-OBJECT` (which represents the container itself) to the static `Order` viewer. This populates the viewer with data from the record selected in the browse window.

> 12. Create an `Update` link from the static `Order` viewer to `THIS-OBJECT`. This will pass the update back to the `Order` SDO maintained in the browse window.

> 13. Create a `Data` link from `THIS-OBJECT` to the `OrderLine` SDO `orderlinfullo`. This will pass the current `OrderNum` in to the `OrderLine` SDO, which, using the `Foreign Fields` information you entered, will filter the `OrderLine` `records` for the current `Order`.

> When you are done, your Links Maintenance window should look like this:

> 14. Save this as orderfoldwin.

> 15. Add a fill-in to the dynamic `OrderLine` viewer. Open the orderlinviewv viewer in the AppBuilder using the Open Object dialog box. Add the fill-in to the layout just as you would for a static viewer. Name it AvailCredit and give it a label of Available Credit and a format of >>>>9.99.

> 16. Define a UI event for this new field using the dynamic property sheet from the AppBuilder to trigger the internal procedure inside the custom super procedure that will, in turn, execute the server-side code to retrieve the credit information.

> It might be a better user interface if there was a button for the user to press to retrieve the credit information, but to keep things simple, retrieve the information `ON` `LEAVE` `OF` the dynamic fill-in itself by continuing the procedure.

> 17. Type an Event Name of LEAVE, type an Action Type of RUN, type an Action Target of SELF, and for the Event Action type the internal procedure name ShowAvailCredit. This defines a UI event that, `ON` `LEAVE` `OF` the dynamic fill-in, will run the procedure `ShowAvailCredit` in the `TARGET-PROCEDURE`.

> 18. Save this new UI event.

> 19. Create the custom super procedure for the `OrderLine` viewer by selecting New Structured Procedure in the AppBuilder. Use the Section Editor to create an internal procedure called `ShowAvailCredit`. This procedure first retrieves the lists of field names and field handles from its `TARGET-PROCEDURE`, as the earlier example did, as shown:

>     {get AllFieldNames   cFieldNames}.
>       {get AllFieldHandles cFieldHandles}.

> The next block of code illustrates how you can retrieve values not only from fields in the viewer, but from any other related object in the container. Because the Customer Number is the preferred key value to use to retrieve the Credit Limit, but that field is not normally displayed in an `OrderLine` viewer, the code attempts to locate an `Order` SDO with the customer number in it. So it retrieves first the data source of the current viewer, which is the `OrderLine` SDO, and then its data source, which should be the `Order` SDO if there is one, as shown:

>     hDataSource = DYNAMIC-FUNCTION('getDataSource':U IN TARGET-PROCEDURE).
>       hDataSource = DYNAMIC-FUNCTION('getDataSource':U IN hDataSource).

> Note that these two statements are effectively identical to this `{get}` include file syntax, as shown:

>     {get DataSource hDataSource}.
>       {get DataSource hDataSource hDataSource}.

> This version might look confusing because the handle is reused to retrieve the second data source from the first. The second statement amounts to this:

> - Get the value of the `DataSource` attribute and put it into the variable `hDataSource`. Use `hDataSource` itself as the handle of the object to query for its `DataSource`.
> - If this optional third argument to the `{get}` include file is not there, it uses the `TARGET-PROCEDURE` by default.

> In some cases there is no `Order` SDO above the `OrderLine` SDO. Because you should always write code that is as general-purpose as possible, it is good to allow for any combination of objects in any custom code you write. So the block of code checks to see whether this second `DataSource` in fact returned a valid procedure handle. If it did, then it uses an SDO function to retrieve the value of the CustNum field for the current row, as shown:

>     iKeyNum = INTEGER(DYNAMIC-FUNCTION
>                 ('columnStringValue':U IN hDataSource, "CustNum":U)).

> It sets another variable to indicate that it retrieved the `CustNum`. But if that second handle is not valid, then the code tries another approach, instead retrieving the value of the Order Number field from the `OrderLine` viewer, using the field names and handles lists, as shown in the following example:

>     ASSIGN ilookup = LOOKUP("OrderNum", cFieldNames)
>            hField = WIDGET-HANDLE(ENTRY(ilookup, cFieldHandles))
>            iKeyNum = INTEGER(hField:SCREEN-VALUE)

> It sets the other variable to indicate that what it retrieved is the `OrderNum`.

> Save this procedure and register it in the Repository. Then associate it with the dynamic `OrderlLine` viewer in the AppBuilder. To do this, open the `OrderLine` viewer, double-click on it to bring up its App Builder property sheet, and set the `Custom` `ADD-SUPER-PROC` field to `orderlinsuper.p`. Note that you do not specify the `.p` filename extension or the relative pathname because this information was placed in the Repository when you registered the procedure.

> This simplified example might seem a bit contrived, but it demonstrates a couple of basic principles that are important to keep in mind when you write this kind of code:

> - Always write your code to work in as many client object configurations as possible — Do not assume a particular combination of objects in the client container, and do not assume what the exact contents of those objects will be. This makes your code more flexible and reusable, and less prone to errors if other objects in the user interface change.
> - Retrieve information from other objects if you need to — Remember that you can get data from anywhere else on the client, as long as you are flexible about how you look for it. The link functions in each SmartObject will connect you to other objects, and various types of functions can retrieve information from those objects. Getting the object’s `ContainerSource` and then searching the container’s `ContainerTargets` can allow you to locate any other object in the same container window as the object you started from. If a search such as this saves you from an extra AppServer hit to retrieve data that might be elsewhere on the client, it is well worth it.

Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095