Understanding Component Scope

In Visual Lansa the concept of  component scope is often misunderstood. I have come across many component definitions that misuse the scope parameter. Often, it is because definitions are copied between Owner Components that do not function the same way and analysis is not done to evaluate the proper component scope.

A defined component in Visual Lansa may be given different levels of Scope within the application; “The SCOPE parameter is used to create component instances that are shared between different instances of component owners” (LANSA User Assistance V11 SP5, Technical Reference Guide 8.4.1). The SCOPE parameter can be specified as *INSTANCE, *LOCAL, *DEFAULT, *SHARED, or *APPLICATION.

*INSTANCE – When a component is defined with parameter SCOPE(*INSTANCE) a new instance of the component is created for each owner component

*LOCAL – When a component is defined with parameter SCOPE(*LOCAL) a new instance of the component is created every time an Event, Method, or Property Routine is executed; as such, this value is only valid when the component is defined immediately after a EVTROUTINE, MTHROUTINE, or PTYROUTINE command.

*DEFAULT – When a component is defined with parameter SCOPE(*DEFAULT) the value is determined from the position of the component definition. Components defined immediately after the BEGIN_COM command are interpreted as *INSTANCE, components defined immediately after EVTROUTINE, MTHROUTINE, or PTYROUTINE commands are interpreted as *LOCAL.

*SHARED – When a component is defined with parameter SCOPE(*SHARED) only one instance of the component is created for all owner components of the same class. Owner components of different classes do not share component instances defined as scope *SHARED.

*APPLICATION – When a component is defined with parameter SCOPE(*APPLICATION) only one instance of the component is created for all owner classes within the application, as such all DLL’s required to implement the defined component and owner component will remain in memory for the lifetime of the component instance and possibly the lifetime of the application.

The following is a visual illustration of different scoping parameters demonstrated with a visual component. The same principal applies for non-visual components. In these examples a parent form and child form(s) of different classes dynamically create instances of a calendar control.

SCOPE(*INSTANCE)

Instance

All components reference their own instance of the calendar control. As such, when a child component closes the reference to the calendar control is set to null and its DestroyInstance event is signaled. This allows the child forms handle count to reach zero at which point its own DestroyInstance event is signaled. The DLLs for both the calendar control and form will be unloaded during garbage cleanup.

SCOPE(*SHARED)

Shared

When the calendar control is defined as SCOPE(*SHARED) a new instance of the component is created for each owner component class; because Parent Form and Child Form are separate classes they do not share an instance of the calendar control. Notice that only one Child Form has a visible calendar control, because the calendar control is shared between the two like classes it can only be visible on one…because the calendar control can only have one owner component. This is not an issue for non-visual components but further illustrates the point that there is only one shared instance. In this case the calendar control will not be destroyed until its reference is set to *Null by both Child Forms, the DLLS for the child forms and calendar control will also not be unloaded until both references are set to *Null and both child forms are closed.

SCOPE(*APPLICATION)

