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:"xxx"});</pre>
当执行到t.set()的时候,Model数据发生变化,将触发change:name事件。
测试用的后端
在介绍与后端交互之前,先来一个简单的后端。
这里用web.py做一个最简单的:
# start.py import webweb.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 "%s" % self.dbconn.insert("task", **kwargs) @kwargs_decorator def PUT_(self, id, kwargs={}): self.dbconn.update("task", where="id=$id", vars={'id':id}, **kwargs) return "" def DELETE_(self, id): self.dbconn.delete("task", where="id=$id", vars={'id':id}) return ""
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]