天天看点

Sencha Touch 2 官方文档翻译之 Controllers(控制器学习指南)

前言: 

控制器 [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();

   }

});

继续阅读