数据
Data包负责加载和保存你应用程序中的所有数据,由41个类构成,其中有三个类是最重要的,分别是模型类(Model),存储类(Store),代理类(Ext.data.proxy.Proxy)。它们几乎在每个应用程序中都被使用到,并且有很多相附类为它们提供支持。
模型类和存储类
模型类(Ext.data.Model)是data包的核心部件。每个模型代表应用程序中的一些数据类型-例如一个电子商务应用程序可以有Users、Products、Orders等模型类。简单来说,模型仅仅是一些域和每个域对应数据的集合。我们将重点研究模型类中的四个主要部分—域(Fields)、代理(Proxies)、关联(Associations)和验证(Validations)。
现在让我们看看如何创建一个模型类:
Ext.define('User', { extend: 'Ext.data.Model', fields: [ { name: 'id', type: 'int' }, { name: 'name', type: 'string' } ] });
模型类通常在存储类中使用,这些存储类主要是一些模型实例的集合。设置存储类和加载它的数据是很简单的:
Ext.create('Ext.data.Store', { model: 'User', proxy: { type: 'ajax', url : 'users.json', reader: 'json' }, autoLoad: true });
我们用Ajax代理( Ajax Proxy)配置我们的存储类,告诉它加载数据的url地址和用来解码数据的读取器(Reader)。这样,我们的服务器将返回JSON格式的数据,我们可以使用设置的Json读取器(Json Reader)来解析响应。上面创建的存储类实例从url地址users.json中自动加载一系列User模型类实例的数据。
users.json应该返回如下所示的JSON字符串:
{ success: true, users: [ { id: 1, name: 'Ed' }, { id: 2, name: 'Tommy' } ] }
请查看Simple Store获取一个演示实例。
内联数据
存储类实例也可以加载内联数据,它们转换每个传递进data中的对象为模型类实例:
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 example)
排序和分组
存储类实例能在本地执行排序、过滤和分组,同样也提供远程排序、过滤和分组:
Ext.create('Ext.data.Store', { model: 'User', sorters: ['name', 'id'], filters: { property: 'name', value : 'Ed' }, groupField: 'age', groupDir: 'DESC' });
我们刚刚创建的存储类实例中,数据首先将按照name排序,其次按id排序;并且数据将被过滤为仅包含name为’Ed’的Users,然后数据将按年龄进行分组且遵循由小到大的顺序。任何时候调用存储类的API进行排序、过滤和分组都是是很轻松的。查看排序、分组、过滤存储类实例(Sorting Grouping Filtering Store)获取一个演示示例。
代理
代理类被存储类使用以便于管理加载和保存模型类数据。有两种类型的代理:客户端代理(Client)和服务器端代理(Server)。客户端代理包括存储数据在浏览器内存的内存方式(Memory)和使用HTML5本地存储器(可用时)的本地存储方式(Local Storage)。服务器端代理操作一些从远程服务器调度来的数据,例如包括Ajax,Jsonp和Rest方式。
代理方式可以直接在模型类中定义,如下:
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模型类的存储类实例以相同方式加载数据变得可行,这样我们避免了必须为每个存储类实例复制相同代理方式的定义。其次,现在我们可以不必使用存储类来加载和保存模型数据:
// Gives us a reference to the User class // 创建一个User类的引用 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 //我们可以直接保存ed而不用先把它添加到一个存储类中,因为我们配置了 // configured a RestProxy this will automatically send a POST request to the url /users //一个能自动发送一个POST请求到指定url的Rest代理 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 1并对其一些相关操作(执行一个GET请求到 /users/1) User.load(1, { success: function(user) { console.log("Loaded user 1: " + user.get('name')); } });
也有利用HTML5新功能-- LocalStorage和 SessionStorage – 的代理模式。尽管旧的浏览器不支持这些新的HTML5 APIs,它们仍然是有用的,因为很多应用程序将从这些新特性的存在中受益。
直接在模型类中使用代理的例子(Example of a Model that uses a Proxy directly)
关联
模型类之间可以通过关联API链接在一起。大多数应用程序需要处理很多不同的模型类,并且这些模型类之间通常是相关联的。例如一个博客写作应用程序可能有User(用户)、Post(文章)、Comment(评论)等模型类。每个用户(User)可以创建多篇文章(Posts),并且每篇文章接受很多评论(Comments)。我们可以如下表示这些关系:
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' });
这将使得在你的应用程序中表示这种复杂关系变得简单。 每个模型类可以和其他模型类有任意多的关联,并且你的模型类可以按任意顺序定义。一旦我们创建了一个模型类实例,我们可以很轻松地遍历与其相关联的数据 -- 例如,如果我们想记录一个给定用户的每篇文章的所有相关评论,我们可以如下这样操作:
// Loads User with ID 1 and related posts and comments using User's Proxy //加载User使用ID 1和相关的文章和评论使用User的代理 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)文章(Posts),这将为我们添加user.posts()方法,如上面代码段中使用的那样。调用user.posts()方法将返回一个配置了Post模型的存储类实例。依次类推,Post模型实例获取了一个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赋值给Post中的user_id字段。调用sync()方法,将通过配置的代理方式来保存新创建的Post模型类实例 – 再者,如果你想让操作完成时得到反馈,你可以调用异步操作并传递进一个回调函数来实现。属于(belongsTo)关联也能在模型类实例中生成一个新方法,如我们下面介绍的这个示例:
// get the user reference from the post's belongsTo association //得到user实例引用从post实例的belongsTo关联配置 post.getUser(function(user) { console.log('Just got the user reference from the post: ' + user.get('name')) }); // try to change the post's user //尝试改变文章的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)是异步调用的,并且需要一个回调函数作为参数来获得user实例。setUser方法仅更新外键(本例中的user_id字段)的值为100,并保存这个Post模型类实例。通常,不管成功与否,当保存操作完成时,传递进去的回调函数都将被触发。
加载内嵌的数据
你或许想知道为什么调用User.load方法时,我们传递一个success方法,但当访问User的文章(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' } ] } ] } ] }
这些数据将被框架自动解析出来。配置模型类的代理方式以用来加载任何地方的数据会变得很轻松,并且它们的阅读器模式几乎可以处理任何格式的响应数据。和Ext JS 3一样,模型类和存储类在整个框架中被许多组件使用,例如表格,树,表单。
查看关联和验证(Associations and Validations)的演示示例以获取一个可实际操作并且具有关联关系的模型实例。
当然,你可以以一种非嵌套的方式加载你的数据。如果你仅想需要时加载相关的数据,这种“懒惰加载”模式将可能是有效地。如前所做,我们仅加载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')); }); }); } }); } });
查看懒惰关联(Lazy Associations)模式可以获取一个完整的示例
验证
自Ext JS 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外的其他任何名称。一些验证器可能具有其他的可选配置 -- 例如,长度验证可以具有最大和最小属性,格式(format)可以具有匹配(matcher)属性等。Ext JS有五种内置的验证器,且可以轻松地添加用户自定义规则。首先让我们看看这五种类型:
既然我们已经理解了不同验证器的功能,让我们尝试在一个User实例中使用它们。
我们创建一个user实例并在其中运行验证器,注意产生的任何错误:
// now lets try to create a new user with as many validation errors as we can // 现在让我们尝试创建一个user实例,并产生尽量多的验证错误 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 // 在我们刚刚创建的user实例中运行一些验证器 var errors = newUser.validate(); console.log('Is User valid?', errors.isValid()); //returns 'false' as there were validation errors 当有验证器错误产生时,返回false 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返回age域产生的错误
这里的关键函数是validate(),该函数运行所有配置验证器并返回一个Errors对象。这个简单的对象为所有错误的集合,并且添加了一些便利的方法,例如isValid() -- 当任何域都没有错误产生时,返回true,还有getByField()方法,返回给定域产生的所有错误。
请查看关联和验证(Associations and Validations)示例以获取一个使用验证器的复杂例子。