Backbone笔记之二(Model/Collection)

Model

View的部分基本上就是这样了,现在开始来谈Model。

Model其实就是一条数据记录。只不过它不是一般的静态数据记录,它不但可以不限定数据结构,还可以自动与后端交互,甚至还可以绑定事件以实现相应View的自动更新。

最简单的Model就是这样:

var Foo = Backbone.Model.extend({});
// 或是初始化默认数据
var Foo = Backbone.Model.extend({
    defaults: {
        name: "hello",
        value: "233"
    }
});
//  或是运行时赋值。
//  比如这样:
    var foo = new Foo({name:"world", value:"874"});
//  或这样:
    var foo = new Foo;
    foo.set({name:"world", value:"874"});

可以显示一下结果看看效果:

$("#body").text(JSON.stringify(foo.toJSON()));

Collection

Collection就是一个Model集合。因为Model是一条数据记录,也就是说,Collection相当于是一个数据集。

同样,一个最简单的Collection如下:

var FooList = Backbone.Collection.extend({
    model: Foo
});

之后你可以往里增加数据:

var foos = new FooList;
    foos.add([{name:"hello", value:"233"},{name:"world", value:"874"}]);
    $("#body").text(JSON.stringify(foos.at(0)));

更新删除的方式请参考官方文档。

注意,每个Model记录会自动有一个id/cid的属性,是记录的唯一标志。比如:

foos.get(foos.at(0).cid)

Model的事件绑定

为了能在数据变更之后及时更新View上的显示,那就需要通过事件机制来处理。

var Task = Backbone.Model.extend({
    initialize: function () {
        this.on("change:name", function (model) {
            alert("new name is : " + model.get("name"));
        });
    }
});
var t = new Task({tid:"3333", name:"oooo", state:"working"});
t.set({name:&quot;xxx&quot;});</pre>

当执行到t.set()的时候,Model数据发生变化,将触发change:name事件。

测试用的后端

在介绍与后端交互之前,先来一个简单的后端。

这里用web.py做一个最简单的:

# start.py
import web

web.config.debug = False

from webpyext.webcommon import WebNotfoundError from webpyext.apiserver import RestBaseHandler, kwargs_decorator

import logging

logger = logging.getLogger(name)

urls = ( "/task/?" , "Tasks", "/task/([0-9]+)/?" , "Tasks", )

app = web.application(urls, locals())

class APIHandler(RestBaseHandler): def init(self): self.dbconn = web.database(dbn='sqlite', db='xllxweb.dat')

class Tasks(APIHandler): def GET_(self, id=""): if id: result=self.dbconn.select("task", where="id=$id", vars={'id':int(id)}).list() if not len(result): raise WebNotfoundError("This task was not found!") return result[0] else: return self.dbconn.select("task").list()

@kwargs_decorator
def POST_(self, kwargs={}):
    return &quot;%s&quot; % self.dbconn.insert(&quot;task&quot;, **kwargs)

@kwargs_decorator
def PUT_(self, id, kwargs={}):
    self.dbconn.update(&quot;task&quot;, where=&quot;id=$id&quot;, vars={&#39;id&#39;:id}, **kwargs)
    return &quot;&quot;

def DELETE_(self, id):
    self.dbconn.delete(&quot;task&quot;, where=&quot;id=$id&quot;, vars={&#39;id&#39;:id})
    return &quot;&quot;

if name == "main": import logging logging.basicConfig(level=logging.DEBUG) dbconn = web.database(dbn='sqlite', db='xllxweb.dat') sql = """create table if not exists task ( id integer primary key not null, name varchar not null, state varchar )""" dbconn.query(sql) app.run()

其中 webpyext 是我自己写的一个扩展 web.py 的工具库,提供一些增强功能。比如这里用到的三个东西:RestBaseHandler, kwargs_decorator, WebNotfoundError。

其 中RestBaseHandler是提供一个统一的route handler去处理API调用,把固定格式的REST API调用请求转为对方法的调用,其中还包括了对返回数据的JSON转换。kwargs_decorator用于把HTTP请求参数转为方法调用中的kwargs参数,可以自动处理backbone的JSON格式请求(不是一般的HTTP标准请求)。WebNotfoundError是一组HTTP响应专用的异常之一。

本例使用一个SQLite数据库:xllxweb.dat 作为测试,其中只包含一个表:task ,表结构见代码。

现在这个后台实现了这些功能:

/task : 取得所有tasks(GET)或创建一个新task(POST,带参数为task字段,返回记录id号)
/task/[:tid] : 取得某个TaskID的task(GET)或修改(PUT,参数为需要修改的字段)或删除(DELETE,无参数)

以上完全是按照backbone的要求实现,并符合REST规范。

运行 python start.py 即可通过 http://localhost:8080 访问。

Model/Collection与后端交互

首先是写相应的Model和Collection:

var Task = Backbone.Model.extend({
    urlRoot: "/task",
});

var TaskList = Backbone.Collection.extend({ url: "/task", model: Task, });

就是最简单的实现,与前面的例子相比,就是增加了url/urlRoot,这就是与后端交互的机关所在。

之后就可以在这个基础上进行CRUD操作了。

// Create
var t = new Task({name:"hello", state:"waiting"});
t.save();
alert("Saved");
var tid = t.id
// Read
t = new Task({id:tid});
t.fetch({success: function(task) {
    alert(JSON.stringify(task.toJSON()));
}});
// Update
t = new Task({id:tid});
t.set({name:"world"});
t.save();
alert("Updated");
// Delete
t = new Task({id:tid});
t.destroy();

需要注意的是:这些所有的操作都是异步的,如果需要获得操作结果(比如Read操作),必须通过回调函数(比如fetch里那个success回调),否则通常将无法取得正确的结果(比如在fetch执行后立即读取t变量的值,将会只有id一个值,其它的属性值都会为空)。代码中插入的alert主要是起等待作用。

以上是Model的操作方式,Collection与此类似,区别只在于Collection取得的是一组Model列表。

关于以上操作对应的HTTP请求及响应格式如下:

  Model Request Model Response Collection Request Collection Response
Create POST /task/
Content-Type: application/json
{tid: "1234"...}
... No No
Read GET /task/1 {tid: "1234"...} GET /task/ [{tid: "1234"...}...]
Update PUT /task/1
Content-Type: application/json
{name: "world"...}
... No No
Delete DELETE /task/1 ... No No

上表供进行后端开发时参考。如开发中遇到任何问题,建议立即打开FireBug分析。

其中Collection只有Read,其它的Create/Update/Delete其实最终都是调用Model的相应方法处理的。

Model的数据校验

为了防止往Model里存入不正确的值,可以对其加一个校验检查,类似于数据库的“约束(constraint)”。

下面的代码约束name字段必须长于3个字符。

var Task = Backbone.Model.extend({
    urlRoot: "/task",
    initialize: function () {
        this.on("invalid", function (model, error) {
            alert(error);
        });
    },
    validate: function (attrs, options) {
        if (attrs.name.length<3) {
            return "name must longer than 3 chars!";
        }
    },
});

var t = new Task({id:1}); t.fetch({success: function (task) { task.set({name:"hi"}); task.save(); } });

注意,backbone有个坑爹的设定,那就是如果set一个字段为空,比如 set({name:""}) 时将不会触发validate校验,在实际应用中要小心这个坑。

除此之外,还有一个坑就是:在我现在用的这个版本(0.9.10)里,validate只在save的时候触发,网上很多资料说它会在set的时候触发可能是以前版本的设定,这个坑也要小心。

(待续)

推送到[go4pro.org]