Sencha Inc. | HTML5 Apps

Blog

Deep Dive Into Ext JS 5 Data

July 08, 2014 | Don Griffin

Introduction

Ext JS 5 DataWith the introduction of Ext JS 5, we have greatly expanded the capabilities of the Sencha Data Package, which was initially released with Ext JS 4 and Sencha Touch 2. The data package is one of the cornerstones of application development: the oft neglected “M” (for Model) in the buzzing world of “MVC” and “MVVM”. These new capabilities can be seen throughout the entire application development process, from declaring your data model, to presenting your data and validating user input, and finally, saving it back to the server.

In this article, we’ll walk through each of these features. Let’s start at the beginning.

To learn more, join us for the upcoming webinar — Deep Dive into Ext JS 5 Data, Thursday, 7/17 at 10:00am PDT

UPDATE July 18, 2014: Thanks to everyone who attended the webinar! The recording is available here.

New Declarative Features

Typical applications have many classes that make up their data model. With Ext JS 5, we reduced the amount of repetitive, boilerplate code needed in these Model classes. Many of these enhancements are covered in What’s New In Ext JS 5 so I won’t repeat too much of that here.

Fields

It may seem contradictory in this context, but in Ext JS 5, you are no longer required to declare a Model’s fields. The advantage here is that data can be more easily extended, so long as it needs no special processing prior to record creation. It's not just easier, meaning you have fewer things that you must declare, but it can be faster as well. This optimization occurs because the data is not processed on a per-field basis, unless needed.

While eliminating field declarations is convenient at times and an easy way to eliminate per-field processing, this is not always possible. For example, the defaultValue and convert configs require processing for the field on which they are declared. When adding validators and references on fields, you can still avoid this processing by using the default field type “auto” (and avoiding the configs just mentioned).

Custom Field Types and Validators

One area where declaring your fields is most advantageous is also perhaps the most repetitive type of code in Models: validation. Prior to Ext JS 5, the declaration of validation logic was uniquely a job for Ext.data.Model-derived classes. As such, these validations were not something other classes dealing with similar fields could reuse. At the same time, these validations were frequently related to the type of content in the field. For example, email addresses, phone numbers, gender, date of birth, etc.

With the introduction of custom field types, you can now reuse this logic across all your models. For example, we can declare a “gender” field type:

 
Ext.define('App.fields.Gender', {
    extend: 'Ext.data.field.String',
    alias: 'data.field.gender',
    validators: {
        type: 'inclusion',
        list: [ 'female', 'male' ]
    }
});
 

And then use it in any number of Models:

 
Ext.define('App.model.Person', {
    extend: 'Ext.data.Model',
 
    fields: [{
        name: 'name', type: 'string'
    },{
        name: 'gender', type: 'gender'
    }]
});
 

See this fiddle to experiment with the above example. In real-world applications, there are likely to be many opportunities to reuse field types and their validators.

Reference Fields and Associations

Declaring associations is another area in Ext JS 5 where we have reduced boilerplate code requirements. In previous releases, the hasMany, hasOne and belongsTo configs required that you manually maintain symmetric declarations on both “sides” of an association. This is no longer the case. You can declare an association in either of the associated classes (though typically on the “many” side).

For example, you can use hasMany on one Model and the corresponding belongsTo is no longer needed. Consider this set of declarations:

 
Ext.define('App.model.Base', {
    extend: 'Ext.data.Model',
    schema: {
        namespace: 'App.model'
    }
});
 
Ext.define('App.model.Person', {
    extend: 'App.model.Base',
 
    fields: [ 'name' ],
 
    hasMany: 'Order' // the "entityName" for "App.model.Order" (see below)
});
 
Ext.define('App.model.Order', {
    extend: 'App.model.Base',
 
    fields: [ 'name' ],
 
    hasMany: 'OrderItem'
});
 
Ext.define('App.model.OrderItem', {
    extend: 'App.model.Base',
 
    fields: [ 'name' ]
});
 