Application
When the calendar control is defined as SCOPE(*APPLICATION) a single instance of the component is created for all components within the application (that are defined as SCOPE(*APPLICATION). The single component instance is shared regardless of owner component class. Owner components containing components defined as SCOPE(*APPLICATION) will not be destroyed (and DLLS will not be unloaded) until all references of the Defined Component within the application are set to *Null.

The first question to ask when determining the proper component scope is ‘Is the component used in multiple owner components?’ – If No, then the Defined Component should be SCOPE(*INSTANCE) when defined after a BEGIN_COM or SCOPE(*LOCAL) when defined within an event, property, or method routine…this is the default component scope.

If a component is used in multiple owner owner components ask ‘Are the Owner Components main features of the application that will exist as long as the application is running?’ – If No, then the Defined Component should be SCOPE(*INSTANCE) when defined after a BEGIN_COM or SCOPE(*LOCAL) when defined within an event, property, or method routine…this is the default component scope.

If the Owner Components are main application features that should share an instance of the Defined Component ask ‘Are the Owner Components of the same component class?’ – If Yes, then the Defined Component should be SCOPE(*SHARED); If No, the Defined Component should be SCOPE(*APPLICATION).

A great deal of thought must be given to the Scope parameter when defining components within a large application. Allowing components within an application to share instances of a component provides improved performance by avoiding multiple CreateInstance  routines, but it also may prevent DLLs from being unloaded and system resources being freed.

Poll Results: Lansa Version 12



Below are the reults for the recently posted poll regarding new features in Lansa Version 12:

Poll Results

I think that it is interesting that new SQL Database features head the list. I think this reflects the fact that even through all of the recent developments in presenting user’s with richer content and sophisticated interfaces Lansa developers are still constantly seeking the most efficient ways to transform data into information and present it to the end-user in a meaningful way.

Using File Level Triggers for Validation Routines

Processing orders into our system has provided some very unique challenges.

  • Orders enter the system via three conduits: EDI, Batch Processing, and Manual Entry
  • Orders are filled for hundreds of customers from small town shops to the world’s largest retailers
  • Many customers have unique processing and validation steps

We wanted one central processsing and validation routine for all orders, regardless of their entry conduit. The result I came up with is the File Level Trigger. I have used trigger functions for many other tasks, mostly for recording fields changes. The rules could be intricately added to the file itself, but that would require the file to be re-compiled everytime there was a change in processing. A trigger function can be changed and re-compiled as needed.

Step one was to enable the order entry files for RDMLX. These were old files that had been in the system since version 8 or 9! RDMLX is not required; but since EPC845 provided the ability to remotely debug RDMLX functions on the server it is a great aid. On a side note: Yes, remote debugging does work with RDMLX trigger functions providing an excellent troubleshooting tool.

The structure of the trigger function is very simple. The function handles Before Update and Before Insert trigger operations. The calling program is notified of errors through the normal IO_STATUS value returned from file operations. Messages are issued from the trigger function and recorded by the calling function. I include the field name in the error message to the calling function can display the field in error. The trigger function can run code explicitly coded for a specific customer.

There are many different methods of data validation, but using a File Level Trigger to centralize the processing and remain flexible for future coding.

Dynamically Setting Field Errors

I’m currently working on the import of EDI orders from our EDI processor to Lansa. We have many (hundreds) of EDI partners all with specific business rules and data validation. I validate order data with file-level triggers (another post, another day), the errors are recorded in a file in fields #FILE [File Name], #FIELD [Field Name], and #ERROR [Error Message]. What is displayed below is the method by which I take the errors recorded in the file and set those fields to error on the form.

MTHROUTINE NAME(SetErrors)

 * Check for Errors for All Visual Edit Fields
FOR EACH(#Control) IN(#COM_OWNER.ComponentMembers) OPERATION(*INSTANCE_OF #PRIM_EVEF)
#COM_OWNER.CheckError( #Control )
ENDFOR

 * Check for Errors for Visual Edit Fields on Reusable Parts
FOR EACH(#Panel) IN(#COM_OWNER.ComponentMembers) OPERATION(*INSTANCE_OF #PRIM_PANL)
FOR EACH(#Control) IN(#Panel.ComponentControls) OPERATION(*INSTANCE_OF #PRIM_EVEF)
#COM_OWNER.CheckError( #Control )
ENDFOR
ENDFOR
ENDROUTINE

MTHROUTINE NAME(CheckError)
DEFINE_MAP FOR(*INPUT) CLASS(#PRIM_EVEF) NAME(#iField) PASS(*BY_REFERENCE)

* Check if Visual Edit Field Name is in Error Field List
LOC_ENTRY IN_LIST(#FLD_LIST) WHERE(#FIELD = #iField.ComponentPatternName)
IF_STATUS IS(*OKAY)
SET COM(#iField) SHOWERROR(True)
ENDIF
ENDROUTINE

  • #FLD_LIST contains the list of fields with validation errors in fields #FILE, #FIELD, and #ERROR.
  • The SetError method calls the CheckError method for every #PRIM_EVEF (Visual Edit) on the form, including those on Reusable Parts
  • The CheckError method checks if the visual edit’s ComponentPatternName matches a #FIELD entry in #FLD_LIST
  • Because #Control is cast as an *INSTANCE_OF #PRIM_EVEF, access is granted to the ShowError property

This simple code results in easily presentable errors for the end-user: