skip to main content
OpenEdge Development: ADM and SmartObjects
Developing ADM Extensions : Adding application logic to your new or customized ADM class
 
Adding application logic to your new or customized ADM class
Regardless of whether you are creating a new class or customizing an existing class, you must modify class files to provide the application logic for the new or customized class:
*If you are creating a new class, you modify the standard class files.
*If you are customizing an existing class, you modify the custom class files.
Although you are working on different sets of files, the process is similar: you modify the standard class files with the same sorts of changes you apply to the custom class files. This section describes the custom class files, then provides design rules and some special information on adding properties and writing super procedures. You should read the custom class files section even if you are modifying standard files, since you modify them similarly. For examples of both processes, see the “Examples” section.
Custom class files
The custom class files for a given ADM class are built into the class. These files contain a structure for the required application logic, as well as instructions for adding this code. The following list describes the custom class files, including for each file the filename format, the file type, and a discussion of the types of changes you can make with the file:
*Custom primary include file (filename: prim-incl-filecustom.i) — This include file is referenced in the standard primary include file. It contains the code to start the custom super procedure. This code is initially commented out; you must uncomment it manually once you develop your custom super procedure. You also can use this file to initialize properties.
*Custom property file (filename:prop-filecustom.i) — This include file is referenced in the standard property include file. It allows you to extend the behavior of the class by defining new properties.
*Custom super procedure (filename: super-proccustom.p) — This structured procedure file is referenced (instantiated) in the custom primary include file. It allows you to define internal functions and procedures to support new properties in the custom property file (that is, to define new behavior), and generally to extend or override the standard behavior of the class it belongs to:
*To extend the standard behavior of a class, create a local version of a standard function that contains the statement SUPER( ) or procedure that contains the statement RUN SUPER, then add code lines before and/or after the RUN SUPER statement that provide additional behavior before and/or after the standard behavior.
*To override a particular behavior, remove the standard behavior by defining an EXCLUDE-{proc-or-func-name} preprocessor value in the custom exclude definitions file for this class. This causes the removal of the corresponding procedure or function from the standard super procedure file. You then define in the custom super procedure a procedure or function of your own that has the same name as the procedure or function that you are overriding. (The custom exclude definitions file is described later in this section.) Be sure to include the RUN SUPER or SUPER( ) statement if the procedure or function that you are placing has it.
*Custom prototype file (filename: proto-filecustom.i) — This include file is referenced in the custom property include file. It allows you to define the function and procedure prototypes to match the internal entries of the custom super procedure. These prototypes allow an Open4GL client (for example, a Java client) to identify the entry points of the corresponding SmartObject.
*Custom exclude definition file (filename: class-nameexclcustom.i) — This include file is referenced in the standard super procedure file. It allows you to exclude procedures and/or functions by defining an EXCLUDE-{proc-or-func-name} preprocessor value. Each internal entry in a super procedure file is enclosed by special preprocessor code that checks for the existence of a corresponding EXCLUDE-{proc-or-func-name} preprocessor value and removes the procedure or function from the compilation. You typically exclude a procedure or function so that you can rewrite it in the custom super procedure. The EXCLUDE mechanism is available not only for super procedures but also for any standard structured procedure created in the AppBuilder.
The standard class files do not contain a file corresponding to the custom exclude definition file.
*Custom instance definition file (filename:class-namedefscustom.i) — This include file is referenced in the standard property include file. It allows you to modify the list of instance properties for the class, usually by adding properties. You can also use it to change the instance Properties dialog itself, by specifying the filename of a different instance properties dialog box.
The standard class files do not contain a file corresponding to the custom instance definition file.
Design rules
This section summarizes the design rules for constructing a new SmartObject that inherits from the new class that you created. These rules are fairly simple; follow them to assure the right application structure. The design rules are:
*Each SmartObject type (template) includes a primary include file that, by convention, has the same name as the template but with a .i extension. For example, viewer.w includes %DLC%\src\adm2\viewer.i (Windows) or $DLC/src/adm2/viewer.i (UNIX).
*Each primary include file such as viewer.i includes its parent’s support include file, plus its own property include file. For example, viewer.i includes datavis.i, because a SmartDataViewer is a visual object that displays data, and also viewprop.i, where the properties specific to SmartDataViewers are defined. It also starts the associated super procedure.
*Each super procedure file includes its own property include file. These nest in such a way as to define all object properties in the proper order.
In addition, the presence of super procedures means it is possible to run many internal procedures and user‑defined functions in SmartObjects that are not in those objects, but only in their super procedures.
*Each prototype file should include a prototype definition for each internal procedure and function implemented in a SmartObject’s super procedures. The contents of the prototype file are implementation dependent, so initially it is empty. Use the AppBuilder’s Pro*Tool ProtoGen to generate the prototype file based on your super procedure. For more information on the ProtoGen utility, see the online help.
Follow these rules when building a new SmartObject type, to ensure all the relationships are established.
Adding properties
When you extend the ADM, whether by creating a new class or by customizing an existing class, you define properties that support the special behavior of your class. If you are creating a new class, you define standard properties in the standard property file. If you are customizing an existing class, you define custom properties in the custom property file.
Within the relevant file, you define properties in the same way. The property definition section of these files appears near the end of the main block section of the file. It has the following format:
 
