前言:
控制器 [Controller] 在Sencha Touch MVC中起到的是紐帶作用,它控制視圖 [View] 的展示邏輯,又負責以資料模型 [Model] 為基礎的資料 [Data] 處理邏輯(包括資料的加載、展示、更新、增删等等)。控制器就像膠水,有了它才能夠把一個Sencha Touch(後面一律簡寫做ST)應用程式 [Application] 的各項元素黏合在一起,使之協調工作并完成預期的任務。
文章英文原址是:http://docs.sencha.com/touch/2-0/#!/guide/controllers
原文标題是 Controllers ,直譯就是控制器,不過在官方文檔中也稱之為Controllers Guide ,我覺得用這個标題更為恰切一些。
Sencha Touch 交流QQ群213119459
Controllers Guide
控制器學習指南
Controllers are responsible for responding to events that occur within your app. If your app contains a Logout button that your user can tap on, a Controller would listen to the Button's tap event and take the appropriate action. It allows the View classes to handle the display of data and the Model classes to handle theloading and saving of data - the Controller is the glue that binds them together.
控制器用來響應發生在應用程式中的事件,如果你的應用程式包含一個讓使用者點選的登出按鈕,那麼就應該有一個控制器來偵聽該按鈕的點選事件然後作出恰當的反應。控制器就像膠水,它控制視圖類展示資料,并通過資料模型類來加載和儲存資料,于是兩者就這樣被黏合在一起。
Relation to Ext.app.Application
控制器與Ext.app.Application的關系
Controllers exist within the context of an Application. An Application usually consists of a number of Controllers, each of which handle a specific part of the app. For example, an Application that handles the orders for an online shopping site might have controllers for Orders, Customers and Products.
控制器存在于應用程式的上下文環境中,一個應用程式通常有若幹的控制器組成,每一個控制器分别負責實作應用程式中某一個特定的功能部分,例如一個處理線上商城銷售訂單的應用程式就可能會包含訂單、客戶、産品等控制器。
All of the Controllers that an Application uses are specified in the Application's Ext.app.Application.controllers config. The Application automatically instantiates each Controller and keeps references to each, so it is unusual to need to instantiate Controllers directly. By convention each Controller is named after the thing (usually the Model) that it deals with primarily, usually in the plural - for example if your app is called 'MyApp' and you have a Controller that manages Products, convention is to create a MyApp.controller.Products class in the file app/controller/Products.js.
所有應用程式用到的控制器都在Ext.app.Application.controllers 中進行聲明,應用程式會自動執行個體化每一個控制器并保持對它們的引用,是以我們一般無需自己去直接執行個體化控制器。按照慣例,每一個控制器的命名應該對應他要處理的事物(通常是資料模型),而且常常是複數形式。例如你的應用程式叫做 “MyApp”,你需要一個用來管理産品的控制器,按照正常就應該在app/controller/Products.js 檔案中建立一個MyApp.controller.Products 的類。
Refs and Control
Refs和Control(控制器構造函數中的兩個參數)
The centerpiece of Controllers is the twin configurations refs and control. These are used to easily gain references to Components inside your app and to take action on them based on events that they fire. Let's look at refs first:
控制器的核心部分是refs和control這對雙生參數,它們被用于獲得該應用程式所含元件的引用,并根據其觸發的事件給予相應的處理,先看 refs。
Refs
Refs leverage the powerful ComponentQuery syntax to easily locate Components on your page. We can define as many refs as we like for each Controller, for example here we define a ref called 'nav' that finds a Component on the page with the ID 'mainNav'. We then use that ref in the addLogoutButton beneath it:
Refs擴充了本已很強大的 ComponentQuery 文法,使之能夠很容易的在網頁上定位到元件,每個控制器都可以定義多個refs子項,例如這兒我們就定義了一個叫做“nav”的ref來引用該頁面上id為“mainNav”的元件,在下面的addLogoutButton函數中我們會用到它。
Ext.define('MyApp.controller.Main', {
extend: 'Ext.app.Controller',
config: {
refs: {
nav: '#mainNav'
}
},
addLogoutButton: function() {
this.getNav().add({
text: 'Logout'
});
}
});
Usually, a ref is just a key/value pair - the key ('nav' in this case) is the name of the reference that will be generated, the value ('#mainNav' in this case) is the ComponentQuery selector that will be used to find the Component.
通常一個ref就是一個鍵值對,鍵名(本例中是“nav”)就是這個即将生成的ref引用的名稱,值(本例中是”#mainNav”)就是被用來查找元件的 ComponentQuery 選擇器。
Underneath that, we have created a simple function called addLogoutButton which uses this ref via its generated 'getNav' function. These getter functions are generated based on the refs you define and always follow the same format - 'get' followed by the capitalized ref name. In this case we're treating the nav reference as though it's a Toolbar, and adding a Logout button to it when our function is called. This ref would recognize a Toolbar like this:
後面我們建立了一個名叫addLogoutButton的簡單函數,它通過一個叫做“getNav”的方法使用了這個ref,這種以get開頭的函數是根據你在refs中定義的ref名稱按照指定格式自動生成的,即get後面緊跟(首字母)大寫的ref名稱。在這個例子當中我們把nav設定成一個Toolbar工具欄,而調用addLogoutButton會給nav工具欄添加一個Logout按鈕,這個ref可以識别類似下面這樣的Toolbar:
Ext.create('Ext.Toolbar', {
id: 'mainNav',
items: [
{
text: 'Some Button'
}
]
});
Assuming this Toolbar has already been created by the time we run our 'addLogoutButton' function (we'll see how that is invoked later), it will get the second button added to it.
假定這個toolbar已經被建立了(看代碼,它裡面已經有了一個button),我們調用'addLogoutButton'函數後,它會被加上第二個button。
Advanced Refs
Refs進階
Refs can also be passed a couple of additional options, beyond name and selector. These are autoCreate and xtype, which are almost always used together:
除了name和selector以外,Refs的值還可以接受另一對參數,那就是autoCreate和xtype,他們也幾乎總是一起出現的。
Ext.define('MyApp.controller.Main', {
extend: 'Ext.app.Controller',
config: {
refs: {
nav: '#mainNav',
infoPanel: {
selector: 'tabpanel panel[name=fish] infopanel',
xtype: 'infopanel',
autoCreate: true
}
}
}
});
We've added a second ref to our Controller. Again the name is the key, 'infoPanel' in this case, but this time we've passed an object as the value instead. This time we've used a slightly more complex selector query - in this example imagine that your app contains a tab panel and that one of the items in the tab panel has been given the name 'fish'. Our selector matches any Component with the xtype 'infopanel' inside that tab panel item.
我們給控制器增加了第二個ref,同樣名稱'infoPanel'作為鍵,不過這次值成了一個object對象,除此之外我們還用了一個稍微複雜一點的選擇器,此例中我們假定你的應用程式包含一個tabpanel并且其中之一的item的name是'fish',選擇器會比對該item當中xtype類型為'infopanel'的元件。
The difference here is that if that infopanel does not exist already inside the 'fish' panel, it will be automatically created when you call this.getInfoPanel inside your Controller. The Controller is able to do this because we provided the xtype to instantiate with in the event that the selector did not return anything.
這次的問題在于這個infopanel并不存在于名為'fish'的panel中,而當我們在控制器中調用this.getInfoPanel方法時,它會自動建立一個infopanel。之是以能夠做到這樣是因為,哪怕選擇器無法傳回任何符合條件的對象,但由于我們提供了xtype類型,是以控制器也能夠幹脆自己建立一個。
Control
The sister config to refs is control. Control is the means by which your listen to events fired by Components and have your Controller react in some way. Control accepts both ComponentQuery selectors and refs as its keys, and listener objects as values - for example:
Refs的伴生配置屬性是control(因為它們幾乎總是配對出現),control是一種手段,通過它你可以偵聽元件觸發的事件,并且使你的控制器做出某種反應。control既可以接受ComponentQuery選擇器又能接受refs名稱作為它的鍵名,但是control的值必須是listener對象,比如下面:
Ext.define('MyApp.controller.Main', {
extend: 'Ext.app.Controller',
config: {
control: {
loginButton: {
tap: 'doLogin'
},
'button[action=logout]': {
tap: 'doLogout'
}
},
refs: {
loginButton: 'button[action=login]'
}
},
doLogin: function() {
//called whenever the Login button is tapped
},
doLogout: function() {
//called whenever any Button with action=logout is tapped
}
});
Here we have set up two control declarations - one for our loginButton ref and the other for any Button on the page that has been given the action 'logout'. For each declaration we passed in a single event handler - in each case listening for the 'tap' event, specifying the action that should be called when that Button fires the tap event. Note that we specified the 'doLogin' and 'doLogout' methods as strings inside the control block - this is important.
這裡我們給出了兩個control聲明,一個針對名為loginButton的ref,另一個針對頁面上所有action被設定為logout的按鈕。我們給每一個聲明都傳入了一個事件處理程式,偵聽到tap事件來自哪個按鈕,哪個按鈕指定的響應動作就會被執行。注意我們在control代碼塊中是使用字元串的方式來指定doLogin和doLogout函數的,這一點很重要。
You can listen to as many events as you like in each control declaration, and mix and match ComponentQuery selectors and refs as the keys.
每一個control聲明中都可以按照你的需要偵聽多個事件,并且允許混合使用ComponentQuery選擇器和refs作為control的key。
Routes
路由
As of Sencha Touch 2, Controllers can now directly specify which routes they are interested in. This enables us to provide history support within our app, as well as the ability to deeply link to any part of the application that we provide a route for.
在ST2中,控制器可以直接指定哪個路由是它感興趣的。這就使得我們可以在應用程式中提供通路曆史支援,同樣我們提供路由也可以實作深度連結,即任意連結至應用程式的任意部分。
For example, let's say we have a Controller responsible for logging in and viewing user profiles, and want to make those screens accessible via urls. We could achieve that like this:
假設說我們有一個負責響應登入和浏覽使用者資料的控制器,并且希望這些界面可以通過url直接通路到,我們可以這樣做:
Ext.define('MyApp.controller.Users', {
extend: 'Ext.app.Controller',
config: {
routes: {
'login': 'showLogin',
'user/:id': 'showUserById'
},
refs: {
main: '#mainTabPanel'
}
},
//uses our 'main' ref above to add a loginpanel to our main TabPanel (note that
//當添加loginpanel到主TabPanel的時候需要用到上面定義的叫做main的ref
//'loginpanel' is a custom xtype created for this application)
//注意’loginpanel’是我們為這個應用程式自定義的一個xtype類型
showLogin: function() {
this.getMain().add({
xtype: 'loginpanel'
});
},
//Loads the User then adds a 'userprofile' view to the main TabPanel
//加載使用者Model并添加一個userprofile視圖到主TabPanel界面
showUserById: function(id) {
MyApp.model.User.load(id, {
scope: this,
success: function(user) {
this.getMain().add({
xtype: 'userprofile',
user: user
});
}
});
}
});
The routes we specified above simply map the contents of the browser address bar to a Controller function to call when that route is matched. The routes can be simple text like the login route, which matches against http://myapp.com/#login, or contain wildcards like the 'user/:id' route, which matches urls like http://myapp.com/#user/123. Whenever the address changes the Controller automatically calls the function specified.
上面我們指定的routes可以很容易就把浏覽器位址欄路徑映射到一個與之比對的控制器函數,routes可以是像“login”項那樣的字元串,他比對的路徑是http://myapp.com/#login,也可以像'user/:id'那樣包含通配符,他比對的路徑是類似http://myapp.com/#user/123 這種,當位址欄路徑改變的時候,控制器就會自動調用指定的函數。
Note that in the showUserById function we had to first load the User instance. Whenever you use a route, the function that is called by that route is completely responsible for loading its data and restoring state. This is because your user could either send that url to another person or simply refresh the page, which we wipe clear any cached data you had already loaded. There is a more thorough discussion of restoring state with routes in the application architecture guides.
注意在showUIserById函數中,我們先加載了User Model的執行個體。當使用路由功能時,每個路由對應的函數都必須負責加載資料并還原狀态,因為你的使用者有可能把這個url發給其他人或者直接重新整理了頁面,這些情況下頁面都會清空掉我們本已加載好的資料。關于如何在使用路由時還原狀态,在應用程式架構指南裡有更深入的探讨。
Before Filters
Before篩選器
The final thing that Controllers provide within the context of Routing is the ability to define filter functions that are run before the function specified in the route. These are an excellent place to authenticate or authorize users for specific actions, or to load classes that are not yet on the page. For example, let's say we want to authenticate a user before allowing them to edit a Product in an e-commerce backend:
控制器還在路由的上下文環境裡提供一個過濾器功能,該過濾器可以設定在運作路由指定的函數之前必須先調用另一個函數。對于某些特殊操作來講,在這裡放置使用者身份驗證功能,或者加載一些新的類,是非常合适的。比如我們電子商務背景的商品編輯功能,顯然就需要首先驗證使用者身份。
Ext.define('MyApp.controller.Products', {
config: {
before: {
editProduct: 'authenticate'
},
routes: {
'product/edit/:id': 'editProduct'
}
},
//this is not directly because our before filter is called first
//該函數不會直接運作因為before給它指定了一個需要優先執行的篩選器
editProduct: function() {
//... performs the product editing logic
// 在這裡執行商品編輯邏輯
},
//this is run before editProduct
//該函數會在editProduct之前被調用
authenticate: function(action) {
MyApp.authenticate({
success: function() {
action.resume();
},
failure: function() {
Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
}
});
}
});
Whenever the user navigates to a url like http://myapp.com/#product/edit/123 the Controller's authenticate function will be called and passed the Ext.app.Action that would have been executed if the before filter did not exist. An Action simply represents the Controller, function (editProduct in this case) and other data like the ID parsed from the url.
每當使用者導航至一個這樣的連結http://myapp.com/#product/edit/123,控制器的authenticate函數都會被首先調用并且被傳入一個Ext.app.Action作為參數,該action其實就是在路由中指定的那個函數(如果沒有before篩選器,這個函數才是應該被執行的)。這個action簡單的描繪出了控制器、功能函數以及其他資料(比如url傳過來的id)之間的關系。
The filter can now perform any kind of processing it needs to, either synchronously or asynchronously. In this case we're using our application's authenticate function to check that the user is currently logged in. This could entail an AJAX request to check the user's credentials on the server so it runs asynchronously - if the authentication was successful we continue the action by calling action.resume(), if not we tell the user that they need to log in first.
篩選器能夠以任意方式執行,同步異步均可。本例中我們使用了應用程式的authenticate方法去驗證目前使用者的身份。該方法将會觸發一個AJAX請求來驗證伺服器上的使用者憑證,是以它是使用同步方式執行的,驗證成功後調用action.resume()将會把控制權交回給傳入的action,失敗則會告知使用者他需要首先登陸。
Before filters can also be used to load additional classes before certain actions are performed. For example, if some actions are rarely used you may wish to defer loading of their source code until they are needed so that the application boots up faster. To achieve this you can simply set up a filter that uses Ext.Loader to load code on demand.
Before篩選器同樣可以用于在某些特定動作執行之前加載其他類檔案,例如對于一些不常用的操作你可能會希望直到需用時才去加載(延遲按需加載)它的代碼檔案,這樣應用程式可以啟動更快些,為達到這個目的你可以設定一個篩選器中來使用Ext.Loader加載它們。
Any number of before filters can be specified for each action, to use more than one filter just pass in an array:
使用Before,你可以為每個action指定任意個數的篩選器,多個篩選器的時候隻需要像這樣傳入一個數組:
Ext.define('MyApp.controller.Products', {
config: {
before: {
editProduct: ['authenticate', 'ensureLoaded']
},
routes: {
'product/edit/:id': 'editProduct'
}
},
//this is not directly because our before filter is called first
//該函數不會直接運作因為before給它指定了一個需要優先執行的篩選器
editProduct: function() {
//... performs the product editing logic
// 在這裡執行商品編輯邏輯
},
//this is the first filter that is called
//這是被調用的第一個篩選器函數
authenticate: function(action) {
MyApp.authenticate({
success: function() {
action.resume();
},
failure: function() {
Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
}
});
},
//this is the second filter that is called
//這是第二個要被調用的篩選器函數
ensureLoaded: function(action) {
Ext.require(['MyApp.custom.Class', 'MyApp.another.Class'], function() {
action.resume();
});
}
});
The filters are called in order, and must each call action.resume() to continue the processing.
篩選器函數會被依次調用,每一個當中都必須使用action.resume() 将程式控制權轉交回來。
Profile-specific Controllers
多裝置配置下的控制器
Superclass, shared stuff:
超類,控制器的共享部分(該控制器将被不同裝置的控制器繼承)
Ext.define('MyApp.controller.Users', {
extend: 'Ext.app.Controller',
config: {
routes: {
'login': 'showLogin'
},
refs: {
loginPanel: {
selector: 'loginpanel',
xtype: 'loginpanel',
autoCreate: true
}
},
control: {
'logoutbutton': {
tap: 'logout'
}
}
},
logout: function() {
//code to close the user's session
}
});
Phone Controller:
手機的控制器
Ext.define('MyApp.controller.phone.Users', {
extend: 'MypApp.controller.Users',
config: {
refs: {
nav: '#mainNav'
}
},
showLogin: function() {
this.getNav().setActiveItem(this.getLoginPanel());
}
});
Tablet Controller:
平闆電腦控制器
Ext.define('MyApp.controller.tablet.Users', {
extend: 'MyApp.controller.Users',
showLogin: function() {
this.getLoginPanel().show();
}
});