大多数情况下,data包中的model,store和proxy的用法如下:
- Model: model代表你应用关心的一个实体。用户,联系人,地址,和产品都可以是model。简单地说,model只是字段(field)和相应数据的集合,当然,它可以做得更多。
- Store: store只是一个model的实例集合。大多数情况下,它只是一个高级的数组,当然它也提供如排序,筛选和分组,以及引发事件等功能。
- Proxy: proxy负责实现所有加载和保存数据的工作。通常你会创建一个AJAX proxy从你的服务器获取数据并加载到store。
model 和 store
最简单的Model仅仅是一些字段(field)和相应数据的集合。我们来看看Ext.data.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
- });
我们在store里配置了Ajax Proxy,提供URL名称,要求返回的数据编码格式能够被Reader正常使用。在本例中我们的服务器返回JSON格式的数据,所以我们已经建立了一个JSON Reader来读取返回数据。store从URL users.json自动加载了model User的实例集合。users.json的URL应该返回一个JSON字符串,像这样:
- {
- success: true,
- users: [
- { id: 1, name: 'Ed' },
- { id: 2, name: 'Tommy' }
- ]
- }
实时效果,请见Simple Store示例。
内部数据
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' }
- ]
- });
Inline Data 示例
排序和分组
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的用户,按age降序分组。随时都可以简单地通过Store API改变排序,筛选和分组。实时效果,请见Sorting Grouping Filtering Store 示例。
Proxy
proxy被store用于处理model数据加载和保存。proxy有两种类型:客户端和服务器端。在客户端Proxy例子里,如果HTML5的本地存储特性可用,则可以使用浏览器内存和本地存储来存储数据。服务器端proxy处理远程服务器的封装数据,例如AJAX,JSONP和Rest。
proxy可以直接在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 model的每个store都需要以同样的方法加载数据,因此我们需要避免重复为每个store定义proxy。其次,我们可以不使用store直接加载和保存model数据:
- // 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'));
- }
- });
porxy也可以利用HTML5新功能的优势 - LocalStorage和SessionStorage。虽然老的浏览器不支持HTML5的这些新API,但是对很多应用程序来说,使用它们的将可以使你受益匪浅。Model直接使用Proxy的示例。
关联关系
Model之间可以通过Associations 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'
- });
很容易就可以把你应用程序中不同model之间丰富关系表示出来。每个model可以与多个其他model建立关联,你的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'));
- });
- });
- }
- });
我们上面创建的model都添加了一个新的方法hasMany。这是声明每个User model有多个Post。在上面的代码片段中,我们用到了user.posts()方法。调用user.posts()方法返回一个Post model配置的store。同样,因为在Post model也声明了与Comment的hasMany关系,我们可以调用comments()方法。
关联关系不仅在加载数据时有用,在添加新记录时也有用:
- 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对象。你别看这个方法名称叫sync(),这是一个异步操作,如果你想在操作完成时得到通知,你要传递一个回调方法。
在model中,也有新的方法生成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方法只需要更新外键(在这个例子里面是user_id)为100,再保存Post model。同样的,只要保存操作结束,不管是否保存成功,回调方法都会被执行。
加载嵌套数据
你也许会奇怪,为什么我们在调用User.load时会传递一个success方法,但是在存取用户的帖子和评论时又没有这样做。这是因为上面的例子中我们假定当我请求获取一个用户时,服务器返回的用户数据已经嵌套了该用户的所有帖子和评论。通过建立前面例子中那样的关联关系,只需要一次请求框架可以自动解析出嵌套的数据。服务器一次返回所有数据,不仅是用户数据,还包括帖子数据和评论数据,数据格式如下:
- {
- success: true,
- users: [
- {
- id: 1,
- name: 'Ed',
- age: 25,
- gender: 'male',
- posts: [
- {
- id : 12,
- title: 'All about data in Sencha Touch 2',
- body : 'One areas that has seen the most improvement...',
- comments: [
- {
- id: 123,
- name: 'S Jobs',
- message: 'One more thing'
- }
- ]
- }
- ]
- }
- ]
- }
这些数据全部被框架自动解析。你可以很容易从几乎所有地方通过配置model的proxy加载数据,然后通过它们的reader处理返回格式。像Sencha Touch1一样,model和store在整个框架中被很多个组件用到,如Grid,Tree,Form等等。
在model中怎么样使用这些关联关系,请看 Associations and Validations 示例。
当然,我们很可能使用非嵌套的方式加载数据。如果你需要“懒加载”(lazy load)相关数据,这可能对你有用。假定返回的数据只有用户数据而不包含任务关联的帖子,像我们以前做的,我们只需要加载用户数据。然后,我们要在回调方法里面调用user.posts().load()方法以获取相关的帖子数据:
- // 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
验证
Sencha Touch2的Model对数据验证有丰富的支持。为了证明这一点,我们将创建一些例子来说明。首先,让我们在User model里添加一些验证:
- 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属性,格式可以使用匹配正则表达式等等。Sencha Touch 2内置了5种验证,添加自定义规则也很容易。首先,让我们看看内置的:
- presence 确保该字段有一个值。0是一个有效的值,但空字符串不是的。
- length 确保一个字符串的长度在最小和最大长度之间。这两个约束是可选的。
- format 确保一个字符串匹配一个正则表达式格式。在上面的例子中,我们必须确保年龄字段是由至少一个以上的数字。
- inclusion 确保一个值是在一个特定值的集合内(例如,确保性别是男或女)。
- exclusion 确保一个值不在一个特定值的集合内(例如,列入黑名单的用户名,如“管理员”)。
现在,我们已经掌握了不同的验证,让我们尝试对用户实例中使用它们。我们将创建一个用户,针对它运行验证,并提示失败信息:
- // 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(),它运行所有配置的验证,并返回一个错误对象。这个简单的对象仅仅是一个已找到的所有错误集合,加上一些方便的方法如isValid(),如果所有字段都没有错误则返回true,getByField()方法返回一个给定字段中的所有错误。
使用验证的完整示例请见 Associations and Validations
延伸阅读
数据只是整个Sencha Touch 2 生态系统的一部分。要了解更多框架内容以及它是怎样工作的,我们建议继续阅读如下文章:
原文地址:http://html5mobi.gotoip1.com/discussion/222/sencha-touch-2-sencha-touch2-data-the-data-package
【编辑推荐】