As a Uniface developer, I’ve seen a lot of Uniface applications first hand. On more than one occasion I encountered a situation where developers put all their code in the component. This happened for a number of reasons—access to the model or the library was constricted, there wasn’t enough time in the project to do it correctly, or just unfamiliarity with Uniface. I cannot speak on behalf of project managers or architects, but I can tell you how I code my projects.
The first rule of Uniface is that you do not copy and paste! (Very obvious movie reference!) If you find yourself in a situation that you think you need to copy code: stop! You are probably better off removing the code from its original source, putting it in either the application model or the library, and then reusing it in both the original component and the component where you wanted to paste it.
Single field implementation
Consider the following:
if (HEIGHT.PERSON < 0) HEIGHT.PERSON = 0 endif
A person can never have a height that is smaller than 0 meters. Maybe there are people with a negative size, but I have never seen one. So if someone enters a negative value we reset the value to 0. If you were to put this in a component, than you need to copy and paste it the next time you need it. Remember the first rule? So where would you put it? The most logical place would be in the trigger of the modeled field HEIGHT in the PERSON entity. Creating an entry on entity level and then calling it from the leave field trigger would score equally well. This way the inheritance in Uniface will provide this piece of code in every component you use the field on.
Multiple field implementation
On record level
But what about two fields in the same entity? The formula for the Body Mass Index of a person would be:
BMI.PERSON = WEIGHT.PERSON/$sqrt(HEIGHT.PERSON)
The content of BMI is calculated by dividing the WEIGHT by the square root of a person’s HEIGHT. In order to calculate the BMI we need the value of two different fields in the entity PERSON.
If you thought about putting it in the modelled entity you’d be correct. I would create an entry that can be reused in (for instance) the value changed triggers of the WEIGHT and HEIGHT field or call it from a collection operation if you wanted to update all the BMI’s in some type of batch.
Here is a classic. The total amount of an order is calculated by multiplying the price by the number of items in an order line, and then adding that to the total of the order. :
forentity "ORDERLINE" TOTAL.ORDER += PRIZE.ORDERLINE * NUMBERITEMS.ORDERLINE endfor
The second rule of Uniface (you can actually here the voice of Brad Pitt, can’t you?) is that you never make a reference to another entity from a modelled entity. If you do, you need to include the referenced entity on every component you use the modeled entity on or the compiler will keep wagging its finger at you.
So we can’t reference the TOTAL.ORDER field in the trigger of the ORDERLINES entity. The only logical place is to put it in in a component. In this case, I would put it in a service that can be called from other locations as well. I can even activate that service in the modelled trigger of the ORDER entity.
What if it is a non-database entity?
Non-database entities come in two distinct flavors. The modelled ones and the non-modelled ones. An example of a modelled non-database entity is the entity containing a list of buttons containing default behavior that you can reuse when creating components. With these particular non-database entities the same rules apply as for the modelled database entities.
Non-modelled entities are created on the fly on a component. In this case there is only one place to put your code. The component level.
And non-database fields?
Non-database fields, have the same distinct flavors. They are either modelled (for instance a button that shows detailed information about a certain record of a modelled entity) or the non-modelled ones. If the non-database field is in the application model, code it there, otherwise code it in the component.
When I mention the component, there are actually three levels where to place your code. In the triggers of the component, in the triggers of the non-database entity, or in the triggers of the non-database field. Based on the previous rules you should be able to determine the correct position.
There is no entity or field reference
Once more for good measure:
if ($status < 0) return $status endif
This code contains no field references and is of a more technical nature. This is an example of the smallest form of error handling in Uniface. If you intend to use it only once, the component is the best place to put it. If you need it in other places, you should move the code to the library and include it where required.
Can I use A Global Proc, instead of an Include Proc?
I have not used a Global Proc since the introduction of Include Procs. In my mind it is a deprecated feature of Uniface. From a component based development perspective Include Procs are better (but that is for another story). Besides using Global Procs for error handling purposes has one drawback. What happens when your Global Proc fails? Where are you going to catch that?
|Code references exactly one field in one modeled entity||Trigger level of the modelled field|
|Code references more than one field in one modeled entity||Trigger level of the modelled entity|
|Code references more than one modeled entity||In the component, preferably a service.|
|Code references a non-modeled entity||If a non-modeled entity is used more than once, it should be defined as a modeled non-database entity. If it is a very specific non-modeled entity, it can be only in the component.|
|Code does not reference a field or an entity.||Include proc. Never in a global proc. Component only, when it is really specific.|