Models and persistence

Ext JS 4 models are similar to JPA entities in that they define data fields that represent columns in the underlying database tables. Each model instance represents a row in the table. The primary key field is defined using the idProperty of the model, which must match one of the field names. The User model can now be updated as shown:

Ext.define('TTT.model.User', {
    extend: 'Ext.data.Model',
    
    fields: [
        { name: 'username', type: 'string' },
        { name: 'firstName', type: 'string' },
        { name: 'lastName', type: 'string' },
        { name: 'fullName', type: 'string' },
        { name: 'email', type: 'string' },
        { name: 'password', type: 'string' },
        { name: 'adminRole', type: 'string' }
    ],
  idProperty: 'username'
});

Defining the proxy

Each model can be made persistent aware by configuring an appropriate proxy. All loading and saving of data is then handled by the proxy when the load, save, or destroy method on the model is called. There are several different types of proxies but the most widely used is the Ext.data.ajax.Proxy (alternate name Ext.data.AjaxProxy). The AjaxProxy uses AJAX requests to read and write data from the server. Requests are sent as GET or POST methods depending on the operation.

A second useful proxy is Ajax.data.RestProxy. The RestProxy is a specialization of the AjaxProxy that maps the four CRUD actions to the appropriate RESTful HTTP methods (GET, POST, PUT, and DELETE). The RestProxy would be used when connecting to RESTful web services. Our application will use AjaxProxy.

The User model definition including proxy follows:

Ext.define('TTT.model.User', {
    extend: 'Ext.data.Model',
    
    fields: [
        { name: 'username', type: 'string' },
        { name: 'firstName', type: 'string' },
        { name: 'lastName', type: 'string' },
        { name: 'fullName', type: 'string', persist:false },
        { name: 'email', type: 'string' },
        { name: 'password', type: 'string' },
        { name: 'adminRole', type: 'string' }
    ],
    idProperty: 'username',
    proxy: {
        type: 'ajax',
        idParam:'username',
        api:{
            create:'ttt/user/store.json',
            read:'ttt/user/find.json',
            update:'ttt/user/store.json',
            destroy:'ttt/user/remove.json'
        },
        reader: {
            type: 'json',            
            root: 'data'
        },
        writer: {
            type: 'json',
            allowSingle:true,
            encode:true,
            root:'data',
            writeAllFields: true
        }
    }
});

The proxy is defined as type ajax and specifies the primary key field in the model with the idParam property. The idParam is used when generating the URL for the read operation. For example, if trying to load the user record with username bjones, the proxy would generate a URL as follows:

ttt/user/find.json?username=bjones

If the idParam property was omitted, the URL generated would be as follows:

ttt/user/find.json?id=bjones

The api properties define the URLs to call on CRUD action methods. Each URL maps to an appropriate handler method in UserHandler. Note that the update and create URLs are the same as both actions are handled by the UserHandler.store method.

It is important to note that the AjaxProxy read operation uses a GET request while all other operations use POST requests. This is different from the RestProxy method, which uses a different request method for each operation.

Comparing AJAX and REST proxies

Our request handling layer has been designed to consume AJAX requests in a format submitted by Ext JS 4 clients. Each handler that processes an update action is configured with RequestMethod.POST and expects a data parameter that holds the JSON object applicable to the action.

We could have implemented the request handling layer as a RESTful API where each method is mapped to an appropriate request method type (GET, POST, PUT, or DELETE). Implementing a delete action would then encode the id of the item in the URL of a DELETE submitted request. Deleting the bjones user, for example, could be achieved by submitting a DELETE request method URL as follows:

user/bjones

The UserHandler.remove method could then be defined as:

@RequestMapping(value = "/user/{username}", 
method=RequestMethod.DELETE)
@ResponseBody
public String remove(final @PathVariable String username, final HttpServletRequest request) {
// code continues…

The @PathVariable extracts the username (in our sample URL this is bjones) from the URL, which is then used in the call to the userService.remove method. The @RequestMapping method of RequestMethod.DELETE ensures the method is only executed when a DELETE request matching the URL path of /user/{username} is submitted.

The RESTful API is a specific style of using HTTP that encodes the item you want to retrieve or manipulate in the URL itself (via its ID) and encodes what action you want to perform on it in the HTTP method used (GET for retrieving, POST for changing, PUT for creating, DELETE for deleting). The Rest proxy in Ext JS is a specialization of the AjaxProxy that simply maps the four CRUD actions to their RESTful HTTP equivalent method.

There is no significant difference in implementing either the AJAX or REST alternative in Ext JS 4. Configuring the proxy with type:'ajax' or type:'rest' is all that is required. The request handling layer, however, would need to be implemented in a very different way to process the @PathVariable parameters. We prefer the AJAX implementation for the following reasons:

  • REST has traditionally been used for server-to-server communication, most notably in web services, and not for browser-server interactions.
  • The URLs for CRUD AJAX requests are unique and become self-documenting.
  • The 3T application is not a web service and is based on HTML 5.
  • The HTML 5 specification no longer supports PUT and DELETE as HTTP methods for form elements (see http://www.w3.org/TR/2010/WD-html5-diff-20101019/#changes-2010-06-24).
  • REST is not a flexible solution and is usually based around atomic actions (one item processed per request). AJAX and Ext JS combine to allow more complex interactions with bulk updating possible (many updates in a single request are possible for all create, update, and destroy URLs. This will be explained later in the Defining the writer section).
  • PUT and DELETE requests are often considered a security risk (in addition to OPTIONS, TRACE, and CONNECT methods) and are often disabled in enterprise web application environments. Applications that specifically require these methods (for example, web services) usually expose these URLs to a limited number of trusted users under secure conditions (usually with SSL certificates).

There is no definitive or compelling reason to use AJAX over REST or vice versa. In fact the online discussions around when to use REST over AJAX are quite extensive, and often very confusing. We have chosen what we believe to be is the simplest and most flexible implementation by using AJAX without the need for REST.

Defining the reader

The reader with type json instantiates a Ext.data.reader.Json instance to decode the server's response to an operation. It reads the JSON data node (identified by the root property of the reader) and populates the field values in the model. Executing a read operation for the User model using ttt/user/find.json?username=bjones will return:

{
    success: true,
    data: {
        "username": "bjones",
        "firstName": "Betty",
        "lastName": "Jones",
        "fullName": "Betty Jones",
        "email": "[email protected]",
        "adminRole": "Y"
    }
}

The reader will then parse the JSON file and set the corresponding field values on the model.

Defining the writer

The writer with type json instantiates an Ext.data.writer.Json instance to encode any request sent to the server in the JSON format. The encode:true property combines with the root property to define the HTTP request parameter that holds the JSON data. This combination ensures that a single request parameter with name data will hold the JSON representation of the model. For example, saving the previous bjones user record will result in a request being submitted with one parameter named data holding the following string:

{
    "username": "bjones",
    "firstName": "Betty",
    "lastName": "Jones",
    "email": "[email protected]",
    "password": "thepassword",
    "adminRole": "Y"
}

It should be noted that this representation is formatted for readability; the actual data will be a string of characters on one line. This representation is then parsed into a JsonObject in the UserHandler.store method:

JsonObject jsonObj = parseJsonObject(jsonData);

The appropriate jsonObject values are then extracted as required.

The writeAllFields property will ensure that all fields in the model are sent in the request, not just the modified fields. Our handler methods require all model fields to be present. However, note that we have added the persist:false property to the fullName field. This field is not required as it is not a persistent field in the User domain object.

The final writer property that needs explanation is allowSingle:true. This is the default value and ensures a single record is sent without a wrapping array. If your application performs bulk updates (multiple records are sent in the same single request) then you will need to set this property to false. This would result in single records being sent within an array, as shown in the following code:

[{  
    "username": "bjones",
    "firstName": "Betty",
    "lastName": "Jones",
    "email": "[email protected]",
    "password": "thepassword",
    "adminRole": "Y"
}]

The 3T application does not implement bulk updates and always expects a single JSON record to be sent in each request.

Defining validations

Each model has built-in support for validating field data. The core validation functions include checks for presence, length, inclusion, exclusion, format (using regular expressions), and email. A model instance can be validated by calling the validate function, which returns an Ext.data.Errors object. The errors object can then be tested to see if there are any validation errors.

The User model validations are as follows:

validations: [
  {type: 'presence',  field: 'username'},
  {type: 'length', field: 'username', min: 4},
  {type: 'presence',  field: 'firstName'},
  {type: 'length', field: 'firstName', min: 2},
  {type: 'presence',  field: 'lastName'},
  {type: 'length', field: 'lastName', min: 2},
  {type: 'presence',  field: 'email'},
  {type: 'email',  field: 'email'},
  {type: 'presence',  field: 'password'},
  {type: 'length', field: 'password', min: 6},
  {type: 'inclusion', field: 'adminRole', list:['Y','N']}
]

The presence validation ensures that a value is present for the field. The length validation checks for field size. Our validations require a minimum password size of six characters and a minimum username of four characters. First and last names have a minimum size of two characters. The inclusion validation tests to ensure the field value is one of the entries in the defined list. As a result, our adminRole value must be either a Y or N. The email validation ensures the e-mail field has a valid e-mail format.

The final code listing for our User model can now be defined as:

Ext.define('TTT.model.User', {
    extend: 'Ext.data.Model',
    
    fields: [
        { name: 'username', type: 'string' },
        { name: 'firstName', type: 'string' },
        { name: 'lastName', type: 'string' },
        { name: 'fullName', type: 'string', persist:false },
        { name: 'email', type: 'string' },
        { name: 'password', type: 'string' },
        { name: 'adminRole', type: 'string' }
    ],
    idProperty: 'username',
    proxy: {
        type: 'ajax',
        idParam:'username',
        api:{
            create:'ttt/user/store.json',
            read:'ttt/user/find.json',
            update:'ttt/user/store.json',
            destroy:'ttt/user/remove.json'
        },
        reader: {
            type: 'json',            
            root: 'data'
        },
        writer: {
            type: 'json',
            allowSingle:true,
            encode:true,
            root:'data',
            writeAllFields: true
        }
    },
    validations: [
        {type: 'presence',  field: 'username'},
        {type: 'length', field: 'username', min: 4},
        {type: 'presence',  field: 'firstName'},
        {type: 'length', field: 'firstName', min: 2},
        {type: 'presence',  field: 'lastName'},
        {type: 'length', field: 'lastName', min: 2},
        {type: 'presence',  field: 'email'},
        {type: 'email',  field: 'email'},
        {type: 'presence',  field: 'password'},
        {type: 'length', field: 'password', min: 6},
        {type: 'inclusion', field: 'adminRole', list:['Y','N']}
    ]        
});
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.218.226