Form controllers

In the section about forms we have already seen how the Form class provides support for many tasks related to the handling of forms like reading and writing the contents of input elements or performing validation. Compared with the amount of code that would be required if all these steps were implemented by hand this is certainly a major improvement. However, it is still not the full story

While the Form class implements a great deal of functionality needed for the handling of a form's data it leaves some important questions unanswered:

  • When should initial values be written into the input fields of the form?
  • When should validation be performed?
  • How are validation results presented to the user?
  • When should the form be closed and what should be done with the data entered by the user?
  • The user must also be able to cancel the form and throw away all the data entered so far.

These issues are closely related to the life cycle of a form. In desktop applications a form is always associated with a (dialog) window. When the window pops up the input fields must be initialized. While the user interacts with the input elements validation can be performed and fields containing invalid data can be highlighted somehow. Typically the window contains buttons which can be used to commit or to cancel the form. If the user presses the OK button, the data entered has to be validated again and - if validation succeeds - stored in the model for further processing. Then the window can be closed. The Cancel button in contrast directly closes the window.

In the JGUIraffe library a form controller is responsible for managing the life-cycle of a form. It establishes the connection between the Form class and the data it maintains and the window that embeds the form.

Fundamental concepts

It is an unwritten law that all libraries that deal with the processing of user input must emphasize that they implement the model view controller (MVC) pattern in some way. This is a design pattern which has proven to be successful especially in UI development. It focuses on the separation between the graphical component that presents data to the user (the view) and the component that stores the data (the model). A third component, the controller, evaluates user actions in the view and updates the model correspondingly. So the controller mediates between the model and the view.

The JGUIraffe library is no exception from this rule. It also adheres to the separation between model, view, and controller by making use of the following components:

  • The model can be an arbitrary data object appropriate for the application. It must be capable of storing all data a user can enter in the form's input fields.
  • The view is the dialog window displayed to the user containing all input elements of the form.
  • The controller is represented by an object of the class FormController or a derived class. Together with an instance of Form the interaction between the model and the view is managed.

In this section we focus on the FormController class. The idea is that every time a window displaying a form is constructed, an instance of FormController is created, too, and associated with both the window and the Form object managing the user's input. After its creation the FormController object is registered as listener for various events at the window and its input components. Thus it receives notifications for important status changes and can react accordingly, e.g. by invoking methods on the Form object.

For instance, when the window is opened for the first time the controller object reveives a corresponding event. It can then trigger the Form object to fetch initial values from its model and to write it into the input components.

The form controller is also aware of the actions that can close the form - namely the buttons for Okay and Cancel. If corresponding events are received, it can trigger a validation and decide whether the window can be closed. It is also possible to configure command objects that are to be executed when the form is closed on reaction of an Okay or Cancel action. This allows an application to immediately process the data entered by the user.

Finally, a FormController object can act itself as an event source. It can send certain events releated to changes in the validation status of input fields. Such events are fired for instance after each validation operation. Interested components can register as listeners for these events and perform corresponding UI updates. For instance, if a validation operation detects that a field contains invalid data, an event listener can highlight this input field.

This was a high-level overview over the functionality provided by the FormController class. We will now discuss its API in more detail.

The FormController API

FormController is a concrete class and can be used as is. If a dialog window has specific requirements that go beyond the standard functionality for input validation and processing provided by the class, a custom class can be created that extends FormController.

After the creation of an instance some references to dependent objects must be set. This way the controller is associated with the Form object and other components it has to interact with. Typically a developer does not have to care about the initialization of a form controller; the framework performs all necessary steps behind the scenes (we will discuss this in more detail when we describe the builders provided by JGUIraffe). In this subsection we assume that a FormController instance has already been created and initialized and focus on the methods that can be used to customize this instance.

The FormController object must be able to detect when the user wants to close the dialog window. Depending on the action triggered by the user (OK or Cancel) the controller has to react accordingly to ensure that the data entered by the user is processed correctly. In order to detect clicks on the OK or the Cancel button the controller has to know the names of the corresponding button components. For this purpose the methods setBtnOkName() and setBtnCancelName() are used. Both methods expect a string with the name of the corresponding button. The controller registeres itself as action listener at these button components so that it is notified when the user clicks on these buttons.

