Ext JS 4 is Sencha’s latest JavaScript framework for developing cross-platform web applications. Built upon web standards, Ext JS provides a comprehensive library of user interface widgets and data manipulation classes to turbo-charge your application’s development.
In this article, written by Stuart Ashworth and Andrew Duncan, the authors of Ext JS 4 Web Application Development Cookbook, we will cover:
This article introduces forms in Ext JS 4. We begin by creating a support ticket form in the first recipe. To get the most out of this article you should be aware that this form is used by a number of recipes throughout the article. Instead of focusing on how to configure specific fields, we demonstrate more generic tasks for working with forms. Specifically, these are populating forms, submitting forms, performing client-side validation, and handling callbacks/exceptions.
In the previous releases of Ext JS, complicated form layouts were quite difficult to achieve. This was due to the nature of the FormLayout, which was required to display labels and error messages correctly, and how it had to be combined with other nested layouts.
Ext JS 4 takes a different approach and utilizes the Ext.form.Labelable mixin, which allows form fields to be decorated with labels and error messages without requiring a specific layout to be applied to the container. This means we can combine all of the layout types the framework has to offer without having to overnest components in order to satisfy the form field’s layout requirements.
We will describe how to create a complex form using multiple nested layouts and demonstrate how easy it is to get a form to look exactly as we want. Our example will take the structure of a Support Ticket Request form and, once we are finished, it will look like the following screenshot:
(Move the mouse over the image to enlarge.)
var formPanel = Ext.create(‘Ext.form.Panel’, { title: ‘Support Ticket Request’, width: 650, height: 500, renderTo: Ext.getBody(), style: ‘margin: 50px’, items: [] });
var formPanel = Ext.create(‘Ext.form.Panel’, { title: ‘Support Ticket Request’, width: 650, height: 500, renderTo: Ext.getBody(), style: ‘margin: 50px’, items: [{ xtype: ‘container’, layout: ‘hbox’, items: [{ xtype: ‘textfield’, fieldLabel: ‘First Name’, name: ‘FirstName’, labelAlign: ‘top’, cls: ‘field-margin’, flex: 1 }, { xtype: ‘textfield’, fieldLabel: ‘Last Name’, name: ‘LastName’, labelAlign: ‘top’, cls: ‘field-margin’, flex: 1 }] }] });
<style type=”text/css”>
.field-margin {
margin: 10px;
}
</style>
items: [ … { xtype: ‘container’, layout: ‘column’, items: [{ xtype: ‘textfield’, fieldLabel: ‘Email Address’, name: ‘EmailAddress’, labelAlign: ‘top’, cls: ‘field-margin’, columnWidth: 0.6 }, { xtype: ‘fieldcontainer’, layout: ‘hbox’, fieldLabel: ‘Tel. Number’, labelAlign: ‘top’, cls: ‘field-margin’, columnWidth: 0.4, items: [{ xtype: ‘textfield’, name: ‘TelNumberCode’, style: ‘margin-right: 5px;’, flex: 2 }, { xtype: ‘textfield’, name: ‘TelNumber’, flex: 4 }] }] } … ]
items: [ … { xtype: ‘container’, layout: ‘hbox’, items: [{ xtype: ‘textarea’, fieldLabel: ‘Request Details’, name: ‘RequestDetails’, labelAlign: ‘top’, cls: ‘field-margin’, height: 250, flex: 2 }, { xtype: ‘checkboxgroup’, name: ‘RequestType’, fieldLabel: ‘Request Type’, labelAlign: ‘top’, columns: 1, cls: ‘field-margin’, vertical: true, items: [{ boxLabel: ‘Type 1’, name: ‘type1’, inputValue: ‘1’ }, { boxLabel: ‘Type 2’, name: ‘type2’, inputValue: ‘2’ }, { boxLabel: ‘Type 3’, name: ‘type3’, inputValue: ‘3’ }, { boxLabel: ‘Type 4’, name: ‘type4’, inputValue: ‘4’ }, { boxLabel: ‘Type 5’, name: ‘type5’, inputValue: ‘5’ }, { boxLabel: ‘Type 6’, name: ‘type6’, inputValue: ‘6’ }], flex: 1 }] } … ]
items: [ … { xtype: ‘filefield’, cls: ‘field-margin’, fieldLabel: ‘Attachment’, width: 300 } … ]
All Ext JS form fields inherit from the base Ext.Component class and so can be included in all of the framework’s layouts. For this reason, we can include form fields as children of containers with layouts (such as hbox and column layouts) and their position and size will be calculated accordingly.
Upgrade Tip: Ext JS 4 does not have a form layout meaning a level of nesting can be removed and the form fields’ labels will still be displayed correctly by just specifying the fieldLabel config.
The Ext.form.FieldContainer class used in step 4 is a special component that allows us to combine multiple fields into a single container, which also implements the Ext.form. Labelable mixin . This allows the container itself to display its own label that applies to all of its child fields while also giving us the opportunity to configure a layout for its child components.
After creating our beautifully crafted and user-friendly form we will inevitably need to populate it with some data so users can edit it. Ext JS makes this easy, and this recipe will demonstrate four simple ways of achieving it.
We will start by explaining how to populate the form on a field-by-field basis, then move on to ways of populating the entire form at once. We will also cover populating it from a simple object, a Model instance, and a remote server call.
We will be using the form created in this article’s first recipe as our base for this section, and many of the subsequent recipes in this article, so please look back if you are not familiar with it.
All the code we will write in this recipe should be placed under the definition of this form panel.
You will also require a working web server for the There’s More example, which loads data from an external file.
We’ll demonstrate how to populate an entire form’s fields in bulk and also how to populate them individually.
var firstNameField = formPanel.items.get(0).items.get(0);
firstNameField.setValue(‘Joe’);
var requestData = { FirstName: ‘Joe’, LastName: ‘Bloggs’, EmailAddress: ‘info@swarmonline.com’, TelNumberCode: ‘0777’, TelNumber: ‘7777777’, RequestDetails: ‘This is some Request Detail body text’, RequestType: { type1: true, type2: false, type3: false, type4: true, type5: true, type6: false } };
formPanel.getForm().setValues(requestData);
Each field contains a method called setValue , which updates the field’s value with the value that is passed in. We can see this in action in the first part of the How to do it section.
A form panel contains an internal instance of the Ext.form.Basic class (accessible through the getForm method ), which provides all of the validation, submission, loading, and general field management that is required by a form.
This class contains a setValues method , which can be used to populate all of the fields that are managed by the basic form class. This method works by simply iterating through all of the fields it contains and calling their respective setValue methods.
This method accepts either a simple data object, as in our example, whose properties are mapped to fields based on the field’s name property. Alternatively, an array of objects can be supplied, containing id and value properties, with the id mapping to the field’s name property. The following code snippet demonstrates this usage:
formPanel.getForm().setValues([{id: ‘FirstName’, value: ‘Joe’}]);
Further to the two previously discussed methods there are two others that we will demonstrate here.
Being able to populate a form directly from a Model instance is extremely useful and is very simple to achieve. This allows us to easily translate our data structures into a form without having to manually map it to each field.
We initially define a Model and create an instance of it (using the data object we used earlier in the recipe):
Ext.define(‘Request’, { extend: ‘Ext.data.Model’, fields: [ ‘FirstName’, ‘LastName’, ‘EmailAddress’, ‘TelNumberCode’, ‘TelNumber’, ‘RequestDetails’, ‘RequestType’ ] }); var requestModel = Ext.create(‘Request’, requestData);
Following this we call the loadRecord method of the Ext.form.Basic class and supply the Model instance as its only parameter. This will populate the form, mapping each Model field to its corresponding form field based on the name:
formPanel.getForm().loadRecord(requestModel);
It is also possible to load a form’s data directly from the server through an AJAX call.
Firstly, we define a JSON file, containing our request data, which will be loaded by the form:
{ “success”: true, “data”: { “FirstName”: “Joe”, “LastName”: “Bloggs”, “EmailAddress”: “info@swarmonline.com”, “TelNumberCode”: “0777”, “TelNumber”: “7777777”, “RequestDetails”: “This is some Request Detail body text”, “RequestType”: { “type1”: true, “type2”: false, “type3”: false, “type4”: true, “type5”: true, “type6”: false } } }
Notice the format of the data: we must provide a success property to indicate that the load was successful and put our form data inside a data property.
Next we use the basic form’s load method and provide it with a configuration object containing a url property pointing to our JSON file:
formPanel.getForm().load({ url: ‘requestDetails.json’ });
This method automatically performs an AJAX request to the specified URL and populates the form’s fields with the data that was retrieved. This is all that is required to successfully load the JSON data into the form.
The basic form’s load method accepts similar configuration options to a regular AJAX request
Having taken care of populating the form it’s now time to look at sending newly added or edited data back to the server. As with form population you’ll learn just how easy this is with the Ext JS framework.
There are two parts to this example. Firstly, we will submit data using the options of the basic form that wraps the form panel. The second example will demonstrate binding the form to a Model and saving our data.
We will be using the form created in the first recipe as our base for this section, so refer to the Constructing a complex form layout recipe, if you are not familiar with it.
var submitForm = function(){ formPanel.getForm().submit({ url: ‘submit.php’ }); };
var formPanel = Ext.create(‘Ext.form.Panel’, { … buttons: [{ text: ‘Submit Form’, handler: submitForm }], items: [ … ] });
As we learned in the previous recipe, a form panel contains an internal instance of the Ext.form.Basic class (accessible through the getForm method).
The submit method in Ext.form.Basic is a shortcut to the Ext.form.action.Submit action. This class handles the form submission for us. All we are required to do is provide it with a URL and it will handle the rest.
It’s also possible to define the URL in the configuration for the Ext.form.Panel..
Before submitting, it must first gather the data from the form. The Ext.form.Basic class contains a getValues method, which is used to gather the data values for each form field. It does this by iterating through all fields in the form making a call to their respective getValue methods.
The previous recipe demonstrated how to populate the form from a Model instance. Here we will take it a step further and use the same Model instance to submit the form as well.
xt.define(‘Request’, { extend: ‘Ext.data.Model’, fields: [‘FirstName’, ‘LastName’, ‘EmailAddress’, ‘TelNumberCode’, ‘TelNumber’, ‘RequestDetails’, ‘RequestType’], proxy: { type: ‘ajax’, api: { create: ‘addTicketRequest.php’, update: ‘updateTicketRequest.php’ }, reader: { type: ‘json’ } } }); var requestModel = Ext.create(‘Request’, { FirstName: ‘Joe’, LastName: ‘Bloggs’, EmailAddress: ‘info@swarmonline.com’ }); formPanel.getForm().loadRecord(requestModel);
var submitForm = function(){ var record = formPanel.getForm().getRecord(); formPanel.getForm().updateRecord(record); record.save(); };
In addition to form fields’ built-in validation (such as allowBlank and minLength), we can apply more advanced and more extensible validation by using VTypes. A VType (contained in the Ext.form.field.VTypes singleton) can be applied to a field and its validation logic will be executed as part of the field’s periodic validation routine.
A VType encapsulates a validation function, an error message (which will be displayed if the validation fails), and a regular expression mask to prevent any undesired characters from being entered into the field.
This recipe will explain how to apply a VType to the e-mail address field in our example form, so that only properly formatted e-mail addresses are deemed valid and an error will be displayed if it doesn’t conform to this pattern.
{ xtype: ‘textfield’, fieldLabel: ‘Email Address’, name: ‘EmailAddress’, labelAlign: ‘top’, cls: ‘field-margin’, columnWidth: 0.6, vtype: ’email’ }
When a field is validated it runs through various checks. When a VType is defined the associated validation routine is executed and will flag the field invalid or not .
As previously mentioned, each VType has an error message coupled with it, which is displayed if it is found to be invalid, and a mask expression which prevents unwanted characters being entered.
Unfortunately, only one VType can be applied to a field and so, if multiple checks are required, a custom hybrid may need to be created. See the next recipe for details on how to do this.
Along with the e-mail VType, the framework provides three other VTypes that can be applied straight out of the box. These are:
We have seen in the previous recipe how to use VTypes to apply more advanced validation to our form’s fields. The built-in VTypes provided by the framework are excellent but we will often want to create custom implementations to impose more complex and domain specific validation to a field.
We will walkthrough creating a custom VType to be applied to our telephone number field to ensure it is in the format that a telephone number should be.
Although our telephone number field is split into two (the first field for the area code and the second for the rest of the number), for this example we will combine them so our VType is more comprehensive.
For this example, we will be validating a very simple, strict telephone number format of “0777-777-7777”.
var telNumberVType = { telNumber: function(val, field){ // function executed when field is validated // return true when field’s value (val) is valid return true; }, telNumberText: ‘Your Telephone Number must only include numbers and hyphens.’, telNumberMask: /[d-]/ };
telNumber: function(val, field){ var telNumberRegex = /^d{4}-d{3}-d{4}$/; return true; }
telNumber: function(val, field){ var telNumberRegex = /^d{4}-d{3}-d{4}$/; return telNumberRegex.test(val); }
Ext.apply(Ext.form.field.VTypes, telNumberVType);
{ xtype: ‘textfield’, name: ‘TelNumber’, flex: 4, vtype: ‘telNumber’ }
A VType consists of three parts:
VTypes rely heavily on naming conventions so they can be executed dynamically within a field’s validation routine. This means that each of these three parts must follow the standard convention. The validation function’s name will become the name used to reference the VType and form the prefix for the other two properties. In our example, this name was telNumber, which can be seen referencing the VType in Step 5.
The error text property is then named with the VType’s name prefixing the word Text (that is, telNumberText ). Similarly, the filtering mask is the VType’s name followed by the word Mask (that is, telNumberMask ).
The final step to create our VType is to merge it into the Ext.form.field.VTypes singleton allowing it to be accessed dynamically during validation. The Ext.apply function does this by merging the VType’s three properties into the Ext.form.field.VTypes class instance.
When the field is validated, and a vtype is defined, the VType’s validation function is executed with the current value of the field and a reference to the field itself being passed in.
If the function returns true then all is well and the routine moves on. However, if it evaluates to false the VType’s Text property is retrieved and pushed onto the errors array. This message is then displayed to the user as our screenshot shown earlier.
This process can be seen in the code snippet as follows, taken directly from the framework:
if (vtype) { if(!vtypes[vtype](value, me)){ errors.push(me.vtypeText || vtypes[vtype +’Text’]); } }
It is often necessary to validate fields based on the values of other fields as well as their own. We will demonstrate this by creating a simple VType for validating that a confirm password field’s value matches the value entered in an initial password field. We start by creating our VType structure as we did before:
Ext.apply(Ext.form.field.VTypes, { password: function(val, field){ return false; }, passwordText: ‘Your Passwords do not match.’ });
We then complete the validation logic. We use the field’s up method to get a reference to its parent form. Using that reference, we get the values for all of the form’s fields by using the getValues method :
password: function(val, field){ var parentForm = field.up(‘form’); // get parent form // get the form’s values var formValues = parentForm.getValues(); return false; }
The next step is to get the first password field’s value. We do this by using an extra property ( firstPasswordFieldName) that we will specify when we add our VType to the confirm password field. This property will contain the name of the initial password field (in this example Password ). We can then compare the confirm password’s value with the retrieved value and return the outcome:
password: function(val, field){ var parentForm = field.up(‘form’); // get parent form // get the form’s values var formValues = parentForm.getValues(); // get the value from the configured ‘First Password’ field var firstPasswordValue = formValues[field.firstPasswordFieldName]; // return true if they match return val === firstPasswordValue; }
The VType is added to the confirm password field in exactly the same way as before but we must include the extra firstPasswordFieldName option to link the fields together:
{ xtype: ‘textfield’, fieldLabel: ‘Confirm Password’, name: ‘ConfirmPassword’, labelAlign: ‘top’, cls: ‘field-margin’, flex: 1, vtype: ‘password’, firstPasswordFieldName: ‘Password’ }
Uploading files is very straightforward with Ext JS 4. This recipe will demonstrate how to create a basic file upload form and send the data to your server:
This recipe requires the use of a web server for accepting the uploaded file. A PHP file is provided to handle the file upload; however, you can integrate this Ext JS code with any server-side technology you wish.
Ext.create(‘Ext.form.Panel’, { title: ‘Document Upload’, width: 400, bodyPadding: 10, renderTo: Ext.getBody(), style: ‘margin: 50px’, items: [], buttons: [] });
Ext.create(‘Ext.form.Panel’, { … items: [{ xtype: ‘filefield’, name: ‘document’, fieldLabel: ‘Document’, msgTarget: ‘side’, allowBlank: false, anchor: ‘100%’ }], buttons: [] });
Ext.create(‘Ext.form.Panel’, { … buttons: [{ text: ‘Upload Document’, handler: function(){ var form = this.up(‘form’).getForm(); if (form.isValid()) { form.submit({ url: ‘upload.php’, waitMsg: ‘Uploading…’ }); } } }] });
Your server-side code should handle these form submissions in the same way they would handle a regular HTML file upload form. You should not have to do anything special to make your server-side code compatible with Ext JS.
The example works by defining an Ext.form.field.File ( xtype: ‘filefield’ ), which takes care of the styling and the button for selecting local files.
The form submission handler works the same way as any other form submission; however, behind the scenes the framework tweaks how the form is submitted to the server.
A form with a file upload field is not submitted using an XMLHttpRequest object—instead the framework creates and submits a temporary hidden <form> element whose target is referenced to a temporary hidden <iframe>. The request header’s Content-Type is set to multipart/form. When the upload is finished and the server has responded, the temporary form and <iframe> are removed.
A fake XMLHttpRequest object is then created containing a responseText property (populated from the contents of the <iframe> ) to ensure that event handlers and callbacks work as if we were submitting the form using AJAX.
If your server is responding to the client with JSON, you must ensure that the response Content-Type header is text/html.
It’s possible to customize your Ext.form.field.File. Some useful config options are highlighted as follows:
Setting buttonOnly: true removes the visible text field from the file field.
If you wish to change the text in the button from the default of “Browse…” it’s possible to do so by setting the buttonText config option.
Changing the entire configuration of the button is done by defining a standard Ext.button. Button config object in the buttonConfig option. Anything defined in the buttonText config option will be ignored if you use this.
This recipe demonstrates how to handle callbacks when loading and submitting forms. This is particularly useful for two reasons:
The recipe shows you what to do in the following circumstances:
The following recipe requires you to submit values to a server. An example submit.php file has been provided. However, please ensure you have a web server for serving this file.
var formPanel = Ext.create(‘Ext.form.Panel’, { title: ‘Form’, width: 300, bodyPadding: 10, renderTo: Ext.getBody(), style: ‘margin: 50px’, items: [], buttons: [] });
var formPanel = Ext.create(‘Ext.form.Panel’, { … items: [{ xtype: ‘textfield’, fieldLabel: ‘Text field’, name: ‘field’, allowBlank: false }], buttons: [] });
var formPanel = Ext.create(‘Ext.form.Panel’, { … buttons: [{ text: ‘Submit’, handler: function(){ formPanel.getForm().submit({ url: ‘submit.php’, success: function(form, action){ Ext.Msg.alert(‘Success’, action.result.message); }, failure: function(form, action){ if (action.failureType === Ext.form.action.Action. CLIENT_INVALID) { Ext.Msg.alert(‘CLIENT_INVALID’, ‘Something has been missed. Please check and try again.’); } if (action.failureType === Ext.form.action.Action. CONNECT_FAILURE) { Ext.Msg.alert(‘CONNECT_FAILURE’, ‘Status: ‘ + action.response.status + ‘: ‘ + action.response.statusText); } if (action.failureType === Ext.form.action.Action. SERVER_INVALID) { Ext.Msg.alert(‘SERVER_INVALID’, action.result. message); } } }); } }] });
The Ext.form.action.Submit and Ext.form.action.Load classes both have a failure and success function. One of these two functions will be called depending on the outcome of the action.
The success callback is called when the action is successful and the success property is true.
The failure callback , on the other hand, can be extended to look for specific reasons why the failure occurred (for example, there was an internal server error, the form did not pass client-side validation, and so on). This is done by looking at the failureType property of the action parameter.
Ext.form.action.Action has four failureType static properties: CLIENT_INVALID, SERVER_INVALID, CONNECT_FAILURE, and LOAD_FAILURE, which can be used to compare with what has been returned by the server.
A number of additional options are described as follows:
The Ext.form.action.Action.LOAD_FAILURE static property can be used in the failure callback when loading data into your form. The LOAD_FAILURE is returned as the action parameter’s failureType when the success property is false or the data property contains no fields. The following code shows how this failure type can be caught inside the failure callback function:
failure: function(form, action){ … if(action.failureType == Ext.form.action.Action.LOAD_FAILURE){ Ext.Msg.alert(‘LOAD_FAILURE’, action.result.message); } … }
The isValid method in Ext.form.Basic is an alternative method for handling client-side validation before the form is submitted. isValid will return true when client-side validation passes:
handler: function(){ if (formPanel.getForm().isValid()) { formPanel.getForm().submit({ url: ‘submit.php’ }); } }
Further resources on this subject:
I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…
Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…
Once we learn how to deploy an Ubuntu server, how to manage users, and how…
Key-takeaways: Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…
While developing a web application, or setting dynamic pages and meta tags we need to deal with…
Software architecture is one of the most discussed topics in the software industry today, and…