&IF "{&ADMSuper}":U = "":U &THEN
ghADMProps:ADD-NEW-FIELD(prop-specs)
ghADMProps:ADD-NEW-FIELD(prop-specs)
ghADMProps:ADD-NEW-FIELD(prop-specs)
. . .
&ENDIF
When adding properties to the property file for a new or customized class, consider defining any properties that will be accessed frequently and for which the best possible performance is important as direct access properties. Properties are stored in the ADMProps temp-table; direct access properties are accessed directly from the temp-table rather than using get/set functions. The lack of function‑call overhead can significantly improve performance.
You specify that a property is a direct access property by defining a preprocessor constant of the form xppropname for it. The preprocessor constant definition section is in the main block of the property file, just before the property definition section. The definitions have this format:
 
&GLOBAL-DEFINE xppropname
Sometimes you might want to read or write a direct access property from outside its own class; however, the ADMProps temp-table can be accessed directly only from objects of its own class and super procedure. To work around this problem, you must define get and/or set functions (as needed) that can be called from outside the class but that internally access the required property directly. This special‑purpose get function has the following format:
 
FUNCTION getpropname RETURNS LOGICAL ( ):
  DEFINE VARIABLE lpropname AS LOGICAL NO-UNDO.
 
  {get propname lpropname}.
  RETURN lpropname.
END FUNCTION.
Its companion set function has the following format:
 
FUNCTION setpropname RETURNS LOGICAL (pxvariable AS data-type):
  {set propname pxvariable}.
  RETURN TRUE.
END FUNCTION.
Writing super procedures
Another task you perform when you extend the ADM, whether by creating a new class or by customizing an existing class, is writing super procedures; that is, adding to the super procedure file custom functions and/or internal procedures that support the special behavior of your class. If you are creating a new class, you do this for the standard super procedure file. If you are customizing an existing class, you do it for the custom super procedure file.
Two important points to keep in mind when writing any functions or internal procedures that go into an ADM super procedure are:
*The functions and internal procedures in an ADM super procedure must always refer to the procedure on whose behalf they run.
*An ADM super procedure is designed to be shared among all running instances of the SmartObjects that use that super procedure.
References in super procedures to other procedures
When you write the functions or internal procedures that go into an ADM super procedure, keep in mind there is nothing about a super procedure that identifies it as a super procedure. It is simply a Progress procedure file that is run as a persistent procedure and then added as a super procedure to some other running procedure. The source code or compiled r‑code for a super procedure contains nothing special that identifies it as a super procedure.
What is special about a super procedure is the style of the code within its functions and internal procedures, which must account for the fact that these entry points are run on behalf of some other procedure. ADM super procedures, therefore, always must refer to the procedure on whose behalf they run. The basic mechanism is the TARGET-PROCEDURE built‑in function, which evaluates to the procedure handle of the procedure from which the entry point originally was invoked. The {get} and {set} include files, which provide a pseudo‑syntax for getting and setting ADM properties, support this mechanism by always referring back to the TARGET-PROCEDURE, so you always should use {get} and {set} when writing property references in super procedures. In addition, these include files identify whether a given property can be accessed directly out of the ADMProps temp-table record in the TARGET-PROCEDURE or instead requires the execution of the actual getpropname or setpropname function. For detailed information on these property mechanisms, see the “{get} and {set} pseudo-syntax for object properties” section, and the “Get and set functions for object properties” section.
For example, an internal procedure that operates on the RowObject buffer handle of its TARGET begins by retrieving that property value:
 
