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

译自:http://docs.sencha.com/ext-js/4-1/#!/guide/mvc_pt2

在前一篇Ext JS 架构的文章中,我们展开讨论了如何使用Ext JS来设计Pandora风格的程序架构。我们还看了下MVC架构和如何将它应用在相关的复杂界面的应用程序上,而且这些应用程序具有多个视图和模型。在本文中,我们开始实现应用程序,然后展开讨论如何设计和编写控制器和模型。我们先从Ext.application 方法和 Viewport 类开始吧.

定义我们的应用

在Ext JS 3 中,Ext.onReady 方法是程序的入口,然后程序员要自己设计app的架构。在  Ext JS 4中,我们有一个介绍个的类似MVC的模式,这个模式能帮助你按照最佳实践来创建你的app。

使用新的MVC框架时,你需要使用Ext.application 方法作为app的入口。这个方法会创建一个 Ext.app.Application 实例,然后在页面解析完后触发 lauch 方法。这样替代了使用Ext.onReady 来增加一些新功能, 例如自动创建一个viewport和设置命名空间。

app/Application.js
Ext.application({
    name: 'Panda',
    autoCreateViewport: true,
    launch: function() {
        // 当页面结构解析完后立刻执行
    }
});

配置name为 'Panda' 会创建一新的命名空间。所有views、models、 stores 和 controllers会在这个命名空间之下。设置autoCreateViewport 为true,则会按照默认的规则,引入app/view/Viewport.js文件。在这个文件里,应该要定义一个名为Panda.view.Viewport 的类。 Viewport符合name配置项所指定的命名空间

Viewport 类

当我们考虑我们的界面需要那些view的时候,我们会非常关注各个独立的部分。而一个 app 的 Viewport 则扮演这些独立部分的黏合剂。它加载这些必要的view然后定义一些完成整体布局所需的配置。我们非常确信,定义你的视图,然后把他们添加到viewport中,这是创建界面基本骨架的最快方式。

在构建viewport的过程中,非常重要的一点是,我们要关注如果把这些view组织搭建起来,而不是关注某个具体的view。就像雕刻一样,起初最重要的是雕出一个粗造的轮廓,之后再慢慢地处理局部。

创建积木

根据前面文章所做的功课,我们可以立马定义一些视图了。

均衡

app/view/NewStation.js
Ext.define('Panda.view.NewStation', {
    extend: 'Ext.form.field.ComboBox',
    alias: 'widget.newstation',
    store: 'SearchResults',
    ... 更多的配置 ...
});
app/view/SongControls.js
Ext.define('Panda.view.SongControls', {
    extend: 'Ext.Container',
    alias: 'widget.songcontrols',
    ... 更多的配置 ...
});
app/view/StationsList.js
Ext.define('Panda.view.StationsList', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.stationslist',
    store: 'Stations',
    ... 更多的配置 ...
});
app/view/RecentlyPlayedScroller.js
Ext.define('Panda.view.RecentlyPlayedScroller', {
    extend: 'Ext.view.View',
    alias: 'widget.recentlyplayedscroller',
    itemTpl: '<div></div>',
    store: 'RecentSongs',
    ... 更多的配置 ...
});
app/view/SongInfo.js
Ext.define('Panda.view.SongInfo', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.songinfo',
    tpl: '<h1>About </h1><p></p>',
    ... 更多的配置 ...
});

我们省略了某些配置,因为组件配置不在本文讨论范围。

在上面的配置中,你可以注意到我们有三个配置了stores。这些store名称的对应关系在前一篇文章中介绍过,现在我们继续创建这些store吧

商店

模型和存储

很多时候,在开发前期,用包含模拟数据的静态json文件作为后台数据是很好的。以后可以利用这些静态数据作为参考,实现真正的后台功能。

我们的app决定使用两个模型, Station 和 Song。我们同时需要三个使用这两个model的store,这三个store将会绑定到我们的数据组件 (电台列表,最近播放…)。每个store将会从后台加载它的数据,我们的模拟数据文件如下:

