数据(Data)
所谓数据包,它是用来装载和保存程序中的数据,并且组成41个类。其中Model、Store和Ext.data.proxy.Proxy是最重要的三个;它们几乎会在每一个应用中都被应用到,并且被一系列的卫星类(satellite classes)支持:
模型(Models)和存储(Stores)
数据包的核心就是Ext.data.Model。Model用来表示程序中的数据类型,例如:一个电子商务系统会有Users模型、Products模型和Orders模型等。简单来说,模型(Model)就是一系列字段和字段值的集合。我们来看看比较重要的四种模型—Fields、Proxies、Associations和Validations。
来看一下如何创建一个新的Model:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id', type: 'int' },
{ name: 'name', type: 'string' }
]
});
Model通常结合Store一起使用,总体来说,Store就是一系列实例Model的集合。建立一个Store并且加载数据都比较简单:
Ext.create('Ext.data.Store', {
model: 'User',
proxy: {
type: 'ajax',
url : 'users.json',
reader: 'json'
},
autoLoad: true
});
以上配置是通过Ajax代理(Ajax Proxy)来获取数据的,这里需要指定数据加载地址(url)和Reader(用来解析数据)。本例中,由于服务器返回的是JSON,所以reader配置为“json”(Json Reader)来读取返回的数据。Store会自动从user.json这个地址加载数据,从user.json返回的JSON数据格式应该如下:
{
success: true,
users: [
{ id: 1, name: 'Ed' },
{ id: 2, name: 'Tommy' }
]
}
点击Simple Store亲手试一试。
内联数据(Inline data)
Store也可以加载内联数据。其实在本质上,Store是将我们传递给它的数据最终都转换成了Model实例:
Ext.create('Ext.data.Store', {
model: 'User',
data: [
{ firstName: 'Ed', lastName: 'Spencer' },
{ firstName: 'Tommy', lastName: 'Maintz' },
{ firstName: 'Aaron', lastName: 'Conran' },
{ firstName: 'Jamie', lastName: 'Avins' }
]
});
排序和分组(Sorting and Grouping)
Store可以在本地执行排序、过滤和分组操作,当然也支持远程的排序、过滤和分组:
Ext.create('Ext.data.Store', {
model: 'User',
sorters: ['name', 'id'],
filters: {
property: 'name',
value : 'Ed'
},
groupField: 'age',
groupDir: 'DESC'
});
在我们刚刚创建的store中,数据将会首先按照name进行排序,然后按照id进行排序;过滤器会将name值为Ed的User给过滤出来;然后数据还会按照age进行降序分组。详情请参见API文档
代理(Proxies)
Store通过代理(Proxy)来加载和保存数据模型。代理分为两种:客户端代理和服务端代理。像浏览器内存储和HTML本地化都是客户端代理的例子。服务端代理处理远程服务器的数据封装,例如:Ajax、JsonP和Reset。
代理可以直接定义在一个模型里面:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['id', 'name', 'age', 'gender'],
proxy: {
type: 'rest',
url : 'data/users',
reader: {
type: 'json',
root: 'users'
}
}
});
// 使用用户模型代理
Ext.create('Ext.data.Store', {
model: 'User'
});
这给我们带来了两点好处。首先,极有可能每个使用到user模型的Store都会以相同的方式来加载数据,这样就避免了为每个Store重复定义代理(Proxy)。其次,我们可以无需通过Store进行加载和保存数据。
// 获取一个User类的引用
var User = Ext.ModelMgr.getModel('User');
var ed = Ext.create('User', {
name: 'Ed Spencer',
age : 25
});
// 我们可以直接保存Ed,而不是必须首先将其添加到Store,这是由于我们配置了RestProxy,它会自动向/users这个地址发送POST请求
ed.save({
success: function(ed) {
console.log("Saved Ed! His ID is "+ ed.getId());
}
});
// 加载User 1并执行相关操作 (向 /users/1发起GET请求)
User.load(1, {
success: function(user) {
console.log("Loaded user 1: " + user.get('name'));
}
});
Proxy也受益于HTML5的新功能-LocalStorage和SessionStorage。尽管老版本的浏览器不支持HTML5的API,但它们确实是十分有用的,许多应用将极大的受益于他们。
关联(Associations)
多个Model可以通过Assocaition API联系在一起。绝大部门的应用都会处理许多不同的Model,而这些Model通常都是有联系的。例如一个博客应用程序中会有User、Post和Comment这些Model。用户可以发表文章,文章也会收到评论。我们可以向这样表示这些关系:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['id', 'name'],
proxy: {
type: 'rest',
url : 'data/users',
reader: {
type: 'json',
root: 'users'
}
},
hasMany: 'Post' // shorthand for { model: 'Post', name: 'posts' }
});
Ext.define('Post', {
extend: 'Ext.data.Model',
fields: ['id', 'user_id', 'title', 'body'],
proxy: {
type: 'rest',
url : 'data/posts',
reader: {
type: 'json',
root: 'posts'
}
},
belongsTo: 'User',
hasMany: { model: 'Comment', name: 'comments' }
});
Ext.define('Comment', {
extend: 'Ext.data.Model',
fields: ['id', 'post_id', 'name', 'message'],
belongsTo: 'Post'
});
可以很容易地表达您的应用程序中的不同Models之间的丰富关系。每个Model可以与其他Model有任意数量的关联(Associations),Model可以按照任何的顺序定义。一旦我们有一个Model的实例,我们可以很容易地遍历相关的数据 - 例如,如果我们想对指定用户的每篇文章的所有评论作记录,就可以这样做:
// Loads User with ID 1 and related posts and comments using User's Proxy
User.load(1, {
success: function(user) {
console.log("User: " + user.get('name'));
user.posts().each(function(post) {
console.log("Comments for post: " + post.get('title'));
post.comments().each(function(comment) {
console.log(comment.get('message'));
});
});
}
});
在我们上面创建的每一个hasMany关联都会产生一个新的函数添加到Model中。我们定义User model hasMany Posts,会新增user.posts()函数供我们在上面的代码片段使用。调用guser.posts()返回Post model的 Store配置。反过来,Post model获取comments() 函数是因为我们设置的Comments的hasMany关联。
关联不仅仅用于加载数据 – 创建新纪录的时候也非常有用:
user.posts().add({
title: 'Ext JS 4.0 MVC Architecture',
body: 'It\'s a great Idea to structure your Ext JS Applications using the built in MVC Architecture...'
});
user.posts().sync();
在这里,我们实例化一个新的Post(文章),user_id字段会自动填充上用户ID。调用sync()通过其配置的Proxy保存新Post - 这又是一个异步操作。如果你想操作完成时发送通知,也可以添加一个回调函数(callback)。
belongsTo关联也可以生成新的Model方法,可以这样使用这些方法:
// get the user reference from the post's belongsTo association
post.getUser(function(user) {
console.log('Just got the user reference from the post: ' + user.get('name'))
});
// try to change the post's user
post.setUser(100, {
callback: function(product, operation) {
if (operation.wasSuccessful()) {
console.log('Post\'s user was updated');
} else {
console.log('Post\'s user could not be updated');
}
}
});
再次,加载函数(getUser)是异步的,需要一个回调函数来获取用户实例。setUser方法只需更新foreign_key(这里是user_id)为100并保存Post model。通常情况下,无论成功与否,都可以在完成保存操作的时候触发回调函数
加载嵌套数据(Loading Nested Data)
你也许会奇怪,为什么我们发送一个成功函数到User.load的调用,但在访问用户的文章和评论时却没有这样做。这是因为上面的例子中假定,当我们提出请求获取一个用户,服务器返回的用户数据会嵌套所有的文章和评论。通过设立像上面那样的关联,框架可以自动解析出嵌套在单一请求中的数据。框架不是先获取用户数据,然后调用另一个请求获取文章数据,然后再发出更多的请求获取评论,而是在一个服务器响应里返回所有数据:
{
success: true,
users: [
{
id: 1,
name: 'Ed',
age: 25,
gender: 'male',
posts: [
{
id : 12,
title: 'All about data in Ext JS 4',
body : 'One areas that has seen the most improvement...',
comments: [
{
id: 123,
name: 'S Jobs',
message: 'One more thing'
}
]
}
]
}
]
}
数据全部是框架自动分析的。可以很容易地配置Model的代理来加载任何地方的数据,用他们的Reader来处理几乎任何返回格式(的数据)。至于Ext JS3,整个框架的许多组件都用到了Model和Store,如Grids、Trees 和Forms。
当然,可以用非嵌套的方式来加载数据。如果你需要“延迟加载(只有被需要的时候才会加载)”,这就会有用了。我们来像以前那样加载User数据,但我们假设响应只包含User数据而没有任何关联的Post。然后,我们将在回调中添加一个调用user.posts().load()的方法,以获得相关的Post数据。
// Loads User with ID 1 User's Proxy
User.load(1, {
success: function(user) {
console.log("User: " + user.get('name'));
// Loads posts for user 1 using Post's Proxy
user.posts().load({
callback: function(posts, operation) {
Ext.each(posts, function(post) {
console.log("Comments for post: " + post.get('title'));
post.comments().each(function(comment) {
console.log(comment.get('message'));
});
});
}
});
}
});
验证(Validations)
Ext JS 4的数据验证变得丰富了许多。为了证明这一点,我们要用上述关联的例子。首先,让我们为User模型添加一些验证(validations):
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ...,
validations: [
{type: 'presence', name: 'name'},
{type: 'length', name: 'name', min: 5},
{type: 'format', name: 'age', matcher: /\d+/},
{type: 'inclusion', name: 'gender', list: ['male', 'female']},
{type: 'exclusion', name: 'name', list: ['admin']}
],
proxy: ...
});
验证的定义与字段的定义遵循相同的格式。在每一种情况下,我们指定一个验证的字段和类型。此例中,我们希望name字段必须存在,并且长度至少为5;age字段是一个数字,gender(性别)字段要么是“male”要么是“female”,username可以是除了“admin”的任何东西。某些验证还会采取额外的可选配置 - 例如长度验证可以采取Min和Max属性,格式可以用正则表达式等等。Ext JS的内置验证总共有5个,添加自定义的规则也很容易。首先,让我们看看内置验证:
- presence 确保该字段有一个值。计数0有效,但是空字符串无效。
- length 确保一个字符串在最小(min)和最大(max)长度之间。这两种约束(min和max)是可选的.
- format 确保一个字符串匹配一个正则表达式格式。在上面的例子中,我们确保年龄字段是由数字组成的。
- inclusion 确保值是特定的值(例如确保性别是男性或女性)。
- exclusion 确保不是某值(例如列入黑名单的username,像是“admin”)。
到目前位置,我们已经掌握了不同验证的用处,让我们尝试在用户实例中使用它们。我们将创建一个用户,并针对它运行验证,并指出任何错误:
// now lets try to create a new user with as many validation errors as we can
var newUser = Ext.create('User', {
name: 'admin',
age: 'twenty-nine',
gender: 'not a valid gender'
});
// run some validation on the new user we just created
var errors = newUser.validate();
console.log('Is User valid?', errors.isValid()); //returns 'false' as there were validation errors
console.log('All Errors:', errors.items); //returns the array of all errors found on this model instance
console.log('Age Errors:', errors.getByField('age')); //returns the errors for the age field
这里的主要是validate()方法,它运行所有配置过的验证,并返回一个错误对象(Errors object)。这个简单的对象仅仅是一个被我们发现的所有错误的集合,还有一些方便的方法:比如isValid(),如果任何字段都没有错误就返回true; getByField(),它返回一个给定字段中的所有错误。