Further properties and methods of the FormController class are related to the handling of validation operations. First there is the validate() method which performs a validation of the form associated with the controller and returns the results. The method can be called at any time; the controller also calls it at certain points in the life-cycle of the dialog window, e.g. after it is opened or when the user clicks the OK button. A validation also causes all registered listeners of type FormControllerValidationListener to be notified. Listeners of this type can be added using the addValidationListener() method.

The default behavior of the FormController class when the user presses the OK button is to perform a validation to ensure that all data entered so far is valid. If this is not the case, a message box with validation messages is displayed, and the form cannot be closed. There are a couple of methods to configure the message box displayed in this situation:

  • setMessageOutput() allows setting the MessageOutput object that is used for displaying the message box. If no specific MessageOutput object is set, the application-global default object is used - which is probably desired in most cases. One use case for this method is to suppress the message box with validation errors at all. This can be done by setting a NullMessageOutput object.
  • With the setValidationMessageBoxCaption() method the title of the message box with validation messages can be determined. If no title has been set, a default title (defined by a default resource ID and obtained through the current resource manager) is used.
  • Finally it is possible to influence the content of the message box with validation messages. This can be achieved by passing a FormValidationMessageFormat object to the setValidationMessageFormat() method. FormValidationMessageFormat can translate a list of validation messages to a plain string which can then be displayed in a message box. Again, a default object is used if no custom format object is specified.

Processing of form data

When the user clicks the OK or the Cancel button (or performs a corresponding action, e.g. pressing the Escape key) the dialog window has to be closed. In addition, the controller can be instructed to execute specific code which reacts on the closing of the form. This is a convenient way to immediately process the data entered by the user. For instance, if the current form allows entering the data for a new domain object, code can be invoked in reaction of the OK button which writes the data into the database.

There are two ways to execute code when the dialog window associated with the controller is closed:

  • It is possible to set command objects that are to be executed for the OK and the Cancel action using the methods setOkCommand() and setCancelCommand() respective. When the associated dialog window is closed the controller checks whether a command is defined for the corresponding closing type (OK or Cancel). If this is the case, the command is put into the global command queue and executed by a worker thread - hence the UI does not block. (Refer to the Commands section for more information about commands and their execution.) By making use of command objects complex operations can be performed with the data entered in the form, e.g. database or file operations.
  • Another option is to register special event listeners at the controller. The addFormListener() method expects an object implementing the FormControllerFormListener interface. This interface defines a single formClosed() method which is called when the dialog window associated with the controller is closed. It is passed a FormControllerFormEvent object which contains information about the way the form was closed. So the listener can react accordingly. The main difference between the event listener approach and command objects is that event listeners are directly called from within the event dispatch thread. Therefore they are appropriate for short-running operations only. If processing of form data takes a little longer, a command object should be preferred; otherwise the UI might become non-responsive.

The two options for processing form data when the form was closed are not mutual exclusive. It is well possible to define both a command and an event listener. This may make sense in certain situations, e.g. the event listener could update the UI to give the user some feedback while a command performs a longer-running operation in the background.

The use of command objects or event listeners for the processing of form data is one reason why the FormController class can be used out of the box: There is no need to override methods that define how the data is to be processed; just associate the controller with corresponding command objects or listeners. Note that these associations are fully optional. If there are no commands nor event listeners, the dialog window is just closed without performing any further steps - of course, if the OK button is pressed, the data entered by the user is copied into the model object of the form.

Additional validation support

One important task of a FormController instance is to provide feedback about the current validation status of the form's input fields. In most cases the user wants to see directly if fields contain invalid data. However, there is no default strategy that defines how and when such a feedback is to be provided. An application may decide to do without immediate validation feedback. In this scenario a validation is performed only at the end of data entering, i.e. when the user hits the OK button. Other applications might want to provide a feedback earlier, e.g. after the user leaves an input field or even as soon as data is typed in. All of these strategies have their specific advantages and disadvantages, and a library supporting forms should be flexible enough to support all of them.

The next question is the way how feedback about invalid fields is to be given. Again there are multiple possibilities thinkable, for instance

  • input fields containing invalid data are somehow highlighted, e.g. they could be given different colors or a warning icon could be displayed next to them.
  • tool tipps for input fields can be adapted dynamically to show hints if the fields have invalid content.
  • there can be a specialized area in the form window that displays the current validation messages.
So this is another area where a large amount of flexibility is required.

