If you find a better way to do anything in the demo or this document please respond and I will do my best to incorporate it in the demo and in this document.
EXTJS Ajax Support and Best Practices
This document explains EXTJS's use of AJAX communication that every developer will have to learn. This document along with the working demo should provide a lot of insight into developing with EXTJS.
This document explains how to communicate using EXTJS with an application running on a server in the following ways:
1. Retrieving data to populate (load) an Ext.data.Store (Client side cache of Ext.data.Record objects)
2. Retrieving data to populate (load) a Form
3. Submitting data from a Form
4. Making an AJAX call to the server using Ext.data.Connection
All 4 of these topics will each address 3 main issues in communicating with a server using EXTJS:
1. Successful response from the server.
2. Failed response due to a HTTP failure.
3. Failed response due to an application failure.
Retrieving data to populate an Ext.data.Store
Ext.data.Store
The Store class encapsulates a client side cache of Ext.data.Record objects which provide input data for Components such as the Ext.grid.GridPanel, Ext.form.ComboBox, or the Ext.DataView.
A Store object uses its configured implementation of Ext.data.DataProxy to access a data object unless you call the Store's loadData function directly and pass in your data.
A Store object has no knowledge of the format of the data returned by the Proxy.
A Store object uses its configured implementation of Ext.data.DataReader to create Ext.data.Record instances from the data object. These Records are cached and made available through accessor functions.
Ext.data.Record
Instances of this class encapsulate both Record definition information, and Record value information for use in Ext.data.Store objects, or any code which needs to access Records cached in an Ext.data.Store object.
Constructors for this class are generated by passing an Array of field definition objects to create. Instances are usually only created by Ext.data.Reader implementations when processing unformatted data objects.
Record objects generated by this constructor inherit all the methods of Ext.data.Record.
Ext.data.DataProxy
This class is an abstract base class for implementations which provide retrieval of unformatted data objects.
DataProxy implementations are usually used in conjunction with an implementation of Ext.data.DataReader (of the appropriate type which knows how to parse the data object) to provide a block of Ext.data.Record to an Ext.data.Store.
Custom implementations must implement the load method as described in Ext.data.HttpProxy.load
Ext.data.HttpProxy
An implementation of Ext.data.DataProxy that reads a data object from an Ext.data.Connection object configured to reference a certain URL.
Note that this class cannot be used to retrieve data from a domain other than the domain from which the running page was served.
For cross-domain access to remote data, use an Ext.data.ScriptTagProxy.
Ext.data.DataReader
Abstract base class for reading structured data from a data source and converting it into an object containing Ext.data.Record objects and metadata for use by an Ext.data.Store.
This class is intended to be extended and should not be created directly. For existing implementations, see Ext.data.ArrayReader, Ext.data.JsonReader and Ext.data.XmlReader.
Ext.data.JsonReader
Data reader class to create an Array of Ext.data.Record objects from a JSON response based on mappings in a provided Ext.data.Record constructor.
Successful retrieval of data to populate an Ext.data.Store
Code:
// A Store object uses its configured implementation of DataReader to
// create Record instances from the data object. These Records are
// cached and made available through accessor functions.
var personDataStore = new Ext.data.Store({
proxy: httpProxy,
reader: personReader
});
Set an event handler on the Ext.data.Store object that listens for the load event.
Code:
// Fires after a new set of Records has been loaded successfully.
personDataStore.on('load', loadSuccessful);
Here is an example of two person objects returned by the application in JSON format.
A Person object consists of its instance variables; id, firstName, lastName, birthdate, Address and a List of phoneNumbers.
An Address object consists of its instance variables; personId, street, city, state and a Zipcode.
A Phone object consists of its instance variables; personId, name and number.
A Zipcode object consists of its instance variables; zipcode.
Data that EXTJS expects when loading a data store is:
data - The rows that will be converted by the Ext.data.JsonReader into an array of Ext.data.Record objects that will be stored in the Ext.data.Store
success - The name of the property from which to retrieve the success attribute used by forms.
totalRows - Name of the property from which to retrieve the total number of records in the dataset. This is only needed if the whole dataset is not passed in one go, but is being paged from the remote server.
The response from the server should take the following format:
Code:
{"people":
{"data":
[
{"address":
{"city":"Boulder","personId":5,"state":"CO","street":"7240 Angelica Rd","zip":
{"zipcode":"80301"}
},
"birthdate":"1970/05/06","firstName":"Nick","id":5,"lastName":"Victorios","phoneNumbers":
[
{"name":"Cell","number":"773-528-1737","personId":5},
{"name":"Home","number":"720-678-9876","personId":5}
]
},
{"address":
{"city":"Chicago","personId":6,"state":"IL","street":"1940 Crystal Ave.","zip":
{"zipcode":"60649"}},
"birthdate":"1972/05/06","firstName":"Pete","id":6,"lastName":"Tutorios","phoneNumbers":
[
{"name":"Home","number":"312-558-2300","personId":6}
]
}
],
"message":"Successfully retrieved persons","success":true,"totalRows":2
}
}
Here is the code that handles a successful Store load (although you probably won't display a dialog box on a store load, but if you wanted to this is how):
Code:
/**
* Handle the occurrence of new set of Records have been loaded successfully.
* @param {Store} store Store instance.
* @param {Ext.data.Record[]} recordArray The Records that were loaded.
* @param {Object} options The loading options that were specified (see load for details).
*/
function loadSuccessful(store, recordArray, options) {
// After any data loads, the raw JSON data is available for further
// custom processing. If no data is loaded or there is a load exception
// this property will be undefined.
Ext.MessageBox.alert('Confirm', personReader.jsonData.people.message);
}
Failed retrieval of data to populate an Ext.data.Store
Request Failure (HTTP Failure)
The Ext.data.HttpProxy uses Ext.data.Connection object to connect and make a request to the server. Retrieve the Ext.data.Connection object off of the Ext.data.HttpProxy object and append an event handler to it. The requestexception event is fired if an error HTTP status was returned from the server.
Code:
// Create an http proxy to be used to call the server and retrieve data to
// be loaded using the reader and cached in the store.
var httpProxy = new Ext.data.HttpProxy({
url: '/ext/personServlet?action=list'
});
// Fires if an error HTTP status was returned from the server. See
// HTTP Status Code Definitions for details of HTTP status codes.
// Handle any exception that may occur in the connection and http request.
httpProxy.getConnection().on('requestexception', requestFailed);
Here is the code that handles the request failure:
Code:
/**
* Handle the occurrence of an error HTTP status that was returned from the server.
* See HTTP Status Code Definitions for details of HTTP status codes.
* @param {Ext.data.Connection} connection The Connection object.
* @param {Object} response The XHR object containing the response data.
* See [http://www.w3.org/TR/XMLHttpRequest/] for details about accessing elements
* of the response.
* @param {Object} options The options config object passed to the request method.
*/
function requestFailed(connection, response, options) {
Ext.MessageBox.alert('Error Message',
"Please contact support with the following: " +
"Status: " + response.status +
", Status Text: " + response.statusText);
}
Load Failure (Application Failure)
Append an event handler to the Ext.data.Store object. The loadexception event can be fired for one of two reasons:
1. The load call returned success: false. This means the server logic returned a failure status and there is no data to read.
2. The load succeeded but the reader could not read the response. This means the server returned data, but the configured Reader threw an error while reading the data.
Code:
// Fires if an exception occurs in the Proxy during data loading.
// Called with the signature of the Proxy's "loadexception" event.
// Handle any exception that may occur in the Proxy during data loading.
personDataStore.on('loadexception', loadFailed);
The response from the server should take the following format (notice there is no 'data' key in the response):
Code:
{people: {success: false, message: "Error occurred trying to retrieve all the persons."}}
Here is the code that handles the application failure:
Code:
/**
* Handle the occurrence of an exception that occurs in the Proxy during data
* loading. This event can be fired for one of two reasons:
* The load call returned success: false. This means the server logic returned
* a failure status and there is no data to read. In this case, this event will
* be raised and the fourth parameter (read error) will be null.
* The load succeeded but the reader could not read the response. This means the
* server returned data, but the configured Reader threw an error while reading
* the data. In this case, this event will be raised and the caught error will be
* passed along as the fourth parameter of this event.
* @param {Object} proxy The HttpProxy object.
* @param {Object) options The loading options that were specified (see load for details).
* @param {Object} response The XMLHttpRequest object containing the response data.
* See [http://www.w3.org/TR/XMLHttpRequest/] for details about accessing elements
* of the response.
* @param {Error} error The JavaScript Error object caught if the configured Reader
* could not read the data. If the load call returned success: false, this parameter
* will be null.
*/
function loadFailed(proxy, options, response, error) {
// Decodes (parses) a JSON string to an object. If the JSON is invalid,
// this function throws a SyntaxError.
var object = Ext.util.JSON.decode(response.responseText);
var errorMessage = "Error loading data.";
// If the load from the server was successful and this method was called then
// the reader could not read the response.
if (object.people.success) {
errorMessage = "The data returned from the server is in the wrong format. " +
"Please notify support with the following information: " + response.responseText;
} else {
// Error on the server side will include an error message in the response.
errorMessage = object.people.message;
}
Ext.MessageBox.alert('Error Message', errorMessage);
}
Retrieving data to populate a Form
Ext.form.FormPanel
Standard form container.
By default, Ext Forms are submitted through Ajax, using Ext.form.Action. To enable normal browser submission of the Ext Form contained in this FormPanel, override the Form's onSubmit, and submit methods.
Ext.form.BasicForm
Supplies the functionality to do "actions" on forms and initialize Ext.form.Field types on existing markup.
By default, Ext Forms are submitted through Ajax, using Ext.form.Action. To enable normal browser submission of an Ext Form, use the standardSubmit config option.
Ext.form.Action
The subclasses of this class provide actions to perform upon Ext.form.BasicForms.
Instances of this class are only created by a Ext.form.BasicForm when the Form needs to perform an action such as submit or load. The Configuration options listed for this class are set through the Ext.form.BasicForm's action methods: submit, load and doAction.
The instance of Action which performed the action is passed to the success and failure callbacks of the Ext.form.BasicForm's action methods (submit, load and doAction), and to the actioncomplete and actionfailed event handlers.
Ext.form.Action.Load
A class which handles loading of data from a server into the Fields of an Ext.form.BasicForm.
Instances of this class are only created by an Ext.form.BasicForm when submitting.
A response packet must contain a boolean success property, and a data property. The data property contains the values of Fields to load. The individual value object for each Field is passed to the Field's setValue method.
Successful retrieval of data to populate an Ext.form.FormPanel
Set the configuration option success with a function to call if the response from the server was a successful attempt (load in this case).
Code:
// Call the server and retrieve the data then load the form.
// Use the Ext.form.Action.Load class to load the form.
formPanel.form.load({
url: '/ext/personServlet?action=loadPerson&id=' + selectedId,
waitMsg: 'Loading',
// The function to call when the response from the server was a failed
// attempt (load in this case), or when an error occurred in the Ajax
// communication.
failure: loadFailed,
// The function to call when the response from the server was a successful
// attempt (load in this case).
success: loadSuccessful
});
The response from the server should take the following format:
Code:
{"person":
{"data":
[
{"address":
{"city":"Chicago","personId":8,"state":"IL","street":"1940 Crystal Ave.","zip":
{"zipcode":"60649"}
},
"birthdate":"1972/05/06","class":"mypackage.person.Person","firstName":"Pete","id":8,"lastName":"Tutorios","phoneNumbers":
[
{"name":"Home","number":"312-558-2300","personId":8}
]
}
],
"message":"Successfully found the person.","success":true,"totalRows":1
}
}
Here is the code that handles a successful Form load (although you probably won't display a dialog box on a form load, but if you wanted to this is how):
Code:
/**
* The function to call when the response from the server was a successful
* attempt (load in this case). The function is passed the following parameters:
*
* @param {Ext.form.BasicForm} form The form that requested the action.
* @param {Ext.form.Action} action The Action class.
* The result property of this object may be examined to perform custom postprocessing.
*/
function loadSuccessful(form, action) {
// Decodes (parses) a JSON string to an object. If the JSON is invalid,
// this function throws a SyntaxError.
var object = Ext.util.JSON.decode(action.response.responseText);
Ext.MessageBox.alert('Confirm', object.person.message);
}
Failed retrieval of data to populate an Ext.form.FormPanel
Request Failure (HTTP Failure)
Set the configuration option failure with a function to call if the response from the server was a failed attempt (load in this case), or when an error occurred in the Ajax communication.
Code:
// Call the server and retrieve the data then load the form.
// Use the Ext.form.Action.Load class to load the form.
formPanel.form.load({
url: '/ext/personServlet?action=loadPerson&id=' + selectedId,
waitMsg: 'Loading',
// The function to call when the response from the server was a failed
// attempt (load in this case), or when an error occurred in the Ajax
// communication.
failure: loadFailed,
// The function to call when the response from the server was a successful
// attempt (load in this case).
success: loadSuccessful
});
Load Failure (Application Failure)
See the Request Failure (HTTP Failure) above for code example. Set the configuration option failure with a function to call if the response from the server was a failed attempt (load in this case), or when an error occurred in the Ajax communication.
The response from the server should take the following format:
Code:
{"person":{"data":[],"message":"Exception occurred during the retrieval of the person.","success":false,"totalRows":0}}
The Ext.data.DataReader (Ext.data.JsonReader in this case) expects the data parameter to be returned, even though it is an empty array.
Here is the code that handles all the errors that can occur during a Form load:
Code:
/**
* The function to call when the response from the server was a failed
* attempt (load in this case), or when an error occurred in the Ajax
* communication. The function is passed the following parameters:
* @param {Ext.form.BasicForm} form The form that requested the action.
* @param {Ext.form.Action} action The Action class.
* If an Ajax error occurred, the failure type will be in failureType.
* The result property of this object may be examined to perform custom postprocessing.
*
* A failed attempt (load in this case) response from the server will be:
* {success: false, message: 'Failure message to be displayed.'}
*/
function loadFailed(form, action) {
var failureMessage = "Error occurred trying to retrieve data.";
// Failure type returned when no field values are returned in the
// response's data property or the successProperty value is false.
if (action.failureType == Ext.form.Action.LOAD_FAILURE) {
// Error on the server side will include an error message in
// the response.
// Decodes (parses) a JSON string to an object. If the JSON is invalid,
// this function throws a SyntaxError.
var object = Ext.util.JSON.decode(action.response.responseText);
failureMessage = object.person.message;
}
// Failure type returned when a communication error happens when
// attempting to send a request to the remote server.
else if (action.failureType == Ext.form.Action.CONNECT_FAILURE) {
// The XMLHttpRequest object containing the
// response data. See http://www.w3.org/TR/XMLHttpRequest/ for
// details about accessing elements of the response.
failureMessage = "Please contact support with the following: " +
"Status: " + action.response.status +
", Status Text: " + action.response.statusText;
}
Ext.MessageBox.alert('Error Message', failureMessage);
}
Submitting data from an Ext.form.FormPanel
Ext.data.Action.Submit
A class which handles submission of data from Ext.form.BasicForms and processes the returned response.
Instances of this class are only created by a Ext.form.BasicForm when submitting.
A response packet must contain a boolean success property, and, optionally an errors property. The errors property contains error messages for invalid fields.
By default, response packets are assumed to be JSON.
Successful submit of Ext.form.FormPanel
Set the configuration option success with a function to call if the response from the server was a successful attempt (save in this case).
Code:
// To enable normal browser submission of the Ext Form contained in this
// FormPanel, override the submit method.
// Use the Ext.form.Action.Submit class to submit the form.
formPanel.form.submit({
waitMsg: 'In processing',
// The function to call when the response from the server was a failed
// attempt (save in this case), or when an error occurred in the Ajax
// communication.
failure: submitFailed,
// The function to call when the response from the server was a successful
// attempt (save in this case).
success: submitSuccessful
});
The response from the server should take the following format:
Code:
{success: true, message: 'Person was saved successfully.'}
Here is the code that handles a successfully submitted Form:
Code:
/**
* The function to call when the response from the server was a successful
* attempt (save in this case). The function is passed the following parameters:
*
* @param {Ext.form.BasicForm} form The form that requested the action.
* @param {Ext.form.Action} action The Action class.
* The result property of this object may be examined to perform custom postprocessing.
*
* A successful attempt (save in this case) response from the server will be:
*
* {success: true, message: 'Success message to be displayed.'}
*/
function submitSuccessful(form, action) {
Ext.MessageBox.alert('Confirm', action.result.message);
// Hide the popup window.
window.hide();
// Reload the data store with a call to the server and load
// the grid again with the newly added person.
personDataStore.load({params:{start:0, limit:10}});
}
Failed submit of Ext.form.FormPanel
Request Failure (HTTP Failure)
Set the configuration option failure with a function to call if the response from the server was a failed attempt (save in this case), or when an error occurred in the Ajax communication.
Code:
// To enable normal browser submission of the Ext Form contained in this
// FormPanel, override the submit method.
// Use the Ext.form.Action.Submit class to submit the form.
formPanel.form.submit({
waitMsg: 'In processing',
// The function to call when the response from the server was a failed
// attempt (save in this case), or when an error occurred in the Ajax
// communication.
failure: submitFailed,
// The function to call when the response from the server was a successful
// attempt (save in this case).
success: submitSuccessful
});
Failed response from server (Application Failure)
See the Request Failure (HTTP Failure) above for code example.
Set the configuration option failure with a function to call if the response from the server was a failed attempt (save in this case), or when an error occurred in the Ajax communication.
The response from the server should take the following format:
Code:
{success: false, message: 'Person was not saved successfully. Please try again.'}
Failed form validation response from server (Server-side Validation Failure)
See the Request Failure (HTTP Failure) above for code example.
Set the configuration option failure with a function to call if the response from the server was a failed attempt (save in this case), or when an error occurred in the Ajax communication.
The response from the server should take the following format:
Code:
{success: false, errors:{"phone":"Phone must be in the format of xxx-xxx-xxxx"}, message: 'Please fix all highlighted errors.'}
Here is the code that handles all the errors that can occur during a Form submittal:
Code:
/**
* The function to call when the response from the server was a failed
* attempt (save in this case), or when an error occurred in the Ajax
* communication. The function is passed the following parameters:
* @param {Ext.form.BasicForm} form The form that requested the action.
* @param {Ext.form.Action} action The Action class.
* If an Ajax error ocurred, the failure type will be in failureType.
* The result property of this object may be examined to perform custom postprocessing.
*
* A failed attempt (save in this case) response from the server will be:
* {success: false, message: 'Failure message to be displayed.'}
*
* A failed attempt (validation in this case) response from the server will be:
* {
* success: false,
* errors: {
* "clientCode": "Client not found",
* "phone": "This field must be in the format of xxx-xxx-xxxx"
* },
* message : 'Validation Errors, please fix the errors noted.'
* }
*/
function submitFailed(form, action) {
var failureMessage = "Error occurred trying to save data.";
// Failure type returned when no field values are returned in the
// response's data property or the successProperty value is false.
if (action.failureType == Ext.form.Action.LOAD_FAILURE) {
// Error on the server side will include an error message in
// the response.
failureMessage = action.result.message;
}
// Failure type returned when a communication error happens when
// attempting to send a request to the remote server.
else if (action.failureType == Ext.form.Action.CONNECT_FAILURE) {
// The XMLHttpRequest object containing the
// response data. See http://www.w3.org/TR/XMLHttpRequest/ for
// details about accessing elements of the response.
failureMessage = "Please contact support with the following: " +
"Status: " + action.response.status +
", Status Text: " + action.response.statusText;
}
// Failure type returned when server side validation of the Form fails
// indicating that field-specific error messages have been returned in
// the response's errors property.
else if (action.failureType == Ext.form.Action.SERVER_INVALID) {
// Validation on the server side will include an error message in
// the response.
failureMessage = action.result.message;
}
// Failure type returned when client side validation of the Form fails
// thus aborting a submit action.
else if (action.failureType == Ext.form.Action.CLIENT_INVALID) {
failureMessage = "Please fix any and all validation errors.";
}
// Since none of the failure types handled the error, simply retrieve
// the message off of the response. The response from the server on a
// failed submital (application error) is:
// {success: false, message: 'Person was not saved successfully. Please try again.')
else {
failureMessage = action.result.message;
}
Ext.MessageBox.alert('Error Message', failureMessage);
}
Making an AJAX call to the server
Ext.data.Connection
The class encapsulates a connection to the page's originating domain, allowing requests to be made either to a configured URL, or to a URL specified at request time.
Requests made by this class are asynchronous, and will return immediately. No data from the server will be available to the statement immediately following the request call. To process returned data, use a callback in the request options object, or an event listener.
Successful AJAX call to the server
Set the configuration option success with a function to call if the response from the server was a successful attempt (delete in this case).
Code:
// Send a HTTP request to a remote server.
// Important: Ajax server requests are asynchronous, and this
// call will return before the response has been recieved.
// Process any returned data in a callback function.
new Ext.data.Connection().request({
url: '/ext/personServlet?action=delete',
params: {id: selectedPersons\[0\].get("id")},
failure: requestFailed,
success: requestSuccessful
});
In the example above we are deleting a person from the database, passing in the person's id on the request.
The response from the server should take the following format:
Code:
{success: true, message: 'Person was deleted successfully.'}
Here is the code that handles a successful HTTP request request:
Code:
/**
* Handle a successful connection and http request to the server.
* The response from the application may still be unsuccessful so
* that needs to be checked.
* @param {Object} response The XMLHttpRequest object containing the
* response data. See [http://www.w3.org/TR/XMLHttpRequest/] for
* details about accessing elements of the response.
* @param {Object} options The parameter to the request call.
*/
function requestSuccessful(response, options) {
// Decodes (parses) a JSON string to an object. If the JSON is invalid,
// this function throws a SyntaxError.
// The response text from the server is:
// {success: true, message: 'Person was deleted successfully.'}
// The object will contain two variables: success and message.
var object = Ext.util.JSON.decode(response.responseText);
// If the delete was successfully executed on server.
if (object.success) {
Ext.MessageBox.alert('Confirm', object.message);
} else {
Ext.MessageBox.alert('Error Message', object.message);
}
}
Failed AJAX call to the server
Request Failure (HTTP Failure)
Code:
/**
* Handle an unsuccessful connection or http request to the server.
* This has nothing to do with the response from the application.
* @param {Object} response The XMLHttpRequest object containing the
* response data. See [http://www.w3.org/TR/XMLHttpRequest/] for
* details about accessing elements of the response.
* @param {Object} options The parameter to the request call.
*/
function requestFailed(response, options) {
// The request to the server was unsuccessful.
Ext.MessageBox.alert('Error Message',
"Please contact support with the following: " +
"Status: " + response.status +
", Status Text: " + response.statusText);
}
Failed response from the server (Application Failure)
The code that handles the application failure is the same as the code that handles the success. See Successful AJAX call to server.
The only difference is the content in the response from the server:
Code:
{success: false, message: 'Person was not deleted due to an internal error. Please try again.'}