数据包(data package)是用来加载和保存你应用程序中的数据的东西,包含41个类,但是其中有三个类比所有其他类更加重要——Model,Store和Ext.data.proxy.Proxy。这些类被几乎所有的应用程序用到了,并且受到了许多其他卫星类(satellite class)的支持。
Models 和 Stores
数据包的中心是 Ext.data.Model。一个Model在一个应用程序中展现一些类型的数据 —— 例如一个commerce应用也许会有用户、产品和订单的模型。把它看得简单点一个模型就是属性域和它们的数据的集合。下面我们来看看Model中四个重要的部分 —— 属性与Field,代理Proxy,关联Association 和 验证Validation。
现在让我们来看看如何创建一个模型:
Ext.define('User', { extend: 'Ext.data.Model', fields: [ { name: 'id', type: 'int' }, { name: 'name', type: 'string' } ] });
模型典型的被使用于一个 存储(Store),它基本上就是一个模型实体的集合。设置一个存储并且加载它的数据是简单那的:
Ext.create('Ext.data.Store', { model: 'User', proxy: { type: 'ajax', url : 'users.json', reader: 'json' }, autoLoad: true });
我们使用了一个Ajax代理来设置我们的存储,告诉它数据加载的url来源还有解码数据的阅读器(Reader)。在这种情况下,服务器会返回JSON,因此我们设置了一个Json阅读器去读取回应(response)。存储自动加载users.json这个url中的User模型实体的集合。users.json的url应该返回一个像下面这样的JSON字符串:
{ success: true, users: [ { id: 1, name: 'Ed' }, { id: 2, name: 'Tommy' } ] }
见Simple Store实例是一个生动的demo。
内联数据
存储也可以加载内联数据。本质上,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' } ] });
排序和分组
存储能够在本地排序,过滤和分组数据,也支持远程的排序,过滤和分组数据:
Ext.create('Ext.data.Store', { model: 'User', sorters: ['name', 'id'], filters: { property: 'name', value : 'Ed' }, groupField: 'age', groupDir: 'DESC' });
在我们刚刚创建的存储中,数据首先会根据name排序,然后根据id排序;它会被过滤为仅包含name为‘ED’的用户,并且数据会根据年龄按降序分组排列。在任何时候使用存储的API进行排序,过滤盒分组都是容易的。见Sorting Grouping Filtering 实例是一个生动的demo。
代理
代理被存储用来控制加载和保存模型的数据。有两种类型的代理:客户端的和服务器端的。客户端的代理实例包括在浏览器的内存中存储数据的Memory和在可用的时候使用HTML5本地存储(local storage)特性的LocalStorage。服务器上的代理把分组的数据处理到远程的服务器上,示例包括 Ajax,JsonP 和 Rest。
代理可以像这样直接定义在一个Model中:
Ext.define('User', { extend: 'Ext.data.Model', fields: ['id', 'name', 'age', 'gender'], proxy: { type: 'rest', url : 'data/users', reader: { type: 'json', root: 'users' } } }); // Uses the User Model's Proxy Ext.create('Ext.data.Store', { model: 'User' });
这通过两种方式帮助了我们。首先,貌似每一个使用这个User模型的Store都将需要用同样的方式加载它们的数据,因此我们避免了为每一个Store都重新定义代理。第二,我们现在可以不用Store就加载和保存模型数据了。
// Gives us a reference to the User class var User = Ext.ModelMgr.getModel('User'); var ed = Ext.create('User', { name: 'Ed Spencer', age : 25 }); // We can save Ed directly without having to add him to a Store first because we // configured a RestProxy this will automatically send a POST request to the url /users ed.save({ success: function(ed) { console.log("Saved Ed! His ID is "+ ed.getId()); } }); // Load User 1 and do something with it (performs a GET request to /users/1) User.load(1, { success: function(user) { console.log("Loaded user 1: " + user.get('name')); } });
也有利用了新式的HTML5的能力——LocalStorage和SessionStorage的代理。尽管老一点的浏览器不支持这些新的HTML5标签,由于大量的应用程序将会受益于它们的表现,还是很有用的。
关联(Association)
Model可以使用Association API链接起来。大部分应用程序需要处理不懂的模型,并且这些模型几乎都总是有关联的。一个博客创作应用程序也许会有User,Post和Comment模型。每一个User创建了Post,并且每一个Post接收Comment。我们可以像这样表示那些关系:
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' });
在你的应用程序中表示不同的模型之间的丰富管理是很简单的一件事。每一个模型能够拥有任何数量的与其它模型的关联,并且你的模型可以以任何顺序被定义。一旦我们有了一个模型实体,我们就能够很容易的横扫这些关联的数据——举个例子,如果我们想记录一个User的每一个Post的所有Comment,我们可以像下面这样做:
// 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关联被添加到模型中。我们声明了每一个User模型有许多(hasMany)Post,在上面的一小段中就添加了user.posts() 函数。调用user.posts() 返回一个配置为Post模型的存储。同样的,Post模型获得了一个comments()函数,因为我们有有许多(hasMany)Comment 关联的设置。
关联不仅仅对于加载数据有帮助——它们对于创建新的记录也很有用:
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的user_id域中的id。调用sync()通过它配置的代理保存了这个新的Post —— 这里再次是一个传入一个你想在操作完成时通知到的回调的同步操作。
belongsTo关联也会在模型中生成新的方法。这里我们可以像那样使用:
// 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方法简单的更新并保存了Post模型的外键(这里是user_id)为100.一般的,回调能够被传入会在操作完成时被触发的东西里面——不管是成功与否。
加载嵌套的(Nested)数据
你可能会想到为什么我们认为在User.load调用中传入了一个success函数,但是没有在访问User的posts和comments函数也同样这么做。这是因为上面的例子假设当我们做一次获取所有用户的请求时服务器返回用户数据,附带返回它所有内嵌的Post和Comments。通过像我们上面那样设置关联,框架能够自动的在一个单独的请求中转出内嵌的数据。而不是为User数据做一次请求,另外再为Post数据做一次请求,还有然后为每一个Post加载Comment做更多的请求,我们可以像下面这样在一个单独的服务器回应中返回所有的数据:
{ 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' } ] } ] } ] }
数据全是由框架自动转出的。配置你模型的代理在任何地方加载数据是很容易的,并且他们的阅读器可以处理任何回应(response)形式。使用ExtJS 3,模型和存储在框架的许多的组件如Grid,Tree和Form被用到。
可以工作的模型使用关联的例子,见Associations and Validations。
淡然,以无内嵌的方式加载你的数据也是可能的。如果你需要仅在必要时“懒加载”关联的数据,这会是很有用的。让我像以前以前只加载User的数据,除了我们假定回应只包含User数据,没有任何关联的Post。然后我们向我们的回调中添加一个user.Post().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')); }); }); } }); } });
完整的示例见 Lazy Associations。
验证(Validations)
随着对数据的验证支持,ExtJS 4模型功能变得更加丰富。为了展示这个功能,我们将构建一个我们在上面为了关联使用过的例子。首先,让我们向User模型添加一些验证。
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”,还有用户名除了“admin”意外可以是任何东西。一些验证使用附加的配置的选项——例如长度验证可以使用min和max属性,格式可以使用一个matcher,等等。ExtJS中有五种验证,并且添加定制的规则也是容易的。首先,让我恩看看内置的这些:
- presence:简单的确保值域有一个值。零算做事一个值,但是字符串不算。
- length:确保一个字符串在一个最小和最大的长度之间,两个限制都是可选的。
- format:确保一个字符串符合一个正则表达式描述的格式。在上面的实例中我们确保了age值域只包含数字。
- inclusion:确保一个值在一个特定的值的集合之中(比如:确保性别不是男就是女)。
- exclusion:确保一个值不在一个特定的值的集合之中(比如:黑名单中的“admin”)。
现在我们已经掌握了不同的验证做些什么,让我们试试针对一个User实体使用它们。我们将会创建一个用户并且针对它运行验证,注意任何验证失败:
// 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(),它运行所有配置好的验证并且返回Error对象。这个简单的对象只是一个任何被发现的验证错误的集合,加上一些便利的函数比如 isValid() —— 如果任何值域都没有错误,它就返回true——还有 getByField(),它返回一个指定值域的所有验证错误。
完整的使用验证的实例,见Associations and Validations。
我的开源中国博客: http://my.oschina.net/xuleo/blog