The above example takes advantage of two new features in Ext JS 5:

  • Declaration of associations on only one of the associated Models.
  • Automatically generated entityName values (by setting the Schema namespace). This allows you to have friendly names for your Models and their generated getter and setter methods while still following the recommended namespace structure in your application. Schema is also new to Ext JS 5 and has some other useful options worth looking into (such as its proxy config), but because there is typically not much need to directly interact with the schema, we won’t cover it further here.

The above example is, however, overly simplified from the perspective of true applications where these records are often linked to each other by holding id values in certain fields. For example, an OrderItem will often have an orderId field and the Order will likely have a personId field. These are sometimes called “foreign keys” and maintaining the integrity of these fields is critical.

When such fields are present, the new reference config can be used to indicate the type of Model a particular field references. Reworking the above code in this example:

 
Ext.define('App.model.Person', {
    extend: 'App.model.Base',
    fields: [ 'name' ]
});
 
Ext.define('App.model.Order', {
    extend: 'App.model.Base',
 
    fields: [{
        name: 'name',
        type: 'string'
    },{
        name: 'personId',
        reference: 'Person'
    }]
});
 
Ext.define('App.model.OrderItem', {
    extend: 'App.model.Base',
 
    fields: [{
        name: 'name',
        type: 'string'
    },{
        name: 'orderId',
        reference: 'Order'
    }]
});
 

Given the new reference configs shown here, the same associations are created as with the hasMany example. More than that, Ext JS 5 will maintain these fields as well. For example, adding an OrderItem to the orderItems association of an Order will automatically set the orderId field.

Many-to-Many Associations

In addition to streamlining associations using reference fields, we also added support in Ext JS 5 for a new type of association: many-to-many. A typical scenario where this comes up is modeling Users and Groups. A user can be a member of multiple groups, and a group can have multiple users. Let’s consider a simple declaration like this:

 
Ext.define('App.model.User', {
    extend: 'App.model.Base',
 
    fields: [ 'name' ],
 
    manyToMany: 'Group'
});
 
Ext.define('App.model.Group', {
    extend: 'App.model.Base',
 
    fields: [ 'name' ]
});
 

Notice again that only one Model has to declare the association (using the manyToMany config). This creates methods on each class similar to hasMany. In the above case, the User class gets a groups method and Group gets a users method. As with hasMany, these return stores that hold the associated records. Under the covers, however, careful bookkeeping has to take place to maintain this type of association.

When models store keys to each other, maintenance of their relationship is straightforward. In the case of many-to-many associations, however, it is not possible to represent the relationship as fields of either associated record. Further, adding a Group to the groups store for a User must add the User record to that Group’s users store (if it exists).

To see this in action, check out this example. One thing is quite new and different in this example:

 
var session = new Ext.data.Session();
 

We’ll see more on this later, but for now it is sufficient to say that the extra maintenance required for many-to-many associations is handled by this session instance. We’ll see later how we can use the session to look at the pending edits for these associations.

Connecting The Pieces

Now that we have our data model declared, we’re ready to see some new ways we can use it in our application. Using data binding in Ext JS 5, we can much more easily get our data presented to the user, but then the fun starts as soon as the user begins editing that data.

Form Validation

With the introduction of ViewModels, Ext JS 5 now has the ability to know the connection between the value of a form field and the underlying data storage. Data binding for form fields goes to the next logical step and also connects the validators from Model fields when you set the new config: modelValidation.

For example:

 
Ext.create({
    xtype: 'window',
    title: 'Validation',
    width: 350,
    layout: 'form',
    autoShow: true,
    viewModel: {
        data: {
            person: new App.model.Person({
                name: 'Bob',
                gender: 'mal' // typo
            })
        }
    },
    defaultType: 'textfield',
 
    modelValidation: true,
 
    items: [{
        fieldLabel: 'Name',
        bind: '{person.name}'
    },{
        fieldLabel: 'Gender',
        bind: '{person.gender}'
    }]
});
 

The above form is just a basic data binding form using a Person record in the ViewModel. By adding modelValidation: true, however, the child form fields automatically bind to the validator information for their value bindings. In this case, the gender field of the person record in the ViewModel is invalid and that is reflected by the Gender text field’s error indicator.

Data Sessions

