Table of Contents
Form Handling
The qx.ui.form package contains several classes to for constructing forms. Some widgets – like Button, List or TextField – may look familiar if you have worked with HTML before, but this package also contains more complex widgets that you may know from your operating system and/or native desktop applications (e.g. Spinner, Slider or DateField).
Idea
The idea of the form API is to make handling of form widgets as simple as possible, but also as generic as possible within the entire framework. There has been a thorough discussion on what's the best solution and how to design a solid API. This is what we ended up with.
Demos
If you like to see some of the form management of qooxdoo in action, take a look at the following samples in the demo browser:
Widgets
Validation and Resetting
Rendering
Data Binding
Interfaces
The whole form API is defined by a couple of interfaces. These interfaces contain the most important methods and events for the form widgets. The following listing shows the interfaces, what's their purpose and how you can benefit from them.
Form
The interface called qx.ui.form.IForm defines a set of methods and events for every visible form widget. It contains the listed events and methods.
As you can see, the interface defines accessors for four different properties.
- The enabled property is usually inherited from the widget class and is for switching of form element.
- The required property is just a boolean flag signaling that the form widget is required. This can be used by some kind of form manager or form widget to display the status of the widget.
- The valid property is a boolean flag containing
trueif the content of the widget is valid, but the form widgets does not have any kind of code to set this property. It needs to be set from outside. If it is set tofalse, the appearance will change automatically and signals the invalid state properly. - The invalidMessage property should contain a message, which will be shown in an invalid tooltip if the valid flag is set to
false. If no message is given, no tooltip will appear.
Executable
The qx.ui.form.IExecutable interface defines the essential parts for all executable widgets. The best example for a executable widget is a button. It defines the following events and methods.
As you can see, the interface defines accessors for only one property.
- The command property can take a
qx.event.Command. The execute method executes the given command.
Range
The qx.ui.form.IRange interface defines the essential parts for all widgets dealing with ranges. It defines the following methods.
As you can see, the interface defines accessors for four properties.
- The minimum of the range is defined by the Minimum property.
- The maximum of the range is defined by the Maximum property.
- Each range does have a single step value which is defined by the SingleStep property.
- As the single step, there is a page step in every range which is defined by the PageStep property.
Number / String / Color / Date / Boolean
Each of the listed interfaces define the same methods and events. The only difference in the interfaces is - like the name says - the type of the data processed by the implementing widget. With that solution, we have the same API for every form widget but can still determinate which type of value the widget expects by checking for the different interfaces.
Interfaces
- Number :
qx.ui.form.INumberForm - String :
qx.ui.form.IStringForm - Color :
qx.ui.form.IColorForm - Date :
qx.ui.form.IDateForm - Boolean :
qx.ui.form.IBooleanForm
The color interface takes a string which has to be formated like usual colors in qooxdoo.
As you can see, the interface can be implemented with only one property.
- The value property takes the value of the widget. This is for example a boolean in a checkbox widget or a string in a textfield widget.
Model / ModelSelection
Most of the form items handling a selection had a value property in the old API. We replaced that value property due to the usage of the value property for the user input values with a model property. The method for accessing the model data are defined in an interface called qx.ui.form.IModel.
The model property can be used to store additional data which is represented by the widget. The data does not need to be a string like in the old value property. You can store references to objects, numbers, strings and so on.
Accessing the model is very easy. Every widget containing a widget implementing the qx.ui.form.IModel interface has its own interface to access the current selected model.
As you can see in the diagram, you can get the current selected model and also set the selection using the models.
Widgets
The following listing shows the form widgets and their corresponding interfaces. To see more details about a widget, take a look at the widgets documentation.
| IForm | IExecutable | IRange | INumber Form |
IString Form |
IColor Form |
IDate Form |
IBoolean Form |
IModel | IModel Selection |
|
| Label | X | |||||||||
| Text Input | ||||||||||
| TextField | X | X | ||||||||
| TextArea | X | X | ||||||||
| PasswordField | X | X | ||||||||
| Supported Text Input | ||||||||||
| ComboBox | X | X | ||||||||
| DateField | X | X | ||||||||
| Number Input | ||||||||||
| Slider | X | X | X | |||||||
| Spinner | X | X | X | |||||||
| Boolean Input | ||||||||||
| CheckBox | X | X | X | X | ||||||
| RadioButton | X | X | X | X | ||||||
| Color Input | ||||||||||
| ColorPopup | X | |||||||||
| ColorSelector | X | |||||||||
| Date Input | ||||||||||
| DateChooser | X | X | X | |||||||
| Selections | ||||||||||
| SelectBox | X | X | ||||||||
| List | X | X | ||||||||
| ListItem | X | |||||||||
| tree.Tree | X | |||||||||
| tree.TreeFolder | X | |||||||||
| tree.TreeFile | X | |||||||||
| Grouping | ||||||||||
| GroupBox | X | |||||||||
| CheckGroupBox | X | X | X | |||||||
| RadioGroupBox | X | X | X | |||||||
| RadioGroup | X | X | ||||||||
| RadioButtonGroup | X | X | ||||||||
| Buttons | ||||||||||
| Button | X | |||||||||
| MenuButton | X | |||||||||
| RepeatButton | X | |||||||||
| SplitButton | X | |||||||||
| ToggleButton | X | X | ||||||||
| tabView.Button | X | |||||||||
| menu.CheckBox | X | X | ||||||||
| menu.RedioButton | X | X | ||||||||
| menu.Button | X | |||||||||
Sample Usage
The first example is a simple one, showing how to use two widgets implementing the IStringForm interface:
// create and add a textfield var textfield = new qx.ui.form.TextField(); this.getRoot().add(textfield, {left: 10, top: 10}); // create and add a label var label = new qx.ui.basic.Label(); this.getRoot().add(label, {left: 10, top: 40}); // set the text of both widgets textfield.setValue("Text"); label.setValue("Text");
The second example shows how to react on a change in a widget implementing the INumberForm interface. The value of the slider will be shown as a label:
// create and add a slider var slider = new qx.ui.form.Slider(); slider.setWidth(200); this.getRoot().add(slider, {left: 10, top: 10}); // create and add a label var label = new qx.ui.basic.Label(); this.getRoot().add(label, {left: 220, top: 10}); // add the listener slider.addListener("changeValue", function(e) { // convert the number to a string label.setValue(e.getData() + ""); }, this);
The last example shows how to use the IForm interface and how to mark a widget as invalid:
// create and add a slider var slider = new qx.ui.form.Slider(); slider.setWidth(200); slider.setValue(100); this.getRoot().add(slider, {left: 10, top: 10}); // set the invalid message slider.setInvalidMessage("Please use a number above 50."); // add the validation slider.addListener("changeValue", function(e) { if (e.getData() > 50) { slider.setValid(true); } else { slider.setValid(false); } }, this);
All examples work in the playground application. But keep in mind to use a playground for qooxdoo 0.8.3 or above.
Migrating to the new API
There are some important topics you have to take care if you want to migrate from the former Form API to the new one.
IFormElement
The previous form interface called qx.ui.form.IFormElement is deprecated now. therefore, also the name and the value property for storing string information for serialization is gone. If you are using those constructs, you can instead use regular user data:
widget.setName("field1"); // old widget.setUserData("name", "field1"); // new
This works identical to the old code. The HTML name property will not be set after the call in both cases.
CheckBox and RadioButton
Widgets like CheckBox or RadioButton had a checked property for their state. This property is deprecated and is now called value.
changeValue on List and SelectBox
It was quite common to use the changeValue event of a SelectBox or List to handle a change of the selection. Due to the removal of value, the changeValue event has also been removed. Please use the changeSelection event instead.
Label
The former content property of the Label class has been renamed to make it consistent with the rest of the framework. So the new name is like in every other widget: value.
Validation
Form validation is essential in most of the common use cases of forms. Thats why qooxdoo supports the application developer with a validation component named qx.ui.form.validation.Manager. This manager is responsible for managing the form items, which need to be validated.
We tried to keep the API as minimal as possible but in the same way, as flexible as possible. The following class diagram shows the user API of the component.
The events, properties and methods can be divided into three groups:
- Validation
- getValid()
- isValid()
- validate()
- validator - property
- complete - event
- changeValid - event
- Form Item Management
- add(formItem, validator)
- reset()
- Invalid Messages
- getInvalidMessages()
- invalidMessage - property
The first part with which the application developer gets in contact is the add method. It takes form items and a validator. But what are form items?
Requirements
Form items need two things. First of all, it is necessary that the given form item can handle an invalid state and has an invalid message. This is guaranteed by the IForm interface already introduced. But thats not all, the manager needs to acces the value of the form item. Therefore, the form item need to specify a value property. This value property is defined in the data specific form interfaces also introduced above. So all widgets implementing the IForm interface and one of the value defining interfaces can be used by the validation. For a list which widget implement which interface, take a look at the widgets section in the document.
Now that we know what the manager can validate, its time to learn how to validate. In general, there are two different approaches in validation. The first approach is a client side validation, which is in common cases synchronous. On the other hand, a server side validation is asynchronous in most cases. We will cover both possibilities in the following sections.
Synchronous
The following subsections cover some common scenarios of synchronous validation. See this code snippet as basis for all the examples shown in the subsections.
var manager = new qx.ui.form.validation.Manager(); var textField = new qx.ui.form.TextField(); var checkBox = new qx.ui.form.CheckBox();
Required Form Fields
One of the most obvious validation is a validation for a non empty field. This can be seen in common forms as required fields which are easy to define in qooxdoo. Just define the specific widget as required and add it without a validator to the validation manager.
textField.setRequired(true); manager.add(textField);
The validation manager will take all the necessary steps to mark the field as invalid as soon as the validate method will be invoked, if the text field is empty.
Default Validator
Another common use case of validation is to check for specific input types like email adresses, urls or similar. For those common checks, qooxdoo offers a set of predefined validators in qx.util.Validate. The example here shows the usage of a predefined email validator.
manager.add(textField, qx.util.Validate.email());
Custom Validator
Sometimes, the predefined validators are not enough and you need to create a application specific validator. Thats also no problem because the synchronous validator is just a JavaScript function. In this function, you can either return a boolean, which signals the validation result or you can throw a qx.core.ValidationError containing the message to display as invalid message. The validation manager can handle both kinds of validators.
The example here checks if the value of the text field has at least a length of 3.
manager.add(textField, function(value) { return value.length >= 3; });
Validation in the context of the form
All shown validation rules validate each form item in its own context. But it might be necessary to have more than one form item on the validation. For such scenarios, the manager itself can have a validator too. The example here shows how to ensure, that the text field is not empty, if the checkbox is checked.
manager.setValidator(function(items) { if (checkBox.getValue()) { var value = textField.getValue(); if (!value || value.length == 0) { textField.setValid(false); return false; } } textField.setValid(true); return true; });
Asynchronous
Imagine the scenario you want to check if a username is already take during a registration process or you want to check a credit card number. Such kind of validation can only be done by a server and not in the client. But you don't want the user to wait for the server to process your request and send the answer back. So you need some kind of asynchronous validation.
For all asynchronous validation cases, we need a wrapper for the validator, the qx.ui.form.validation.AsyncValidator. But that does not mean much work for you as application developer. Just take a look at the following example to see the AsyncValidator in action.
manager.add(textField, new qx.ui.form.validation.AsyncValidator( function(validator, value) { // here comes the async call window.setTimeout(function() { // callback for the async validation validator.setValid(false); }, 1000); } ));
The only difference to the synchronous case is the wrapping of your validator function, at least for the code you have to write as developer. Take a look at the following sequence diagram to get an insight on how the asynchronous validation is handled.
The asynchronous validation can not only be used for form items. Also the manager itself can handle instances of the AsyncValidator as validator.
Serialization
Entering data into a form is one part of the process. But that entered data needs to be send to the server most of the time. So serialization is a big topic when it comes to forms. We decided not to integrate the serialization in one form manager responsible for validation and serialization.
Idea
The main idea of that was to ensure, that it works good together with features like a form widget and the corresponding data binding components. So we decided to split the problem into two different parts. The first part is storing the data held in the view components into a model. The second part takes that model and serializes the data in the model. Sounds like data binding? It is data binding!
But you don't have to connect all these widgets yourself. We do offer a object controller which can take care of most of the work. But where do you get the model? Writing a specific qooxdoo class for every form sounds like a bit overkill. But qooxdoo has a solution for that, too. The creation of classes and model instances is already part of the data binding and can also be used here. Sounds weired? Take a look at the following common scenarios to see how it works.
Common Scenarios
The most common scenario is to serialize an amount of form items without any special additions. Just take the values of the whole form and serialize it.
// create the ui var name = new qx.ui.form.TextField(); var password = new qx.ui.form.PasswordField(); // create the model var model = qx.data.marshal.Json.createModel({name: "a", password: "b"}); // create the controller and connect the form items var controller = new qx.data.controller.Object(model); controller.addTarget(name, "value", "name", true); controller.addTarget(password, "value", "password", true); // serialize qx.util.Serializer.toUriParameter(model);
The result will be name=a&password=b because the initial values of the model are a and b.
That way, the serialization in completely cut of from the form itself. So hidden form fields are as easy as it could be. Just add another property to the model.
var model = qx.data.marshal.Json.createModel( {name: "a", password: "b", c: "i am hidden"} );
I guess some of you are asking yourself: "What if i want to convert the values for serialization. My server needs some different values…". That brings in the topic of conversion. But as we have seen before, the mapping from the view to the model is handled by the data binding which already includes conversion. Take a look at the data binding documentation for more information on the conversion.
Need something special?
In some cases, you might want to have something really special like serializing one value only if another value has a special value or something else. In that case, you can write your own serializer which can handle the serialization the way you need to.
Resetting
A third useful feature of a form besides validation and serialization is resetting the whole form with one call. Sounds not too complicated that a separate class is needed. But we decided to do it anyway for some reasons.
- The validation manager is not the right place for reseting becaus it handles only the validation.
- The form widget, responsible for layouting forms, is a good place, but we don't want to force developers to use the form if they just want the reset feature.
So we decided to create a standalone implementation for resetting called qx.ui.form.Resetter.
Like the task of resetting itself, the API is not too complicated. We do have one method for adding items, and another one for reseting all added items.
How It Works
Technically, its not really a challenge thanks to the new form API. You can add all items, having either a value property defined by one of the data specific form interfaces or implementing the selection API of qooxdoo. On every add. the resetter grabs the current value and stores it. On a reset all stored values are set back.
Sample Usage
The following sample shows how to use the resetter with three input fields, a textfield, a checkbox and a list.
// create a textfield var textField = new qx.ui.form.TextField("acb"); this.getRoot().add(textField, {left: 10, top: 10}); // create a checkbox var checkBox = new qx.ui.form.CheckBox("box"); this.getRoot().add(checkBox, {left: 10, top: 40}); // create a list var list = new qx.ui.form.List(); list.add(new qx.ui.form.ListItem("a")); list.add(new qx.ui.form.ListItem("b")); list.setSelection([list.getSelectables()[0]]); this.getRoot().add(list, {left: 10, top: 70}); // create the resetter var resetter = new qx.ui.form.Resetter(); // add the form items resetter.add(textField); resetter.add(checkBox); resetter.add(list); // add a reset button var resetButton = new qx.ui.form.Button("Reset"); resetButton.addListener("execute", function() { resetter.reset(); }); this.getRoot().add(resetButton, {left: 120, top: 10});
Form Object
We have already covered most parts of form handling. But one thing we spared out completely until now is layouting the form items. Thats where the qx.ui.form.Form comes in play.
What is it?
The qooxdoo form is a object, which includes three main parts.
- Validation using the
qx.ui.form.validation.Managerclass - Resetting using the
qx.ui.form.Resetterclass - Handling the layout of the form
As we have already talked about the first two items, I'll cover the last item in a more detailed way.
The layout of a form is in most cases application specific. It depends on the space available in the application and much other factors. Thats why qooxdoo has this flexible form layouting tool, which includes a set of default possibilities to layout a form. On of the main requirements of the solution was the extensibility so everyone could have the layout as his application requires.
To get that, we applied a pattern used widely across the qooxdoo framework, which takes all UI related code to renderer classes. These renderer are as lightweight as possible to make it easy for developers to write their own custom renderer, as you can see in this UML diagram:
Renderer
As the diagram shows, qooxdoo provides an interface for FormRenderer, the IFormRenderer interface. It defines two methods, one for adding a group of form items and one for adding buttons.
- addItems(items : qx.ui.form.IForm[], names : String[], title : String) : void
- addButton(button : qx.ui.form.Button) : void
Sure you have recognized the difference to the API of the form itself. You add the widgets one by one to the form but the renderer gets always a group of widgets at once. This gives the renderer additional information which it may need to render the form based on the number of groups rather then on the number of widgets.
You may ask yourself why we didn't use the layouts, we usually use in such scenarios if we ant to render widgets on the screen. It may be necessary, that a rendere contains even more than one widget. Imagine a wizard or a form shared among different tabs. Thats all not possible using layouts instead of renderer widgets.
The following sections show the renderer included in qooxdoo, which you can use out of the box.
Default (Single Column)
If you don't care about renderer and you don't specify a rendere, the default renderer is used and thats a single column renderer.
As you can see in the picture, the rendere adds an asterisk to every required field, adds a colon at the end of every label and defines the vertical layout.
Double Column
The double column renderer has the same features like the already introduced single column renderer but renders the in two columns, as you can see in the following picture.
Single Column with Placeholer
This renderer is more a demo showing how easy it can be to implement your own renderer. It has a limitation that i can only render input fields which do have the placeholder property. But the result is pretty nice:
Sample Usage
After we have seen how it should work, here come some examples how it works. In this example, we want to create a form for an address management tool. So we divide our input fields into two groups. The first group contains of two textfields, one for the first name and one for the last name. The second group contains some contact data like email, phone number and company. At last, we want to add two buttons to the form, one for saving the data if it is valid and another for reseting the form. So here we go…
First, we need a form object.
// create the form var form = new qx.ui.form.Form();
After that, we can create the first two input fields. As these two fields are required, we should mark them as required.
// create the first two input fields var firstname = new qx.ui.form.TextField(); firstname.setRequired(true); var lastname = new qx.ui.form.TextField(); lastname.setRequired(true);
As you can see, the input fields are textfields as described in the text above. Next we can add those input fields to the form.
// add the first group form.addGroupHeader("Name"); form.add(firstname, "Firstname"); form.add(lastname, "Lastname");
First, we added a group header to have a headline above the two input fields. After that, we added them with a name but without a validator. The required flag we set earlier is enough. We need to add another group of input fields for the contact data.
// add the second group form.addGroupHeader("Contact"); form.add(new qx.ui.form.TextField(), "Email", qx.util.Validate.email()); form.add(new qx.ui.form.TextField(), "Phone");
After adding the second group header, you see the textfield for the email, which uses a predefined email validator from the framework. The phone numer does not get any validator at all. The last missing thing are the buttons. First add the save button.
// add a save button var savebutton = new qx.ui.form.Button("Save"); savebutton.addListener("execute", function() { if (form.validate()) { alert("You can save now..."); } }); form.addButton(savebutton);
The save button gets an execute listener which first validates the form and if the form is valid, alerts the user. The reset button is analogical.
// add a reset button var resetbutton = new qx.ui.form.Button("Reset"); resetbutton.addListener("execute", function() { form.reset(); }); form.addButton(resetbutton);
Now the form is complete and we can use the default rendere to render the form and add it to the document.
// create the view and add it this.getRoot().add(form.createView(), {left: 10, top: 10});
Running this code will create a form as described above and will look like this.
If you want to get another look & feel, you can specify a different renderer in the createView method.
// create the view and add it this.getRoot().add( form.createView(qx.ui.form.renderer.SinglePlaceholder), {left: 10, top: 10} );
Just give it a try in the playground.
Form Controller
Data binding for a form certainly is a handy feature. Using a model to access data in the form brings form handling to another level of abstraction. That's exactly what the form controller offers.
The form controller is fully covered in the data binding documentation.
Sample Usage
The following example shows how to use the controller with a simple form, which contains three text fields: one for salutation, one for first name and one for last name.
First, we create the form:
// create the form var form = new qx.ui.form.Form();
In a second step we add the three text fields. Important here is, that if no name is given - as in the first two cases - each label will also be used as a name. For that all spaces in the label are being removed.
// add the first TextField ("Salutation" will be the property name) form.add(new qx.ui.form.TextField(), "Salutation"); // add the second TextField ("FirstName" will be the property name) form.add(new qx.ui.form.TextField(), "First Name"); // add the third TextField ("last" will be the property name) form.add(new qx.ui.form.TextField(), "Last Name", null, "last");
After we add the text fields, we can add the view to the application root.
// add the form to the root this.getRoot().add(form.createView());
Now that the form has been created, we can take care of the data binding controller. We simply supply the form instance as an argument to the constructor. But we don't have a model yet, so we just pass null for the model.
// create the controller with the form var controller = new qx.data.controller.Form(null, form);
The final step for data binding is creating the actual model.
// create the model var model = controller.createModel();
Take a look at the following sequence diagram to see how it internally works.
Now we have managed to setup a form and a model connected by bidirectional bindings. So we can simply use the model to set values in the form.
// set some values in the form model.setSalutation("Mr."); model.setFirstName("Martin"); model.setLast("Wittemann");
As you can see here, the properties (and therefore setters) are defined according to the names we gave the text fields when adding them.
See the code in action in the playground.
Still to come...
- A way to create a form out of a JSON definition














