1. 程式人生 > >Backbone筆記之二(Model/Collection)

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 RequestModel ResponseCollection RequestCollection Response
CreatePOST /task/
Content-Type: application/json
{tid: "1234"...}
...NoNo
ReadGET /task/1{tid: "1234"...}GET /task/[{tid: "1234"...}...]
UpdatePUT /task/1
Content-Type: application/json
{name: "world"...}
...NoNo
DeleteDELETE /task/1...NoNo

上表供進行後端開發時參考。如開發中遇到任何問題,建議立即開啟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的時候觸發可能是以前版本的設定,這個坑也要小心。

(待續)