【翻译】Architecting Your App in Ext JS 4, Part 2

This Tutorial is most relevant to Ext JS, 4.x. In the previous Ext JS Architecture article, we explored how to architect a Pandora-style application using Ext JS. We took a look at the Model-View-Controller architecture and how to apply it to a relatively complex UI application that had multiple views and models. In this article, we’re going to move beyond architecting the application visually, and explore how to design and code the controllers and models, starting with Ext.application and the Viewport class. Let’s just jump in and start writing the application.

发表日期:2011-08-01 | 作者:Tommy Maintz | 类别:教程 | 难度:中级

这个教程基于Ext JS 4.x版本。

在上一篇中,我们探索了如何用Ext JS设计一个Pandora风格的应用。我们研究了MVC架构,以及如何把它应用到一个相对复杂的有多个视图和模型的UI应用中。这篇文章中,我们将跳出应用设计的视觉部分,转而从Ext.application 和Viewport类开始,探索如何编写控制器和模型。

Defining our application

定义应用

In Ext JS 3, the Ext.onReady method was the entry point into your application, and the developer had to come up with an application architecture. In Ext JS 4, we have an introduced an MVC-like pattern. This pattern helps you to follow best practices when creating your applications.

The entry point into an application written with the new MVC package requires that you use theExt.application method. This method will create an Ext.app.Application instance for you and will fire the launch method as soon as the page is ready. This essentially replaces the need to useExt.onReady while adding new functionality such as automatically creating a viewport and setting up your namespace.

在Ext JS 3中,Ext.onReady方法是你应用程序的入口点,开发者都必须以此开始应用设计。在Ext JS 4中,我们引入了MVC风格的模式。这个模式能帮助你在创建应用时遵循最佳实践。

新的MVC包中的应用入口点需要你使用Ext.application方法。这个方法将创建一个Ext.app.Application实例,并且一旦页面准备好了,就为你触发launch事件。这个本质上取代了当添加新功能时使用Ext.onReady 的需求,比如自动创建一个视口或设置你的命名空间。


app/Application.js
Ext.application({
    name: 'Panda',    
    autoCreateViewport: true,
    launch: function() {
        // This is fired as soon as the page is ready
    }
});

The name configuration causes a new namespace to be created. All our views, models, stores and controllers will live in this namespace. By setting autoCreateViewport to true, the framework will, by convention, include the app/view/Viewport.js file. In this file, a class should be defined with the name Panda.view.Viewport, matching the namespace that was specified by the nameconfiguration of your application.

这里的name 属性将创建一个新的命名空间。所有我们的视图、模型、存储和控制器都存在于这个命名空间之中。通过设置autoCreateViewport 属性为true,框架将按约定引入app/view/Viewport.js文件。在这个文件中,必须定义一个名为Panda.view.Viewport的类,以匹配应用配置中的name属性。

The Viewport class

视口类

When we looked at which views we needed for our UI, we were very focused on the individual parts. The Viewport of an application acts as the glue for these individual parts. It loads the required views and defines the configuration needed to achieve your app’s overall layout. We have found that progressively defining your views and adding them to the viewport is the fastest way to create the base structure of your UI.

It is important during this process to focus on scaffolding your views and not on the individual views themselves. It’s almost like sculpting. We start by creating the very rough shapes of our views and add more detail to them later.

当我们思考UI中所需的视图时,我们要将注意力集中到那些独立的部分。应用视口就像胶水一样把那些独立部分粘合在一起,它加载所需的视图并且定义所需的配置来完成应用的总体布局。我们逐渐发现,定义你的视图,然后把他们添加到视口是创建你UI结构的最快的方式。

Creating the building blocks

生成构建块

Leveraging the work we already did in the previous article, we are able to define many of the views at once.

运用上一篇文章中的成果,我们能一次性定义许多视图:

balance_view.jpg

app/view/NewStation.js


Ext.define('Panda.view.NewStation', {
    extend: 'Ext.form.field.ComboBox',
    alias: 'widget.newstation',
    store: 'SearchResults',
    ... more configuration ...
});

