第七天(Using Device Profiles)

原文地址:http://docs.sencha.com/touch/2.2.0/#!/guide/profiles


Using Device Profiles
使用设备配置

Today's mobile web applications are expected to work on a wide variety of devices, spanning from the smallest mobile phones to the largest tablets. These devices have a wide range of screen resolutions and are used in different ways. People tend to use phone apps while out of the house to rapidly gather some information or to perform some action - often in a very short time. Tablet apps are more likely to be used for longer periods of time, usually within the home or somewhere else where they can sit for a long time.

现在的手机web app期望运行于众多的设备上,从最小的手机到最大的平板电脑。这些设备拥有不同的屏幕分辨率,并用于不同的途径。人们趋向于在室外用手机快速的收集一些信息或者做一些事情--通常都是很短的时间。平板app用的时间更长些,通常是在家里或者某些人们可以坐很长时间的地方。

All of this means that people expect different app experiences on different devices. However, much of your application logic and assets can be shared between the different experiences. Writing separate apps for each platform is time consuming, error-prone and plain boring. Thankfully, Device Profiles give us a simple way to share between device types as much code as needed, while making it easy to customize behavior, appearance, and workflows for each device.

以上这些都意味着用户希望不同的app运行于不同的设备上。然而,你的应用的很多逻辑、资源能够在不同的设备上是能够共享的。为每种平台编写不同的app是耗时的、易出错的并且非常烦。值得高兴的是,设备配置使我们能够以一种简洁的方式在不同的设备间共享代码,使得为每种设备定制用户行为,展现形式、工作形式变得更加简单。

Setting Up Profiles

定制配置

Device Profiles exist within the context of an Application, for example if we wanted to create an email app with phone and tablet profiles, we could define our app.js shown in the following code sample (see the Intro to Apps guide if this is not familiar):

设备配置存在于Application的的上下文中,例如,如果我们想为手机和平板创建一个邮件app,我们可以像下面这样定义我们的app.js文件(如果这个不熟悉,请查看Intro to Apps guide ):

Ext.application({
    name: 'Mail',

    profiles: ['Phone', 'Tablet']
});

Since we did not give our Application a launch function, at the moment it is only going to load those two Profiles. By convention, it expects to find them inapp/profile/Phone.js and app/profile/Tablet.js. The following code shows the Phone profile:

由于我们没有为Application定义launch方法,目前它只会加载这两个配置。根据惯例,可以在app/profile/Phone.js和app/profile/Tablet.js中找到它们。下面的代码展示了Phone配置:

Ext.define('Mail.profile.Phone', {
    extend: 'Ext.app.Profile',

    config: {
        name: 'Phone',
        views: ['Main']
    },

    isActive: function() {
        return Ext.os.is('Phone');
    }
});

The Tablet profile follows the same pattern. In the Phone profile we only supplied three pieces of information - the Profile name, the optional set of additionalviews to load when this Profile is activated, and an isActive function.

平板设备形式类似。在手机配置中,我们只提供了3种信息---配置名,当当前配置可用时需要加载的额外的一些视图以及isActive方法。

