数据相关概念

Model可以根据名称管理一到N组数据,这里的数据既可以是string、bool这样的简单数据,也可以是复杂的数据实体和集合。数据的读写通常都是通过Model的get和set方法完成的,Model的get和set方法的使用方式可以直接参考本文后面对Entity对象的get和set方法的说明。

你可以通过DataType来声明某项数据的数据类型,虽然这个操作不是必须的,但声明数据类型可以获得很多额外的好处,必须获得自动的Javascript类型转换、隐式的数据校验、格式化输出等等。Cola中默认支持的数据类型有:

  • 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中的属性值。例如:

  1. person.get("name");
  2. person.set("age", 23);

Entity的属性值也可以是复杂的数据类型,例如:

  1. account.set("address", {
  2. city: "Shanghai",
  3. street: "Dongfang road",
  4. zipCode: 200010
  5. });

根据之前的描述,Cola遇到JSON类型的数值时会自动转换成Entity,因此当我们再次从account中读取address时将会得到一个Entity对象的实例。

  1. var address = account.get("address");
  2. alert(address.get("city"));

Entity的get和set方法都支持迭代式的属性读取和写入,例如:

  1. var city = account.get("address.city");
  2. account.set("address.city", "beijing");

无论address的值目前是Entity类型还是JSON对象,上面的读写操作都可以成功的执行,Cola会自动根据每一级上对象的类型完成不同方式的数据钻取。

Entity属性也可以被批量的设置。例如:

  1. address.set({
  2. city: "Beijing",
  3. street: "Zhichun Road",
  4. zipCode: "100020"
  5. });

Entity除了实现上述较基本的数据管理之外还可以实现对属性值的校验、数据懒装载、装载管理等功能。具体请参考Entity的API文档。

EntityList(数据实体集合)

EntityList是Entity的集合,相对于数组它提供了更加方便高效的插入、删除,新增了当前Entity的概念,提供了数据分页和数据懒加载的功能。

例如当我们要迭代EntityList中的所有Entity时,代码可以是这样的:

  1. employees.each(function(employee) {
  2. ... ...
  3. });

EntityList的更多用法请参考API文档。

EntityDataType(实体数据类型)

model.EntityDataType是专门用于描述Entity的DataType。例如我们可以用这样的一段声明来描述person这种数据实体…

  1. model.describe("person", {
  2. dataType: {
  3. properties:{
  4. name: {
  5. label: "姓名",
  6. required: true
  7. },
  8. gendar: {
  9. label: "性别",
  10. dataType: "bool"
  11. },
  12. age: {
  13. label: "年龄",
  14. dataType: "int",
  15. validators: [
  16. {
  17. $type: "number",
  18. min: 18,
  19. max: 70
  20. }
  21. ]
  22. }
  23. }
  24. }
  25. });

Cola会自动根据此处dataType对应的那段JSON创建一个EntityDataType实例,该DataType可以限定person实体中各属性的显示名称、数据类型、校验规则等等。

我们也可以利用EntityDataType来定义属性的数据懒装载,例如在下面的例子中指定了Category的products属性是一个支持数据懒装载的属性,同时还用一段子JSON还声明了products中每一个数据实体的DataType。

  1. model.describe("categories", {
  2. properties:{
  3. id: {
  4. required: true
  5. },
  6. name: {
  7. label: "分类名称",
  8. required: true
  9. },
  10. products: {
  11. provider: {
  12. url: "/data/products.action",
  13. parameter: ":id"
  14. },
  15. dataType: {
  16. properties: {
  17. id: {
  18. dataType: "int",
  19. required: true
  20. },
  21. name: {
  22. label: "产品名称",
  23. required: true
  24. },
  25. price: {
  26. label: "价格",
  27. dataType: "float"
  28. }
  29. }
  30. }
  31. }
  32. }
  33. });

下面的例子定义一个递归的树状结构,我们在定义DataType时为其声明了name属性,例如指定name为”Category”。之后我们就可以在其他地方通过”Category”这个名称来引用这个DataType了。例如此例中我们在categories属性中引用了”Category”,那就相当于又引用了自身。

  1. model.describe("categories", {
  2. name: "Category",
  3. properties:{
  4. id: {
  5. required: true
  6. },
  7. name: {
  8. label: "分类名称",
  9. required: true
  10. },
  11. categories: {
  12. provider: {
  13. url: "/data/categories.action",
  14. parameter: ":id"
  15. },
  16. dataType: "Category"
  17. }
  18. }
  19. });

