Model可以根据名称管理一到N组数据,这里的数据既可以是string、bool这样的简单数据,也可以是复杂的数据实体和集合。数据的读写通常都是通过Model的get和set方法完成的,Model的get和set方法的使用方式可以直接参考本文后面对Entity对象的get和set方法的说明。
你可以通过DataType来声明某项数据的数据类型,虽然这个操作不是必须的,但声明数据类型可以获得很多额外的好处,比如获得自动的Javascript类型转换、隐式的数据校验、格式化输出等等。Cola-UI中默认支持的数据类型有:
string —— 字符串。这种类型通常无须额外声明。
bool —— 逻辑型。
int —— 整型。
float —— 浮点型。
number —— 数字,即不特别声明究竟是整型还是浮点型。
date —— 日期型。
entity —— 数据实体或数据实体的集合。
json —— JSON对象或Array。
上述的类型中需要特别说明的是entity和json这两种类型。
Cola默认使用Entity和EntityList来封装JSON数据。其中Entity对应JSON对象,EntityList对应Array。Entity和EntityList我们统称为实体数据。Cola中的很多特性(比如双向数据绑定)都依赖于实体数据。
在这一点的设计上,Cola于AngularJS是不同的,而与EmberJS比较相似。虽然AngularJS的数据在使用时看起来似乎更方便。但在现有的Javascript规范下却损失了一部分性能,且在功能扩展上也会受到制约。
Cola默认使用Entity和EntityList来封装JSON数据。当我们把复杂的JSON数据设置到Model中时,Cola会自动的将其转换成实体数据。因此,当我们再一次从Model中读取出该项数据时,我们得到的将不再是当初的JSON了。正因为如此,Cola才会特别提供一种名为json的数据类型,以声明在某些情况下不要转换遇到的JSON对象和JSON数组。
数据路径
由于Model中的数据可能是比较复杂的树形结构,因此当我们需要将一个控件绑定到术中一个比较深的位置,或者需要从树中读取一个位置较深的数据项时就会用到数据路径。
数据路径是一个以“.”连接的属性链。例如:person.name
数据路径并不能用于指示集合中的某个具体项。通过数据路径访问集合时我们得到的总是集合中的当前项。相应的,EntityList总是会管理着一个当前项只要这个EntityList不是空的。EntityList的当前项可以通过API或绑定的控件被改变。例如当我们使用products.name来为某个SPAN建立的绑定,当我们通过API或其他方式改变了products集合的当前项时,由于双向数据绑定功能的作用,SPAN中的内容会立刻切换成新的当前产品名称。
Entity(数据实体)
Entity的结构类似于一个Map,其中可以有若干个属性。这个特征和Model很像,事实上Model内部正是通过一个Entity来管理数据的,所以我们在这里介绍的Entity的get和set方法的各种特性同样适用于Model的get和set方法。我们可以通过get和set方法来读写一个Entity中的属性值。例如:
person.get("name");
person.set("age", 23);
Entity的属性值也可以是复杂的数据类型,例如:
account.set("address", {
city: "Shanghai",
street: "Dongfang road",
zipCode: 200010
});
根据之前的描述,Cola遇到JSON类型的数值时会自动转换成Entity,因此当我们再次从account中读取address时将会得到一个Entity对象的实例。
var address = account.get("address");
alert(address.get("city"));
Entity的get和set方法都支持迭代式的属性读取和写入,例如:
var city = account.get("address.city");
account.set("address.city", "beijing");
无论address的值目前是Entity类型还是JSON对象,上面的读写操作都可以成功的执行,Cola会自动根据每一级上对象的类型完成不同方式的数据钻取。
Entity属性也可以被批量的设置。例如:
address.set({
city: "Beijing",
street: "Jiangtai Road",
zipCode: "100020"
});
Entity除了实现上述较基本的数据管理之外还可以实现对属性值的校验、数据懒装载、装载管理等功能。
具体请参考Entity的API文档。
EntityList(数据实体集合)
EntityList是Entity的集合,相对于数组它提供了更加方便高效的插入、删除,新增了当前Entity的概念,提供了数据分页和数据懒加载的功能。
例如当我们要迭代EntityList中的所有Entity时,代码可以是这样的:
EntityList的更多用法请参考API文档。
EntityDataType(实体数据类型)
model.EntityDataType是专门用于描述Entity的DataType。例如我们可以用这样的一段声明来描述person这种数据实体:
model.dataType({
name: "Employee",
properties: {
name: {
caption: "姓名",
validators: ["required"]
},
age: {
caption: "年龄",
dataType: "int",
validators: [{
$type: "number",
min: 16,
max: 80
}]
},
gender: {
caption: "性别"
},
email: {
caption: "电邮",
validators: [
"email",
function(value) {
if (value && value.toLowerCase().indexOf("qq.com") > 0) {
return "不能使用qq邮箱!";
}
}
]
},
province: {
caption: "省"
},
city: {
caption: "市"
},
district: {
caption: "区"
},
comment: {
caption: "备注"
}
}
});
Cola会自动根据此处dataType对应的那段JSON创建一个EntityDataType实例,该DataType可以限定person实体中各属性的显示名称、数据类型、校验规则等等。
也可以在实例中测试以上这些校验规则...
下面的例子定义一个递归的树状结构,我们在定义DataType时为其声明了name属性,例如指定name为"Category"。之后我们就可以在其他地方通过"Category"这个名称来引用这个DataType了。例如此例中我们在categories属性中引用了"Category",那就相当于又引用了自身。
model.dataType({
name: "Category",
properties:{
id: {
required: true
},
name: {
label: "分类名称",
required: true
},
categories: {
dataType: "Category"
}
}
});
也可以预先利用Model.dataType()声明好DataType,再到cola.data()中使用,就像下面的这个例子...
model.dataType([
{
name: "Product",
properties:[
id: {
dataType: "int",
required: true
},
name: {
label: "产品名称",
required: true
},
price: {
label: "价格",
dataType: "int"
}
]
},
{
name: "Category",
properties: {
name: {
label: "分类名称",
required: true
},
products: {
dataType: "Product"
}
}
}
]);
model.describe("categories", "Category");
Provider(数据装载器)
Provider的基本用法
Provider是用于为数据模型提供数据的,通常是用于声明让Model自动从Server端通过Ajax装载数据。
如果我们把一个Provider作为数据设置到Model或Entity中,或者利用describe为某个数据项声明好了Provider。那么当我们之后尝试从Model和Entity中读取这项数据时,Cola会自动调用该Provider尝试获得最终的数据。例如:
model.describe("employees", {
provider: "/service/employees"
});
或
model.set("employees", new cola.Provider({
url: "/service/employees"
}));
以上的第一段代码演示的是一种极简的Provider的定义方法,如果只需要定义一个Provider的url,那么就可以直接通过一个代表url的字符串来定义。但事实上Provider还支持更多的属性和设置,如果有需要我们还可以通过JSON配置对象的醒来来定义的Provider。
下面的代码将触发Model从Server端装载数据
model.get("employees", function(employees) {
// 异步方式读取employees属性,可以在回调方法中得到装载到的employees集合。
});
当我们利用Provider来为某种Entity的某个属性定义数据懒装载时,你会需要向Ajax服务传递当前Entity的id或类似的唯一标示,以便于服务器区分究竟应该装载那些数据。这种参数的值只有在实际运行时才能最终确定,因此需要利用特殊的定义方法。见下面的DataType声明:
model.dataType({
name: "Category",
properties: {
name: {
label: "分类名称",
required: true
},
products: {
provider: {
url: "/data/products",
parameter: "{{@id}}"
}
}
}
});
在products对应的属性的provider中,我们通过{{@id}}来定义了参数。这表示Provider会在最终被执行之前从当前所属的Entity的id属性中读取该参数的值,即获得当前对应的Category的id作为参数值。
{{ }}中间的内容是Cola-UI的表达式,如果变量名以@开头那表示从当前所属的Entity中读取属性值,否则表示从当前的Model中读取数据项。
见实例...
另外,对于那些比较简单的parameter值,Cola会直接把它作为Request的GET参数(参数名为parameter)来传递,例如:/data/get-items.action?from=20&limit=10¶meter=foo,这里的from和limit可能是Cola根据当前EntityList的分页情况自动添加的,其中from表示从第几条记录开始(从0开始计数),limit表示最多返回多少条记录(相当于每页的大小)。
不过,当你的参数是一个结构复杂的JSON对象时,上面这种传递方式可能就不适用了。这种情况下我们可以设定Provider的sendJson属性为true,这样Provider会以JSON的形式传递所有参数,并且默认也会使用POST方式来发出Request。例如:
model.describe("itmes", {
provider: {
url: "/data/products",
parameter: {
startDate: "2016-08-23",
endDate: "2016-11-18"
},
sendJson: true
}
});
数据分页
在前面的内容中,你已经接触到了通过Provider数据装载器来实现数据分页装载。此功能最终需要由Server端的逻辑提供相应的支持,因为分页本身就是为了提高效率降低网络带宽的压力,不能简单的认为是Cola在客户端对数据进行分页显示。
我们在Cola中设置的pageSize参数最终会变成Ajax请求中的参数,例如最终发往服务器端的请求可能是/data/products?from=0&limit=100。
Provider支持两种风格的方式将分页参数传网服务器端,from+limit风格或pageSize+pageNo模式,具体请参考Provider数据装载器。
需要特别加以注意的是如果你只为Provider发出的请求返回一个简单的数组,EntityList将无法知道总共有多少页数据。这可能会导致DataPilot控件中的"最后一页"按钮不可用,因为Cola不知道最后一页是哪一页。当然,不指定总页数在很多场景中都是毫无问题的,我们只要确保向后翻页的功能可用就可以了。
但在另一些场景中,我们可能就必须要知道总共有多少页。此时通过装载数据的请求告诉Cola是一个选择。只要按照下面的方法来提供Response数据,以下的两种任选其一即可。
{
$entityCount: 100, //总记录数
$data: [
{...},
{...},
{...},
{...},
{...},
{...},
{...},
{...},
{...},
{...}
]
}
在上例的返回结果中,$data+$entityCount和data$+entityCount$两种命名方式都是可被识别的。具体使用哪种取决于你的个人喜好。