The isActive function determines if a given profile should be active on the device your app runs on. By far the most common approach is to create profiles for Phone and Tablet, using the built-in Ext.os.is method (e.g. Ext.os.is(Phone). You can write any code you like in the isActive function, as long as it always returns true or false for the device it is running on.

isActive方法决定给定的配置在当前app所运行的设备上是否是活动的。目前,最常用的方法是为手机和平板创建配置,用内置的Ext.os.i方法判断(例如Ext.os.i(Phone))。你能够在isActive方法里写任何你喜欢的代码,只要它在它所运行的设备上返回true或者false。

Determining the Active Profile

决定活动的配置

Once the Profiles have been loaded, their isActive functions are called in turn. The first one that return true is the Profile that the Application boots with. This Profile is then set to the Application's currentProfile, and the Application prepares to load all of its dependencies - the models, views, controllers, and other classes that constitute the app. It does this by combining its own dependencies with those specified in the active profile.

一旦配置被加载,他们的isActive方法会轮流调用。第一个返回true的配置是Application启动的配置。然后这个配置被设置为Application的currentProfile,Application准备加载它的所有依赖 -- 模型、视图、控制器及组成app的其它类。它(Application)通过结合它自身的依赖及活动配置的依赖达到此目的。

For example, let us amend our previous Application so that it loads its own Models and Views:

例如,修改我们上一个Application,让它加载它自身的模型和视图:

Ext.application({
    name: 'Mail',

    profiles: ['Phone', 'Tablet'],

    models: ['User'],
    views: ['Navigation', 'Login']
});

Finally, when we load the app on a phone, the Phone profile is activated and the application loads the following files:

最终,当我们在手机上运行该app时,手机配置是活动的,application会加载如下文件:

  • app/model/User.js
  • app/view/Navigation.js
  • app/view/Login.js
  • app/view/phone/Main.js

The first three items are specified in the Application itself - the User model plus the Navigation and Login views. The fourth item is specified by the Phone Profile and follows a special form. By convention, classes that are specific to a Profile are expected to be defined in a subdirectory with the same name as the Profile. For example, the 'Main' view specified in the Phone Profile is loaded from app/view/phone/Main.js, whereas if we had defined 'Main' in the Application, it would be loaded from app/view/Main.js.

前面3个是有Application自身指定的--用户模型、导航机登录视图。第四个是有手机配置指定的并遵从一种特定的形式。按照惯例,配置指定的类会在与配置同名的子目录中定义。例如,手机配置中指定的Main视图是从app/view/phone/Main.js中加载的,反之,我们在Application中定义Main,它会从app/view/Main.js中加载。

The same applies to all models, views, controllers, and stores loaded in a Profile. This is important as it enables you to easily share behavior, view logic, and more between profiles (see the specializing views and controllers sections below). If you need to load classes that do not comply with this convention, you can specify the full class name instead:

上面的规则对配置中加载的模型、视图、控制器、数据存储同样适用。这是非常重要的,因为它能够使你共享处理行为、视图逻辑及不同设备间其它一些东西(查看后续关于视图和控制器的专门章节了解更多信息)。如果你想加载不符合该规范的类,你可以使用类全名来代替:

Ext.define('Mail.profile.Phone', {
    extend: 'Ext.app.Profile',

    config: {
        name: 'Phone',
        views: ['Main', 'Mail.view.SpecialView'],
        models: ['Mail.model.Message']
    },

    isActive: function() {
        return Ext.os.is('Phone');
    }
});

As evident from this example, you can mix and match fully-qualified class names (e.g. 'Mail.view.SomeView') and relatively specified class names (e.g.'Main', which becomes 'Mail.view.phone.Main'). Be aware that all models, views, controllers, and stores specified for a Profile are treated this way. This means if there are Models or Stores that you want to load for Tablets only, but do not want to create classes like Mail.model.tablet.User, you should specify the fully-qualified class names instead (e.g. Mail.model.User in this case).

从这个例子中可以很明显的看出来,你可以混合搭配类全名(如'Mail.view.SomeView')与相对类名(如'Main'来源于’Mail.view.phone.Main‘)。你要意识到设备指定的所有模型、视图、控制器、数据存储都是以这种方式处理的。这意味着如果你有些只为平板加载的模型或者数据存储,但是你又不想想Mail.mobile.tablet.User这样创建类,那么你应该指定类全名(这个例子中是Mail.model.User)。

The Launch Process

启动处理

The launch process using Profiles is almost exactly the same as that without using Profiles. Profile-based apps have a 3-stage launch process; after all of the dependencies have been loaded, the following happens:

用设备配置的启动处理逻辑与不用设备配置的启动处理逻辑几乎一样。基于设备配置的应用有3个阶段的启动处理逻辑;在所有的依赖被加载后,接下来发生了如下一些事:

  1. Controllers are instantiated; each Controller's init function is called  控制器被实例化,每一个控制器的init方法被调用
  2. The Profile's launch function is called 设备配置的launch方法被调用
  3. The Application's launch function is called.  Application的launch方法被调用

When using Profiles it is common to use the Profile launch functions to create the app's initial UI. In many cases this means the Application's launch function is completely removed, as the initial UI is usually different in each Profile (you can still specify an Application-wide launch function for setting up items such as analytics or other profile-agnostic setup).

当使用设备配置时,通常用配置的launch方法创建应用的初始化ui界面。在很多情况下,这意味着Application的launch方法被完全去掉了,因为不同的配置即不同的设备的初始化ui往往不同(你依旧可以为了诸如分析、设备判定而指定Application的launch方法)。

A typical Profile launch function might look like in the following sample:

一个典型的设备配置的launch方法如下所示:

Ext.define('Mail.profile.Phone', {
    extend: 'Ext.app.Profile',

    config: {
        name: 'Phone',
        views: ['Main']
    },

    isActive: function() {
        return Ext.os.is('Phone');
    },

    launch: function() {
        Ext.create('Mail.view.phone.Main');
    }
});

Note that both Profile and Application launch functions are optional - if you do not define them, they are not called.

注意不论是设备配置还是Application的launch方法都是可选的 -- 如果你没有定义它们,那它们不会被调用。

Specializing Views

视图的具体化

Most of the specialization implemented by a Profile occurs in the Views and the Controllers. Let us assume that we have the following tablet Profile:

设备配置的具体化多发生在视图和控制器。假设我们有如下所示的平板配置:

Ext.define('Mail.profile.Tablet', {
    extend: 'Ext.app.Profile',

    config: {
        views: ['Main']
    },

    launch: function() {
        Ext.create('Mail.view.tablet.Main');
    }
});

When this app boots on a tablet device, the file app/views/tablet/Main.js is loaded as usual. The following code sample is the content of the app/views/tablet/Main.js file:

当该app在平板设备上运行时,app/views/tablet/Main.js被加载。下面是app/views/tablet/Main.js文件的代码:

Ext.define('Mail.view.tablet.Main', {
    extend: 'Mail.view.Main',

    config: {
        title: 'Tablet-specific version'
    }
});

Usually, when we define a view class we extend one of Sencha Touch's built in views, but in the previous example we extend Mail.view.Main, one of our own views. The following code sample illustrates how Mail.view.Main looks like:

通常,我们定义的视图类会继承自st的内部view,但是在上面的例子中,我们继承自Main.view.Main,一个我们自己的视图。下面的代码展示了Main.view.Main:

Ext.define('Mail.view.Main', {
    extend: 'Ext.Panel',

    config: {
        title: 'Generic version',
        html: 'This is the main screen'
    }
});

We thus have a superclass (Mail.view.Main) and a Profile-specific subclass (Mail.view.tablet.Main), which can customize any aspect of the superclass. In this case we change the title of the Main view from "Generic version" to "Tablet-specific version" in our subclass, so that is what we see when the app loads.

因此我们有了一个超类(Main.view.Main)和一个指定配置下的子类(Main.view.tablet.Main),该子类能够定制超类的任意方面。在这个例子中,我们在子类中将Main视图的标题从"Generic version"改为了"Tablet-specific version",就是我们启动app时所看到的那样。

Since these are normal classes, it is easy to customize almost any part of the superclass using the flexible config system. For example, assuming that we also have a phone version of the app, we could customize its version of the Main view as follows (app/view/phone/Main.js):

既然这些都是标准类,那我们很容易通过使用复杂的config配置来定制超类的任意部分。例如,假设该app我们还有一个手机版本,我们可以像如下这样定制它的版本信息(app/view/phoen/Main.js):

Ext.define('Mail.view.phone.Main', {
    extend: 'Mail.view.Main',

    config: {
        title: 'Phone-specific version',

        items: [
            {
                xtype: 'button',
                text: 'This is a phone...'
            }
        ]
    }
});

Sharing Sub-Views

共享子视图

While the previous example is useful, it is more common to share certain pieces of views and stitch them together in different ways for different profiles. For example, imagine an email app where the tablet UI is a split screen with a message list on the left and the current message loaded on the right. The Phone version is the exact same message list and a similar message view, but this time in a card layout, since there is not enough screen space to display both views simultaneously.

虽然上面的例子很有用,但是共享某些已经存在的视图并根据不同的配置将它们整合在一起更常见。例如,假设一个邮件app,在平板上ui界面是,左侧是信息列表,右侧是当前信息预览的界面。在手机上,也是信息列表和信息预览界面,但是是卡片布局,因为没有足够的屏幕空间同时容纳这两个界面。

To achieve this we have to create the two shared sub-views - the message list and the message viewer. In each case we have left the class config out for brevity:

为了达到该目的,我们创建了两个共享的子视图--信息列表和信息预览。为简便起见,我们没有设置config配置项:

Ext.define('Mail.view.MessageList', {
    extend: 'Ext.List',
    xtype: 'messagelist'

    // config goes here...
});

And the Message Viewer:

Ext.define('Mail.view.MessageViewer', {
    extend: 'Ext.Panel',
    xtype: 'messageviewer'

    // config goes here...
});

Eventually, in order to achieve the target layout that contains two views, the tablet Main view might use the following code :

最后,为了达到含有这两个视图的布局,平板设备的Main视图应该像下面这样:

Ext.define('Mail.view.tablet.Main', {
    extend: 'Ext.Container',

    config: {
        layout: 'fit',
        items: [
            {
                xtype: 'messagelist',
                width: 200,
                docked: 'left'
            },
            {
                xtype: 'messageviewer'
            }
        ]
    }
});

This creates a 200px wide messagelist on the left, and uses the rest of the device's screen space to show the message viewer. Now let us assume we wanted to achieve the Phone layout:

上面的代码在左侧创建了一个宽为200px的信息列表,用剩下的屏幕空间放置信息预览视图。现在我们要实现在手机上的布局设计:

Ext.define('Mail.view.phone.Main', {
    extend: 'Ext.Container',

    config: {
        layout: 'card',
        items: [
            {
                xtype: 'messagelist'
            },
            {
                xtype: 'messageviewer'
            }
        ]
    }
});

In this case we use a Container with a card layout (a layout that only shows one item at a time), and put both the list and the viewer into it. In this case we still need to add in some logic that tells the Container to show the messageviewer when a message in the list is tapped on, but we have easily reused our two sub-views in different configurations based on the currently loaded Profile.

这里我们使用了一个卡片布局(一种一次只展示一个子项的布局)的容器,将信息列表和预览视图都放在该容器中。在这个例子中,我们依旧需要添加一些逻辑告诉容器:当点击了信息列表中的一条信息时展示该信息的预览界面,但是我们很容易的在不同的设备配置中重用了两个子视图。

Similar to the previous example, we have the option to customize the two shared views for each Profile - for example we could create theMail.view.phone.MessageViewer and the Mail.view.tablet.MessageViewer subclasses, both of which extend the Mail.view.MessageViewer superclass. This enables us to again share a lot of view code between those classes, while presenting customizations appropriate for the actual device used.

如前一个例子类似,我们可以为每一中设备配置定制共享的两个子视图 -- 例如我们可以创建继承自Main.view.MessageViewer的Main.view.phone.MessageViewer和Main.view.tablet.MessageViewer两个子类。这使我们在为实际的设备类型展示定制的合适的视图时能够再一次的共用更多的代码。

Specializing Controllers

控制器的具体化

Similar to Views, many applications have some Controller logic that can be shared across multiple Profiles. In the case of Profiles, the most important differences are usually workflow-related. For example, an app's tablet profile may allow you to complete a workflow on a single page, whereas the phone profile presents a multi-page wizard.

如视图类似,很多应用在多个设备配置上共用控制器逻辑。就设备配置来说,最重要的区别经常是与工作流程相关的。例如,一个应该的平板配置允许你在一个页面完成工作流程,而在手机上是以多个页面展示的。

In the following example we have a simple Phone profile that loads a view called Main and a controller called Messages. As before, this app loadsapp/view/phone/Main.js and app/controller/phone/Messages.js:

在下面的例子中,我们有一个简单的手机配置,该配置加载了Main视图和Messages控制器。像前面所说的样,这个app加载app/view/phoen/Main.js和app/controller/phone/Messages.js:

Ext.define('Mail.profile.Phone', {
    extend: 'Ext.app.Profile',

    config: {
        views: ['Main'],
        controllers: ['Messages']
    },

    launch: function() {
        Ext.create('Mail.view.phone.Main');
    }
});

Since we already know that our phone and tablet-specific controllers share most of their functionality, we create a controller superclass inapp/controller/Messages.js:

既然我们已经知道手机和平板配置的控制器有很多的逻辑是一致的,故我们在app/controller/Messages.js中创建一个控制器超类:

Ext.define('Mail.controller.Messages', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            viewer: 'messageviewer',
            messageList: 'messagelist'
        },
        control: {
            messageList: {
                itemtap: 'loadMessage'
            }
        }
    },

    loadMessage: function(item) {
        this.getViewer().load(item);
    }
});

The Controller from the previous example performs three operations:

上面例子中的控制器完成了3中操作:

  1. Sets up refs referencing views that we care about.    建立起了对我们所关心的视图的引用
  2. Listens for the itemtap event on the message list and calls the loadMessage() function when itemtap is fired.   在信息列表上监听itemtap事件,并在itemtap事件触发时调用loadMessage方法
  3. Loads the selected message item into the Viewer when loadMessage() is called.   当loadMessage方法被调用是将选中的信息加载至信息预览视图。

We can now create the phone-specific Controller as follows:

现在我们可以像下面这样创建为手机定制的控制器:

Ext.define('Mail.controller.phone.Messages', {
    extend: 'Mail.controller.Messages',

    config: {
        refs: {
            main: '#mainPanel'
        }
    },

    loadMessage: function(item) {
        this.callParent(arguments);
        this.getMain().setActiveItem(1);
    }
});

This code extends the Messages superclass controller and provides the following functionality:

这段代码继承在Messages超类控制器,并提供了如下功能:

  1. Adds another ref for the phone UI's main panel   添加了手机设备上ui中main面板的引用
  2. Extends the loadMessage function to perform the original logic and then sets the main panel's active item to the message viewer.     扩展了loadMessage方法,在原有的操作上,设置main面板的活动面板为信息预览项。

