ExtJS 4應用架構設計 (一)

在《ExtJS應用架構設計》一文,我們探討了如何使用ExtJS構建一個潘多拉風格的應用程序。我們採用了MVC架構,並將它應用到一個比較複雜的用戶界面,應用中帶有多個視圖和模型。在這篇文章中,我們將在架構的基礎上繼續探討控制和模型的設計與代碼問題,並開始使用Ext.application和Viewprot類。

         現在,讓我們開始編寫應用。

         定義應用

        在ExtJS 3,Ext.onReady方法是應用程序和開發人員開始編寫應用架構的入口。在ExtJS 4,我們推出了類似MVC模式,該模式可以讓你在創建應用程序時遵循最佳做法。

        新的MVC包要求使用Ext.application方法作爲入口,該方法將創建一個Ext.app.Application實例,並在頁面準備好以後觸發launch方法。它取代了在Ext.onReady內添加諸如創建Viewport和設置命名空間等功能的這種寫法。

         app/Application.js

[javascript] view plaincopy
  1. Ext.application({  
  2.     name: 'Panda',      
  3.     autoCreateViewport: true,  
  4.     launch: function() {  
  5.         // This is fired as soon as the page is ready  
  6.     }  
  7. });  

        配置項name將會創建一個命名空間。所有視圖、模型和Store和控制器都會以該命名空間爲命名。設置autoCreateViewport爲true,框架將字段加載app/view/Viewport.js文件。在該文件內,將會定義一個名稱爲Panda.view.Viewport的類,類名必須使用應用中name指定的命名空間。


        The Viewport class

        在UI中的視圖,都是獨立的部件,因而需要使用Viewport將它們粘合起來。它會加載要求的視圖及它們的定義需要的配置項,以實現應用的整體佈局。我們發現,通過定義視圖並將它們加載到viewprot,是創建UI基本結構最快的方法。

        重要的是,這個過程的重點在搭建視圖,而不是單個視圖本身,就如雕刻一樣,先開始創建視圖的粗糙形狀,然後細化它們。

        創建構造塊

        利用前文中的工作,我們現在可以開始定義視圖了。


app/view/NewStation.js

[javascript] view plaincopy
  1. Ext.define('Panda.view.NewStation', {  
  2.     extend: 'Ext.form.field.ComboBox',  
  3.     alias: 'widget.newstation',  
  4.     store: 'SearchResults',  
  5.     ... more configuration ...  
  6. });  

app/view/SongControls.js
[javascript] view plaincopy
  1. Ext.define('Panda.view.SongControls', {  
  2.     extend: 'Ext.Container',  
  3.     alias: 'widget.songcontrols',  
  4.     ... more configuration ...  
  5. });  

app/view/StationsList
[javascript] view plaincopy
  1. Ext.define('Panda.view.StationsList', {  
  2.     extend: 'Ext.grid.Panel',  
  3.     alias: 'widget.stationslist',  
  4.     store: 'Stations',  
  5.     ... more configuration ...  
  6. });  

app/view/RecentlyPlayedScroller.js
[javascript] view plaincopy
  1. Ext.define('Panda.view.RecentlyPlayedScroller', {  
  2.     extend: 'Ext.view.View',  
  3.     alias: 'widget.recentlyplayedscroller',  
  4.     itemTpl: '<div></div>',  
  5.     store: 'RecentSongs',  
  6.     ... more configuration ...  
  7. });  

app/view/SongInfo.js
[javascript] view plaincopy
  1. Ext.define('Panda.view.SongInfo', {  
  2.     extend: 'Ext.panel.Panel',  
  3.     alias: 'widget.songinfo',      
  4.     tpl: '<h1>About </h1><p></p>',  
  5.     ... more configuration ...  
  6. });  

        我們只是列了一些定義,組件的具體配置將不在本文進行討論。

        在上述配置,可看到使用了三個Store,這些Store的名稱都是上一篇文章定義的。現在,我們開始創建Store。


        模型和Store

        通常,在初始階段,以包含數據的靜的json文件作爲服務器端是相當有用。以後,這些靜態文件可以作爲實際的服務器端動態文件的參考。

        在當前應用,要使用Station和Song兩個模型,還需要定義使用這兩個模型並綁定到數據組件的Store。每個Store都會從服務器端加載數據,模擬的數據文件格式如下:

        靜態數據

data/songs.json

