MVC應用的資料建模(基於Dojo)

來源:互聯網
上載者:User
文章目錄
  • MVC應用的資料建模(基於Dojo)
  • Model
  • 資料繫結
  • 資料模型進階
  • 資料繫結: dojo/Stateful
  • 總結

本文翻譯自: http://dojotoolkit.org/documentation/tutorials/1.7/data_modeling/

MVC,即模型(Model),視圖(View)和控制Controller),是當今應用開發的主導模式。這裡,我們要從Dojo的基礎功能出發,來看看Dojo是如何支援MVC應用的。通過這篇文章我們會瞭解到如何通過Dojo的object stores 和 Stateful objects(有狀態的對象)來構建MVC應用,以及如何基於這些模型實現我們的顯示層和控制層。

MVC應用的資料建模(基於Dojo)

MVC模式是當今應用開發的主導模式。該模式主要目的是組織嚴密的,易於管理的的代碼結構。Dojo控制項陳列庫自身的代碼結構就是高度基於MVC模式的,同時也能很好的支援那些基於MVC模式的應用。一個好的MVC應用的核心是它能擁有一個完美的資料模式。接下來我們會介紹一下如何通過Dojo的Object stores和stateful objects(有狀態物件)去構建一個包含view和controller的完美模式。

Model

Model層是MVC中的“M”,資料層指的是那些應用中需要訪問和處理的核心資料資訊,他是一個應用的中心,viewer和controller層則主要用來作為使用者和Model(資料)層之間互動的一個橋樑。Model層則封裝了儲存和驗證的過程。

Dojo的object store完美的擔當了Dojo應用中Model的這一個角色。store的介面就是按照分離出資料層的思想來設計的。不同的儲存媒介基於相同的store介面。Stores本身也支援擴充更為強大的功能。接下來我們來看看如何構建一個基本的store。我們將使用一個JsonRest的store,同時緩衝住我們取到的資料:

require(["dojo/store/JsonRest", "dojo/store/Memory", "dojo/store/Cache", "dojo/store/Observable"],         function(JsonRest, Memory, Cache, Observable){    masterStore = new JsonRest({        target: "/Inventory/",        idProperty: "name"    });    cacheStore = new Memory({ idProperty: "name" });    inventoryStore = Cache(masterStore, cacheStore)

現在,我們的“inventoryStore”就成了我們的資料層,我們可以通過“get()”取資料,“query()”查詢資料,“put()”修改資料。store這裡封裝了對真實資料的預存程序,包括與伺服器(server)端的互動。

我們的視圖層(viewer)便可以開始查詢結果了:

results = inventoryStore.query("some-query");viewResults(results);  // pass the results on to the viewfunction viewResults(results){    var container = dom.byId("container");      // results object provides a forEach method for iteration    results.forEach(addRow);      function addRow(item){        var row = domConstruct.create("div",{            innerHTML: item.name + " quantity: " + item.quantity        }, container);    }}

我們的viewResults在這裡就是充當了資料層的一個viewer。我們還能夠通過dojo/string的substitute方法實現簡單的模板化視圖。

function addRow(item){    var row = domConstruct.create("div",{        innerHTML: string.substitute(tmpl, item);    }, container);}
資料繫結

MVC裡面比較重要的一塊就是view層應該監聽資料層的改動,然後及時的反應在介面上。這種方式避免了用controller去監聽資料層的無畏的資源開銷。Controller只需要更新model層,viewer會自動將該改動反映到應用的介面上。我們能夠通過dojo/store/Observable來實現這一點。

masterStore = Observable(masterStore);...inventoryStore = Cache(masterStore, cacheStore);

現在我們的viewer能夠通過observe的方式來監聽查詢結果的變化了

function viewResults(results){    var container = dom.byId("container");    var rows = [];          results.forEach(insertRow);      results.observe(function(item, removedIndex, insertedIndex){        // this will be called any time a item is added, removed, and updates        if(removedIndex > -1){            removeRow(removedIndex);        }        if(insertedIndex > -1){            insertRow(item, insertedIndex);        }    }, true); // we can indicate to be notified of object updates as well      function insertRow(item, i){        var row = domConstruct.create("div", {            innerHTML: item.name + " quantity: " + item.quantity        });        rows.splice(i, 0, container.insertBefore(row, rows[i]));    }      function removeRow(i){        domConstruct.destroy(rows.splice(i, 1)[0]);    }}

現在我們viewer已經能夠即時的反映model資料的變化了,與此同時,我們的controller相關代碼也能夠基於使用者的操作來對model資料作出相應的修改。Controller可以通過put(), add(), 和 remove()來修改資料。通常來講,Controller相關代碼主要用來處理事件,比如:當使用者點擊add按鈕時,我們便建立一個新的資料對象。

on(addButton, "click", function(){    inventoryStore.add({        name: "Shoes",        category: "Clothing",        quantity: 40    });});

該行為會直接更新我們的應用介面(viewer),我們無需再用額外的代碼去直接操作我們的介面了。這個時候我們的Controller就只需要基於使用者的操作來修改model資料了。此刻,Model資料層的儲存和view層的更新渲染就與我們的邏輯完全隔離開來了
資料模型進階

之前我們用到的store都非常簡單,沒有包含任何邏輯(可能服務端會包含一些邏輯和驗證)。我們其實可以在不影響其他模組的同時對store加入一些額外的功能。

驗證

驗證功能便可作為store的一個擴充,這個擴充對JsonRestStore來說非常簡單,因為所有的更新都會調用put()方法(add() 會調用 put()),我們只需要擴充一下put方法即可。

var oldPut = inventoryStore.put;inventoryStore.put = function(object, options){    if(object.quantity < 0){        throw new Error("quantity must not be negative");    }    // now call the original    oldPut.call(this, object, options);};

此時,驗證邏輯已經被加入了。

inventoryStore.put({    name: "Donuts",    category: "Food",    quantity: -1});

由於quantity的值小於0,所以此時該方法會拋出異常。

Hierarchy階層

如同我們給我們的資料模型加入邏輯功能一樣,我們也為中繼資料加入了一些特有的含義,其中之一就是階層。object store定義了getChildren()方法是我們能夠實現我們的父---子結構。存放這種父---子結構有很多種方式。

stored objects可以存放一個指向其所有子物件的數組引用。這種做法適用於小的,順序的列表資料。同樣,objects也可以存放一個指向其父物件的引用,這種做法伸縮性更強。

為了實現第二種資料結構,我們可以加入getChildren()方法。在如下樣本中,我們的構造的階層來源於我們的含有獨立子物件的執行個體,我們建立getChildren()方法用於找到所有category屬性值為該父物件名稱的對象集合,這些對象就是該父物件的所有子物件。這就是將父/子關係定義為子物件的一個屬性的解決方案。

inventoryStore.getChildren = function(parent, options){    return this.query({        category: parent.name    }, options);};

現在,我們可以不用管資料的內部結構,而直接通過getChildren()擷取父物件的所有子節點,檢索子節點方式如下:

require(["dojo/_base/Deferred"],         function(Deferred){    Deferred.when(inventoryStore.get("Food"), function(foodCategory){        // retrieved the food category object, now get it's children        inventoryStore.getChildren(foodCategory).forEach(function(food){            // handle each item in the food category        });    });

至此,我們能取得子節點的對象,接下來我們來看看如何修改他們。我們知道category定義了資料的階層,如果我們改變某個對象元素的階層,我們只需要修改category的屬性值即可。

donut.category = "Junk Food";inventoryStore.put(donut);

Dojo stores的一個核心思想就是提供統一的資料操作介面。如果我們想簡單的通過設定父物件的方式來定義對象的階層,我們可以在put()方法裡使用options參數集裡的parent屬性。

inventoryStore.put = function(object, options){    if(options.parent){        object.category = parent;    }    // ...};

現在我們可以設定父物件了。

inventoryStore.put(donut, {parent: "Junk Food"});
有序的Store

預設情況下,一個store通常是一群無序對象的集合。儘管如此,我們還是可以實現store的有序排列,尤其是對象集合中已經存在預留的隱含的序列屬性時。實現有序store的第一個工作就是在調用query()方法後返回一個有序的store(若且唯若沒有其他排序設定的影響)。實現它往往不需要對store作擴充,你只需要返回相應順序的資料序列即可。

有序的Store通常還有一個需求,就是其元素能夠前後移動,甚至直接到最前或者最後等等。我們可以通過put()方法的options參數中加入before屬性來實現。

inventoryStore.put = function(object, options){    if(options.before){        // we set the reference object's name in the object's "insertBefore"         // so the server can put the object in the right order        object.insertBefore = options.before.name;    }    // ...};

伺服器現在可以通過insertBefore屬性來做排序了。我們的controller層的代碼可以開始移動我們的對象了(我們使用事件代理並假設節點的itemIdbeforeId已經在建立時設定了):

require(["dojo/on"],         function(on){    on(moveUpButton, ".move-up:click", function(){        // |this| in event delegation is the node        // matching the given selector        inventoryStore.put(inventoryStore.get(this.itemId), {            before: inventoryStore.get(this.beforeId)        });    }); 
事物

事物是應用程式裡面比較重要的一塊,通常用於把一系列應用的邏輯操作綁定到一起,一次性執行。他的作用之一就是集合一系列操作然後通過單一請求一次性提交,樣本如下:

require(["dojo/_base/lang"],        function(lang){    lang.mixin(inventoryStore, {        transaction: function(){            // start a transaction, create a new array of operations            this.operations = [];            var store = this;            return {                commit: function(){                    // commit the transaction, sending all the operations in a single request                    return xhr.post({                        url:"/Inventory/",                         // send all the operations in the body                        postData: JSON.stringify(store.operations)                    });                },                abort: function(){                    store.operations = [];                }            };        },        put: function(object, options){            // ... any other logic ...                          // add it to the queue of operations            this.operations.push({action:"put", object:object});        },        remove: function(id){            // add it to the queue of operations            this.operations.push({action:"remove", id:id});        }    });

基於上述代碼,我們可以利用事物來構建我們的自訂動作:

removeCategory: function(category){    // atomically remove entire category and the items within the category    var transaction = this.transaction();          var store = this;    this.getChildren(category).forEach(function(item){        // remove each child        store.remove(item.name);    }, this).then(function(){        // now remove the category        store.remove(category.name);        // all done, commit the changes        transaction.commit();    });}
資料繫結: dojo/Stateful

Dojo對集合層次和實體層次的資料模型有一個清晰的界限,Dojo的store提供了集合層次的模型,接下來我們來看看實體層次的物件模型。Dojo對獨立的對象也使用了統一的介面。這裡我們可以使用dojo/Stateful的介面與對象互動。介面很簡單,主要有三個方法:

  • get(name) - 檢索屬性值
  • set(name, value) - 設定屬性值
  • watch(name, listener) - 註冊一個監聽屬性變化的callback方法(不設第一個參數則表示監聽所有屬性改動)

和之前介紹的viewer綁定資料一樣,他能及時反映相應的資料變化。首先,我們先建立一個viewer,綁定到一個對象:

<form id="itemForm">    Name: <input type="text" name="name" />    Quantity: <input type="text" name="quantity" /></form>

然後,我們綁定到HTML:

function viewInForm(object, form){    // copy initial values into form inputs    for(var i in object){        updateInput(i, null, object.get(i));    }    // watch for any future changes in the object    object.watch(updateInput);    function updateInput(name, oldValue, newValue){        var input = query("input[name=" + name + "]", form)[0];        if(input){            input.value = newValue;        }    }}

現在我們可以通過store裡面的一個對象初始化form:

require(["dojo/Stateful", "dojo/_base/Deferred"],         function(Stateful, Deferred){    Deferred.when(store.get("Donut"), function(item){        item = new Stateful(item); // wrap with stateful        viewInForm(item, dom.byId("itemForm"));    });

現在我們如果通過controller代碼修改這個對象,在viewer上就會馬上反映出來。

item.set("quantity", 4);

在這個例子中,我們可能也想加入onchange事件用於監聽使用者輸入進而修改對象本身,以達到一個雙向的綁定。(object的改動會反映到form上,同樣form的改動也會影響object)。Dojo的form manager(dojox.form.manager)同樣也提供了很多進階的互動功能。

接下來我們還要記住將修改了的對象傳回store裡面,如下:

on(saveButton, "click", function(){    inventoryStore.put(currentItem); // save the current state of the Stateful item});
總結

通過使用Dojo的store架構和stateful(有狀態的)介面,我們便有了構建我們MVC應用的利器。Viewers能監聽資料的變化。Controllers能通過統一的介面來操作資料而不用知道資料的特殊結構,同時也不需要額外的代碼來操作viewer的變化。集合和實體的介面邊界清晰。所有這些都能協助您構建您自己的MVC應用。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.