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:
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.
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:
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.
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:
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:
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.
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
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:
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:
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:
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.