The entire superclass configuration is inherited by the subclass that extends it. In the case of duplicated configs such as refs, the configuration is merged, so the phone Messages controller class has three refs - main, viewer, and messageList. As with any class that extends another class, we can usecallParent to extend an existing function in the superclass.

超类的所有配置被继承它的子类所实现。就复制的配置如refs(引用)来讲,配置被合并,因此手机的Messages控制器有3个引用 -- main,viewer及messageList。如同一个类继承其它类,我们可以使用callParent来扩展超类中存在的方法。

Remember that the Mail.controller.Messages superclass is not declared as a dependency by either the Application or the Profile. It it automatically loaded because our Mail.controller.phone.Messages controller extends it.

记住,超类Mail.controller.Messages既没有在Application也没有在Profile中被声明为依赖。它会被自动加载,因为我们的Mail.controller.phone.Messages控制器继承自它。

What Code to Share

共用哪些代码

In the previous example we were able to share some (but not all) of our refs. We were also able to share the single event that we listen for by using the Controller's control config. Generally speaking, the more an app diverges between profiles, the fewer refs and control configs you will be able to share.

在上一个例子中,我们能够共用一些(但不是所有)refs(引用)。我们也可以共用通过control配置的唯一监听事件。通常来讲,不同设备间的偏差越大,你能够共用的refs和control越少。

