现在的位置: 首页 > 综合 > 正文

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

2013年07月22日 ⁄ 综合 ⁄ 共 18120字 ⁄ 字号 评论关闭
文章目录

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

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

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

定义应用

在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
    }
});

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

视口类

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

生成构建块

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

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 ... });

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

模型和存储

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

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

静态数据

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'}
    ]
}

模型

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'
        }
    }
});

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

现在开始定义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'
        }
    }
});

存储

在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'
});

在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'
});

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

添加存储和模型到应用

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

app/Application.js


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

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

粘合到一起

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

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


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

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


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

然后设置视口中所有的视图依赖。这让我们使用它们的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(); } });

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

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

应用逻辑

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

这样设计有几点好处:

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

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

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

创建控制器

现在我们有了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() {
        ...
    },
    ...
});

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


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

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

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

app/Application.js


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

设置监听器

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

app/controller/Station.js


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

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

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

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

app/controller/Song.js


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

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

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

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

app/controller/Station.js


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

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

结论

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

抱歉!评论已关闭.