原文地址:https://dojotoolkit.org/documentation/tutorials/1.10/data_modeling/index.html
本翻譯項目放在GitBook上,歡迎參與。
GitBook地址:https://www.gitbook.com/book/limeng1900/dojo1-11-tutorials-translation-in-chinese/details
轉載請註明出處:http://blog.csdn.net/taijiedi13/ – 碎夢道
模型-視圖-控制器(MVC)是應用開發的主流範式。這裏我們將瞭解Dojo提供給MVC應用的基礎。我們將瞭解如何在基本模型中利用Dojo對象存儲和Stateful對象,以及如何在模型之上構建模塊化視圖和控制器代碼。
MVC應用的數據模型
MVC通過分離關注點來組織和管理應用代碼。Dojo是基於MVC理念的,它爲MVC結構的應用提供了強力的幫助。優良設計的MVC應用的基礎是一個穩固的數據模型。這裏我們將瞭解如何利用Dojo對象存儲和Stateful對象來創建一個可以用在視圖和控制器代碼的強健的模型。
模型
模型是MVC中的M。數據模型代表你應用中訪問和操作的核心信息。模型是你應用的中心,視圖和控制器則以友好的方式將用戶和數據模型連接起來。模型封裝了存儲和驗證過程。
Dojo對象存儲在Dojo應用中充當模型角色。存儲接口設計用來將數據從應用的其餘部分分離出來。可以在不改變存儲接口的前提下使用不同的存儲媒介。Store還可以拓展出更多功能。讓我們先看一個基本存儲的結構。我們將使用一個JsonRest存儲並緩存它返回的項:
require(["dojo/store/JsonRest", "dojo/store/Memory", "dojo/store/Cache", "dojo/store/Observable"],
function(JsonRest, Memory, Cache, Observable){
masterStore = new JsonRest({
target: "/Inventory/"
});
cacheStore = new Memory({});
inventoryStore = new Cache(masterStore, cacheStore);
inventoryStore代表我們的基礎數據模型。我們可以使用get()來獲取數據,使用query()來查詢數據,使用put()來修改數據。store通過處理服務器的交互封裝了這些信息。
我們可以將視圖與查詢結果相連接:
results = inventoryStore.query("some-query");
viewResults(results);
// pass the results on to the view
function 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);
}
}
現在我們的viewResult
扮演一個數據模型的視圖。我們也可以利用dojo/string
的substitute
函數來製作簡單的模板。
function addRow(item){
var row = domConstruct.create("div",{
innerHTML: string.substitute(tmpl, item);
}, container);
}
集合數據綁定
用視圖監控數據模型並隨時響應變化是MVC中的一個重要方面。它可以避免控制器和視圖之間不必要的耦合。控制器更新模型,然後視圖觀察到並響應這種變化。我們可以使用dojo/store/Observable
包裝來讓數據模型可以觀察。
masterStore = new Observable(masterStore);
...
inventoryStore = new Cache(masterStore, cacheStore);
現在我們的視圖可以通過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 updated
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] || null));
}
function removeRow(i){
domConstruct.destroy(rows.splice(i, 1)[0]);
}
}
我們現在有了一個可以直接響應模型變化的視圖,控制器代碼可以改變store中的數據來響應用戶交互。控制器可以用put()
、add()
和remove()
方法來改變數據。通常控制器代碼用來處理事件,例如,在用戶點擊添加按鈕時可以創建一個新的data對象:
on(addButton, "click", function(){
inventoryStore.add({
name: "Shoes",
category: "Clothing",
quantity: 40
});
});
這將在視圖中觸發一個更新,我們完全不需要直接對視圖起作用。只需要控制器代碼來響應用戶動作和處理模型。模型的數據存儲和視圖的渲染都完全從這些代碼中分離了出來。
豐富數據模型
對於store我們現在都用的很簡單,除了簡單對象儲存之外還沒有包含任何邏輯(雖然服務器端可能有額外的邏輯和驗證)。不影響應用的其他不見,我們可以添加更多的功能。
驗證
我們要給store添加的第一個擴展是驗證。對於JsonRest
store這很簡單,因爲所有的更新都通過put()
方法(add()
調用put()
)。我們可以在構造器調用中添加一個put
方法來擴展inventoryStore :
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
});
它會拋出一個錯誤並拒絕改變。
層級
當我們給數據模型添加邏輯時,就是在給原始數據增添意義。其中一個就是展示層級。對象存儲定義了一個getChildren()
方法來實現父子關係的可視化。有幾種不同的方式來存儲這些關係。
存儲對象可以擁有一組指向子集的引用。這是一個很好的設計,它需要小的有序列表。或者,對象可以記錄他們的父節點。後者是一種可伸縮性更好的設計。
我們可以簡單地添加一個gerChildren()
方法來實現第二種設計。在這個例子中我們的層級從擁有個體項作爲子集的類別對象開始。我們會創建一個gerChildren()
方法將查找所有的對象來對比誰的category 屬性能夠匹配父對象的名稱,就將父子關係定義爲子集的一個屬性。
inventoryStore.getChildren = function(parent, options){
return this.query({
category: parent.id
}, options);
};
現在分層視圖可以調用gerChildren()
來得到一個對象的子集列表,不需要理解數據的結構。子集的檢索可能是這樣的:
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
});
});
});
我們得到了一個對象的子集,現在來看如何改變它。我們知道當使用inventoryStore時層級管理是通過category 屬性來定義的。如果想要改變子集的類別,只需要簡單地改變category 屬性:
donut.category = "Junk Food";
inventoryStore.put(donut);
Dojo store的一個核心概念是在數據模型與其他組件之間提供一個一致的接口。如果我們想要在不需要對象內部結構的情況下讓組件設置一個對象的父級進而定義層級關係,可以使用put()
方法的options
參數的parent
屬性:
inventoryStore.put = function(object, options){
if(options.parent){
object.category = options.parent;
}
// ...
};
現在我們可以這麼改變父級:
inventoryStore.put(donut, {parent: "Junk Food"});
有序存儲
默認情況下,一個store代表對象的一個無序集合。但是如果各項有一個潛在順序我們就可以輕易實現對store的排序。一個有序store的第一需求是從query()
調用(當沒有定義一個替代排序時)返回一個順序對象。這不需要額外擴展,只要你正確按順序響應查詢。
有序store也希望能夠提供一個接口來讓對象可以不同方式排序。應用希望提供一種手段來上下前後移動對象。這可以通過使用put()
的可選參數中的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.id;
}
// ...
};
服務器可以響應insertBefore 屬性來給對象排序。控制器代碼可以移動對象(我們使用事件委託並且假設我們在創建時設置了節點的itemIdentity
和beforeId
屬性):
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.id);
}, this).then(function(){
// now remove the category
store.remove(category.id);
// all done, commit the changes
transaction.commit();
});
}
對象數據綁定:dojo/Stateful
在數據模型的集合層面和實體層面之間Dojo有着清晰的描述。Dojo store提供集合層面的架構。現在我們查看單個對象的建模。Dojo在單個對象建模中使用同一的接口。我們可以使用dojo/Stateful
接口來操作對象。這個接口很簡單,它有三個關鍵方法:
get(name)
—— 檢索給定名稱屬性的值set(name,value)
—— 設置給定名稱屬性的值watch(name,listener)
—— 爲給定屬性的變化註冊一個回調(可以忽略第一個參數來監聽所有變化)
這個結構像store一樣讓視圖可以渲染來響應數據的變化。讓我們創建一個簡單的HTML表單綁定對象的視圖。首先HTML是這樣的:
<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的對象來初始化這個表單:
現在控制器代碼可以修改這個對象,視圖會立即響應:
item.set("quantity", 4);
在表單中我們還想添加onchange
事件監聽器,在輸出改變時更新對象,所以讓數據雙向綁定(對象的改變反映在表單裏,表單的改變也反映到對象中)。Dojo在表單管理中也提供了更多高階表單交互功能。
還要記住在改變準備提交是,包裹的對象可以也應該put()
到store。代碼如下:
on(saveButton, "click", function(){
inventoryStore.put(currentItem); // save the current state of the Stateful item
});
dstore:dojo/store的未來
新的dstore包是dojo/store的繼承者,在Dojo 1.8+可用,也是Dojo 2的計劃API。如果你剛接觸Dojo,我們推薦你看一看dstore。注意它也包含集合和數據建模的支持。
總結
使用Dojo store框架和stateful接口,我們擁有了一個堅固的數據模型來構建我們的MVC應用。視圖可以直接渲染數據模型和直接監聽響應數據的變化。控制器可以以一致的方式與數據交互,而不用耦合到特定的數據結構,也不用顯式地操作視圖。集合和實體結構涇渭分明。所有這些幫助你清晰分離關注點,快速構建有組織的、易於管理的應用。