原文地址:http://docs.sencha.com/extjs/4.0.7/#!/guide/mvc_pt2
【翻译 by 明明如月 QQ 605283073 本章节配套项目代码将在第3节给出】
上一节:Ext JS 4 架构你的应用 第1节 (官方文档翻译)
下一节:Ext JS4 架构你的应用 第3节 (官方文档翻译)
在上一篇文章中我们介绍了,怎样基于Ext JS构建一个 潘多拉风格的应用。
让我们探讨一下Model-View-Controller(模型-视图-控制器)架构以及怎样将其应用到相对复杂一点的
含有多个视图和模型的UI应用中。
定义你的应用
在Ext JS 3中 Ext.onReady是应用的入口,开发者也不得不自己设计一个应用架构。
在 Ext JS 4 中我们介绍了一种类似MVC模式的架构。这个模式将帮助我们创建应用的最佳实践。
通过新的MVC包编写应用的切入点使用的是 Ext.application方法。该方法将为你创建一个Ext.app.Application 实例。
页面加载完成以后将会触发此启动方法。
应该用此方法来代替以前的 Ext.onReady ,在此方法中添加自动创建一个viewport和设置命名空间(namespace).
app/Application.js
代码语言:javascript复制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的类应该在此文件中定义。
Viewport 类
当我们思路我们这个UI需要哪些视图的时候,我们关注的是每个部分。Viewport 所扮演的是应用中各个视图部分的粘合剂。
它加载应用布局所需的各种视图。我们发现渐进式地来定义你的视图并且将其加入viewport中是构建你UI的最快方式。
创建一个构建块
通过利用前面文章介绍的内容,我们可以一次定义出多个视图。
app/view/NewStation.js
代码语言:javascript复制Ext.define('Panda.view.NewStation', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.newstation',
store: 'SearchResults',
... more configuration ...
});
app/view/SongControls.js
代码语言:javascript复制Ext.define('Panda.view.SongControls', {
extend: 'Ext.Container',
alias: 'widget.songcontrols',
... more configuration ...
});
app/view/StationsList
代码语言:javascript复制Ext.define('Panda.view.StationsList', {
extend: 'Ext.grid.Panel',
alias: 'widget.stationslist',
store: 'Stations',
... more configuration ...
});
app/view/RecentlyPlayedScroller.js
代码语言:javascript复制Ext.define('Panda.view.RecentlyPlayedScroller', {
extend: 'Ext.view.View',
alias: 'widget.recentlyplayedscroller',
itemTpl: '',
store: 'RecentSongs',
... more configuration ...
});
app/view/SongInfo.js
代码语言:javascript复制Ext.define('Panda.view.SongInfo', {
extend: 'Ext.panel.Panel',
alias: 'widget.songinfo',
tpl: 'About ',
... more configuration ...
});
这里我们忽略了组件的一些配置,因为这些配置和本文核心内容无关。
在上面的配置中,你可能会注意到我们配置了3个存储。
models 和 stores
通常来说在服务端我们可以使用静态json文件里的模拟的数据。
以后我们可以参考静态json文件的内容去实现服务端的动态数据处理。
对于我们的应用,我们决定使用两个模型Station和Song.我们也需要为这两个模型创建3个存储来绑定到数据组件上。
每个存储都将从服务端来获取数据。模拟的数据文件类似下面的内容。
静态数据
data/songs.json
代码语言:javascript复制{
'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
代码语言:javascript复制{
'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
代码语言:javascript复制{
'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(模型)
在Ext JS4 中的模型和 Ext JS 3中的Records (记录)非常像。
一个关键不同点是在模型中你可以定制一个代理、校验和关联。
在Ext JS4中Song 模型类似如下内容:
app/model/Song.js
代码语言:javascript复制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'
}
}
});
你可以看到我们为模型定义了一个代理(proxy )。
这一般说来是一个比较好的时间,它允许你在不需要存储(store)的条情况下获取和保存模型实例。
我们接着定义Station 类。
app/model/Station.js
代码语言:javascript复制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(存储)
Ext JS 4中,多个存储可以使用同一个数据模型,甚至存储还可以从不同的资源来加载数据。
在我们的例子中 Station 模型将被 SearchResults 和 Stations存储使用,分别从不同的地方加载数据。
一个是返回搜索结果,一个是返回用户喜爱的站点的数据。为了达到这个目的,我们的存储需要重写模型中定义的Proxy。
app/store/SearchResults.js
代码语言:javascript复制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
代码语言:javascript复制Ext.define('Panda.store.Stations', {
extend: 'Ext.data.Store',
requires: 'Panda.model.Station',
model: 'Panda.model.Station'
});
app/store/RecentSongs.js
代码语言:javascript复制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 来动态加载模型的原因。
同样地,根据惯例,我们经常将store的名字写成复数的形式,而把模型名写成单数形式。
为我们的应用添加 stores (存储)和models(模型)
app/Application.js
代码语言:javascript复制Ext.application({
...
models: ['Station', 'Song'],
stores: ['Stations', 'RecentSongs', 'SearchResults']
...
})
Ext JS 4 MVC 包的另外一个优势是应用将自动加载在application中配置的模型和存储。
应用粘合剂(viewport)
目前为止views, models 和stores都有了,我们需要将其粘合在一起。我们将一个个视图添加到viewport中。
这对于调试任何一个错误的视图配置非常容易。
代码语言:javascript复制Ext.define('Panda.view.Viewport', {
extend: 'Ext.container.Viewport',
Viewport 类通常拓展自 Ext.container.Viewport 这就使得你的应用可以占据浏览器窗口的所有可用空间。
代码语言:javascript复制 requires: [
'Panda.view.NewStation',
'Panda.view.SongControls',
'Panda.view.StationsList',
'Panda.view.RecentlyPlayedScroller',
'Panda.view.SongInfo'
],
我们也设置了viewport的视图依赖。这里允许我们使用之前在视图中定义好的别名作为xtype的值。
代码语言:javascript复制 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();
}
});
由于 Viewport拓展自Container,Container(容器)还不能有停驻元素,我们添加一个面板作为viewport的唯一元素。
由于viewport中laylout(布局)设置为fit,这个面板将和viewport的尺寸相同。
我们在视图中没有定义flex, width, height等属性。因此我们可以非常容易的在一个地方就可以调整应用的整体布局,增加了架构的可维护性和灵活性。
应用逻辑
在 Ext JS 3中,我们通常将应用的逻辑添加在视图的按钮处理器方法中,绑定子组件和拓展视图时重写拓展视图的方法。
然而正如你不应该在HTML里面写内联的CSS一样,我们也应该将视图和应用的逻辑分开。
Ext JS 4中我们在MVC包里提供了controlleres(控制器) 类。
他们负责监听来之视图或者其他控制器的事件,并且实现对应事件的逻辑。这样的设计将带来很多好处。
一个好处是,你的应用逻辑不绑定视图实例。
另外在Ext JS 3中,你有也许要嵌套多个视图,每个视图都添加应用逻辑。通过将应用逻辑移到控制器,变得更加集中,使得应用的维护和修改变得更加容易。
创建 Controllers(控制器)
app/controller/Station.js
代码语言:javascript复制Ext.define('Panda.controller.Station', {
extend: 'Ext.app.Controller',
init: function() {
...
},
...
});
app/controller/Song.js
代码语言:javascript复制Ext.define('Panda.controller.Song', {
extend: 'Ext.app.Controller',
init: function() {
...
},
...
});
当你的应用中包含了这些控制器,框架将自动加载这个控制器并且调用功能init方法。
在init方法中,你应该设置对视图和应用事件的监听器。
在大型的应用中,你也许希望在运行时再加载额外的控制器。你可以通过getController 方法来实现。
代码语言:javascript复制someAction: function() {
var controller = this.getController('AnotherController');
// Remember to call the init method manually
controller.init();
}
当你在运行时想加载外部控制器,你一定要记住手动去调用init方法。
对我们的例子来说,如果我们想让框架加载和初始化我们的控制器,我们需要将其添加到application 的控制器数组中。
app/Application.js
代码语言:javascript复制Ext.application({
...
controllers: ['Station', 'Song']
});
设置监听器
代码语言:javascript复制...
init: function() {
this.control({
'stationslist': {
selectionchange: this.onStationSelect
},
'newstation': {
select: this.onNewStationSelect
}
});
}
...
control方法传递一个组件必须的对象。在我们例子里,组件只需要简单的使用视图的xtype属性。
每个查询都绑定一个监听器配置。 在每个监听器配置里,我们想要监听事件名。
如果想要了解有哪些可用的事件可以查询API 文档搜索events(事件)部分。
监听器配置的值是一个当事件发生时执行的方法。这个方法的范围一般是控制器自身。
app/controller/Song.js
代码语言:javascript复制...
init: function() {
this.control({
'recentlyplayedscroller': {
selectionchange: this.onSongSelect
}
});
this.application.on({
stationstart: this.onStationStart,
scope: this
});
}
...
除了RecentlyPlayedScroller视图中监听selectionchange 事件,我们也为应用事件设置了一个监听器。
我们通过application实例的on方法来实现。
每个控制器使用 this.application来访问application 实例。
Application 事件对于事件和多个控制器对应的情况下非常有用。
不是在每个控制器中都监听同一个视图事件,只有一个控制器监听此视图事件和出发一个应用范围的事件,其他控制器来监听。
这也允许控制器在不知道或者不相互依赖的情况下彼此交互。
我们的Song 控制器对新创建站点比较敏感,因为当创建新的站点时它需要更新song滚动条和歌曲信息。
我们看一下Station 控制器:
app/controller/Station.js
代码语言:javascript复制..
onStationSelect: function(selModel, selection) {
this.application.fireEvent('stationstart', selection[0]);
}
...
我们只是简单地获取selectionchange 事件提供的单个选择项然后将其作为触发stationstart 事件的一个参数。
结论
在本文中,我们了解了应用的基本架构。下一节我们将了解更高级的控制器技术,通过实现我们的控制器行为和为视图添加更多的细节继续拓展我们的潘多拉应用。