Let's tackle the question about when to perform validation first: A form controller can be passed an object implementing the FormValidationTrigger interface to its setValidationTrigger() method. This object decides when a validation is to be performed. The FormValidationTrigger interface is surprisingly simple: it defines only the single method initTrigger() that is passed a reference to the current FormController. The background behind this concept is that a FormValidationTrigger object is not queried by the controller, but it becomes active itself when it decides that a validation is required now. A typical implementation will use the FormController reference passed to its initTrigger() method to register itself as event listener for all necessary events. In the event handler methods it can then call back to the controller and initiate a validation operation. JGUIraffe ships with the following concrete implementations of the FormValidationTrigger interface:

  • FormValidationTriggerNone never triggers a validation. This is appropriate if no immediate feedback about invalid data is to be provided. If this trigger is used, a validation is only performed when the user presses the OK button (this validation is independent from a validation trigger).
  • FormValidationTriggerFocus registers itself as form-global focus listener. Thus it is notified whenever the user leaves an input field. Then it triggers a validation operation. This seems to be a good compromise: the user is not disturbed by changing validation hints while typing, but the messages are pretty up-to-date. FormValidationTriggerFocus is the default trigger used when no specific trigger was set.

The FormValidationTrigger interface determines when a validation of the user input is to be performed. It does not answer the question what to do when invalid input fields are detected. Because there is a wide range of possible options to handle invalid input fields there is no specific interface for this purpose. Rather, the FormController class provides a generic event listener mechanism that allows interested components to be notified when certain events related to the validation status of input fields occur. The following event listener interfaces are supported:

  • FormControllerValidationListener objects are added using the addValidationListener() method. They are notified whenever the controller's validate() method is invoked. The event object passed to these listeners also contains an object with the results of the validation.
  • FormControllerFieldStatusListener objects are added using the addFieldStatusListener() method. They are notified when the visited status of an input field changes, i.e. when the user leaves an input field for the first time. The background is that fields containing invalid data may be treated differently depending on their visited status. Consider a form that declares some fields as mandatory. When the form is opened these fields are not yet filled in and thus are invalid. An application may not want to present the user a full list with error messages at the very beginning of data entering. Only if the user enters and leaves a field its validity should be taken into account. With listeners for the field status this use case can be implemented. (Note: When the user hits the OK button all input fields are marked as visited.)

These event listener interfaces allow interested components to keep track of the validation status of all input fields in the form. The JGUIraffe library provides a couple of classes implementing these interfaces that can be used out of the box to present hints related to validation results to the user.

The ColorFieldMarker class tracks the validation status of input fields and changes their foreground and background colors correspondingly. A ColorFieldMarker can be configured with specific foreground and background colors for the possible validation states (e.g. colors for fields that are invalid, for which validation warnings exist, which are invalid, but have not yet been visited and so on). When an event from the FormController is received the ColorFieldMarker object determines all input fields whose validation status has changed. It then obtains the colors corresponding to the current status of each field and sets them. If for a specific validation status no colors are defined, the original colors of this input field are set (with other words: the color of such fields is not changed). The following image shows a simple dialog window with input fields that have been marked according to their validation status:

Dialog with input fields marked by ColorFieldMarker

In this example the File name input field is invalid because the file name contains characters which are not allowed. For invalid fields that have already been visited the background color is set to red. The yellow background color of the File content input field indicates a validation warning. The warning was issued because the field is empty. The background color of this field is only changed if it has already been visited. So when the dialog window opens the field is displayed with its normal background color (white) even if it is empty.

Another specialized FormControllerValidationListener implementation is ToolTipFieldMarker. As the name implies, ToolTipFieldMarker uses the tool tips of input elements to provide information about validation errors or warnings to the user. Whenever a validation event is received the ToolTipFieldMarker object checks whether fields with validation errors or warnings exist. If this is the case, the messages are extracted and added to the tool tips of the corresponding elements. The user only has to move the mouse cursor over an input field to see the messages available for this field. ToolTipFieldMarker can be used together with ColorFieldMarker. In this case the colors of input fields indicate whether they contain valid data or not. The actual validation messages can then be displayed by simply moving the mouse cursor over the field.

A ToolTipFieldMarker object is associated with an instance of FormValidationMessageFormat. This object determines how the validation messages are transformed to plain text which is then displayed in the tool tips. Please refer to the documentation of FormValidationMessageFormat for the possible options to customize this transformation.