Introduction
In the previous article I made a global description of implementing a layered application framework for SQL. In the article the following layers are identified:
Domain layer (static domain objects)
User layer (view of various user groups on domain objects)
Interaction layer (Human Computer and Computer Computer Interaction)
In this article I will present an implementation in Visual Object classes how the Human Computer Interaction can be implemented in a generic way. In my daily work I am mainly involved in modelling application logic using a CASE tool. One of the deliverables of the CASE tool is a Visual Objects implementation (and instantiation) of this model as layered objects.
Arrays as carriers
One of the great aspects of Clipper is completely implemented in Visual Objects: the array technology. And of course with reason: arrays are so powerful in VO. For example the possibility to dynamically add elements to the array and the fact that you can store any kind of entities like objects in an array. In the previous article I described the aspect of array carriers briefly. The name–value combination arrays are a way to let layers communicate with each other in a structured way. For communication between the interaction layer and the user interface this technique is also used. However in this situation an array of objects is used.
In the previous article I described the object that implements a servicelayer for communication for interaction. This object has the following method:
METHOD GetListControls(strName) CLASS ServicesObject
LOCAL arrRes :={} AS ARRAY
DO CASE
CASE Upper(strName)=="DETAIL_CONTACT"
AAdd(arrRes,SELF:GetControlItem("CONTACT ","CONTACT_DATE "))
AAdd(arrRes,SELF:GetControlItem("CONTACT ","CONTACT_ID "))
AAdd(arrRes,SELF:GetControlItem("CONTACT ","DESCRIPTION "))
....
ENDCASE
RETURN arrRes
The code shows how an array is dynamically populated with controlitem objects (see next paragraph). The number of elements depends on the servicename passed as argument of the method. The interaction objects can use this carrier array for further processing, like drawing windows and controls. In the figure below the argument passing is explained.
ControlItem
In Visual Obects in datawindows and -servers instances of fieldspec classes are used to implement characteristics of fields. This is the basic idea behind the ControlItem class: it implements the static aspect and some dynamics of an attribute in the domain layer in the sense of a carrier. When the serviceobject is instantiated the init method calles the loadcontrols method. This method again fills an array with controlitem elements. In the code below a number of examples are given:
AAdd(aRet, { "CONTACT", "CONTACTTYPE_ID ",;
ControlItem{ "Contacttype_id", "Contacttype", "I", "Lookup_Contacttype", "1", "", 0} })
AAdd(aRet, { "CONTACT", "CONTACTTYPE_ID ",;
ControlItem{ "Contacttype_id", "Contacttype", "I", "Lookup_Contacttype", "1", "", 0} })
AAdd(aRet, { "CONTACT", "CONTACT_DATE ",;
ControlItem{ "Contact_date", "Contact date", "D", "", "1", "", 10} })
AAdd(aRet, { "CUSTOMER", "CUSTOMER_TYPE ",;
ControlItem{ "Customer_type", "Customer type", "V",;
{ "Business client", "Personal client" } , "0", "", 50} })
The controlitems are representations of attributes of the domain layer. Therefore the items are identified by the name of the object type and the property. A controlitem stores information that a control on a window can use to display the data in a correct manner. For example the second element is the caption used for the control it gives the control a for the user logical name instead of the name of the property. Other items are the datatype , a domain of values (the name of a datasupply service or an array of static elements) the cardinality of the element and the initial value of the control.
The controlitem class has a little more functionality that is mainly used to make transformations between interaction controls and database implementation easier. For example the source code below transforms a dateformat in a control when possible to the format of a date in the database.
METHOD DateFormatter() CLASS ControlItem
LOCAL blnOk:= TRUE AS LOGIC
IF SELF:strTypeContent="D" .and. !Empty(SELF:usuValue)
IF !Instr("-", SELF:usuValue) .AND.;
!Instr("/", SELF:usuValue)
blnOk := FALSE
SELF:usuValue := ""
ELSE
SELF:UsuValue := [] + SubStr(SELF:usuValue,7,4)+"-"+;
SubStr(SELF:usuValue,4,2)+"-"+;SubStr(SELF:usuValue,1,2)
ENDIF
ENDIF
RETURN blnOk
Interaction implementation
In the application framework a number of default interaction classes are defined. These classes are instantiated with the meta data for this specific application and a basic application is available. Most of the time this is an application with sufficient logic. In a small number of application extra user interactions like reports and specific datawindows with a richer interaction. In the figure you can see the default framework windows with on the left hand side a treeview to navigate through the elements and on the righthand side the details of the selected treeview element. In this article I wil only describe the detail window, in a later article I will describe the treeview implementation.
The datawindow is generated based on the carrier array with control items. To make this possible the autolayout method of the datawindow class is implemented in a derived class from the datawindow. A summary of the source code of this method is given below:
FOR liField:=1 UPTO ALen(aVelden)
oField := aVelden[liField]
cType := oField:strTypeContent
…
DO CASE
CASE cType="M"
oPoint:Y := oPoint:Y - (1.5 * editGap)
liHoogte -= (1.5 * editGap)
newControl := CaptionMultilineEdit{SELF, 200+liField,;
oPoint,;
Dimension{(60*TextMetric.tmAveCharWidth)+liCaption,;
editGap*2.5 - 6},; _Or(…) }
CASE …
OTHERWISE
IF ALen(aVelden[liField]:arrValues) > 0
aTemp := aVelden[liField]:arrValues
ELSE
aTemp:=SELF:objService:GetFieldDomain(;
aVelden[liField]:strValues)
ENDIF
DO CASE
CASE ALen(aTemp)>0
newControl := CaptionComboBox{SELF,;
200+liField,;
oPoint,;
Dimension{ CalculateComboSize(aTemp,;
SELF)+liCaption, editHeight}}
nX:=NewControl:Origin:X
nY:=NewControl:Origin:Y - 60
…
NewControl:FillUsing(aTemp)
ENDCASE
ENDCASE
newControl:LinkControl(aVelden[liField])
newcontrol:Value:=aVelden[liField]:strInitValue
newControl:Show()
ENDIF
NEXT
In the summary code you can see how in a for next loop the carrier array is processed. For each element based on a number of conditions the right control is created. Special care is taken to handle the domain of the control correctly. If the domain retrieved by the method GetFieldDomain() is processed. When it is an array it is a static domain when it is a string it is the name of a data supply service.
At the end of the source code you can see how the control is linked to the controlitem class. This takes care of further controlitem handling in the derived control class.
Connecting the servicelayer with the interactionlayer
In the above paragraph a description is given how the interaction layer generates generic datawindows based on the information supplied by the servicelayer in carrier arrays. This is one aspect of the communication between the servicelayer and the various classes in the interactionlayer. For example a datawindow with modified controls have to send these changes to the domainlayer and from there to the database. Or a report has to display data from the domainlayer.
As explained there are two kinds of services datasupply and datamodify services. The first one retrieves data from the domainlayer the second kind modifies the domain objects. This is very easy to implement now. On the datawindow the following code is added to retrieve information from the domainlayer by calling a window generate method datasupply service:
METHOD GenerateForm(strEvent, strDataService, aPara) CLASS ServiceLayer
LOCAL oWnd AS DLADataWindow
oWnd:= DLADataWindow{oMainWindow, SELF, strDataService}
oWnd:SetEvent(strEvent)
…
oWnd:FillControls(SELF:ExecuteSupplyService(;
strDataService, aPara))
…
oWnd:Show()
The method calls the supplyservice and instantiates an wrapper class over the sql statement. This is passed to the window to set the values of the controls with the values of the select statement.
Summary
In this article a description is given of how to implement the interaction layer of a Visual Objects application. This application uses a three layer architecture with services to interact between the layers and the user interaction.
By using generic software it is easy to implement default user presentations that communicate with the servicelayer to retrieve information and modify the state of domain objects in a SQL database. This generic implementations are stored in class libraries and the specific application behaviour is generated by a CASE tool. Applying this work process opens opportunities to develop software very fast and in a cyclus with iterations.
About the author
Bert is software developer for more than 15 years now. In his early programming days he started with Foxbase and Clipper. Later Visual Objects, Microsoft Office and ASP webapplications were included. He is interested in model (data) driven solutions and object oriented methodologies. Bert is an independent software developer. He can be reached by email at bert@dla-architect.nl. His website is: www.dla-architect.nl