In the days before UNIFACE 7 the only way to pass parameters to a form that you were about to initiate was to use General Variables, as in:
$1 = "value1" $2 = "value2" run "FORM2"
or Global Variables, as in:
$$variable1 = "value1" $$variable2 = "value2" run "FORM2"
The disadvantage with General Variables ($1, $2, etc.) is that each one is reused with each new form, therefore there is no way of telling what any particular variable contains at any point in time. All it takes for problems to appear is for FORM1 to load a value into $10 only for FORM2 to read from $11 instead.
The advantage with Global Variables ($$name1, $$name2, etc) is that the developer can create a separate variable for each distinct item of data, usually with the same name as the data item. A problem arises if different developers create different variables for the same data. Unexpected results can occur if FORM1 loads a value into $$CUSTOMER_NO
only for FORM2 to read from $$CUST_NUM
instead.
If you wished to pass a large number of data items, such as the complete contents of an entity, it was possible to use a separate variable for each item. I have seen code which used 40 variables at one time, which required 40 statements to load the data in FORM1, and another 40 statements to unload the data in FORM2. The more sophisticated developer would use a single variable containing an associative list, which can be constructed with a single statement, as follows:
putlistitems/occ $$params, "entity" run "FORM2"
The putlistitems/occ
statement will populate $$params with a list containing both the field names and values for the entire entity, in the format:
'field1=value1;field2=value2;...;fieldn=valuen'
FORM2 is able to extract the contents of this associative list with a single statement, as in the following:
<exec> trigger of FORM2 getlistitems/occ $$params, "entity"
The advantage of using associative lists in this way is that each item of data is identified by its own name in the list, which reduces the possibility of corruption or confusion. It is also possible to vary the contents of the list, such as by modifying the structure of the entity, without having to modify the code in either form.
The only time a problem can occur is if a data item used in FORM1 has a different name when used in FORM2, such as when different entities are involved. However, in my personal development standards I always use the same name for the same item regardless of which entity it is used on, so I never have this problem.
With the arrival of UNIFACE 7 came a new way of initiating forms and passing parameters. Instead of the single RUN
statement there were now two new statements:
NEW_INSTANCE componentname, instancename {,instanceproperties}
followed by:
ACTIVATE instancename {.operationname ({argumentlist}) }
In the pre-UNIFACE 7 days I used a global proc to perform the RUN
, so I saw no reason why I could not use a similar proc for the new statements. I started off with something like this:
$$component = componentname $$instance = instancename $$properties = instanceproperties $$operation = operationname $$params = argumentlist call ACTIVATE_PROC
The purists among you will immediately say "You are not supposed to use global variables to pass parameters!" Well, I only do this to get the values into ACTIVATE_PROC, after which they are loaded into local variables and passed to other procs as proper parameters. This saves me the hassle of having to define a variables...endvariables
block each time I want to use ACTIVATE_PROC
.
Now for a brief explanation of the various parameters:
$$component | The name of the component to be activated. This must also be defined on the Menu and Security database, and unless it is also included in the user's security matrix access will not be granted. |
$$instance | The name to be given to the new instance. If the component name is used then only one instance of the component can exist at any one time, but if a unique name is provided then multiple instances can exist. In my development environment this is usually left blank in the code as details for generating instance names are obtained from the component's entry in the Menu database. |
$$properties | This is usually left blank in the code as details can be obtained from the Menu database at runtime, such as values for HORIZPOS and VERTPOS as used in the DIMENSION property, or TRANSACTION=TRUE for opening up another database connection. |
$$operation | If left blank this will default to 'EXEC' for the <EXEC> trigger. Although it is possible to define any number of operations within a component I have never found a good reason to use anything other than EXEC as the default operation for form components. I reserve other operations for particular circumstances. |
$$params | Although the argumentlist may contain numerous arguments of different types I prefer to use a single string containing an associative list as this has proved to be the most flexible. |
During the processing of ACTIVATE_PROC I include code to interrogate the contents of the Menu database for the specified component. This gives me the ability to adjust various settings according to the contents of the Menu database, which can be changed more easily than any program code. This is a feature which I built into my first 3GL menu system way back in 1985, and it has proved to be just as useful in my Uniface menu.
I now have a proc which is used in my menu system to activate forms, and which can also be used in the <detail> trigger of command buttons within any form to activate a child form. In the case of the latter the following generic code is all that is necessary:
$$component = @$fieldname ; use the button name for the component name $$instance = "" ; obtain from Menu database $$properties = "" ; obtain from Menu database $$operation = "" ; default is 'EXEC' call LP_PRIMARY_KEY ; get values for $$params call ACTIVATE_PROC ; activate component
You will notice that I use a local proc to obtain the contents of $$params.
entry LP_PRIMARY_KEY $$params = $keyfields(<entity>,1) ; load name(s) of primary key item(s) putlistitems/id $$params ; insert representations end LP_PRIMARY_KEY
I have a slightly different version of this local proc in various component templates due to the different generic name given to the target entity. You should notice that it does not matter how many fields are contained within the primary key as all can be accommodated inside the associative list.
Some of you may ask the question "What if I have a form that does not require any parameters? How can the generic code deal with that?" Elementary, my dear Watson. When I come to perform the ACTIVATE
statement inside ACTIVATE_PROC I allow two attempts - one with a single parameter and another with none. If one combination fails I try the other, and it is only if the second attempt fails that I stop processing. The code I use looks something like this:
if (lv_Params = "") ; optional activate lv_Instance.lv_Operation() ; without $$PARAMS if ($procerror = <UPROCERR_NPARAMETERS>) ; param mismatch activate lv_Instance.lv_Operation(lv_Params) ; try with endif else activate lv_Instance.lv_Operation(lv_Params) ; with $$PARAMS if ($procerror = <UPROCERR_NPARAMETERS>) ; param mismatch activate lv_Instance.lv_Operation() ; try without endif endif if ($procerror) ; could not activate call PROC_ERROR($procerrorcontext) return(-1) endif
This allows me to have a component with a parameter string which is entirely optional. I can activate it from a menu screen with a null parameter, or from a parent component with a non-null parameter.
Another advantage I have found with the associative list is that I can include values which are instructions rather than data. For example, I have a component template for a LIST form which in some instances is required to perform an automatic retrieve when it is initiated while in other instances it is not. Rather than having a separate component template for each variation, or having a switch which is set at compile time I opted for the more flexible solution of using a setting on the Menu database which can be changed at will and passed to the component within the parameter string. By default this setting is off, but if I want to turn it on I specify $AUTO_RETRIEVE$=Y
for the component in the Menu database. This is then added to the parameter string by code within ACTIVATE_PROC. The receiving component loads the parameter string into its component variables, then examines the contents of $AUTO_RETRIEVE$ and acts accordingly, as in the following sample code:
params string pi_Params endparams getlistitems/id/component pi_Params ; copy to component variables getlistitems/occ pi_Params, "<entity>" ; copy to entity if ($auto_retrieve$) perform LP_RETRIEVE endif display
The advantage of being able to include a component variable in the associative list is that it will only be processed by the getlistitems/id/component
statement and not by the getlistitems/occ
statement. It is also possible for the list to contain values for 'name', '$name$' and '$$name' without confusion as each is treated as a different name.
Yet another feature I can make use of is the ability for an item within an associative list to contain another associative list. This is known as a 'nested list', and any number of levels of nesting are allowed. I use this feature to identify initial values to be used in components which create new occurrences. I have a function in my Menu system which allows initial values to be defined for any field within a component, and during the processing of ACTIVATE_PROC these values are extracted from the Menu database and put into an associative list. This list is then added to the parameter string which is passed to the component using code similar to the following:
putitem/id lv_Params, "$init_values$", lv_init_values
This means that the associative list contains an item called '$INIT_VALUES$' which in turn contains its own associative list. The component transfers the contents of this nested list into its own component variable called '$INIT_VALUES$' using code already shown previously. When it creates a new occurrence it can then populate fields with their initial values. In this way I can alter the initial values used by a component just by altering the contents of the menu database and not by amending any code.
Using an associative list as a single parameter for all form components has proven to be the most flexible option. There is nothing I can do with the other types of parameter that I cannot do with a single associative list. This string may contain zero, one or more items, and any item may also contain its own separate list. The fact that I have also used a standard ACTIVATE_PROC means that I have been able to add to the processing within this proc in order to implement new features very quickly and easily. In particular I have been able to add extra data items to the component details on my menu database so that ACTIVATE_PROC can include them in its processing. I am thus able to alter the activation or behaviour of components by changing data on the Menu database rather than by changing any program code. These details include:
ACTIVATE_PROC is not used for the following:
ACTIVATE
statements are hard-coded.POSTMESSAGE
statement and the <async interrupt> trigger.A sample application which demonstrates the principles outlined here is available on my Building Blocks page.
Tony Marston
13th July 2001
mailto:tony@tonymarston.net
mailto:TonyMarston@hotmail.com
http://www.tonymarston.net