DEFINE VARIABLE hRowObject AS HANDLE NO-UNDO.
...
{get Rowobject hRowObject}.
To retrieve the value of a property from an object other than the TARGET-PROCEDURE, you can specify an optional final argument for the {get} and {set} include files that is the object’s procedure handle. For example, the following code gets the value of the EnabledTables property from the Data-Source of the TARGET-PROCEDURE:
 
{get DataSource hSource}.
{get EnabledTables cTables hSource}.
You also can use the include file {fn} as a convenient way to invoke a function in TARGET-PROCEDURE; for example:
 
{fn openQuery}  /* Invoke the openQuery function in my TARGET-PROCEDURE */
Similarly, the variant {fnarg} invokes a function that takes a single argument; for example:
 
/* Invoke the colValues function in my TARGET-PROCEDURE, and pass
   pcViewColList as the one input parameter. */
{fnarg colValues pcViewColList}. 
Shared super procedures
ADM super procedures are designed to be shared among all running instances of the SmartObjects that use that procedure.
Note: It is possible to create a super procedure intended to serve exactly one running procedure at a time, but this is not the correct design for super procedures that are part of the ADM class hierarchy and that are started in the standard way (that is, by running start-super-proc in the ADM include files).
Because ADM super procedures are shared, you must ensure their code never assumes continuity between calls. If a particular operation involves executing two entry points in a super procedure, the code should not share property values between calls (whether by using local variables defined in the Definitions section or by using another technique); instead, it should retrieve any property values needed at the top of each entry point. Although this might seem slightly inefficient, using {get} to retrieve a property value from the property temp-table compiles into a single in‑line Progress 4GL ASSIGN statement and is quite fast. Using this technique assures your super procedures can be shared among all running instances of your new class.
Likewise, because super procedures operate on behalf of other procedures, you always must PUBLISH events FROM TARGET-PROCEDURE. If you do not do this, your event normally will not have any effect, because the SmartObjects will not have subscribed to that event in the super procedure itself.
Also, when you invoke one entry point in a super procedure from another, you must preserve the value of TARGET-PROCEDURE in the called entry point. For example, suppose your super procedure contains two procedures, ProcA and ProcB, and you want to run ProcB from ProcA in that super procedure. You have basically two choices, depending on whether ProcB is a PRIVATE internal procedure or function that should never be run from any other procedure, or an entry point you might want to run or to override from its TARGET-PROCEDURE or another procedure:
*If ProcB is a PRIVATE internal procedure or function, one that should never be run from any other procedure, ProcA can run ProcB directly. In this case, if ProcB must refer to the target procedure (to get a property value, for example):
*TARGET-PROCEDURE must be passed as an input parameter to ProcB.
*ProcB must not use {get} or {set} to access the property.
This is because the value of TARGET-PROCEDURE inside ProcB will not be TARGET-PROCEDURE—it will be the super procedure’s procedure handle, since it was executed directly from the super procedure. In such a case, it is normally better to pass any property values to ProcB from ProcA. The following two examples, which illustrate code in ProcA, show two ways to do this:
 
º
RUN procB (INPUT TARGET-PROCEDURE).
º
 
procB:
DEFINE INPUT PARAMETER hTarget AS HANDLE NO-UNDO.
DEFINE VARIABLE cDisplayedFields AS CHARACTER NO-UNDO.
º
cDisplayedFields = dynamic-function(‘getDisplayedFields’ in hTarget).
 
DEFINE VARIABLE cDisplayedFields AS CHARACTER NO-UNDO.
{get DisplayedFields cDisplayedFields}.
º
RUN procB (INPUT cDisplayedFields).
*If ProcB is an entry point that you might want to run or override from its TARGET-PROCEDURE or another procedure, ProcA should invoke ProcB IN TARGET-PROCEDURE even though they are located in the same super procedure. In this way, references to TARGET-PROCEDURE inside ProcB correctly evaluate to the handle of the SmartObject that caused ProcA to be invoked. (This also makes it possible to override ProcB in a SmartObject, if this is necessary and appropriate.) The following example, which illustrates code in ProcA, shows how to do this:
 
º
RUN procB IN TARGET-PROCEDURE.
º
 
procB:
DEFINE VARIABLE cDisplayedFields AS CHARACTER NO-UNDO.
 
/* This works because procB was invoked in TARGET-PROCEDURE. */
{get DisplayedFields cDisplayedFields}.