One Controller config that should be shared across profiles is routes. These config maps URLs to Controller actions and provides back button support and deep linking. It is important to keep the routes config in the superclass, because the same URL should map to the same content regardless of the device.

通过设备配置共用的控制器的一个配置是路由。路由配置将url与控制器的行为进行映射,并提供了了返回按钮支持和深度连接。在超类中保留路由配置很重要,因为相同的url应该得到相同的内容而与设备无关。

For example, if a friend is using the phone version of your app and sends you a link to the app page she is currently on, you should be able to tap that link on your tablet device and see the tablet-specific view for that URL. Keeping all routes in the superclass enables you to keep a consistent URL structure that works regardless of device.

例如,一个朋友使用手机版本的app给你发来一个她所在页面的链接,你应该能够在你的平板上点击该链接并能够查看该链接的页面。在超类中配置路由保证了不同的设备上有一致的url结构。

Specializing Models

模型的具体化

Models are customized per Profile less frequently than Controllers and Views, so that do not usually require a subclass. In this case we only specify the fully qualified class names for models:

每一个设备配置都定制模型发生的频率不像控制器和视图那样高。,所以通常不需要子类。这种情况下,我们只为模型指定类全名:

Ext.define('Mail.profile.Tablet', {
    extend: 'Ext.app.Profile',

    config: {
        models: ['Mail.model.Group']
    }
});

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值