Validation and Leave Triggers

By Peter van Dam (published in 1998)

Introduction

Validation in an event-driven environment is not as easy as in a procedural environment. In version 6 most validation issues could be resolved by validate phrases since they would always fire upon leave of a (modified) field as well as on GO of the frame. This would satisfy most validation needs since there was no way the user could continue with the application without applying a GO to the current frame.

In an event-driven environment this is not the case. Multiple frames in an application can be enabled at the same time and the user can jump around the screen by using the mouse and accelerator keys. Therefore validation is a little more complicated in this case.

Many event-driven programmers are tempted to execute validation routines in leave triggers. This can get you into real trouble. In this article I will discuss the pitfalls as well as some tips and tricks that are of interest to both novice and seasoned Progress developers.

Don’t validate!

One of the basic rules of good application design is to prevent users from making errors. In a GUI environment there are many ways to achieve this. For example, if a user has to provide the salesrep in an order you would typically present all the valid salesreps in a combo-box. Since there is no way for the user to type in a wrong salesrep you do not need to validate this input.

Other GUI widgets such as radio sets, toggle boxes, selection lists and sliders serve exactly the same purpose. In our shop we have the rule that fill-in widgets are only allowed when no other widget is appropriate (this does not mean that our screens don’t contain fill-in fields!).

Field validation

If there is no way you can prevent errors you will have to validate the data the user enters.

Just like in a character environment there are basically two types of field validation:

  • independent validation
  • dependent validation

With dependent validation the validation rule of a field depends on one or more other fields. An example of this is a date range where date1 must be smaller than date2. There is no way to validate this with field-level validation since you never know if the user is going to change the other field before committing. Therefore, dependent validation in general can only be performed when the user commits his work.

Independent validation does not depend on other fields, such as preventing blank data in a particular field. This type of validation can be done at the field level. In some cases, such as entering a valid customer number, immediate validation is necessary. In many other cases it’s questionable, however, whether or not this is helpful to the user.

Let’s consider a few fields on a screen that cannot be left blank. Is it user friendly to block the entire application as soon as the cursor enters such a field? Is it appropriate to enforce modality in an otherwise non-modal application at this time? Why would you not allow the user to enter this data after entering another field?

My inclination is that empty mandatory fields do not need to generate an error before the user commits the entire screen. There is no need to force the user to enter these fields in a particular order. Subtle ways to communicate to the user that the current data is not complete yet are disabling the OK button or coloring invalid fields. The general idea is to allow users to enter whatever data they want; but if it is invalid it does not get saved to the database.

Field validation can be useful in cases where the user must enter a valid number or code in a particular field. Examples of this would be bank accounts and a social security numbers. There is not much point in continuing with the application if the user cannot determine the valid data at this time.

Why you cannot rely exclusively on leave triggers for validation

Leave triggers often do not fire even if you expect them to. Here is a list of the most common situations where leave triggers do not fire:

  1. when the user chooses the default button without use of the mouse (i.e. by pressing the ENTER key);
  2. when the user leaves the frame. There are many ways to accomplish this: by selecting another frame or window in the application (such as another folder page in a smart folder) or by selecting a menu item;
  3. Browse-specific: in a browse the leave trigger fires after repositioning to another record (although there are workarounds for this).

In addition leave triggers do not fire when the user never ENTERs the field.

As you can see there are many situations where leave triggers do not fire and therefore you should never base your validation exclusively on leave triggers.

How to implement field validation

So how would you implement field validation without the use of leave triggers? Here’s the recipe to implement that in a safe way (in combination with the OK validation as presented below).

The standard Progress dictionary validation is much more robust than leave validation because you can easily instruct Progress to repeat this validation on commit (see below). But what about adding validation that is not in the dictionary, e.g. for a variable? Or overruling dictionary validation? Well, v6 programmers were using this all the time and it still works very well in later Progress versions. The only problem is that the Progress ADE group ‘forgot’ to implement this in the UIB and therefore most UIB developers do not know about it.

I am talking about the VALIDATE option of the FORMAT phrase. The syntax for this is:

VALIDATE(condition, msg-expression)

For example, suppose you have a Customer record from the Sports database on your screen and you want to prevent the user from entering a blank value for the name. The validate phrase for this could be:

validate(customer.name ne "","Name cannot be blank").

