使用E xt JS 4搭建APP架构(第三部分)

译自:http://www.sencha.com/learn/architecting-your-app-in-ext-js-4-part-3/

在前面两篇文章中,我们已经展开讨论了使用ExtJs4来创建Pandora风格的体系结构。我们从对复杂的用户界面程序使用mvc模式,包含了很多视图、存储和模型。我们介绍了应用架构的基本技术,像在控制器中控制你的视图或是触发所有控制器都能监听到的app级别的事件。在这篇文章中,我们继续实现程序内基于mvc架构的控制器逻辑。

回顾一下

在我们继续实现我们的程序之前,我们首先要看看Ext JS 4提供的一些更加好用的功能。在前面的些列文章中,我们讲解了如何通过在 Ext.application 配置中添加 stores 和 models 数组来自动加载store 和 model。我们也解释了使用这种方式会为每个加载的 store 创建实例,默认id 为他们的名字。

回顾一下

在我们继续实现我们的程序之前,我们首先要看看Ext JS 4提供的一些更加好用的功能。在前面的些列文章中,我们讲解了如何通过在 Ext.application 配置中添加 stores 和 models 数组来自动加载store 和 model。我们也解释了使用这种方式会为每个加载的 store 创建实例,默认id 为他们的名字。

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

配置 models 和 stores 除了会让程序自动加载和实例化这些类以外,还会自动的创建对应的get 方法来获取他们的实例引用,对于controller 和 view 也是一样的。这意
味着,要获得 Stations 控制器中获得它的 store,只需要把它的store 添加到配置项 stores 数组中。

app/controller/Station.js
...
stores: ['Stations'],
...

现在,我们可以再控制器的任何地方通过自动生成的get方法 getStationsStore 来获得 Stations 的 store 的引用。这种规则是很直接遍历的。:

views: ['StationsList'] // creates getter named 'getStationsListView' -> returns reference to StationsList class
models: ['Station']     // creates getter named 'getStationModel'     -> returns reference to Station model class
controllers: ['Song']   // creates getter named 'getSongController'   -> returns the Song controller instance
stores: ['Stations']    // creates getter named 'getStationsStore'    -> returns the Stations store instance

这里有一点很重要,视图和模型的get方法返回类 (Class) 的引用,而存储和控制器返回的是具体的实例引用。

 

视图实例

前面说到存储、模型、控制器和视图通过配置会自动创建get*方法,这使得我们很方便的获得他们的引用。getStationsListView() 将会返回这个视图类的class引用。在我们的业务流程中,我们希望选在电台列表的第一项,在这种需求下,我们需要的不仅仅是一个视图的类的引用,我们更需要StationsList 在 viewport 中的视图实例引用。

在 Ext JS 3 中,有一种很普遍的方法来获取页面上某个组件的实例,就是通过 Ext.getCmp 方法。在Ext JS 4 中,这个方法依然有效,但不建议您使用。因为使用这个方法需要你提供一个组件的具体id。在新的MVC框架包中,我们可以通过使用 ComponentQuery 这个新方法,在控制器里面获得一个试图的实例引用。

app/controller/Station.js
...
refs: [{
    // A component query
    selector: 'viewport > #west-region > stationslist',
    ref: 'stationsList'
}]
...

通过 refs 配置项,你可以建立视图实例的引用,这样就可以在控制器的交互里面获得和维护页面上的组件。而如何描述该引用指向的具体引组件,则要在selector 配置项中使用一个组件查询 (ComponentQuery). ref属性值是在创建 get 方法作为 get* 方法名的后半部分。例如,通过配置 ref : 'stationsList' ,则会生成控制器的一个名为 getStationsList 的方法。当然,如果你没有配置refs ,你还是可以通过使用 Ext.getCmp 来获得你想要的组件实例,如果你要用 getCmp,则就要你自己去管理整个程序中的组件id,随着项目的扩大会出现很多问题,所以这是很不推荐的。

要记住,无论视图是否能在页面中找到,这些不存在的视图的get方法依然会创建。当你首次调用getXxx方法,对应的选择器成功在页面上匹配到一个组件后,这个结果会被缓存起来,下次调用就会快一点。如果匹配不到组件,则方法返回null。这意味着,如果你的逻辑是建立在某个视图上,而且这个视图可能还未创建到页面上,你就需要在你的逻辑处理中检查一下,要确保getXxxx方法返回一个正确的结果。另外,如果选择器匹配多个组件,只有第一个匹配到的组件会被返回,因此,选择器最好对应单独的组件。最后,当你销毁一个正在引用的组件后,调用getXxx获得这个组件则会返回null,除非选择器能匹配别的组件。

在application开始时分清控制层级

当程序启动时,我们想要加载用户的电台。要完成这个需求,当然你可以使用 onReady 方法,然后把这部分业务逻辑放在里面,不过,MVC 架构提供了 onLauch 方法,他会在所有的控制器、模型和存储实例化后且视图外框绘制后立刻执行。这种方式清晰的把全局应用逻辑和控制器具体的逻辑分开。

