ExtJS应用架构设计(三)

来源:互联网 发布:淘宝订单怎么拆单发货 编辑:程序博客网 时间:2024/06/08 14:58
在该系列文章的前两篇文章中(),我们探讨了如何使用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和艺术家信息。

      在该系列的下一篇文章,将继续完善该应用,重点将是自定义组件创建和样式。不过,目前已经可以共享当前这些源代码。希望你喜欢并提供反馈。

原创粉丝点击