[javascript] view plaincopy
  1. {  
  2.     'success'true,  
  3.     'results': [  
  4.         {  
  5.             'name''Blues At Sunrise (Live)',   
  6.             'artist''Stevie Ray Vaughan',   
  7.             'album''Blues At Sunrise',   
  8.             'description''Description for Stevie',   
  9.             'played_date''1',  
  10.             'station': 1  
  11.         },  
  12.         ...  
  13.     ]  
  14. }  

data/stations.json
[javascript] view plaincopy
  1. {  
  2.     'success'true,  
  3.     'results': [  
  4.         {'id': 1, 'played_date': 4, 'name''Led Zeppelin'},   
  5.         {'id': 2, 'played_date': 3, 'name''The Rolling Stones'},   
  6.         {'id': 3, 'played_date': 2, 'name''Daft Punk'}  
  7.     ]  
  8. }  

data/searchresults.json
[javascript] view plaincopy
  1. {  
  2.     'success'true,      
  3.     'results': [  
  4.         {'id': 1, 'name''Led Zeppelin'},   
  5.         {'id': 2, 'name''The Rolling Stones'},   
  6.         {'id': 3, 'name''Daft Punk'},  
  7.         {'id': 4, 'name''John Mayer'},   
  8.         {'id': 5, 'name''Pete Philly & Perquisite'},   
  9.         {'id': 6, 'name''Black Star'},  
  10.         {'id': 7, 'name''Macy Gray'}  
  11.     ]  
  12. }  

        模型

         ExtJS 4中的模型類似ExtJS 3中的記錄,它們主要的區別是,可以在模型上定義代理、驗證和關聯。應用中的模型Song代碼如下:

app/model/Song.js

[javascript] view plaincopy
  1. Ext.define('Panda.model.Song', {  
  2.     extend: 'Ext.data.Model',  
  3.     fields: ['id''name''artist''album''played_date''station'],  
  4.    
  5.     proxy: {  
  6.         type: 'ajax',  
  7.         url: 'data/recentsongs.json',  
  8.         reader: {  
  9.             type: 'json',  
  10.             root: 'results'  
  11.         }  
  12.     }  
  13. });  

        可以看到,在模型中定義了代理,這是一個好的方式,這樣,就可以直接在模型加載和保存模型實例,而不需要經過Store。而且,當有多個Store使用該模型的時候,也不需要重新爲每個Store定義一次代理。

        下面繼續定義Station 模型:

app/model/Station.js

[javascript] view plaincopy
  1. Ext.define('Panda.model.Station', {  
  2.     extend: 'Ext.data.Model',  
  3.     fields: ['id''name''played_date'],  
  4.    
  5.     proxy: {  
  6.         type: 'ajax',  
  7.         url: 'data/stations.json',  
  8.         reader: {  
  9.             type: 'json',  
  10.             root: 'results'  
  11.         }  
  12.     }  
  13. });  

        Store

        在ExtJS 4,多個Store可以使用相同的數據模型,即使Store需要從不同的來源加載數據。在實例中,模型Station將會在SearchResults和Stations這兩個Store中使用,而它們會從不同的位置加載數據。其中,SearchResults將返回返回搜索結果,而另一個則返回用戶收藏的Station。爲了實現這一點,其中一個Store需要重寫模型中定義的代

理。

app/store/SearchResults.js

[javascript] view plaincopy
  1. Ext.define('Panda.store.SearchResults', {  
  2.     extend: 'Ext.data.Store',  
  3.     requires: 'Panda.model.Station',  
  4.     model: 'Panda.model.Station',  
  5.    
  6.     // Overriding the model's default proxy  
  7.     proxy: {  
  8.         type: 'ajax',  
  9.         url: 'data/searchresults.json',  
  10.         reader: {  
  11.             type: 'json',  
  12.             root: 'results'  
  13.         }  
  14.     }  
  15. });  

app/store/Stations.js
[javascript] view plaincopy
  1. Ext.define('Panda.store.Stations', {  
  2.     extend: 'Ext.data.Store',  
  3.     requires: 'Panda.model.Station',  
  4.     model: 'Panda.model.Station'  
  5. });  

         在SearchResults的定義,已經重寫了Station模型中代理的定義,當調用Store的load方法時,其代理將調用Store的的代理以替換模型中定義的代理。

         當然,也可以在服務器端使用相同的接口實現返回搜索結果和用戶收藏的station,這樣,兩個Store就可以使用模型中定義的默認代理了,只是加載數據時請求的參數不同。

         接着創建RecentSongs:

app/store/RecentSongs.js

