現在,讓我們開始編寫應用。
定義應用
在ExtJS 3,Ext.onReady方法是應用程序和開發人員開始編寫應用架構的入口。在ExtJS 4,我們推出了類似MVC模式,該模式可以讓你在創建應用程序時遵循最佳做法。
新的MVC包要求使用Ext.application方法作爲入口,該方法將創建一個Ext.app.Application實例,並在頁面準備好以後觸發launch方法。它取代了在Ext.onReady內添加諸如創建Viewport和設置命名空間等功能的這種寫法。
app/Application.js
- Ext.application({
- name: 'Panda',
- autoCreateViewport: true,
- launch: function() {
- // This is fired as soon as the page is ready
- }
- });
配置項name將會創建一個命名空間。所有視圖、模型和Store和控制器都會以該命名空間爲命名。設置autoCreateViewport爲true,框架將字段加載app/view/Viewport.js文件。在該文件內,將會定義一個名稱爲Panda.view.Viewport的類,類名必須使用應用中name指定的命名空間。
The Viewport class
在UI中的視圖,都是獨立的部件,因而需要使用Viewport將它們粘合起來。它會加載要求的視圖及它們的定義需要的配置項,以實現應用的整體佈局。我們發現,通過定義視圖並將它們加載到viewprot,是創建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: '<div></div>',
- store: 'RecentSongs',
- ... more configuration ...
- });
app/view/SongInfo.js
- Ext.define('Panda.view.SongInfo', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.songinfo',
- tpl: '<h1>About </h1><p></p>',
- ... more configuration ...
- });
我們只是列了一些定義,組件的具體配置將不在本文進行討論。
在上述配置,可看到使用了三個Store,這些Store的名稱都是上一篇文章定義的。現在,我們開始創建Store。
模型和Store
通常,在初始階段,以包含數據的靜的json文件作爲服務器端是相當有用。以後,這些靜態文件可以作爲實際的服務器端動態文件的參考。
在當前應用,要使用Station和Song兩個模型,還需要定義使用這兩個模型並綁定到數據組件的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 & Perquisite'},
- {'id': 6, 'name': 'Black Star'},
- {'id': 7, 'name': 'Macy Gray'}
- ]
- }
模型
ExtJS 4中的模型類似ExtJS 3中的記錄,它們主要的區別是,可以在模型上定義代理、驗證和關聯。應用中的模型Song代碼如下:
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'
- }
- }
- });
可以看到,在模型中定義了代理,這是一個好的方式,這樣,就可以直接在模型加載和保存模型實例,而不需要經過Store。而且,當有多個Store使用該模型的時候,也不需要重新爲每個Store定義一次代理。
下面繼續定義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'
- }
- }
- });
Store
在ExtJS 4,多個Store可以使用相同的數據模型,即使Store需要從不同的來源加載數據。在實例中,模型Station將會在SearchResults和Stations這兩個Store中使用,而它們會從不同的位置加載數據。其中,SearchResults將返回返回搜索結果,而另一個則返回用戶收藏的Station。爲了實現這一點,其中一個Store需要重寫模型中定義的代
理。
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模型中代理的定義,當調用Store的load方法時,其代理將調用Store的的代理以替換模型中定義的代理。
當然,也可以在服務器端使用相同的接口實現返回搜索結果和用戶收藏的station,這樣,兩個Store就可以使用模型中定義的默認代理了,只是加載數據時請求的參數不同。
接着創建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'
- });
要注意,在當前版本的ExtJS中,在Store中的model屬性還不能自動創建一個依賴,因而需要通過requires配置項指定模型以便能夠實現動態加載模型。
另外,根據約定,Store的類名要使用複數,而模型的類名要使用單數。
增加Store和模型到應用
定義好模型和Store後,現在可以把它們加到應用裏。打開Application.js並添加如下代碼:
app/Application.js
- Ext.application({
- ...
- models: ['Station', 'Song'],
- stores: ['Stations', 'RecentSongs', 'SearchResults']
- ...
- });
使用ExtJS 4 MVC包的另外一個好處是,應用會自動加載在stores和models中定義的Store和模型。當Store加載後,會爲每個Store創建一個實例,並將其名稱作爲storeId。這樣,就可以使用該名稱將其綁定到視圖中數據組件,例如“SearchResults”。
應用粘合劑
在開始的時候,可以將視圖逐一加到Viewport,這樣比較容易調試出視圖錯誤的配置。先在Panda應用中構建Viewport:
- Ext.define('Panda.view.Viewport', {
- extend: 'Ext.container.Viewport',
通常,應用的Viewport類擴展自Ext.container.Viewport,這會讓應用把瀏覽器的可用空間作爲應用的空間。
- requires: [
- 'Panda.view.NewStation',
- 'Panda.view.SongControls',
- 'Panda.view.StationsList',
- 'Panda.view.RecentlyPlayedScroller',
- 'Panda.view.SongInfo'
- ],
這裏要設置viewprot依賴的視圖, 這樣,就可以使用視圖中通過alias屬性定義的名稱作爲xtype配置項的值來定義視圖。
- 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,而Container沒有停靠項,因而必須添加一個面板作爲viewport的第一個子組件,並讓使用fit佈局讓面板與viewprot有相同的大小。
在架構方面,需要特別注意的是,不要在實際視圖中定義有具體配置的佈局,例如,不要定義flex、width或height等配置項,這樣,就可以很容易的調整其在應用程序中某個位置的整體佈局,從而增加架構的可維護性和靈活性。
應用邏輯
在ExtJS 3,通常會使用按鈕的句柄、綁定監聽時間到子組件或擴展時重寫其方法以實現應用邏輯。然後,就像不應該在HTML標記中使用內聯CSS樣式一樣,不應該在視圖中定義應用邏輯。在ExtJS 4,通過MVC包提供的控制器,可響應視圖或其它控制器中的監聽事件,並執行這些事件對應的應用邏輯。這樣的設計有及格好處。
第一個好處是,應用邏輯不用綁定到視圖實例,這意味着在需要時,可以在視圖實例化或銷燬時,應用邏輯可以繼續處理其它事情,如同步數據。
另外,在ExtJS 3,會有許多嵌套視圖,而每一個都會添加一層應用邏輯。當將應用邏輯移動到控制器,將它們集中起來,這樣就易於維護和修改。
最後,控制器基類會提供一些功能,讓控制器易於執行應用邏輯。
創建控制器
現在,已經有了UI的基本架構、模型和Store的配置,是時候爲應用定義控制器了。我們計劃定義兩個控制器,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
- ...
- <span class="me1">init</span><span class="sy0">:</span> <span class="kw2">function</span><span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
- <span class="kw1">this</span>.<span class="me1">control</span><span class="br0">(</span><span class="br0">{</span>
- <span class="st0">'stationslist'</span><span class="sy0">:</span> <span class="br0">{</span>
- selectionchange<span class="sy0">:</span> <span class="kw1">this</span>.<span class="me1">onStationSelect</span>
- <span class="br0">}</span><span class="sy0">,</span>
- <span class="st0">'newstation'</span><span class="sy0">:</span> <span class="br0">{</span>
- select<span class="sy0">:</span> <span class="kw1">this</span>.<span class="me1">onNewStationSelect</span>
- <span class="br0">}</span>
- <span class="br0">}</span><span class="br0">)</span><span class="sy0">;</span>
- <span class="br0">}</span>
- ...
方法control會通過對象的關鍵字查詢組件。在示例中,會通過視圖的xtypes配置項查詢組件。然而,使用組件查詢,必須確保能指向UI中的知道部件。想了解更多有關組件查詢的信息,可訪問這裏。
每一個查詢都會綁定一個監聽配置對象,在每個監聽配置對象內,事件名就是監聽對象的關鍵字,而事件是由查詢的目標組件提供的。在示例中,將使用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需要知道新的station是否已開始,以便更新song滾動條和song信息。
現在在Station控制器內,編寫當應用事件stationstart觸發時的響應代碼:
app/controller/Station.js
- ...
- onStationSelect: function(selModel, selection) {
- this.application.fireEvent('stationstart', selection[0]);
- }
- ...
這裏只是簡單在觸發stationstart事件時,將selectionchange事件中的第一個選擇項作爲唯一參數傳遞給事件。
小結
在這篇文章中,可以瞭解到應用架構的基本技術。當然,這只是其中的一小部分,在該系列的下一部分,將可以看到一些更高級的控制器技術,並繼續在控制器中實現操作,以及在視圖中添加細節,以完成Panda應用。