静态数据

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 &amp; Perquisite'},
        {'id': 6, 'name': 'Black Star'},
        {'id': 7, 'name': 'Macy Gray'}
    ]
}

模型

Ext JS 4 的模型跟 Ext JS 3中的 Records很像。一个关键的不同点是你可以给你的model指定一个代理,还可以指定一些验证和数据关联处理。我们app中的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'
        }
    }
});

你会发现,我们已经在model中定义了proxy配置,一般来说这是种好的方式,因它将允许你无需一个store的配合就可以加载和保存模型的实例。同时,当多个store使用同个model,你不必在每个store定义proxy。

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中,多个store可以共同使用同个数据模型, 即便这些store会从不同的地方加载他们的数据。在我们的案例中,Station模型将会被SearchResults 和 Stations 两个 store 使用, 并且他们分别从不同的地方加载数据, 前者加载返回搜索结果数据,后者加载返回用户喜爱的电台数据。要完成这些功能,则要在其中一个或者两个store中重新定义proxy (覆盖model中定义的proxy)。

app/store/SearchResults.js
Ext.define('Panda.store.SearchResults', {
    extend: 'Ext.data.Store',
    requires: 'Panda.model.Station',
    model: 'Panda.model.Station',

    // 覆盖model中定义的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 store 的定义中,我们通过提供一个不同的proxy定义来覆盖Station model 中的的 proxy 定义。store 的 proxy 会在调用 store 的 load 方法是使用,model 中的 proxy 就不起作用了。

当然,你可以在后台实现同一个API 同时提供搜索结果和用户喜爱电台两种数据,然后你的两个 store 都默认使用model 中定义proxy,在加载store的时候传不同的参数来使用同一个API获得不同数据。

最后我们来创建 RecentSongs store.

app/store/RecentSongs.js
Ext.define('Panda.store.RecentSongs', {
    extend: 'Ext.data.Store',
    model: 'Panda.model.Song',

    // 如果你不是使用Ext JS 4.0.5 或以上版本
    //这里要requires对应的模型
    requires: 'Panda.model.Song'
});

在4.0.5 以前的版本中,store 的 'model' 属性不会自动创建一个依赖到对应模型,所以我们指定了specify 配置,让它动态加载所需模型。

还有,一般我们尽量保持store的名字是复数形式,model的名字是单数。

往app添加store和model

我们已经定义好了 model 和 store, 现在把他们添加到应用程序中。再次回到Application.js

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

使用Ext JS 4 的mvc 的另一个好处是,程序会自动加载stores 配置和models 配置中的store和model。然后给每个加载的store创建一个实例,给定名字作为 store 的 id。这样方便我们随时随地使用store的名字将store绑定到数据组件中,就像我们在app/view/StationsList.js 中使用 store: 'SearchResults' 来绑定SearchResults store 到 StationsList view一样。

把他们粘起来

我们已经准备好视图、模型和存储,现在把他们整合起来。首先把 view 挨个添加到 viewport 中。这样就容易调试发现错误的视图配置。浏览一下我们Panda程序最终的viewport:

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

你的 Viewport 类一般继承 Ext.container.Viewport。这会使得你的app占据整个浏览器窗口的可用空间。

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

我们在 viewport 中创建所有的视图依赖。接着我们就可以使用他们的 xtype 来引用他们,xtype 对应之前 view 定义的 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<br>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();
    }
});

我们的 viewport 继承 Container,然而 Containers 还不支持 docked items,所以我们在viewport 下 增加一个 Panel 作为viewport 的唯一 item。通过定义layout 为 fit 来标识这个 panel 的尺寸跟我们的 viewport 一样。

从结构上来看,这里需要关注的重点之一是,我们目前虽然还没为具体的 view 定义布局配置 (诸如flex, width, height这些属性),然而,我们可以在这里一个地方轻松地调整整体布局, 这是很灵活且易于维护的。

业务逻辑

