用了Extjs快一年了,这里整理一下model。
数据模型
Extjs 中数据包总共包含了40多个类,其中有三个类比其他类有更重要的地位,它们分别是:model
、store
和proxy
,这些类在大部分的应用系统中都会用到并且得到了大量卫星类的支持,如上图。数据包的中心是Ext.data.Model
,一个model代表系统中的一些数据的类型,比如:我们的成果管理系统中的论文、专利、奖励的model。它是真实世界中实体对象在应用系统中的反映。
model和store
我们来看下在Ext中model代码:
Ext.define('Srims.model.patent.patents', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id', type: 'int', mapping: 'Id'},
{ name: 'Title', type: 'string', mapping: 'Title'},
{ name: 'PatentNumber', type: 'string', mapping: 'PatentNumber'},
{ name: 'PatentType', type: 'string', mapping: 'PatentType'},
{ name: 'PatentStatus', type: 'string'},
{ name: 'AuthorizeDateTime',type: 'string', mapping: 'AuthorizeDateTime'},
{ name: 'DepartmentId', type: 'int'},
{ name: 'DepartmentName', type: 'string', mapping: 'DepartmentName'},
{ name: 'ResponsorName', type: 'string', mapping: 'ResponsorName'},
{ name: 'Status', type: 'string'}
]
});
这是一个最简单的model,只有fields,接下来我们还会讲到model的四个主要的部分--Fields,Proxy,Association 和 Validations。
通常,model和store一起使用,如果说model是数据的类型,它的实例是一条数据记录,那么store实例是model实例的集合,创建一个store并且加载数据。
Ext.define("Srims.store.patent.patents", {
extend: 'Ext.data.Store',
model: 'Srims.model.patent.patents',
proxy: {
type: 'ajax',
url:'http://222.195.150.214:24367/api/patents/getbystatus/1',
reader: {
type: 'json',
root: 'DataList',
successProperty: 'success'
}
},
autoLoad: true
});
我们给store配置了一个Ajax Proxy(后面讲),告诉他URL,让它从这个地方获取数据,还配了reader去解析数据(reader后面也讲),服务器返回Json,所以我们创建了一个Json reader 去读取响应(response)。Store自动加载一组model 实例,就像这样
Store还提供了对数据的过滤、排序、分组的功能:
Ext.define("Srims.store.patent.patents", {
extend: 'Ext.data.Store',
model: 'Srims.model.patent.patents',
//……
sorters: ['Title', 'id'];
filters: {
property: 'DepartmentName',
value: '信息科学与工程学院'
}
groupField: 'status',
groupDir: 'Desc'
});
在这个store中,我们将数据先按名称排序,再按Id排序;然后过滤为只有学院为信息科学与工程学院的记录,另外,根据记录状态按照降序分组。
因为store API中有很多有关的方法,可以根据需要随时更改store的排序、过滤、分组方式。比如,在成果管理系统中,所属项目列表,我要将列表中状态为添加(A)和删除(D)的记录拿出来,就可以使用filter。
//给store添加过滤器
store.filter([
function(item) {
return (item.data.Status == 'A') || (item.data.Status == 'D');
}
]);
……
//清除过滤器
store().clearFilter();
一定要注意,一定要在结束后加上这句store().clearFilter(); 将该过滤器清除,不然之后store中就只有这些过滤后的数据。
上图清晰地展示了model的4个重要组成部分。(字段定义、数据代理、模型关联、数据校验)。
proxy
Proxy被用来对model数据的读取和保存的,有两种proxy:客户端proxy(Ext.data.proxy.Client
)和服务器端proxy(Ext.data.proxy.Server
),它们都继承自Ext.data.proxy.Proxy
类。
Ext.data.proxy.Client
是在客户端进行数据存取操作的基类,通常不会进行实例化,而是使用它的子类。Ext.data.proxy.Memory
使用内存变量存取数据:Ext.data.proxy.LocalStorage
和Ext.data.proxy.SessionStorage
是Ext.data.proxy.WebStorage
使用HTML5的新特性DOM Storage机制,用于存储键值对。SessionStorage用于存储与当前浏览器窗口关联的数据,窗口关闭后,sessionStorage中存储的数据将无法使用;localStorage用于长期存储数据,窗口关闭后,localStorage中的数据仍然可以被访问,所有浏览器窗口可以共享localStorage的数据。
Ext.data.proxy.Server
是服务器端代理的父类,一般也不进行实例化。与客户端代理不同,服务器代理会访问远程的服务器资源,适合于长期保存重要的数据资料。
Ext.data.proxy.Ajax
Ext.data.proxy.Ajax
代理是一个在应用程序中使用最广泛的服务器端代理(也是成果管理系统中主要使用的代理),它采用Ajax方式通过请求指定的URL来读写数据,他不能读取跨域数据(Ext.data.proxy.JsonP
主要用于跨域读取数据)。
成果系统中当时因为index.html文件在本地,要请求214服务器上的数据,产生了跨域问题,后来把index文件页放在214上,请求同在214上的数据,避免了跨域问题。
Ajax代理发起请求是会自动插入sorting排序、filtering过滤、paging分页和grouping分组设置到每一个请求中,在成果系统中,在store中这样配置,完成分页:
Ext.define("Srims.store.achievementAudit.auditPatentList", {
extend: 'Ext.data.Store',
model: 'Srims.model.patent.patents',
pageSize : 40, //分页-每页记录数
proxy: {
type: 'ajax',
url: Srims.api.patentlist + '2',
reader: {
totalProperty: ‘TotalRecordCount’, //获取接收到的数据的记录总数
type: 'json',
root: 'DataList',
successProperty: 'success'
}
},
autoLoad: true
});
reader数据读取器
数据读取器主要用于将数据代理读取到的原始数据按照不同的规则进行解析,将解析后的数据保存在Model模型对象中。数据读取器相当于原始数据格式与Extjs标准数据格式之间的桥梁,它屏蔽了原始数据格式不同对程序开发造成的影响。在Extjs中提供的数据解析器主要有如下3种:
Ext.data.reader.Json (JSON数据读取器)
Ext.data.reader.Xml (XML数据读取器)
Ext.data.reader.Array (数组数据读取器)
reader: {
type: 'json',
root: 'DataList', //返回信息的属性名
totalProperty: 'TotalRecordCount', //获取记录总数的属性名
}
writer数据写入器
数据写入器主要用于将数据代理提交到服务器的数据进行编码,相当于Extjs标准数据格式与服务器数据格式之间的桥梁,他屏蔽了服务器端数据格式不同对程序开发造成的影响。在Extjs中提供的数据写入器有:
Ext.data.writer.Json (Json写入器)
Ext.data.writer.Xml (xml写入器)
--
Ext.define("User", {
extend:"Ext.data.Model",
fields:[
{name:'name', type:'string'},
{name:'age', type:'int'}
],
proxy:{
type : "ajax",
url : " fakeData.jsp",
writer:{
type : "json"
}
}
});
var user = Ext.ModelMgr.create({
name: "Tom",
age: 24
} ,"User");
user.save();
Ext.data.proxy.Rest
Ext.data.proxy.Rest是一个特殊化的Ajax代理,将四种动作(create,read,update和destroy)映射到四种restful http动词(put,get,post和delete)上,将请求的URL转化为rest风格,方便进行rest风格的web应用开发。
rest代理会根据前端框架情况,判断要执行的操作,从而判断用什么方法及需要访问的资源,最终确定URL。比如,在调用save方法时,会自动判断Model的id属性是否有值如果有就使用update路径,如果没有就使用create路径:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['id', 'name', 'email'],
proxy: {
type: 'rest',
url : '/users'
}
});
var user = Ext.create('User', {
name: 'Ed Spencer',
email: 'ed@sencha.com'
});
user.save(); //POST /users
//扩展save,添加回调函数
user.save({
success: function(user) {
user.set('name', 'Khan Noonien Singh');
user.save(); //PUT /users/123
}
});
user.destroy(); //DELETE /users/123
association
在应用系统中总会有不同的模型,这些模型之间大部分情况下是有关联的,比如,成果系统中,论文和作者、专利和发明人、奖励和获奖人之间存在一对多的关系,在Extjs4中支持的关联关系包括一对多和多对一两种,分别通过Ext.data.HasManyAssociation
类和Ext.data.BelongsToAssociation
类实现。
这是官网上的一个例子:
//用户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' }
});
//帖子model
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: 'Comment
});
//评论model
Ext.define('Comment', {
extend: 'Ext.data.Model',
fields: ['id',
'post_id',
'name',
'message‘
],
belongsTo: 'Post'
});
关于读数据
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'));
});
});
}
});
load Id为1的user,并且通过user的proxy load了相关的post和comment。
每一个我们创建的hasMany关系就会有个新的方法添加到这个model。根据name生get方法,不然自动生成默认的model名字小写加s,user.posts()
,调用该方法,会返回一个配有post model的store,同样,post获得了一个comments()
方法。
数据大概是这个样子的:
{
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'
}]
}]
}]
}
关于写数据
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(); //通过配置好的proxy保存新的记录
association联系不仅能帮助我们读数据,它对创建新的记录也有帮助(代码)我们创建了一条新的帖子,给了当前user的Id为user_Id的记录。调用sync()
方法通过配置好的proxy保存新的记录,这是异步的操作,我如我们想在操作成功后做其他操作可以在里面添加回调函数。
结合前两部分的一个例子
一个专利对应多个发明人。
//专利详细信息model
Ext.define('Srims.model.patent.patentDetail', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id', type: 'int'}
{ name: 'patentName', type: 'string'},
{ name: 'patentNumber', type: 'string'},
……
],
hasMany: { model: 'Srims.model.patent.inventors',
name: 'Owners',
associationKey: 'Owners',
foreignKey: 'patentId'
}
proxy: {/*??*/}
});
//发明人model
Ext.define('Srims.model.patent.inventors', {
extend: 'Ext.data.Model',
fields: [
{ name: 'Id', type: 'int' },
{ name: 'Ordinal', type: 'int' },
{ name: 'Name', type: 'string' },
……
{ name: 'patentId', type: 'int'}
]
belongsTo: 'Srims.model.patent.patents'
proxy: {/*??*/}
});
URL中都需要参数,那么proxy如何配置?
Ext.define('Srims.model.patent.patentDetail', {
……
proxy: {
type: "rest",
reader: {type: "json"},
url: 'http://222.195.150.214:24367/api/patents'
}
});
var patentModel = Ext.ModelManager.getModel('Srims.model.patent.patentDetail');
patentModel.load(3017);
有关发明人的URL中间某个位置需要参数……
添加一条发明人记录:POST http://222.195.150.214:24367/api/patents/{patentId}/patentInventors
修改一条发明人记录:PUT http://222.195.150.214:24367/api/patents/{patentId/patentInventors/{ordinal}
Ext.define('Srims.model.patent.inventors', {
extend: 'Ext.data.Model',
……
proxy: {
type: "rest",
reader: {type: "json"},
url: 'http://222.195.150.214:24367/api/patents/{}/patentInventors',
//重写buildUrl
buildUrl: function(request) {
var me = this,
operation = request.operation,
records = operation.records || [],
record = records[0],
url = me.getUrl(request),
id = record && record.get("patentId");
Ordinal = record && record.get("Ordinal");
if (me.isValidId(id)) { //将{}替换为patentId
url = url.replace('{}', id);
} else {
throw new Error('A valid id is required');
}
if (operation.action === “update”) { //若为修改操作,在URL末尾加上位次
url += '/' + Ordinal;
}
request.url = url;
return Ext.data.proxy.Rest.superclass.buildUrl.apply(this, arguments);
}
}
});
读写发明人:
var patentModel = Ext.ModelManager.getModel('Srims.model.patent.patentDetail');
patentModel.load(3017, {
success: function (record) {
record.Owners().add({
Ordinal: 6,
Name: "zhanglu"
});
record.Owners().getNewRecords()[0].save({ //添加一条记录 post
success: function(){
record.Owners().sync(); //将其他记录同步(equal to修改)put
}
});
}
});
validation
presence
保证了字段有值。零是有效的,但空字符串无效。length
确保了一个string类型的字段长度必须在最大值和最小值之间,两个值都是可选的。format
确保字符串必须与正则表达式匹配。inclusion
确保该字段的值必须在一个特定的集合中。exclusion
与inclusion
相反,确保该字段的值不在某个特定的集合中。
-
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: ...
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。