数据包
数据包是加载和保存你的程序中所有数据的工具。它包含众多的类,但其中有三个比其它所有类都重要。
这三个类是:
Ext.data.Model(http://docs.sencha.com/extjs/6.5.0/modern/Ext.data.Model.html)
Store(http://docs.sencha.com/extjs/6.5.0/modern/Ext.data.Store.html)
Ext.data.proxy.Proxy(http://docs.sencha.com/extjs/6.5.0/modern/Ext.data.proxy.Proxy.html)
上面的类几乎被所有的程序使用。它们被许多的卫星类所支持。
模型
数据包的核心结构是Ext.data.Model。一个模型代表了程序中的一个实体。例如,一个电子商务app或许会有用户模型,产品模型和订单模型。拿最简单的来说,一个模型定义了一系列字段和相关的业务逻辑。
让我们来看一下模型的几个主要部分:
*字段
*代理
*数据验证
*数据关联
创建模型
通常从一个通用的基类来开始定义你的模型是最好的做法。这个基类让你很容易地在一个地方为你所有的模型配置其特性。这也是配置模式(schema)的一个好地方。模式是你程序中所有模型的管理器。那么,现在我们将集中讲述它的两个最有用的配置选项。
Ext.define('MyApp.model.Base', {
extend: 'Ext.data.Model',
fields: [{
name: 'id',
type: 'int'
}],
schema: {
namespace: 'MyApp.model', // generate auto entityName
proxy: { // Ext.util.ObjectTemplate
type: 'ajax',
url: '{entityName}.json',
reader: {
type: 'json',
rootProperty: '{entityName:lowercase}'
}
}
}
});
你的模型基类的对应内容,尤其是字段,在不同的程序里是不同的。
代理
代理用来为模型和(数据)仓库(store)处理和保存模型数据。有两种类型的代理:客户端和服务端。
代理可以直接在模型基类的模式中定义(如上代码所示)。
客户端代理
客户端代理的示例包括内存和本地存储(Local Storage),这两种代理使用了H5的本地存储(localstorage)特性。虽然旧版本的浏览器不支持这些新的H5 API,但是它们如此有用以至于很多程序从它们受益颇多。
服务端代理
服务端代理处理远程服务端数据的调用。这种类型的代理示例包括AJAX,JSONP和REST。
模式
一个模式是一组彼此间有关联的实体的集合。当一个模型中声明了一个模式(schema)配置项,那么继承自此模型的模型都会有此模式。在上面的示例中,模式被配置了两个值(namespace和proxy),此配置会创建在这个模式下的所有模板的默认配置。
第一个配置是命名空间(namespace)。通过声明这个命名空间,所有的模型都会有一个短名称来调用'entityName'。这个短名称主要用处是定义不同模型之间的关联,我们将在接下来的示例里看到。
示例中的模式同时还定义了'proxy'配置选项。这是一个对象模板,跟基于Ext.XTemplate的文本模板类似。不同点在于当你给对象模板数据时,它会生成对象。在这个示例中,数据将为这些没有显式定义代理的模型自动定义代理。
这非常有用因为每个模型的实例需要以相同的方式加载它的数据,仅仅数据有些不同而已。这避免了每个模型的代理的重复定义。
我们在URL里声明的User.json,url: '{entityName}.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"
}
]
}
仓储
模型典型的用法是与仓储结合使用,仓储基本上是由记录的集合(继承自模型的类的一组实例)组成。创建一个仓储并加载数据的是很简单的:
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记录。当仓储的回调事件被触发时(加载完成时)这些记录会被打印。
内联数据
仓储也可以加载内联数据。在内部,仓储将每个我们作为数据作入的对象转化为与模型类型对应的记录集: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"
}]
});
排序和分组
仓储能够在本地和远程执行排序,过滤和分组。
new Ext.data.Store({
model: 'MyApp.model.User',
sorters: ['name','id'],
filters: {
property: 'name',
value : 'Philip J. Fry'
}
});
在此示例中,数据将首先按name进行排序,然后按id。数据将会被过滤为只有名字为 'Philip J. Fry'的用户。通过仓储的API,在任何时候更改这些项的属性是非常容易的。
联合
模型之间可以通过联合API联接在一起。大多数的程序会处理相互之间关联的不同模型。一个博客写作程序或许有用户和博文模型。每个用户创建博文。所以在这种情况下,一个用户能创建多篇博客,但一个篇博文只能由一个用户创建。这即是被称为多对一的关系模型。我们可以用如下所示来表示这种关系: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',
reference: 'User', // the entityName for MyApp.model.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.posts()函数。访问user.posts()函数会返回一个仓储,此仓储很显然是由博文模型所配置。
关联不仅对于加载数据非常有用,他们对创建新的记录也十分有帮助。user.posts().add({
userId: 1,
title: 'Post 10'
});
user.posts().sync();
这个操作实例化了一篇新的博文,此博文会自动在userId字段赋上用户的id值。访问sync()函数通过它的代理(最终由模式的代理配置定义)保存一篇新的博文。这是一个异步的操作,如果你想在操作结束后得到通知,你可以传一个回调函数。
关联的相反操作也会在博文模型上生成新的方法。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(有时叫做外键)为100的用户,然后保存博文模型。通常,回调函数可以被传入,当保存操作完成时回调会被触发-不管保存是成功还是失败。
加载嵌套数据
当关联被定义时,在一个加载一条记录的请求行为中,会导致加载相关联的记录。例如,考虑如下服务端的响应:{
"success": true,
"user": [{
"id": 1,
"name": "Philip J. Fry",
"posts": [{
"title": "Post 1"
},{
"title": "Post 2"
},{
"title": "Post 3"
}]
}]
}
框架能够自动解析如上所示的嵌套数据为一个请求,而不是用一个请求来请求用户数据,另一个请求来请求博文数据 ,我们可以在一个服务响应中返回所有的数据。
验证
模型也为数据验证提供了很多的支持。为了演示此功能,我们将创建我们上面用到的示例。首先,让我们添加一些验证到用户模型上:Ext.define('MyApp.model.User', {
extend: 'Ext.data.Model',
fields: ...,
validators: {
name: [
'presence',
{ type: 'length', min: 7 },
{ type: 'exclusion', list: ['Bender'] }
]
}
});
数据验证器被定义为一个以字段名称为key的对象,这个对象会被映射到相应的合法字段的规则定义上。这些规则以一个验证器配置对象的形式表示,或者以这些配置对象的数组来表示。在我们示例中的验证器验证了name字段,这个字段必须最少7个字符的长度,而且它的值不能是'Bender'。
有些验证是可选配置-例如长度验证可以有最长和最短的属性,格式化可以有一个匹配器等等。在Ext JS时有5个验证被内置其中,添加自定义规则也是容易的整改。
首先,让我们来做正确的规则建立:
* Presence - 确保该字段有值。数值0是合法值,但空字符串不行。
* Length - 确保字符串的长度在最小值和最大值之间。最大和最小配置选项都是可选的。
* Format - 确保字符串与一个正则表达式匹配。在上面的示例中确保年龄字段仅包含数字。
* Inclusion - 确保值是在定制的一套值当中的一个(例如性别一定是男或女而不能是别的)。
* 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字段上的验证错误,”长度必须大于7“。
因此,让我们提供一个长度大于7个字符的用户名。
newUser.set('name', 'Bender BendingRodriguez');
errors = newUser.getValidation();
这条用户记录现在符合了所有的验证规则。这条记录产生了,它包含超过7个字符,名称也跟'Bender'不匹配。
newUser.isValid()将返回true。当我们访问getValidation()方法 时,验证记录将被更新,然后不会被污染,它所有字段的值都将被设置为true。