也可以预先利用Model.dataType()声明好DataType,再到cola.data()中使用,就像下面的这个例子…

  1. model.dataType([
  2. {
  3. name: "Product",
  4. properties:[
  5. id: {
  6. dataType: "int",
  7. required: true
  8. },
  9. name: {
  10. label: "产品名称",
  11. required: true
  12. },
  13. price: {
  14. label: "价格",
  15. dataType: "int"
  16. }
  17. ]
  18. },
  19. {
  20. name: "Category",
  21. properties: {
  22. name: {
  23. label: "分类名称",
  24. required: true
  25. },
  26. products: {
  27. provider: {
  28. url: "/data/products.action",
  29. parameter: ":id"
  30. },
  31. dataType: "Product"
  32. }
  33. }
  34. }
  35. ]);
  36. model.describe("categories", "Category");

Provider(数据装载器)

Provider的基本用法

Provider是用于为数据模型提供数据的,通常是用于声明让Model自动从Server端通过Ajax装载数据。

如果我们把一个Provider作为数据设置到Model或Entity中,或者利用describe为某个数据项声明好了Provider。那么当我们之后尝试从Model和Entity中读取这项数据时,Cola会自动调用该Provider尝试获得最终的数据。例如:

  1. model.describe("employees", {
  2. provider: "data/employees.json"
  3. });

  1. model.set("employees", new cola.Provider({
  2. url: "data/employees.json"
  3. }));

以上的第一段代码演示的是一种极简的Provider的定义方法,如果只需要定义一个Provider的url,那么就可以直接通过一个代表url的字符串来定义。但事实上Provider还支持更多的属性和设置,如果有需要我们还可以通过JSON配置对象的醒来来定义的Provider。

下面的代码将触发Model从Server端装载数据

  1. model.get("employees", function(employees) {
  2. // 异步方式读取employees属性,可以在回调方法中得到装载到的employees集合。
  3. });

当我们利用Provider来为某种Entity的某个属性定义数据懒装载时,你会需要向Ajax服务传递当前Entity的id或类似的唯一标示,以便于服务器区分究竟应该装载那些数据。这种参数的值只有在实际运行时才能最终确定,因此需要利用特殊的定义方法。见下面的DataType声明:

  1. model.dataType({
  2. name: "Category",
  3. properties: {
  4. name: {
  5. label: "分类名称",
  6. required: true
  7. },
  8. products: {
  9. provider: {
  10. url: "/data/products.action",
  11. parameter: ":id"
  12. }
  13. }
  14. }
  15. });

在products对应的属性的provider中,我们通过:id来定义了参数。这表示Provider会在最终被执行之前从当前所属的Entity的id属性中读取该参数的值,即获得当前对应的Category的id作为参数值。

另外,对于那些比较简单的parameter值,Cola会直接把它作为Request的GET参数(参数名为parameter)来传递,例如:/data/get-items.action?from=20&limit=10&parameter=foo,这里的from和limit可能是Cola根据当前EntityList的分页情况自动添加的,其中from表示从第几条记录开始(从0开始计数),limit表示最多返回多少条记录(相当于每页的大小)。 不过,当你的参数是一个结构复杂的JSON对象时,上面这种传递方式可能就不适用了。这种情况下我们可以设定Provider的sendJson属性为true,这样Provider会以JSON的形式传递所有参数,并且默认也会使用POST方式来发出Request。

数据分页

在前面的内容中,你已经接触到了通过Provider来实现数据分页装载。此功能最终需要由Server端的逻辑提供相应的支持,因为分页本身就是为了提高效率降低网络带宽的压力,不能简单的认为是Cola在客户端对数据进行分页显示。

我们在Cola中设置的pageSize参数最终会变成Ajax请求中的参数,例如最终发往服务器端的请求可能是/data/get-products.do?from=0&limit=100

需要特别加以注意的是如果你只为Provider发出的请求返回一个简单的数组,EntityList将无法知道总共有多少页数据。这可能会导致DataPilot控件中的”最后一页”按钮不可用,因为Cola不知道最后一页是哪一页。当然,不指定总页数在很多场景中都是毫无问题的,我们只要确保向后翻页的功能可用就可以了。 但在另一些场景中,我们可能就必须要知道总共有多少页。此时通过装载数据的请求告诉Cola是一个选择。只要按照下面的方法来提供Response数据,以下的两种任选其一即可。

  1. {
  2. $totalEntityCount: 100, //总记录数
  3. $data: [
  4. {...},
  5. {...},
  6. {...},
  7. {...},
  8. {...},
  9. {...},
  10. {...},
  11. {...},
  12. {...},
  13. {...}
  14. ]
  15. }