ExtJS應用架構設計(三)

在該系列文章的前兩篇文章中(),我們探討了如何使用ExtJS 4的新特性構建一個潘多拉風格的應用程序,並開始將MVC架構應用到多視圖、Store和模型的複雜UI中,而且瞭解應用架構的基本技術,如通過控制器控制視圖、在控制器中通過監聽觸發應用事件。在本文,將繼續完成應用的MVC架構內的控制邏輯。


      獲取引用


      在繼續完成應用之前,先重溫一些MVC包中的高級功能。在前一篇文章中,介紹了在Ext.application的配置項的stores和models的數組中定義的Store和模型會字段加載,而且加載後,會自動創建Store的實例,並將類名作爲storeId的值。

app/Application.js

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

      在stores和models的數組中定義這些類,除了會自動加載和實例化這些類外,還會爲 它們創建get方法。控制器和視圖也是這樣的。配置項stores、models、controllers和views也可以在控制器中使用,其功能與在應用實例中使用是一樣的。這意味着,爲了在Station控制器內獲得Stations的引用,需要在其內加入stores數組:

app/controller/Station.js
[javascript] view plaincopy
  1. ...  
  2. stores: ['Stations'],  
  3. ...  

      現在在控制內的任何位置都可以使用自動生成的getStationsStore方法返回Stations的引用。這個約定是簡單而明確的:
[javascript] view plaincopy
  1. views: ['StationsList'// creates getter named 'getStationsListView' -> returns reference to StationsList class  
  2. models: ['Station']     // creates getter named 'getStationModel'     -> returns reference to Station model class  
  3. controllers: ['Song']   // creates getter named 'getSongController'   -> returns the Song controller instance  
  4. stores: ['Stations']    // creates getter named 'getStationsStore'    -> returns the Stations store instance  

      要注意的是,視圖和模型返回的是類的引用(要求實例化),而Store和控制器則是實例化後的對象。

     引用視圖實例

     在上一節,講述瞭如何定義stores、models、controllers和views配置項以自動創建get方法以返回它們的引用。方法getStationsListView將返回視圖類的引用,而在應用流程中,StationsList將選擇第一記錄,因而,在viewport中,引用的是視圖的實例而不是視圖類。

      在ExtJS 3,通常做法是使用Ext.getCmp方法引用頁面中存在的組件實例。在ExtJS 4中,可以繼續使用該方法,但不建議這樣使用,這是因爲使用Ext.getCmp方法需要爲每一個組件定義一個唯一ID,才能在應用中引用它。在MVC包中,可以通過在控制器內使用ExtJS 4的新特性ComponentQuery對象獲取視圖實例的引用:

app/controller/Station.js

[javascript] view plaincopy
  1. ...  
  2. refs: [{  
  3.     // A component query  
  4.     selector: 'viewport > #west-region > stationslist',  
  5.     ref: 'stationsList'  
  6. }]  
  7. ...  

      在refs配置項,可以定義視圖實例的引用,這樣,就可以在控制器中返回和操作頁面中的組件。要描述引用組件的,需要使用到ComponentQuery配置對象的selector配置項。另外一個必須的配置項是ref,它將會是refs數組內自動產生的get方法名稱的一部分,例如,定義爲“ref: ‘stationsList’ ”(注意大寫L),會在控制內生成getStationsList方法。當然,如果你不想在控制器內定義引用,也可以繼續使用Ext.getCmp方法,然而,我們希望你不要這樣做,因爲它需要在項目中管理組件的唯一ID,隨着應用的增長一般都會出現問題。

      一定要記住的是,無論視圖是否已經存在頁面中,都會獨立的創建這些get方法。當調用get方法並通過選擇符成功獲取頁面組件後,它會在緩存這些結果以便後續get方法的調用變得迅速。而當通過選擇符找不到頁面中任何視圖的時候,get方法會返回null,這意味着,存在應用邏輯依賴一個視圖,而該視圖在頁面不存在的情況,這時,必須根據應用邏輯進行排查,以確保get方法能返回結果,並正確執行應用邏輯。另外要注意的是,如果通過選擇符會返回多個組件,那麼,只會返回第一個組件。因此,讓選擇符只能返回單一的視圖是好的編寫方式。最後,當引用的組件被銷燬的時候,調用get方法也會返回null,除非在頁面中找到另外一個與選擇符匹配的組件。

      在launch方法內級聯控制器邏輯

      當應用開始運行的時候,如果希望加載用戶已經存在的station,可以將邏輯放在應用的onReady方法內。而在MVC架構,提供了onLaunch方法,它會在每一個控制內,當所有控制器、模型和Store被初始化後,初始化視圖被渲染前觸發。這可讓你清晰區分那些是全局應用邏輯,那些是控制器邏輯。

      步驟1

app/controller/Station.js

[javascript] view plaincopy
  1. ...  
  2. onLaunch: function() {  
  3.     // Use the automatically generated getter to get the store  
  4.     var stationsStore = this.getStationsStore();          
  5.     stationsStore.load({  
  6.         callback: this.onStationsLoad,  
  7.         scope: this  
  8.     });  
  9. }  
  10. ...  

      Station控制器的onLaunch方法是調用Station的laod方法的一個完美地方。在代碼中,Station加載後會執行一個回調函數。

      步驟2

app/controller/Station.js

[javascript] view plaincopy
  1. ...  
  2. onStationsLoad: function() {  
  3.     var stationsList = this.getStationsList();  
  4.     stationsList.getSelectionModel().select(0);  
  5. }  
  6. ...  

      在回調函數中,通過自動產生的StationsList實例的get方法,選擇第一個記錄,這會觸發StationsList內的selectionchange事件。


      步驟3

app/controller/Station.js

[javascript] view plaincopy
  1. ...  
  2. init: function() {  
  3.     this.control({  
  4.         'stationslist': {  
  5.             selectionchange: this.onStationSelect  
  6.         },  
  7.         ...  
  8.     });  
  9. },  
  10.   
  11. onStationSelect: function(selModel, selection) {  
  12.     this.application.fireEvent('stationstart', selection[0]);  
  13. },  
  14. ...  

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


      步驟4

app/controller/Song.js

[javascript] view plaincopy
  1. ...  
  2. refs: [{  
  3.     ref: 'songInfo',  
  4.     selector: 'songinfo'  
  5. }, {  
  6.     ref: 'recentlyPlayedScroller',  
  7.     selector: 'recentlyplayedscroller'  
  8. }],  
  9.   
  10. stores: ['RecentSongs'],  
  11.   
  12. init: function() {  
  13.     ...  
  14.     // We listen for the application-wide stationstart event  
  15.     this.application.on({  
  16.         stationstart: this.onStationStart,  
  17.         scope: this  
  18.     });  
  19. },  
  20.   
  21. onStationStart: function(station) {  
  22.     var store = this.getRecentSongsStore();  
  23.   
  24.     store.load({  
  25.         callback: this.onRecentSongsLoad,  
  26.         params: {  
  27.             station: station.get('id')  
  28.         },              
  29.         scope: this  
  30.     });  
  31. }  
  32. ...  

在Song控制器init方法內,定義了監聽應用事件stationstart的方法。當事件觸發時,需要加載station的song到RecentSongs。這將會在onStationStart方法內進行處理,通過get方法引用RecentSongs,然後調用其load方法,當加載完成後,需要指向控制器的操作。


      步驟5

app/controller/Song.js

[javascript] view plaincopy
  1. ...  
  2. onRecentSongsLoad: function(songs, request) {  
  3.     var store = this.getRecentSongsStore(),  
  4.         selModel = this.getRecentlyPlayedScroller().getSelectionModel();     
  5.   
  6.     selModel.select(store.last());  
  7. }  
  8. ...  

當Station的Song已加載到RecentSongs,將在RecentlyPlayedScroller中選擇最後一首,這將通過調用RecentlyPlayedScroller數據視圖的選擇模型的select方法完成此操作,參數是RecentSongs的最後一個記錄。


      步驟6

app/controller/Song.js

[javascript] view plaincopy
  1. ...  
  2. init: function() {  
  3.     this.control({  
  4.         'recentlyplayedscroller': {  
  5.             selectionchange: this.onSongSelect  
  6.         }  
  7.     });  
  8.     ...  
  9. },  
  10.   
  11. onSongSelect: function(selModel, selection) {  
  12.     this.getSongInfo().update(selection[0]);  
  13. }  
  14. ...  

      當在RecentlyPlayedScroller選擇最後一首song的時候,會觸發selectionchange事件。在control方法內,需要監聽該事件,並指向onSongSelect方法。這樣就完成了在SongInfo視圖中更新數據的應用流程。


       啓動一個新的station


      現在,應用變得非常容易實現附加的應用流程,可以爲其添加邏輯以便創建和選擇一個新的station:

app/controller/Station.js

[javascript] view plaincopy
  1. ...  
  2. refs: [{  
  3.     ref: 'stationsList',  
  4.     selector: 'stationslist'  
  5. }],  
  6.   
  7. init: function() {  
  8.     // Listen for the select event on the NewStation combobox  
  9.     this.control({  
  10.         ...  
  11.         'newstation': {  
  12.             select: this.onNewStationSelect  
  13.         }  
  14.     });  
  15. },  
  16.   
  17. onNewStationSelect: function(field, selection) {  
  18.     var selected = selection[0],  
  19.         store = this.getStationsStore(),  
  20.         list = this.getStationsList();  
  21.   
  22.     if (selected && !store.getById(selected.get('id'))) {  
  23.         // If the newly selected station does not exist in our station store we add it  
  24.         store.add(selected);  
  25.     }  
  26.   
  27.     // We select the station in the Station list  
  28.     list.getSelectionModel().select(selected);  
  29. }  
  30. ...  

      總結


      通過示例,可以瞭解到使用高級的控制器技術和保持邏輯與視圖分離,應用架構將變得易於理解和維護。在這個階段,應用程序已經具體一定的功能。可以通過搜索和增加新的station,也可以通過選擇開始station。Station的Song會自動加載,並顯示song和藝術家信息。

      在該系列的下一篇文章,將繼續完善該應用,重點將是自定義組件創建和樣式。不過,目前已經可以共享當前這些源代碼。希望你喜歡並提供反饋。

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