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"});
當執行到t.set()的時候,Model資料發生變化,將觸發change:name事件。
測試用的後端
在介紹與後端互動之前,先來一個簡單的後端。
這裡用web.py做一個最簡單的:
# start.pyimport webweb.config.debug = Falsefrom webpyext.webcommon import WebNotfoundErrorfrom webpyext.apiserver import RestBaseHandler, kwargs_decoratorimport logginglogger = 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操作了。
// Createvar t = new Task({name:"hello", state:"waiting"});t.save();alert("Saved");var tid = t.id// Readt = new Task({id:tid});t.fetch({success: function(task) { alert(JSON.stringify(task.toJSON()));}});// Updatet = new Task({id:tid});t.set({name:"world"});t.save();alert("Updated");// Deletet = 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的時候觸發可能是以前版本的設定,這個坑也要小心。
(待續)