[javascript] view plaincopy
  1. Ext.define('Panda.store.RecentSongs', {  
  2.     extend: 'Ext.data.Store',  
  3.     model: 'Panda.model.Song',  
  4.    
  5.     // Make sure to require your model if you are  
  6.     // not using Ext JS 4.0.5  
  7.     requires: 'Panda.model.Song'  
  8. });  

        要注意,在當前版本的ExtJS中,在Store中的model屬性還不能自動創建一個依賴,因而需要通過requires配置項指定模型以便能夠實現動態加載模型。

        另外,根據約定,Store的類名要使用複數,而模型的類名要使用單數。

        增加Store和模型到應用

        定義好模型和Store後,現在可以把它們加到應用裏。打開Application.js並添加如下代碼:

app/Application.js

[javascript] view plaincopy
  1. Ext.application({  
  2.     ...  
  3.     models: ['Station''Song'],      
  4.     stores: ['Stations''RecentSongs''SearchResults']  
  5.     ...  
  6. });  

         使用ExtJS 4 MVC包的另外一個好處是,應用會自動加載在stores和models中定義的Store和模型。當Store加載後,會爲每個Store創建一個實例,並將其名稱作爲storeId。這樣,就可以使用該名稱將其綁定到視圖中數據組件,例如“SearchResults”。

        應用粘合劑

        在開始的時候,可以將視圖逐一加到Viewport,這樣比較容易調試出視圖錯誤的配置。先在Panda應用中構建Viewport:

[javascript] view plaincopy
  1. Ext.define('Panda.view.Viewport', {  
  2.     extend: 'Ext.container.Viewport',  

         通常,應用的Viewport類擴展自Ext.container.Viewport,這會讓應用把瀏覽器的可用空間作爲應用的空間。
[javascript] view plaincopy
  1. requires: [  
  2.     'Panda.view.NewStation',  
  3.     'Panda.view.SongControls',  
  4.     'Panda.view.StationsList',  
  5.     'Panda.view.RecentlyPlayedScroller',  
  6.     'Panda.view.SongInfo'  
  7. ],  

        這裏要設置viewprot依賴的視圖, 這樣,就可以使用視圖中通過alias屬性定義的名稱作爲xtype配置項的值來定義視圖。
[javascript] view plaincopy
  1. layout: 'fit',  
  2.   
  3. initComponent: function() {  
  4.     this.items = {  
  5.         xtype: 'panel',  
  6.         dockedItems: [{  
  7.             dock: 'top',  
  8.             xtype: 'toolbar',  
  9.             height: 80,  
  10.             items: [{  
  11.                 xtype: 'newstation',  
  12.                 width: 150  
  13.             }, {  
  14.                 xtype: 'songcontrols',  
  15.                 height: 70,  
  16.                 flex: 1  
  17.             }, {  
  18.                 xtype: 'component',  
  19.                 html: 'Panda<br>Internet Radio'  
  20.             }]  
  21.         }],  
  22.         layout: {  
  23.             type: 'hbox',  
  24.             align: 'stretch'  
  25.         },  
  26.         items: [{  
  27.             width: 250,  
  28.             xtype: 'panel',  
  29.             layout: {  
  30.                 type: 'vbox',  
  31.                 align: 'stretch'  
  32.             },  
  33.             items: [{  
  34.                 xtype: 'stationslist',  
  35.                 flex: 1  
  36.             }, {  
  37.                 html: 'Ad',  
  38.                 height: 250,  
  39.                 xtype: 'panel'  
  40.             }]  
  41.         }, {  
  42.             xtype: 'container',  
  43.             flex: 1,  
  44.             layout: {  
  45.                 type: 'vbox',  
  46.                 align: 'stretch'  
  47.             },  
  48.             items: [{  
  49.                 xtype: 'recentlyplayedscroller',  
  50.                 height: 250  
  51.             }, {  
  52.                 xtype: 'songinfo',  
  53.                 flex: 1  
  54.             }]  
  55.         }]  
  56.     };  
  57.   
  58.     this.callParent();  
  59. }  

         因爲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

[javascript] view plaincopy
  1. Ext.define('Panda.controller.Station', {  
  2.     extend: 'Ext.app.Controller',  
  3.     init: function() {  
  4.         ...  
  5.     },  
  6.     ...  
  7. });  

app/controller/Song.js
[javascript] view plaincopy
  1. Ext.define('Panda.controller.Song', {  
  2.     extend: 'Ext.app.Controller',  
  3.     init: function() {  
  4.         ...  
  5.     },  
  6.     ...  
  7. });  

        在應用中包含控制器後,框架會在調用init方法的時候字段加載控制器。在init方法內,可以定義監聽視圖和應用的事件。在大型應用,有時需要在運行時加載額外的控制器,可以使用getController方法加載:

[javascript] view plaincopy
  1. someAction: function() {  
  2.     var controller = this.getController('AnotherController');  
  3.    
  4.     // Remember to call the init method manually  
  5.     controller.init();  
  6. }  

         當在運行時加載額外的控制器時,一定要記得手動調用加載的控制器的init方法。

         在實例應用,只要將控制器加到controllers配置項中的數組,讓框架加載和初始化控制器就可以了。

app/Application.js

[javascript] view plaincopy
  1. Ext.application({  
  2.     ...  
  3.     controllers: ['Station''Song']  
  4. });  

         定義監聽

         現在要在控制器的init方法內調用其control方法控制UI部件:

app/controller/Station.js

[javascript] view plaincopy
  1. ...  
  2. <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>  
  3.     <span class="kw1">this</span>.<span class="me1">control</span><span class="br0">(</span><span class="br0">{</span>  
  4.         <span class="st0">'stationslist'</span><span class="sy0">:</span> <span class="br0">{</span>  
  5.             selectionchange<span class="sy0">:</span> <span class="kw1">this</span>.<span class="me1">onStationSelect</span>  
  6.         <span class="br0">}</span><span class="sy0">,</span>  
  7.         <span class="st0">'newstation'</span><span class="sy0">:</span> <span class="br0">{</span>  
  8.             select<span class="sy0">:</span> <span class="kw1">this</span>.<span class="me1">onNewStationSelect</span>  
  9.         <span class="br0">}</span>  
  10.     <span class="br0">}</span><span class="br0">)</span><span class="sy0">;</span>  
  11. <span class="br0">}</span>  
  12. ...  

        方法control會通過對象的關鍵字查詢組件。在示例中,會通過視圖的xtypes配置項查詢組件。然而,使用組件查詢,必須確保能指向UI中的知道部件。想了解更多有關組件查詢的信息,可訪問這裏

        每一個查詢都會綁定一個監聽配置對象,在每個監聽配置對象內,事件名就是監聽對象的關鍵字,而事件是由查詢的目標組件提供的。在示例中,將使用Grid(在StationsList視圖擴展出的)提供的selectionchange事件,及ComboBox(從NewStation視圖中擴展出的)提供的select事件。要找到特定組件有那些事件,可以在API文檔中查看組件的事件部分。


        監聽配置對象中的值是事件觸發後執行的函數。函數的作用將是控制器本身。

        現在爲Song控制器添加一些監聽:

app/controller/Song.js

[javascript] view plaincopy
  1. ...  
  2. init: function() {  
  3.     this.control({  
  4.         'recentlyplayedscroller': {  
  5.             selectionchange: this.onSongSelect  
  6.         }  
  7.     });  
  8.    
  9.     this.application.on({  
  10.         stationstart: this.onStationStart,  
  11.         scope: this  
  12.     });  
  13. }  
  14. ..  

        除了監聽RecentlyPlayedScroller視圖的selectionchange事件外,這裏還需要監聽應用事件。這需要使用application實例的on方法。每一個控制都可以使用this.application直接訪問application實例。

        當在應用中有許多控制器都需要監聽同一個事件的時候,應用事件這時候非常有用。與在每一個控制器中監聽相同的視圖事件一樣,只需要在一個控制器中監聽視圖的事件,就可以觸發一個應用範圍的事件,這樣,等於其它控制器也監聽了該事件。這樣,就實現了控制器之間進行通信,而無需瞭解或依賴於對方是否存在。

        控制器Song需要知道新的station是否已開始,以便更新song滾動條和song信息。

        現在在Station控制器內,編寫當應用事件stationstart觸發時的響應代碼:

app/controller/Station.js

[javascript] view plaincopy
  1. ...  
  2. onStationSelect: function(selModel, selection) {  
  3.     this.application.fireEvent('stationstart', selection[0]);  
  4. }  
  5. ...  

        這裏只是簡單在觸發stationstart事件時,將selectionchange事件中的第一個選擇項作爲唯一參數傳遞給事件。

        小結

        在這篇文章中,可以瞭解到應用架構的基本技術。當然,這只是其中的一小部分,在該系列的下一部分,將可以看到一些更高級的控制器技術,並繼續在控制器中實現操作,以及在視圖中添加細節,以完成Panda應用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章