app/view/SongControls.js


Ext.define('Panda.view.SongControls', {
    extend: 'Ext.Container',
    alias: 'widget.songcontrols',
    ... more configuration ...
});

app/view/StationsList


Ext.define('Panda.view.StationsList', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.stationslist',
    store: 'Stations',
    ... more configuration ...
});

app/view/RecentlyPlayedScroller.js


Ext.define('Panda.view.RecentlyPlayedScroller', {
    extend: 'Ext.view.View',
    alias: 'widget.recentlyplayedscroller',
    itemTpl: '
', store: 'RecentSongs', ... more configuration ... });

app/view/SongInfo.js


Ext.define('Panda.view.SongInfo', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.songinfo',    
    tpl: '

About

', ... more configuration ... });

We have left out some of the configuration here since component configurations are not in the scope of this article.

In the above configurations, you’ll notice that we have three stores configured. These map to the store names prepared in the previous article. At this point we’ll go ahead and create our stores.

我们在这里去除了一些配置信息,因为组件配置不属于此文的讨论范畴。 在上面的配置中,你可能注意到,我们配置了三个存储。这些映射到存储的名字在上一篇文章中已经提及了。让我们将继续存储的创建:

stores.jpg

The models and stores

模型和存储

Often, it is useful to start with static json files containing mock data to act as our server side. Later, we can use these static files as a reference when we actually implement a dynamic server side.

For our app, we decided to use two models, Station and Song. We also need three stores using these two models that will be bound to our data components. Each store will load its data from the server side. The mock data files would look something like the following.

通常,用一些静态的json模拟数据来扮演服务器端很有用。往后,我们能用这些静态文件,作为动态服务器实际实现时的参考。

我们决定过在应用中使用两个模型:Station 和 Song,并且决定了这两种模型绑定数据组件时需要的三种存储。每种存储将从服务器加载它自已的数据。这些模拟数据看起来像下面表示的。

Static data

静态数据

data/songs.json


{
    'success': true,
    'results': [
        {
            'name': 'Blues At Sunrise (Live)', 
            'artist': 'Stevie Ray Vaughan', 
            'album': 'Blues At Sunrise', 
            'description': 'Description for Stevie', 
            'played_date': '1',
            'station': 1
        },
        ...
    ]
}

data/stations.json


{
    'success': true,
    'results': [
        {'id': 1, 'played_date': 4, 'name': 'Led Zeppelin'}, 
        {'id': 2, 'played_date': 3, 'name': 'The Rolling Stones'}, 
        {'id': 3, 'played_date': 2, 'name': 'Daft Punk'}
    ]
}

data/searchresults.json


{
    'success': true,    
    'results': [
        {'id': 1, 'name': 'Led Zeppelin'}, 
        {'id': 2, 'name': 'The Rolling Stones'}, 
        {'id': 3, 'name': 'Daft Punk'},
        {'id': 4, 'name': 'John Mayer'}, 
        {'id': 5, 'name': 'Pete Philly & Perquisite'}, 
        {'id': 6, 'name': 'Black Star'},
        {'id': 7, 'name': 'Macy Gray'}
    ]
}

Models

模型

Models in Ext JS 4 are very similar to Records which we had in Ext JS 3. One key difference is that you can now specify a proxy on your model, as well as validations and associations. The Song model for our application in Ext JS 4 would look like this.

Ext JS 4中的模型很像Ext JS 3中的记录(Records)。一个关键的不同是你可以在模型上指定一个代理(proxy)、以及验证(validations)和关联(assocaition)。这个应用的Song模型在Ext JS 4看起来是这样的:

app/model/Song.js


Ext.define('Panda.model.Song', {
    extend: 'Ext.data.Model',
    fields: ['id', 'name', 'artist', 'album', 'played_date', 'station'],
 
    proxy: {
        type: 'ajax',
        url: 'data/recentsongs.json',
        reader: {
            type: 'json',
            root: 'results'
        }
    }
});

As you can see, we have defined the proxy on our model. It is generally good practice to do this as it allows you to load and save instances of this model without needing a store. Also, when multiple stores use this same model, you don’t have to redefine your proxy on each one of them.

Let’s go ahead and also define our Station model.

如你所见,我们在模型上定义了代理。这样做通常是一个好的实践,它允许你不需要一个存储就能加载和保存这个模型实例。同样,当多个存储使用同一个模型时,你不须要为它们每个都重新定义你的代理。

现在开始定义Station模型:

app/model/Station.js


Ext.define('Panda.model.Station', {
    extend: 'Ext.data.Model',
    fields: ['id', 'name', 'played_date'],
 
    proxy: {
        type: 'ajax',
        url: 'data/stations.json',
        reader: {
            type: 'json',
            root: 'results'
        }
    }
});

Stores

存储

In Ext JS 4, multiple stores can use the same data model, even if the stores will load their data from different sources. In our example, the Station model will be used by the SearchResults and the Stations store, both loading the data from a different location. One returns search results, the other returns the user’s favorite stations. To achieve this, one of our stores will need to override the proxy defined on the model.

在Ext JS 4中,多个存储能使用同一个数据模型,甚至是这些存储是从不同的数据源来加载它们的数据。在我们的例子中,Station模型将被用于SearchResults 和Stations存储,这两者都从不同的地方加载数据。一个返回搜索结果,另一个返回用户喜爱的电台。为了完成这个,我们的存储其中一个要覆写模型上定义过的代理。

app/store/SearchResults.js


Ext.define('Panda.store.SearchResults', {
    extend: 'Ext.data.Store',
    requires: 'Panda.model.Station',
    model: 'Panda.model.Station',
 
    // Overriding the model's default proxy
    proxy: {
        type: 'ajax',
        url: 'data/searchresults.json',
        reader: {
            type: 'json',
            root: 'results'
        }
    }
});

app/store/Stations.js


Ext.define('Panda.store.Stations', {
    extend: 'Ext.data.Store',
    requires: 'Panda.model.Station',
    model: 'Panda.model.Station'
});

In the SearchResults store definition, we have overridden the proxy defined on the Station model by providing a different proxy configuration. The store’s proxy is used when calling the store’s loadmethod instead of the proxy defined on the model itself.

Note that you could implement your server side to have one API for retrieving both search results and the user’s favorite stations in which case both stores could use the default proxy defined on the model, only passing different parameters to the request when loading the stores.

Lastly, let’s create the RecentSongs store.

在SearchResults 存储的定义中,我们通过提供一个不同的代理配置,覆写了Station模型上定义过的代理。当存储的 load 方法调用时,就会使用这个存储的代理,而不是使用模型本身的代理。

注意,你可以在服务器端实现一个API同时检索搜索结果和用户喜爱的电台。这种情形下,存储都使用模型上定义的代理,只是当加载存储时请求传递不同的参数。

最后,我们创建RecentSongs存储:

app/store/RecentSongs.js


Ext.define('Panda.store.RecentSongs', {
    extend: 'Ext.data.Store',
    model: 'Panda.model.Song',
 
    // Make sure to require your model if you are
    // not using Ext JS 4.0.5
    requires: 'Panda.model.Song'
});

Note that in the current version of Ext JS, the 'model' property on a store doesn’t automatically create a dependency, which is why we have to specify requires in order to be able to dynamically load the model. Also, for convention, we always try to pluralize the store names, while keeping the model names singular.

还要注意,当前的Ext JS版本中,“模型”在存储上的属性不会自动创建依赖。那就是为什么我们必须指定requires属性,以使模型能够动态加载。 同样,为了方便着想,我们总是会使用存储名称的复数形式。这能突出模型的名称。

Adding the stores and models to our application

添加存储和模型到应用

Now that we have defined our models and stores, it’s time to add them to our application. Let’s revisit our Application.js file.

现在我们已经定义好了模型和存储,该把它们添加到应用中了。再看一下Application.js文件:

app/Application.js


Ext.application({
    ...
    models: ['Station', 'Song'],    
    stores: ['Stations', 'RecentSongs', 'SearchResults']
    ...
});

Another advantage of using the new Ext JS 4 MVC package is that the Application will automatically load the stores and models defined in the stores and models configurations. Then, it will create an instance for each store loaded, giving it a storeId equal to its name. This allows us to use the name of the store whenever we bind it to a data component like we did in our views, e.g. store: 'SearchResults'.

新的Ext JS 4 MVC包中另一个优势是,应用能自动加载定义在stores和model属性中的存储和模型。然后,它将为每一个加载好的存储创建实例,分配一个和它的名称相同的storeId。这让我们在任何时候绑定到数据组件都能使用这个名称,就像我们在视图中做过的,比如:“SearchResults”。

Applying the glue 粘合到一起

粘合到一起

Now that we have our views, models and stores, it’s time to glue them together. You start by adding the views one by one to your viewport. This will make it easier to debug any wrong view configurations. Let’s go through the resulting viewport for the Panda app.

现在,我们有了视图、模型和存储。是时候合并了。先把视图一个接着一个添加到视口中。这让调试错误的视图配置更加容易。

让我们检查Panda应用的视口:


Ext.define('Panda.view.Viewport', {
    extend: 'Ext.container.Viewport',

Your Viewport class will usually want to extend Ext.container.Viewport. This will cause your app to take up all the available space in your browser window.

你的视口类通常继承自 Ext.container.Viewport,它使你的应用占据浏览器窗口的所有可用空间。


    requires: [
        'Panda.view.NewStation',
        'Panda.view.SongControls',
        'Panda.view.StationsList',
        'Panda.view.RecentlyPlayedScroller',
        'Panda.view.SongInfo'
    ],

We set up all the view dependencies in our viewport. This will allow us to use their xtypes, previously configured in our views using the alias property.

然后设置视口中所有的视图依赖。这让我们使用它们的xtypes属性,即早先在视图中用alias属性配置过的。


    layout: 'fit',
 
    initComponent: function() {
        this.items = {
            xtype: 'panel',
            dockedItems: [{
                dock: 'top',
                xtype: 'toolbar',
                height: 80,
                items: [{
                    xtype: 'newstation',
                    width: 150
                }, {
                    xtype: 'songcontrols',
                    height: 70,
                    flex: 1
                }, {
                    xtype: 'component',
                    html: 'Panda
Internet Radio' }] }], layout: { type: 'hbox', align: 'stretch' }, items: [{ width: 250, xtype: 'panel', layout: { type: 'vbox', align: 'stretch' }, items: [{ xtype: 'stationslist', flex: 1 }, { html: 'Ad', height: 250, xtype: 'panel' }] }, { xtype: 'container', flex: 1, layout: { type: 'vbox', align: 'stretch' }, items: [{ xtype: 'recentlyplayedscroller', height: 250 }, { xtype: 'songinfo', flex: 1 }] }] }; this.callParent(); } });

Since Viewport extends Container, and Containers can’t have docked items (yet), we have added a Panel as the single item of our viewport. We make this panel the same size as our viewport by defining a layout of fit.

In terms of architecture, one of the most important things to note here is the fact that we have not defined a layout-specific configuration in the actual views. By not defining properties like flex,width, height in the views, we can easily adjust the application’s overall layout in one single place, adding to the maintainability and flexibility of our architecture.

因为视口继承自容器(Container)类,并且容器还没已停靠的项目,我们已经添加了一个面板(Panel)作为视口的单独项。我们通过设置布局属性为“fit”让这个面板的大小和视口具有同样的尺寸。

基于架构的实施考虑,最重要的一点是注意到此时在实际视图中,我们还没有定义一个具体布局配置。通过不在视图中设置诸如 flex、width和width这样的属性,我们能简单地在一个地方调整应用整体布局。这能增强架构的可维护性和灵活性。

Application logic

应用逻辑

In Ext JS 3, we often added our application’s logic to the views themselves using handlers on buttons, binding listeners to subcomponents, and overriding methods on the views when extending them. However, just like you shouldn’t inline CSS styles in your HTML markup, it’s preferrable to separate the application’s logic from the view definitions. In Ext JS 4, we provide controlleres in the MVC package. They are responsible for listening to events fired by the views and other controllers, and for implementing application logic to act on those events. There are several benefits to this design.

One benefit is that your application logic is not bound to instances of views which means we can destroy and instantiate our views, as needed, while the application logic continues processing other things, like synchronizing data.

Additionally in Ext JS 3, you might have had many nested views, each adding layers of application logic. By moving the application logic to controllers, it is centralized, making it easier to maintain and change. Finally, the Controller base class provides you with lots of functionality, making it easier to implement your application logic.

在Ext JS 3中,我们经常添加应用逻辑到视图,它们本身使用按钮上的处理函数、绑定监听函数到子组件,还有当继承它们时覆写方法。尽管如此,就像你不应该在HTML标记中写内联CSS样式,从视图定义分离应用逻辑是更可取的做法。在Ext JS 4中,我们在MVC 包中提供了控制器。它们负责监听视图或其他控制器触发的事件,并且在那些事件实现应用逻辑。

这样设计有几点好处:

其一就是是你的应用逻辑不再绑定到视图实例,这意味着当应用逻辑持续处理其他事情的时候,比如同步数据,我们能按需销毁和实例化视图。

此外,在Ext JS 3中,你可能有过许多嵌套的视图,每个都添加了一层应用逻辑。通过把应用逻辑转移到控制器,这样就中心化了,使得应用更加容易维护和改变。

最后,控制器基类提供给你许多功能,让你处理应用逻辑更简单。

Creating our Controllers

创建控制器

Now that we have the basic architecture for our UI, models and stores set up, it’s time to get in control of our application. We planned to have two controllers, Station and Song, so let’s create the definitions for them.

现在我们有了UI的基本架构,模型和存储都搭建好了,轮到应用控制了。我们打算用两个控制器:Station和Song。现在定义它们:

app/controller/Station.js


Ext.define('Panda.controller.Station', {
    extend: 'Ext.app.Controller',
    init: function() {
        ...
    },
    ...
});

app/controller/Song.js


Ext.define('Panda.controller.Song', {
    extend: 'Ext.app.Controller',
    init: function() {
        ...
    },
    ...
});

When including the controllers in your application, the framework will automatically load the controller and call the init method on it. Inside the init method, you should set up listeners for your view and application events. In larger applications, you might want to load additional controllers at runtime. You can do this by using the getController method.

当在应用中引入控制器时,框架会自动加载控制器并且调用它上面调用init方法。在init方法内部,你应该为你的视图和应用事件设置监听器。在更大型的应用中,你可以会在运行时加载额外的控制器。你可以通过使用getController 来这样做:


someAction: function() {
    var controller = this.getController('AnotherController');
 
    // Remember to call the init method manually
    controller.init();
}

When you load additional controllers at runtime, you have to remember to call the init method on the loaded controller manually.

For the purposes of our example application, we’ll let the framework load and initialize our controllers by adding them to the controllers array in our application definition.

当你在运行时加载额外的控制器,你必须记住手动调用所加载控制器的init方法。

为了这个应用,我们通过把它们添加到controllers 数组,来让框架负责加载和初始化控制器。

app/Application.js


Ext.application({
    ...
    controllers: ['Station', 'Song']
});

Setting up listeners

设置监听器

Let’s start controlling some parts of our UI by using the control method inside of the controller’s init function.

现在,在控制器init函数内部使用control 方法来控制一部分UI:

app/controller/Station.js


...
init: function() {
    this.control({
        'stationslist': {
            selectionchange: this.onStationSelect
        },
        'newstation': {
            select: this.onNewStationSelect
        }
    });
}
...

The control method is passed an object where the keys are component queries. In our example, the component queries are just using the xtypes of our views. However, using these component queries, you can target very specific parts of your UI. To learn more about advanced component queries, you can refer to the API docs. Each query is bound to a listener configuration. Inside each listener configuration, we want to listen for the key which is the event name. The events available are the ones provided by the component that is targeted by your query. In this case, we use the selectionchange event provided by Grid (from which our StationsList view extends) and the select event provided by ComboBox (from which our NewStation view extends). To find out which events are available for a particular component, you can look in the events section available for each component in the API docs.

control 方法被传递到一个键为组件查询(component queries)的对象。我们的例子中,组件查询用是的视图中的xtypes属性。尽管如此,使用组件查询,你还是能非常具体地指定UI部分。要了解更多关于组件查询的信息,可以考虑 API 文档。 每个查询都绑定到一个监听器配置。在每个监听器配置内部,我们可以用事件名做键来监听事件。这些可用的事件是你的查询所指向的组件所提供的。在此例中,我们使用Grid(StationsList视图继承自它)提供的selectionchange 事件和ComboBox(NewStation视图继承自它) 提供的select事件。要找出对于一个特定的组件,哪个事件可用的话,你需要查看 API文档的事件章节。

ext-grid-panel.jpg

The value in the listener configuration is the function that gets executed whenever that event fires. The scope of this function is always the controller itself.

Let’s also set up some listeners in our Song controller.

监听器配置的值,是事件触发时被执行的函数。这个函数的作用域始终是控制器它自已。

现在设置Song控制器的监听器:

app/controller/Song.js


...
init: function() {
    this.control({
        'recentlyplayedscroller': {
            selectionchange: this.onSongSelect
        }
    });
 
    this.application.on({
        stationstart: this.onStationStart,
        scope: this
    });
}
...

In addition to listening for the selectionchange event on our RecentlyPlayedScroller view, we also set up a listener for an application event here. We do this by using the on method on the application instance. Each controller has access to the application instance using thethis.application reference.

Application events are extremely useful for events that have many controllers. Instead of listening for the same view event in each of these controllers, only one controller listens for the view event and fires an application-wide event that the others can listen for. This also allows controllers to communicate with one another without knowing about or depending on each other’s existence.

除了在RecentlyPlayedScroller视图上监听selectionchange 事件,我们还在这里监听了应用的事件。我们这里使用了application实例上的on方法。每个控制器都能通过this.application 引用获得application实例的访问权。

应用的事件对于事件有多个控制器的情况尤为有用。我们不用在这些控制器中为每个都去监听同一个视图事件,而只要一个控制器监听视图事件,并且触发应用层面的、其他控制器都可以监听到的事件就可以了。这也让控制器在不知道或不依赖相互之间的存在的情况下,可以互相通讯。

Our Song controller is interested in a new station being started because it needs to update the song scroller and song info whenever this happens.

Let’s take a look at how the Station controller, which will be the one responsible for firing thisstationstart application event, actually does this.

我们的Song控制器关心的是新电台的启动,因为它需要更新歌曲卷动组件还有歌曲的信息。

app/controller/Station.js


...
onStationSelect: function(selModel, selection) {
    this.application.fireEvent('stationstart', selection[0]);
}
...

We simply get the single selected item provided by the selectionchange event and pass it as the single argument when firing the stationstart event.

我们简单的获取由selectionchange 事件提供的单个选中的项目,并且当stationstart 事件触发时把它当作一个单独的参数。

Conclusion

结论

In this article, we have looked at the basic techniques of architecting your application. Of course, there is a lot to it, and in the next part of this series we will take a look at some more advanced controller techniques and continue wiring up our Panda app by implementing our controller actions and adding some more details to our views.

在此文中,我们考虑了设计应用的基本技巧。当然,还有很多没有谈到。这系列的下一篇中,我们将考虑一些更高级的控制器技巧,以及继续通过实现控制器行为和添加更多细节到视图,来编写Panda应用。

转载于:https://www.cnblogs.com/ambar/archive/2011/08/30/chinese-translation-architecting-your-app-in-ext-js-4-part-2.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值