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"});
當執行到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 "%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的時候觸發可能是以前版本的設定,這個坑也要小心。
(待續)