第一步
app/controller/Station.js
...
onLaunch: function() {
    // Use the automatically generated getter to get the store
    var stationsStore = this.getStationsStore();
    stationsStore.load({
        callback: this.onStationsLoad,
        scope: this
    });
}
...

把Station store的 load 方法在 Station 控制器的onLauch 方法里面调用,这看起来是最好的调用方式。正如你所见,我们配置了一个callback 回调方法,一旦store 加载完后就会执行这个回调函数。

第二步
app/controller/Station.js
...
onStationsLoad: function() {
    var stationsList = this.getStationsList();
    stationsList.getSelectionModel().select(0);
}
...

在这个回调中,我们通过自动生成的 getStationList 方法来获得 StationList 视图实例的引用,然后选择第一个选项。这会触发一个在 StationsList 视图触发 selectionchange 事件

第三步
app/controller/Station.js
...
init: function() {
    this.control({
        'stationslist': {
            selectionchange: this.onStationSelect
        },
        ...
    });
},

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

当你有多个控制器都对同一个事件很感兴趣,这时候app级别的事件就非常有用了。相比在每个控制器监听同个view的同个事件,使用app级别的事件则只需要在一个控制器内监听这个view的事件,然后触发一个app级别的事件,这样其他控制器也能够监听到这个时间。这也是的多个控制器之间无需知道彼此存在而实现通信。在 onStationSelect 的交互中我们触发了一个 stationstart 的app 级别的事件。

第四步
app/controller/Song.js
...
refs: [{
    ref: 'songInfo',
    selector: 'songinfo'
}, {
    ref: 'recentlyPlayedScroller',
    selector: 'recentlyplayedscroller'
}],

stores: ['RecentSongs'],

init: function() {
    ...
    // We listen for the application-wide stationstart event
    this.application.on({
        stationstart: this.onStationStart,
        scope: this
    });
},

onStationStart: function(station) {
    var store = this.getRecentSongsStore();

    store.load({
        callback: this.onRecentSongsLoad,
        params: {
            station: station.get('id')
        },
        scope: this
    });
}
...

在Song 控制器的init 方法中,我们注册了 stationstart 这个 app事件的处理器,当这个事件触发的时候,我们需要属于电台的音乐家在到我们的 RecentSongs store中。我们在 onStationStart 方法中完成这个功能。我们还需要获得 RecentSongs store 的引用和调用它的load 方法,这些业务功能都是在控制器加载后第一时间定义。

第五步
app/controller/Song.js
...
onRecentSongsLoad: function(songs, request) {
    var store = this.getRecentSongsStore(),
        selModel = this.getRecentlyPlayedScroller().getSelectionModel();

    selModel.select(store.last());
}
...

当电台对应的歌曲都加载到 RecentSongs store后,我们选择最 RecentlyPlayedScroller 的最后一首歌曲。我们通过获得 RecentlyPlayedScroller的选择模型 dataview 然后调用select方法并传入 RecentSongs store的最后一个记录作为参数。

第六步
app/controller/Song.js
...
init: function() {
    this.control({
        'recentlyplayedscroller': {
            selectionchange: this.onSongSelect
        }
    });
    ...
},

onSongSelect: function(selModel, selection) {
    this.getSongInfo().update(selection[0]);
}
...

当我们在滚动面板中选择最后一首歌的时候,我们触发了一个 selectionchagne 事件。在控制器里面,我们已经注册了这个事件的处理器 onSongSelect 方法,我们在这个方法里面更新 SongInfo视图的信息。

开始一个新的电台

现在,你就很容易实现别的功能了吧。向下面代码这样增加创建一个新电台的逻辑:

app/controller/Station.js
...
refs: [{
    ref: 'stationsList',
    selector: 'stationslist'
}],

init: function() {
    // Listen for the select event on the NewStation combobox
    this.control({
        ...
        'newstation': {
            select: this.onNewStationSelect
        }
    });
},

onNewStationSelect: function(field, selection) {
    var selected = selection[0],
        store = this.getStationsStore(),
        list = this.getStationsList();

    if (selected && !store.getById(selected.get('id'))) {
        // If the newly selected station does not exist in our station store we add it
        store.add(selected);
    }

    // We select the station in the Station list
    list.getSelectionModel().select(selected);
}
...

总结

我们已经阐述了使用控制器的高级技术来分离业务逻辑和视图界面,程序的架构变得容易理解和维护。现在,我们的案例程序的功能挺丰富的,我们可以搜索和增加电台,然后通过点击选择开始某个电台,然后对应这个电台的歌曲会被加载,然后显示歌曲和歌手的信息。

在该系列的下一篇文章中,我们会把程序做得更好,然后关注样式和自定义组建的创建。

案例项目源码下砸

posted @ 2013-01-02 01:29  追阿史  阅读(231)  评论(0编辑  收藏  举报