在 Ext JS 3 中,我们常常view 自身绑定点击事件处理器,或v其子类绑定监听器,来添加业务逻辑。然后,正如我们不应该在html中内嵌 css 样式一样,我们应该将业务逻辑从视图定义中分割出来。在 Ext JS 4中,我们提供了 mvc 中的 控制器,他们负责监听由视图或其他控制器触发的事件, 在这些事件处理器中实现业务逻辑,这样设计好处多多。

其中一个好处就是,你的业务逻辑将不再绑定到视图中,也意味着我们可以根据需要随时销毁和初始化我们的视图,而不会影响到我们的业务逻辑, 例如同步数据。

另外,在Ext JS 3中,你可能有很多内嵌的视图,每个视图都加了一层业务逻辑。如果将这些业务逻辑移到控制器,把他们集中起来,将会更容易维护和适应需求的变化。

最后,Controller 这个基类提供了很多功能,让你更容易实现你的业务逻辑。

创建控制器

我们已经创建了界面结构、模型和存储,现在是时候创建我们的控制器了。我们设计创建两个控制器,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');

    // 手动加载的控制器,记得手动调用init
    controller.init();
}

当你在运行时加载额外的控制器,你一定要记得手动调用这个加载的控制器的 init 方法。

在我们的案例中,我们会通过在Application 定义中增加一个controllers 数组的配置,使得框架自动加载和初始化我们的控制器。

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

创建监听器

我们开始在控制器的 init 方法里面控制某些UI。

app/controller/Station.js
...
init: function() {
    this.control({
        'stationslist': {
            selectionchange: this.onStationSelect
        },
        'newstation': {
            select: this.onNewStationSelect
        }
    });
}
...

调用 control 方法,传递一个对象作为参数,这个对象的 key 用作查询某个组件。在我们的案例中,使用 view 的 xtypes 来查询获得某个组件。使用组件查询的方式,你可以定位指定的某些UI, 更多高级的查询组件方式,参考 API 文档。

每个(查询到的)组件对应一个监听配置。在每个监听器配置里,我们要监听的事件名作为键。这些事件是由查询定位到的组件提供的。在这个案例中,我们使用 由 Grid (StationsList 视图继承自) 提供的 selectionchange 事件 和 ComboBox (NewStation 视图继承自) 提供的 select 事件。要查找某个组件提供了哪些事件,你可以在 API 文档中查阅每个组件提供了哪些事件。

 

API文档事件

监听器配置中 值 则是一些函数,当对应的事件触发的时候,这些函数就会被调用。函数运行过程中,this 指向配置项 scope 或控制器自身。

我们在 Song 控制器 中也注册一些监听器。

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

    this.application.on({
        stationstart: this.onStationStart,
        scope: this
    });
}
...

除了监听RecentlyPlayedScroller 的 selectionchange 事件以外,我们还注册 app 级别的事件处理器,通过使用 application 实例的 on 方法来实现。每个控制器都可以通过 this.application 引用 application 实例.

对于在多个控制器中使用的事件,使用Application 级别的事件是很好的。如果你不这么做,你就要在某些控制器注册同一个视图的同一个事件,如果你使用 app 级别的事件,你只需要在一个控制器注册这个视图的事件,然后触发一个 app 级别的事件消息,于是其他控制器也能监听到。同时,这样方便控制器在不知道彼此或依赖的情况下实现通信。

我们的 Song  控制器对新的电台 (stationstart 事件) 很感兴趣,因为他需要更新歌曲滚动面板和歌曲信息。

我们浏览一下 Station  控制器,它将负责触发 stationstart 的事件 (app 级别)。

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

我们简单地获取 selectionchange  事件提供的第一个选项,然后触发 stationstart  事件,并把第一个选项作为参数。

结尾

在这篇文章中,我们已经介绍了关于app架构的基本技术。当然,关于这方面的只是还有很多,接着在下一篇文章中,我讲带大家看一下高级的控制器技术,然后实现一些控制器 action 和添加更加详细的视图配置,来完成我们的 Panda 程序。

转载于:https://www.cnblogs.com/feipigzi/archive/2012/12/31/2840726.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值