The final piece of the puzzle then is to gather all of this validated data, so it can be saved to the server. In previous versions, this was done manually by tracking relevant records and stores and calling their save and sync methods. When dealing with multiple records and stores, this could become a complex process of bookkeeping, sequencing and callback chaining.

This is where Ext.data.Session can greatly simplify your code. The primary job of a session is to track records (by their type and id), so all related parties can retrieve a reference to the same record object. By tracking all of these records, the session ensures that the contents of associations are coherently updated and maintained as edits occur.

Creating a Session

Sessions are typically created using the session config. The key decision then is where in the view hierarchy to create a session. Creating a session at the top (or “viewport”) level makes sense if the application uses a relatively stable set of records over its lifetime. If this is not the case, a child view such as a modal window or closable tab would make more sense because the session can be destroyed along with the view that owned it.

Any component can retrieve the appropriate session instance by calling the lookupSession method. This method will consider that component’s session config, if specified, or find the session from the nearest ancestor in the view hierarchy that has a session configured. In other words, the “session” is inherited by child components.

Getting Records

Records can be manually fetched from a session by calling getRecord or created in the session by calling createRecord. These methods are seldom called directly when using ViewModels which automate this using the links config. Whether called directly or automated by ViewModels, these methods ensure the coherency of record instances tracked by the session.

When records are loaded by the session, traversing their associations will load associated records into the same session. This gives the session the ability to gather changes for all of these records when it comes time to save back to the server.

It is worth mentioning that all records loaded or created by a session are owned by that session and cannot be shared with another session.

Inspecting a Session

You can request that the session produce the current state of all its records by calling its getChanges or visitData methods. The information returned by these methods can be used for a variety of useful purposes such as:

  • Saving all changes to the server in a single transaction
  • Transfer the state of the client’s records to the server to enable custom Ajax requests
  • General diagnostics

Updating a Session

The opposite of getChanges is update. This method can be used to inject any number of record changes of any type (create, update, drop) using the same format returned by getChanges. Internally this mechanism is used to support child session (discussed below), but it enables other techniques as well:

  • Preload a session with all initially relevant records at page load time. This can eliminate many independent Ajax calls to load records and stores individually.
  • Transfer results of server-side record changes back to the client (perhaps in the response to a custom Ajax request) for subsequent saving (or discarding). That is to say, you can seamlessly transfer the current state of records from the client to a server-side method and merge the resulting record changes back on the client without having to commit those changes on the server at the same time. This can be helpful when building “what-if” user experiences.

Child Sessions

It is fairly common to present a user with some sort of user interface that allows them to make changes and then accept or reject those changes. For example, a typical “OK/Cancel” window. We can isolate such a window’s ViewModel by creating a new “child” session. This child session communicates with its parent session to retrieve record data but does not immediately modify those records. Instead, it creates records of its own to hold on to changes. When the user accepts the changes, we simply call the save method on the child session.

You can see this in action in the Kitchen Sink example.

Saving Sessions

When it finally comes time to save changes back to the server, we have a couple choices. We can use getChanges described above and send the data in a custom Ajax request. Or, we can use the standard proxy defined for each Model.

To use the standard proxy, we call getSaveBatch on the session. This returns an Ext.data.Batch object on which we call start. The individual operations in this batch are ordered in such a way as to ensure that the server is never presented with records that it is unprepared to handle. This ordering is derived from the Model’s reference fields. For example, since an OrderItem has an orderId field, any created Order records must be sent to the server first before OrderItems with that orderId.

If the server is handling id generation, this is also when the client-generated IDs are corrected and the corresponding reference fields on associated records are updated.

Caveats

For all their usefulness, sessions do have limitations. These stem from the fact that, to do its job, a session must retain references to all records attached to it and can only remove them when a record is dropped. If a session is over-used, it will become a large memory consumer. You must take care in choosing where to create sessions and allowing them to be decommissioned by connecting them to components with a finite lifecycle.

Conclusion

We think these improvements all across the data layer will make it easier to develop and maintain your applications, and even provide you with some techniques that were previously impossible. We’d love to hear how you’ve used these new features in your applications, and of course, if you have questions we’d be happy to assist you in the forums.

There are 10 responses. Add yours.

Vladas Saulis

2 months ago

