
Models
Models represent objects or entities inside our application, for example, Clients, Users, Invoices, and so on. Those models will be used by the data stores. We can define as many models as we need inside our application.
A model may contain fields, validations, and relationships between other models. We can also set a proxy to persist and pull our data.
Note
As of version 5.x, field definitions can be optional unless you need conversion, validations, or set an implicit data type. For more information, take a look at http://docs.sencha.com/extjs/5.1/whats_new/5.0/whats_new.html#Models.
To create a model, let's write the following code:
Ext.define('Myapp.model.Client',{ extend:'Ext.data.Model', // step 1 idProperty:'clientId ', // step 2 fields:[// step 3 {name: 'clientId', type: 'int'}, {name: 'name' , type: 'string'}, {name: 'phone' , type: 'string'}, {name: 'website' , type: 'string'}, {name: 'status' , type: 'string'}, {name: 'clientSince', type: 'date', dateFormat:'Y-m-d H:i'} ] });
As you can notice, we are defining the model in the same way as we defined a class; in step one we extend from the Ext.data.Model
class, which is the one responsible for adding all the functionality to our models.
In the second step we are defining the property in our JSON response that will contain the ID of each record instance. In this case we are going to use the clientId
field, but if we don't define the clientId
configuration, the model will automatically use and generate a property called id
by default.
In the third step we define the fields for our model. The value of this property is an array; each element in the array is an object containing the configuration for each field. In this case we set the name and type of field, and the last field (date) contains a dateFormat
property.
Note
Depending on the type of field, we can add some specific properties. For example, to date
type field, we can add a dateFormat
property. To see more, check documentation on the Ext.data.field
branch.
The available types of data are as follows:
String
Integer
Float
(recommended for use when you are using decimal numbers)Boolean
Date
(remember to set thedateFormat
property to ensure correct date parse and interpretation of the date value)Auto
(this field implies that no conversion is made to the data received)
Once we have defined our model, we can create an HTML file. Let's import the Ext library and our Client
class file to test our model as follows:
var myclient = Ext.create('Myapp.model.Client',{ clientId:10001, name:'Acme corp', phone:'+52-01-55-4444-3210', website:'www.acmecorp.com', status:'Active', clientSince:'2010-01-01 14:35' }); console.log(myclient); console.log("My client's name is = " + myclient.data.name); console.log("My client's website is = " + myclient.data.name);
Using the create
method we can instantiate our model class, the second parameter is an object with the data that our model (virtual record) will contain. Now, we will be able to use the get
and set
methods to read and write any of the defined fields:
// GET METHODS var nameClient = myclient.get('name'); var websiteClient = myclient.get('website'); console.log("My client's info= " + nameClient + " - " + websiteClient); // SET Methods myclient.set('phone','+52-01-55-0001-8888'); // single value console.log("My client's new phone is = " + myclient.get('phone')); myclient.set({ //Multiple values name: 'Acme Corp of AMERICA LTD.', website:'www.acmecorp.net' }); console.log("My client's name changed to = " + myclient.get("name")); console.log("My client's website changed to = " + myclient.get("website") );
The previous code shows how to read and write our data. The set
method allows us to modify one field, or even many fields at the same time, by passing an object containing the new values.
If we inspect the invoice
instance, we'll find that all the information is held in a property called data
. We should always use the get
and set
methods to read and write our models, but if for some reason we need to have access to all the data in our model, we can use the data
object as follows:
//READ console.log("My client's name:" + myclient.data.name); console.log("My client's website:" + myclient.data.website); // Write myclient.data.name = "Acme Corp ASIA LTD."; myclient.data.website = "www.acmecorp.biz";
A nice alternative to this code and a better way for set
and get
data is:
//READ console.log("My client's name:" + myclient.get("name")); console.log("My client's website:" + myclient.get("website")); // Write myclient.set("name", "Acme Corp ASIA LTD. "); myclient.set("website", "www.acmecorp.biz");
We can read and write any fields in our model. However, setting a new value in this way is not good practice at all. The set
method performs some important tasks when setting the new value, such as marking our model as dirty, saving the previous value so that we can reject or accept the changes later, and some other important steps.
Mappings
When defining a field inside the model, we can define where the data will be taken for a field with the property mapping. Let's say it's a path, alternate name, which Ext JS will be used in order to populate the field (data) from the data received from the server such as a JSON file or a XML file. Let's have a look at the following JSON example:
{
"success" :"true",
"id":"id",
"records":[
{
"id": 10001,
"name": "Acme corp2",
"phone": "+52-01-55-4444-3210",
"x0001":"acme_file.pdf"
}
]
}
Here we can see on the JSON (or perhaps XML) example that the response comes a field with the name x0001
. It can happen on some responses that the name of the field has a special code (depending on the database or data design), but in our code, this field is the contract file of the customer. So, using the mapping property, we can populate the field, setting the mapping property for our field, like the following example:
Ext.define('Myapp.model.Client',{
extend: 'Ext.data.Model',
idProperty: 'clientId ',
fields:[
{name: 'clientId', type: 'int' },
{name: 'name' , type: 'string'},
{name: 'phone' , type: 'string'},
{name: 'contractFileName', type: 'string', mapping:'x0001'}
]
});
As you can see, we are defining the contractFileName
field, which will be using the x0001
data/field from the response; in our code there is no need to make reference for x0001
; we will just handle it in our code as contractFileName
. To see it in action, run the mapping_01.html
file from the example code. Open your console window and you will see something similar to the following screenshot:
At this moment there is no need to examine all of the code. Advancing in this chapter, you will understand all the code in this example. The purpose is for you to understand the mapping property.
Validators
A nice feature since version 4 of Ext JS is the ability to validate our data directly in the model. We can define rules for each field and run the validations when we need to. In order to define validations into our models we only need to define a property called validators
that contains an array of rules that will be executed when the validator engine runs. Let's add some validators to our previous model as follows:
Ext.define('Myapp.model.Client',{ extend:'Ext.data.Model', idProperty:'clientId ', fields:[ {name: 'clientId', type: 'int' }, {name: 'name' , type: 'string'}, {name: 'phone' , type: 'string'}, {name: 'website' , type: 'string'}, {name: 'status' , type: 'string'}, {name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'} ], validators:{ name:[ { type:'presence'} ], website:[ { type:'presence', allowEmpty:true}, { type:'length', min: 5, max:250 } ] } });
When adding validations, we use objects to define each rule. The type
property defines the type of rule that we want to add. There are a few types built within the library, such as inclusion, exclusion, presence, length, format, and e-mail; these are very common validations. We can also add new types of validations as needed.
When we define a rule, it is required to always use the type
properties, but some rules require the use of other extra parameters. The type
property represents a function within the Ext.data.validator
subclasses. We can read the documentation of this object to see what specific parameters are needed for each rule.
Let's make some new changes to our previous HTML file and save them with a new name:
//Step 1 var myclient = Ext.create('Myapp.model.Client',{ clientId : '10001', name : 'Acme corp', phone: '+52-01-55-4444-3210', website: 'www.acmecorp.com', status: 'Active', clientSince: '2010-01-01 14:35' }); if (myclient.isValid()){ //Step 2 console.log("myclient model is correct"); } console.log(myclient); console.log("My client's name is = " + myclient.data.name); console.log("My client's website is = " + myclient.data.website); // SET methods //Step 3 myclient.set('name',''); myclient.set('website',''); if (myclient.isValid()){//Step 4 console.log("myclient model is correct"); } else { //Step 5 console.log("myclient model has errors"); var errors = myclient.validate(); errors.each(function(error){ console.log(error.field,error.message); }); }
The steps are explained as follows:
- Step 1: We instantiated our
Client
model using some data. - Step 2: We executed the
isValid
method, which in this case returnstrue
because all the information is correct. - Step 3: We changed the model's values (name and website).
- Step 4: We executed the
isValid
method again to test the validators; in this case the result will befalse
. - Step 5: The
validate
method (myclient.validate();
) will return a collection with the failed validations. Then, the code will iterate this collection to make the output for the fields and error messages.
Note
The collection returned by the validate method is an instance of the class Ext.data.ErrorCollection
, which extends from Ext.util.MixedCollection
. Therefore, we can use each method to iterate in a simple way.
When we execute the previous example we will see in the console some messages according to the flow of the code. Initially, it will display a message saying that validations were successful. After changing the values, the messages will begin displaying the errors on the name and website fields. Take a look at the following screenshot from the console window/tool:
Custom field types
Usually we need to use some types of fields over and over, across different data models, in our application. On Ext 4, there was the practice to create custom validators. On version 5, it's recommended to create custom field types instead of custom validations. Using the following code, we will create a custom field:
Ext.define('Myapp.fields.Status',{ extend: 'Ext.data.field.String', //Step 1 alias: 'data.field.status',//Step 2 validators: {//Step 3 type: 'inclusion', list: [ 'Active', 'Inactive'], message: 'Is not a valid status value, please select the proper options[Active, Inactive]' } });
The steps are explained as follows:
- We extend the new field based on
Ext.data.field.String
. - We define the alias this field will have. It's recommended that the alias does not repeat or override an existent name from the
Ext.data.field
subclasses. - We set the validator(s) the field will have.
Let's make some changes to our Client model:
Ext.define('Myapp.model.Client',{
extend:'Ext.data.Model',
idProperty:'clientId ',
fields:[
{name: 'clientId', type: 'int' },
{name: 'name' , type: 'string'},
{name: 'phone' , type: 'string'},
{name: 'website' , type: 'string'},
{name: 'status' , type: 'status'}, //Using custom field
{name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'}
],
validators:{
...
}
});
On the model, we made the change {name: 'status', type: 'status'}
using the alias we set on the custom field (alias: 'data.field.status'
). Now, let's create the code for test:
var myclient = Ext.create('Myapp.model.Client',{
clientId: '10001',
name: 'Acme corp',
phone: '+52-01-55-4444-3210',
website: 'www.acmecorp.com',
status: 'Active',
clientSince: '2010-01-01 14:35'
});
if(myclient.isValid()){
console.log("myclient model is correct");
}
// SET methods
myclient.set('status','No longer client');
if(myclient.isValid()){
console.log("myclient model is correct");
} else {
console.log("myclient model has errors");
var errors = myclient.validate();
errors.each(function(error){
console.log(error.field,error.message);
});
}
Tip
If you get confused on how to prepare the code, please check the customfields_01.html
and customfields_01.js
files in the chapter_04
folder from the source code.
After we run our HTML file, we will get the following output on the console screen:
As you can see, myclient.set('status','No longer client');
tries to use a value not defined for the acceptable values defined on the custom field Myapp.fields.Status
, so this will get us a validation error for the model.
Using this technique, we can create and reuse many custom field types across many models in our application. Notice that we can extend from the following classes: Ext.data.field.Field
, Ext.data.field.Boolean
, Ext.data.field.Date
, Ext.data.field.Integer
, Ext.data.field.Number
, and Ext.data.field.String
.
As we talked in Chapter 2, The Core Concepts, about extending classes, it's important that you choose which class to extend according to your needs, to avoid using unnecessary extra code if you don't need it.
Relationships
We can create relationships between models to relate our data. For example, a Client has many contact employees, Services, branches, and many more things. Each item is an object with properties. For example:
- Employees for contact (name, title, gender, email, phone, cell phone, and so on)
- Services (service ID, service name, service price, branch where service is provided)
Ext JS 5 extends support to create one-to-many, one-to-one, and many-to-many associations in a very easy way.
One-to-many associations
One-to-many associations are created in the following way:
Ext.define('Myapp.model.Client',{ extend:'Ext.data.Model', // step 1 requires: ['Myapp.model.Employee'], idProperty:'id ', fields:[.... ], hasMany:{ model:'Myapp.model.Employee', name:'employees', associationKey: 'employees' } });
Using the hasMany
property, we can define the association. In this example, we're assigning an array of objects because we can create as many associations as we need. Each object contains a model
property, which defines the model with the Client
class that will be related.
Additionally, we may define the name of the function that will be created in our Client
class to get the items related. In this case, we used employees
; if we don't define any name, Ext JS will pluralize (add an "s") the name of the child model.
Now we need to create the Employee
class. Let's create a new file located at appcode/model/Employee.js
:
Ext.define('Myapp.model.Employee',{ extend:'Ext.data.Model', idProperty:'id ', fields:[ {name: 'id', type: 'int' }, {name: 'clientid' , type: 'int'}, {name: 'name' , type: 'string'}, {name: 'phone' , type: 'string'}, {name: 'email' , type: 'string'}, {name: 'gender' , type: 'string'} ] });
There's nothing new in the previous code, just a regular model with a few fields describing an item of an employee. In order to test our relationship, we need to create an HTML file importing the Ext JS library and our two models. Then, we can test our models as follows:
var myclient = Ext.create('Myapp.model.ClientWithContacts',{ id: 10001, name: 'Acme corp', phone: '+52-01-55-4444-3210', website: 'www.acmecorp.com', status: 'Active', clientSince: '2010-01-01 14:35' }); //Step 2 myclient.employees().add( { id:101, clientId:10001, name:'Juan Perez', phone:'+52-05-2222-333', email:'juan@test.com', gender:'male'}, { id:102, clientId:10001, name:'Sonia Sanchez', phone:'+52-05-1111-444', email:'sonia@test.com',gender:'female'} ); //Step 3 myclient.employees().each(function(record){ console.log(record.get('name') + ' - ' + record.get('email') ); });
The steps are explained as follows:
- We are creating the
Client
class with some data. - We are executing the employee method. When we define our relationship, we set the name of this method using the
name
property in the association configuration. This method returns anExt.data.Store
instance; this class is a collection to manage models in an easy way. We also add two objects to the collection using theadd
method; each object contains the data for theEmployee
model. - We are iterating the items collection from our
Client
model. Using theget
method, we print the description for eachEmployee
model to the console; in this case, we have only two models in our store.
One-to-one associations
To create a one-to-one association, we will create a new class which has a one-to-one relation with a client or customer:
Ext.define('Myapp.model.Contract',{
extend:'Ext.data.Model',
idProperty:'id ',
fields:[
{name: 'id', type: 'int' },
{name: 'contractId', type: 'string'},
{name: 'documentType', type: 'string'}
]
});
As you can see this is a plain model. Now on the Customer class we will define it as follows:
Ext.define('Myapp.model.Customer',{
extend:'Ext.data.Model',
requires: ['Myapp.model.Contract'],
idProperty:'id ',
fields:[
{name: 'id', type: 'int'},
{name: 'name' , type: 'string'},
{name: 'phone' , type: 'string'},
{name: 'website' , type: 'string'},
{name: 'status' , type: 'string'},
{name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'},
{name: 'contractInfo' , reference: 'Contract', unique:true}
]
});
If you notice, we added a new field called contractInfo,
but in this case, instead of the property type
we used the property reference. This property will point to the entity Contract.
As with the previous example, let's modify the JS code, as shown in the following:
var myclient = Ext.create('Myapp.model.Customer',{ id: 10001, name: 'Acme corp', phone: '+52-01-55-4444-3210', website: 'www.acmecorp.com', status: 'Active', clientSince: '2010-01-01 14:35', contractInfo:{ id:444, contractId:'ct-001-444', documentType:'PDF' } });
You will notice that this time we set the data directly on the model configuration on the code contractInfo:{...}
. So, now if you check on the console, it has to appear something like the following screenshot:
As you can see, contractInfo
is an object inside the data which has the same fields defined on the Contract
model. Now, if you don't define the contractInfo
or some other property from the contractInfo
object, then these properties will not be added to the model (record). As shown in the next example, contractInfo
was not defined, and you can see the result in the following screenshot (after Second test):