How would you get this validate in your frame without the help of the UIB? Quite easily. The Progress compiler always combines all references to a frame into one. Therefore you can simply add the following statement in your main block:

define frame

customer.name validate(customer.name ne "","Please enter the Name").

This will instruct the compiler to add the validate phrase to the customer field in the existing frame and you’re in business.

So now we have implemented safe field validation without the use of any leave triggers. An additional advantage of this approach is that your leave triggers are still available for the purpose they were meant for: additional processing on leave of a field.

Just one word of warning: the validate phrase only fires if the field contents are actually modified. Therefore, if you insert a new customer record and do not enter anything in the Name field the validation will not be applied and the user can still leave the field without entering a name (as opposed to blanking out an existing name in the field). So much for field validation? Heck no. You can, if you insist, fix this by adding a LEAVE trigger (am I really saying this?) with the following code in it:

ON LEAVE OF Customer.Name IN FRAME 
DO:

assign self:modified = true.

END.

This is a very neat way to perform the same validation on a different event without duplicating any code.

How to validate on OK

While you can debate on whether validation should be done at the field level, nobody will argue that you need to validate all input on commit. Here is the recipe to achieve that.

For independent validations (field validation) you can invoke the VALIDATE() method on the frame:

ON CHOOSE OF Btn_OK IN FRAME 

DO:

/* part 1: field validation */

def var wfram# as handle no-undo.

assign wfram# = frame :handle.

if wfram#:validate() = false then return no-apply.

END.

This piece of code ensures that all your field validations are applied to all fields regardless of whether the cursor has ever entered any of them.

After the input has passed the independent field validations you should now perform the dependent field validations, if any. This is best done in the same manner in the OK trigger:

/* part 2: extra validations */

do on error undo, return no-apply:

run my-validations.

end.

Where my-validations is an internal procedure that performs the actual dependent validations. This internal procedure contains the following steps for each validation:

  • Check validation rule

  • If validation fails:

  1. display a message
  2. apply "entry" to most relevant field (remember, there is more than one field involved)
  3. return error.

Only after all validations are passed you should start the actual transaction.

But what about CANCEL?

The most common problem programmers run into when enforcing field validation is the inability to cancel invalid input. The system refuses to let the user leave a field with invalid data, even for the CANCEL button. The strange consequence of this is that the user must enter valid data in the field (which can be difficult) before the entire operation can be canceled. An even stranger consequence is that the user cannot even press the HELP button to get help!

Fortunately there is no need for this ever to happen in your Progress application. Progress has a very good provision for this that many programmers do not know about since it is not clearly spelled out in the documentation.

For all your CANCEL buttons check the Auto-End-Key and Cancel Button properties in the UIB. Only one button in any frame can have the Cancel Button property.

These properties allow the user to CANCEL the operation by clicking the button or pressing ESC, even if the cursor is in a field that fails validation.

So what about HELP and other buttons?

In addition to CANCEL there may be other buttons you wish to make available to the user at any time, even if the cursor is in a field that currently fails validation. A typical example of this is the HELP button. Surely you want to provide help to a user that does not know what to type into a mandatory field, don’t you?

This can be accomplished by checking the Auto-End-Key property for the HELP button. In addition, the last statement in your HELP button trigger should be RETURN NO-APPLY. Auto-End-Key frees up the key, and
RETURN NO-APPLY, in effect, negates the Auto-End-Key property - meaning it won't close the window or dialog box.

Taking this one step further you can even apply this principle to lookup buttons.

For example, suppose the user needs to type in a customer number into a fill-in field followed by a lookup button. The cust-num field does have a validate phrase so that you cannot leave it without entering a valid customer number. Except when you click on the lookup button. You can accomplish this by setting the Auto-End-Key property of the button and the following trigger code:

on choose of butt-cust do:

def var cust-num# like customer.cust-num no-undo.

run some-lookup-program.w (output cust-num#).

assign order.cust-num:screen-value = string(cust-num#).

apply "entry" to order.cust-num.

return no-apply.

end.

Skywalker

An interesting matter that will have to be addressed in the future is three-tier validation. Separating the User Interface from the Business Logic will also lead to a separation of validation issues. The question then becomes what types of validation information travel between the different layers and how you handle this in your application.

Some people have already experimented with this in version 8 and I am curious as to what philosophy Progress will introduce in Skywalker. Maybe we will have to learn a completely new way of thinking again...