With introduction of ExtJS 4 and continuing to ExtJS 5 all good things in ExtJs has been messed up. This is my own opinion, but if development would continue on ExtJS 3 basis - I think it would be much better. Not many people like MVC model, which came out from Microsoft ASP and likes. Not every project requires MVC, nor it needs it.

Oleg

2 months ago

It would be great if we weren’t asked the same registration data every time we want to attend a webinar.

Joeri Sebrechts

2 months ago

Do sessions support the command history pattern for global undo/redo? Seems like an ideal place to put that kind of logic, and lots of LOB applications would benefit.

Don Griffin Sencha Employee

2 months ago

@Joeri -

Sorry, sessions do not provide undo support. We discussed undo/redo in that context but there are plenty of use cases that would not handle. Perhaps an approach that included a ViewModel would include more of those use cases. That said, I think you could look at the session data via getChanges and update methods to manage that if your needs would be covered well enough at that level.

Tom Coulton

2 months ago

Thanks for this article. We’ve posted the Japanese translation here: http://www.xenophy.com/sencha-blog/11334

The Japanese Sencha meetup: http://www.meetup.com/Japan-Sencha-User-Group/

Dave Bush

2 months ago

Vladas

The MVC design pattern isn’t something that came out of Microsoft. It was very popular in the Java world with the implementation of Struts (open source btw) and was a standard design pattern long before that.  Check out “Design Patterns” by the Gang of Four. 

My beef with the EXT implementation is that the C in MVC is more accurately described as a ViewController than what the GoF, Struts or Microsoft describe as a Controller.  Once you understand that EXT really uses what is better characterized as an MVVC pattern, things make a lot more sense.

Vladas Saulis

2 months ago

Dave,

I’m not against MVC if you can choose it as one of options. Let it be, so it can be used by Java’ers. But Javascript way of doing things is not that in Java. Why I forced to declare Models, Views and Controllers when I don’t want to use them. That is my problem with the latest ExtJS versions. This is why I have to stick with ExtJS 3.

Dmitry Burlea

1 month ago

Vladas,

In order to do something efficiently you should have a plan and all your actions should be ordered and follow some logical pattern. This doesn’t refer only to coding, that’s applies to almost everything. And if to speak about coding, this definitely doesn’t refer only to Java but almost to each modern programming languages which JavaScript also makes part of.
I don’t really have the intention to teach you how to live or to do the work. I just want to say that using a well-defined path and/or pattern makes your life better and code cleaner and much more maintainable.
I have used MVC approach long before I started with ExtJS and I have never coded in ASP, if you like.
ExtJS team did great job with ExtJS 4 and looking back to the applications written in ExtJS 3.x I see what a mess they are. Me and my company team had and continue to have hard hours of supporting these apps and rewriting them to ExtJS 4/5.
Now with ExtJS 5 it is the next step forward which together with the new approach to MVC and MVVM and all the features described in this post make things even cleaner.
More than that, you are not imposed to use MVC/MVVM and all these things and can continue coding in ExtJS 3 style using ExtJS 4/5 frameworks. But I’m sure if you try to use and follow these patterns in a correct way, after a while you will figure out how useful they are and will see the benefits described above.

Don Griffin Sencha Employee

1 month ago

@Vladas -

Echoing what Dmitry said, Ext JS 5 (and 4) do not *require* you use MVC or MVVM for your application. We still provide Ext.onReady, Ext.create and the like so your fundamental approach to coding in Ext JS 3 will still work. While we believe there are benefits to these ways of structuring applications, we don’t insist that you follow any particular paradigm.

Vladas Saulis

1 month ago

@Don -

Yes, I’m aware that it’s possible to develop application in ExtJS 3 style. But quick look at the article convinced me that the abstraction level for the data model had significantly increased. I’m an old school programmer, so I like to control things as much as possible, and ExtJS 3 was at balanced level of abstraction for me. Now, with ExtJS 4/5 I can see there too much details are hidden behind the scenes. In other words it looks like now we have ExtJS 5 on top of ExtJS 3 on top of ExtJS base on top of DOM, and so on. Please excuse me for such my understanding.

Comments are Gravatar enabled. Your email address will not be shown.

Fields marked with * are required.