前言:
數據模型[Model]的主要職責是描述存儲和管理應用程序的數據,堪稱MVC應用程序的肌肉和組織,缺少了Model的應用程序只能是一具沒多大實用價值的空殼。事實上,幾乎當前所有的互聯網應用程序都是以數據的傳遞和交互為主要目的。
這篇文章的英文原址是http://docs.sencha.com/touch/2-0/#!/guide/models
原文標題是:Using Models(使用數據模型)。在官方文檔目錄中,它事實上的地位是MVC概述之后開篇三板斧之一,鑒於Sencha Touch MVC的特點,這三板斧的介紹順序是倒過來的,先C控制器再V視圖最后才是M數據模型,但是其重要性卻不分先后。
Sencha Touch 交流QQ群213119459歡迎您的加入。
Using Models in your Applications
在應用程序中使用數據模型
注:為方便起見,文中所有出現 Sencha Touch 的地方均以 ST 簡寫替代。
At its simplest a Model is just a set of fields and their data. We’re going to look at four of the principal parts of Ext.data.Model — Fields, Proxies, Associations and Validations.
在最簡單的情況下,一個數據模型只是一組字段和數據,我們來看一下Ext.data.Model的四個主要部分,Fields(字段), Proxies(代理), Associations(關聯),Validations(驗證)。
Let's look at how we create a model now:
來看一下如何創建一個數據模型:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id', type: 'int' },
{ name: 'name', type: 'string' }
]
});
Using Proxies
使用代理
Proxies are used by stores to handle the loading and saving of model data. There are two types of proxy: client and server. Examples of client proxies include Memory for storing data in the browser's memory and Local Storage which uses the HTML 5 local storage feature when available. Server proxies handle the marshaling of data to some remote server and examples include Ajax, JsonP, and Rest.
通常store會使用proxy來處理model數據的加載和存儲。Proxy有兩種類型,客戶端和服務端。客戶端類型的例子里包含了瀏覽器內存存儲和html5本地存儲兩種。服務端代理通過配置遠程服務器來獲取數據,例子包含了Ajax/JsonP/Rest的方式。
Proxies can be defined directly on a model, like so:
代理可以直接配置在數據模型上,比如這樣:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['id', 'name', 'age', 'gender'],
proxy: {
type: 'rest',
url : 'data/users',
reader: {
type: 'json',
root: 'users'
}
}
});
// Uses the User Model's Proxy(使用User數據模型代理)
Ext.create('Ext.data.Store', {
model: 'User'
});
This helps in two ways. First, it's likely that every store that uses the User model will need to load its data the same way, so we avoid having to duplicate the proxy definition for each store. Second, we can now load and save model data without a store:
這么做有兩個好處,首先每一個使用User數據模型的store都通過同樣的方式加載數據,這樣我們就無需把proxy的配置每個地方復制一遍了。第二我們可以在不需要store的情況下就加載和保存數據。
// Gives us a reference to the User class(實現對User類的引用)
var User = Ext.ModelMgr.getModel('User');
var ed = Ext.create('User', {
name: 'Ed Spencer',
age : 25
});
// We can save Ed directly without having to add him to a Store first because
// we configured a RestProxy this will automatically send a POST request
// to the url /users
// 我們可以直接保存Ed而無需把它加入到Store,因為我們已經配置了一個RestProxy
// 它會自動發送一個Post請求到/users這個url
ed.save({
success: function(ed) {
console.log("Saved Ed! His ID is "+ ed.getId());
}
});
// Load User 1 and do something with it (performs a GET request to /users/1)
// 加載User 1然后做點什么(發送一個Get請求到/users/1)
User.load(1, {
success: function(user) {
console.log("Loaded user 1: " + user.get('name'));
}
});
There are also proxies that take advantage of the new capabilities of HTML5 - LocalStorage and SessionStorage. Although older browsers don't support these new HTML5 APIs, they're so useful that a lot of applications will benefit enormously by using them.
另外一種proxy類型利用了html5的新能力——本地存儲和會話存儲。雖然老一些的瀏覽器不支持這些html5的API,但它們依然很有用,很多應用程序都可以通過使用它們獲益。
Example of a Model that uses a Proxy directly(該鏈接是一個使用了代理的Model例子)
Associations
關聯
Models can be linked together with the Associations API. Most applications deal with many different models, and the models are almost always related. A blog authoring application might have models for User, Post, and Comment. Each user creates posts and each post receives comments. We can express those relationships like so:
數據模型可以通過Association API關聯在一起。大部分應用程序都要處理很多不同的數據模型,數據模型之間幾乎總是存在關聯。一個博客應用程序可能會包含用戶、發帖和評論等數據模型。每一個用戶創建很多帖子,每個帖子又會收到很多評論。我們可以像下面這樣來定義他們的關聯。
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['id', 'name'],
proxy: {
type: 'rest',
url : 'data/users',
reader: {
type: 'json',
root: 'users'
}
},
hasMany: 'Post' // 等價於 { model: 'Post', name: 'posts' }
});
Ext.define('Post', {
extend: 'Ext.data.Model',
fields: ['id', 'user_id', 'title', 'body'],
proxy: {
type: 'rest',
url : 'data/posts',
reader: {
type: 'json',
root: 'posts'
}
},
belongsTo: 'User',
hasMany: { model: 'Comment', name: 'comments' }
});
Ext.define('Comment', {
extend: 'Ext.data.Model',
fields: ['id', 'post_id', 'name', 'message'],
belongsTo: 'Post'
});
It's easy to express rich relationships between different models in your application. Each model can have any number of associations with other models and your models can be defined in any order. Once we have a model instance we can easily traverse the associated data. For example, to log all comments made on each post for a given user, do something like this:
在應用程序中描述不同數據模型之間的豐富關聯是很容易的。每一個數據模型都可以包含無限多個關聯關系,你的數據模型定義順序也可以隨意。我們得到一個數據模型的實例之后就可以輕松地把它跟它的相關數據貫穿起來。比如要列出給定用戶每個帖子中的全部評論,我們可以這樣做:
// Loads User with ID 1 and related posts and comments using User's Proxy
// 使用User的代理來加載ID為1的用戶和他的相關帖子及評論
User.load(1, {
success: function(user) {
console.log("User: " + user.get('name'));
user.posts().each(function(post) {
console.log("Comments for post: " + post.get('title'));
post.comments().each(function(comment) {
console.log(comment.get('message'));
});
});
}
});
Each of the hasMany associations we created above adds a new function to the Model. We declared that each User model hasMany Posts, which added the user.posts() function we used in the snippet above. Calling user.posts() returns a Store configured with the Post model. In turn, the Post model gets a comments() function because of the hasMany Comments association we set up.
上面例子中的每個hasMany關聯都會自動創建一個新的方法。比如我們定義了“每個User數據模型都hasMany個post”,所以一個user.posts()的方法被自動創建出來。調用這個方法就會返回一個由post數據模型定義的Store,同理,由於我們定義了post數據模型hasMany個comment模型,所以也會得到一個post.comments()方法。
Associations aren't just helpful for loading data, they're useful for creating new records too:
關聯不僅僅對加載數據有幫助,在創建新數據時依然很有用:
user.posts().add({
title: 'Ext JS 4.0 MVC Architecture',
body: 'It\'s a great Idea to structure your Ext JS Applications using the built in MVC Architecture...'
});
user.posts().sync();
Here we instantiate a new Post, which is automatically given the User id in the user_id field. Calling sync() saves the new Post via its configured proxy. This, again, is an asynchronous operation to which you can pass a callback if you want to be notified when the operation completed.
這里我們實例化一個新的post,它的user_id字段會自動被賦予給定user的id。調用sync()方法可以通過配置的代理來保存新post。這同樣是一個異步的操作,如果你想得到操作結束的提示可以傳一個callback函數進去。
The belongsTo association also generates new methods on the model. Here's how to use that:
belongsTo關聯同樣會產生數據模型的新方法,示例如下:
// get the user reference from the post's belongsTo association
// 通過post的belongsTo關聯獲得對user的引用
post.getUser(function(user) {
console.log('Just got the user reference from the post: ' + user.get('name'))
});
// try to change the post's user(嘗試修改post的user)
post.setUser(100, {
callback: function(product, operation) {
if (operation.wasSuccessful()) {
console.log('Post\'s user was updated');
} else {
console.log('Post\'s user could not be updated');
}
}
});
Once more, the loading function (getUser) is asynchronous and requires a callback function to get at the user instance. The setUser method simply updates the foreign_key (user_id in this case) to 100 and saves the Post model. As usual, callbacks can be passed in that will be triggered when the save operation has completed, whether successfully or not.
getUser是一個異步方法,因此需要一個回調函數來獲得返回的user實例。而setUser方法把外鍵(user_id)更改成為100並應用對post數據的更改。像往常一樣,我們傳入一個回調函數以確保保存操作結束的時候會被觸發,不論是否成功(譯者注:操作結束意味着網絡層面數據交互的完成,而是否成功則代表邏輯層面更新數據的成敗,千萬不要混淆)。
Validations
驗證
Sencha Touch 2 Models have rich support for validating their data. To demonstrate this we're going to build upon the example we created that illustrated associations. First, let's add some validations to the User model:
ST2的數據模型對數據驗證有着豐富的支持。為了演示這些,我們繼續在前面創建的例子基礎之上進行擴展。首先給User數據模型加入一些驗證:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ...,
validations: [
{type: 'presence', name: 'name'},
{type: 'length', name: 'name', min: 5},
{type: 'format', name: 'age', matcher: /\d+/},
{type: 'inclusion', name: 'gender', list: ['male', 'female']},
{type: 'exclusion', name: 'name', list: ['admin']}
],
proxy: ...
});
Validations follow the same format as field definitions. In each case, we specify a field and a type of validation. The validations in our example are expecting the name field to be present and to be at least five characters in length, the age field to be a number, the gender field to be either "male" or "female", and the username to be anything but "admin". Some validations take additional optional configuration - for example the length validation can take min and max properties, format can take a matcher, etc. There are five validations built into Sencha Touch 2, and adding custom rules is easy. First, let's look at the ones built right in:
驗證功能遵循與field定義同樣的格式。每一次我們只能定義一個字段的一種驗證類型。例子當中的驗證要求name字段是必須的並且最少5個字符,age字段必須是數字,gender字段要么是male要么是female,username不能為admin。某些驗證還可能使用更多選項配置,比如字符長度可以有min和max屬性,format可以是一個matcher等等。ST2內置有5種驗證類型,想要增加自定義規則同樣很簡單,首先看一下內置的:
presence simply ensures that the field has a value. Zero counts as a valid value but empty strings do not.
Presence保證某個字段必須有一個值。如:0是一個有效地值,但空字符串則不行。
length ensures that a string is between a minimum and maximum length. Both constraints are optional.
Length確保字符串的長度在許可范圍之內,不過這兩個約束條件是可選的。
format ensures that a string matches a regular expression format. In the example above we ensure that the age field is four numbers followed by at least one letter.
Format確保字符串符合正則表達式的規定。例子當中我們要求age字段是至少4個數字並且后面跟着1個字母(莫非文檔作者暈了頭?沒看出代碼中是這么約束的呀)
inclusion ensures that a value is within a specific set of values (for example, ensuring gender is either male or female).
Inclusion保證其值必須是指定范圍其中之一(比如性別要么是男要么是女)
exclusion ensures that a value is not one of the specific set of values (for example, blacklisting usernames like 'admin').
Exclusion保證值不能是指定范圍的其中之一(比如username的黑名單就禁止admin)
Now that we have a grasp of what the different validations do, let's try using them against a User instance. We'll create a user and run the validations against it, noting any failures:
現在我們掌握了不同驗證規則類型的定義,下面來嘗試一下如何應用到User實例當中去。我們將創建一個user然后對她進行驗證,並指出驗證失敗的地方:
// now lets try to create a new user with as many validation errors as we can
// 故意新建一個有錯誤,鐵定無法通過驗證的用戶
var newUser = Ext.create('User', {
name: 'admin',
age: 'twenty-nine',
gender: 'not a valid gender'
});
// run some validation on the new user we just created
// 對我們剛創建的新用戶進行驗證
var errors = newUser.validate();
console.log('Is User valid?', errors.isValid()); //returns 'false' as there were validation errors(有錯誤無法通過驗證,因此當然會返回false)
console.log('All Errors:', errors.items); //returns the array of all errors found on this model instance(返回數據模型實例中所有錯誤的數組)
console.log('Age Errors:', errors.getByField('age')); //returns the errors for the age field(返回age字段的所有錯誤)
The key function here is validate(), which runs all of the configured validations and returns an Errors object. This simple object is just a collection of any errors that were found, plus some convenience methods such as isValid(), which returns true if there were no errors on any field, and getByField(), which returns all errors for a given field.
這兒的核心代碼是validate()方法,該方法會運行validation中配置的所有規則檢查並返回錯誤對象。這個對象是一個由所有錯誤組成的數組組合,同時提供了一系列便捷的方法,比如isValid()在所有字段都沒出現錯誤的時候就會返回true,而getByField()方法會返回指定字段的所有錯誤。
For a complete example that uses validations please seeAssociations and Validations。
完整的驗證實例請參見這里Associations and Validations