数据组件包-Data Package
数据组件包是用于管理应用程序加载和存储数据,包括许多的类,最重要的是下列的三个类:
- Ext.data.Model
- Store
- Ext.data.proxy.Proxy
绝大多数的应用都会使用上述的三个类,并且有很多类为这三个类提供支持。
实体模型-Models
数据组件包的核心是实体模型Ext.data.Model
。一个模型对应应用的一个实体,例如:一个电商的应用可能包含 Users、Products 和 Orders 等实体。最简单的情形,每个Model
定义一系列字段(field
) ,这些字段与业务逻辑相关。
实体模型的主要部件:
- 字段(Fields)
- 代理(Proxies)
- 验证(Validations)
- 关联(Associations)
创建实体模型-Creating a Model
定义Model
的最佳方式是从一个共通的基类扩展。该基类可以集中配置一些参数,同时也可以集中配置架构(schema)。架构是所有Model
的管理者。现在聚焦一下两个最有用的配置选项:
Ext.define('MyApp.model.Base', {
extend: 'Ext.data.Model',
fields: [{
name: 'id',
type: 'int'
}],
schema: {
// generate auto entityName
namespace: 'MyApp.model',
// Ext.util.ObjectTemplate
proxy: {
type: 'ajax',
url: '{entityName}.json',
reader: {
type: 'json',
rootProperty: '{entityName:lowercase}'
}
}
}
});
代理-Proxies
Model
和Store
使用代理加载和存储数据,有两种代理:客户端(Client)和服务器端(Server)。代理可以直接定义在 Model 基类的 schema 对象中(参见上述示例)。
客户端代理-Client Proxy
客户端代理的示例包括:Memory
和 Local Storage
(HTML5 localstorage 特性)。尽管较早的浏览器不支持 HTML5 新的 APIs,但是如果能应用这些特性,还是会给应用带来巨大的益处。
服务器端代理-Server Proxy
服务器端代理封装数据并且发送到远程服务器。例如:AJAX、JSONP 和 REST。
架构-Schema
架构是一系列相关实体的集合。当一个实体设置了 "schema" 配置,那么所有继承它的实体都继承该架构。在上述的示例中,架构设置了两个属性,这两个属性是该架构下所有实体的缺省设置。
第一个是命名空间("namespace")。通过设置命名空间,所有实体都可以获得一个简称("entityName")。这个简称在定义实体关系时非常有用。
第二个是代理("proxy")。这是一个基于Ext.XTemplate
的对象模板,类似于文本模板。 不同之处在于,对象模板在获得数据后生成一个对象。在这个示例中,对于未显式定义代理的实体,将根据数据自动生成代理配置的定义。
由于实体加载数据的方式非常相似,只是加载的内容不同,所以这种方法非常有效,可以避免重复定义每个实体的代理。
在 URL(url: '{entityName}.json'
)中配置的 User.json
应该返回 JSON 字符串。例如:
{
"success": "true",
"user": [
{
"id": 1,
"name": "Philip J. Fry"
},
{
"id": 2,
"name": "Hubert Farnsworth"
},
{
"id": 3,
"name": "Turanga Leela"
},
{
"id": 4,
"name": "Amy Wong"
}
]
}
Stores
通常情况下,Models 与 Store 结合使用,Store 是 Model 数据记录的集合。创建一个 Store ,加载数据,实现起来非常简单:
var store = new Ext.data.Store ({
model: 'MyApp.model.User'
});
store.load({
callback:function(){
var first_name = this.first().get('name');
console.log(first_name);
}
});
我们手动加载了 MyApp.model.User
的数据,加载成功后,输出第一条记录的name
字段值。
内联数据-Inline data
Stores 也可以加载内联数据。Store 内部将加载的数据转换为对应实体类型的记录:
new Ext.data.Store({
model: 'MyApp.model.User',
data: [{
id: 1,
name: "Philip J. Fry"
},{
id: 2,
name: "Hubert Farnsworth"
},{
id: 3,
name: "Turanga Leela"
},{
id: 4,
name: "Amy Wong"
}]
});
排序和分组-Sorting and Grouping
Stores 可以进行排序(sorting)、筛选(filtering)和分组(grouping),分为本地(locally)和远程(remotely)两种方式。
new Ext.data.Store({
model: 'MyApp.model.User',
sorters: ['name','id'],
filters: {
property: 'name',
value : 'Philip J. Fry'
}
});
上述示例中,数据先按name
,再按id
进行排序。数据将按 name
等于Philip J. Fry
条件进行筛选。可以通过 Store 的 API 设置这些信息。
关联-Associations
实体可以通过关联 API 实现链接。大多数的应用需要处理很多不同的实体,而且这些实体之间经常具有一定的关联关系。一个博客的应用可能包括 User 和 Post 实体,每个 User 发表多个 Post。在这个示例中,一个用户有多个博文,每个博文只有一个用户。这就是通常所说的多对一的关系(ManyToOne)。我们可以这样表示它们之间的关系:
Ext.define('MyApp.model.User', {
extend: 'MyApp.model.Base',
fields: [{
name: 'name',
type: 'string'
}]
});
Ext.define('MyApp.model.Post', {
extend: 'MyApp.model.Base',
fields: [{
name: 'userId',
// the entityName for MyApp.model.User
reference: 'User',
type: 'int'
}, {
name: 'title',
type: 'string'
}]
});
在应用中,很容易在不同的实体之间建立丰富的关联关系。每个实体可以与其他实体有多个关系。此外,实体可以任意的顺序定义。一旦你获得一个实体的数据,便可以获取其关联的数据。例如:你想获取一个用户的所有博文,可以这样处理:
// Loads User with ID 1 and related posts and comments
// using User's Proxy
MyApp.model.User.load(1, {
callback: function(user) {
console.log('User: ' + user.get('name'));
user.posts(function(posts){
posts.each(function(post) {
console.log('Post: ' + post.get('title'));
});
});
}
});
上述的关联关系,会自动为实体添加一个方法。一个 User 有多个 Post,将为 User 实体添加 posts()
方法,调用 user.posts()
方法将返回配置为 Post 实体模型的 Store?。
关联不仅有助于加载数据,而且对于创建一条新的数据记录也很有帮助:
user.posts().add({
userId: 1,
title: 'Post 10'
});
user.posts().sync();
上述示例新建一个 Post,自动设置关联 User 的 id
属性为 userId
。 调用 sync()
方法将通过 schema 定义的代理保存新建的 Post 数据 。这是异步操作,如果想要获取操作是否完成的通知信息,可以传递一个回调函数。
反过来,该关联关系也为 Post 实体自动添加了方法:
MyApp.model.Post.load(1, {
callback: function(post) {
post.getUser(function(user) {
console.log('Got user from post: ' + user.get('name'));
});
}
});
MyApp.model.Post.load(2, {
callback: function(post) {
post.setUser(100);
}
});
加载方法 getUser()
是异步操作, 如果要获得由用户实例需要传递回调函数。setUser()
方法只是简单地设置 userId
(有时称作外键)并保存 Post 实体数据。与其他方法一样,也可以传递一个回调函数,不论保存操作是否成功都将触发该函数。
加载嵌套数据-Loading Nested Data
当定义了实体间的关联关系,可以通过一次请求即可加载实体及其关联实体的数据。服务器端的响应可以像下述示例一样:
{
"success": true,
"user": [{
"id": 1,
"name": "Philip J. Fry",
"posts": [{
"title": "Post 1"
},{
"title": "Post 2"
},{
"title": "Post 3"
}]
}]
}
对于上述的响应结果,框架能够自动解析嵌套的数据。不必针对每个用户再单独请求Post数据,可以通过一次请求返回所有的数据。
验证-Validations
实体模型提供一套数据验证的支持。结合上述的示例演示一下。首先为 User 实体添加一些验证:
Ext.define('MyApp.model.User', {
extend: 'Ext.data.Model',
fields: ...,
validators: {
name: [
'presence',
{ type: 'length', min: 7 },
{ type: 'exclusion', list: ['Bender'] }
]
}
});
实体验证是以一个对象的方式定义的,字段的名称是要验证字段的规则名称。验证规则是以规则对象或规则数组的方式定义。示例演示的是name
字段的验证规则,至少7个字符,并且不能是Bender
。一些验证规则有多个配置选项,例如:长度验证规则可以配置min
和max
选项,格式化可以配置matcher
等。Ext JS 有5个内置的验证规则,也可以创建自定义的验证规则。
内置的验证规则如下:
- Presence - 字段值不可以为空。
0
是有效值,空字符串不是。 - Length - 字段值长度介于
min
和max
之间。两个约束是可选配置。 - Format - 字符串必须符合一个正则表达式
regular expression
。在上述示例中可以定义一个规则,验证age
字段只允许包含数字。 - Inclusion - 字段值必须是给定的可选值,例如:性别必须是
male
或者female
。 - Exclusion - 字段值不能是给定范围的值,例如:用户名不能是黑名单内的值('admin')。
我们基本掌握了不同的验证规则,来试试如何使用它。创建一个用户,然后进行验证:
// now lets try to create a new user with as many validation
// errors as we can
var newUser = new MyApp.model.User({
id: 10,
name: 'Bender'
});
// run some validation on the new user we just created
console.log('Is User valid?', newUser.isValid());
//returns 'false' as there were validation errors
var errors = newUser.getValidation(),
error = errors.get('name');
console.log("Error is: " + error);
这里的重点功能是 getValidation()
,其执行所有配置的验证规则,返回一个对象。 对于不符合验证规则的字段返回不符合的第一个规则的错误信息,对于符合规则的字段返回true
。该对象是延迟创建的对象,只有在请求该对象时才更新。
在上述示例中,name
字段的第一个错误信息是:"Length must be greater than 7"。再试一下名称字段大于7个字符的情况:
newUser.set('name', 'Bender Bending Rodriguez');
errors = newUser.getValidation();
这时用户记录符合了所有的验证规则。newUser.isValid()
将返回 true
。当调用getValidation()
方法时,验证结果对象将被更新,所有字段的验证结果都是 true
。