ExtJS 4 - 在 MVC 架构中重用组件

Posted

技术标签:

【中文标题】ExtJS 4 - 在 MVC 架构中重用组件【英文标题】:ExtJS 4 - Reuse components in MVC architecture 【发布时间】:2011-05-24 13:51:16 【问题描述】:

我有一个用户列表,如果我单击此列表中的某个项目,则会打开一个窗口。这是每个用户的同一个窗口,并且可以同时打开多个窗口。该窗口显示用户信息,因此对于此组件,我有相同的商店和相同的型号。

但如果我在特定窗口中加载数据,我会在所有其他打开的窗口中加载相同的数据。

Ext.define('Cc.view.absence.Grid', 
  extend: 'Ext.grid.Panel',
  alias: 'widget.absencegrid',

  border:false,

  initComponent: function() 
    Ext.apply(this, 
      store: Ext.create('Ext.data.Store', 
        model: 'Cc.model.Absence',
        autoLoad: false,
        proxy: 
          type: 'ajax',
          reader: 
            type: 'json'
          
        
      ),
      columns: [
        header: 'Du', dataIndex: 'startdate', flex: 3, renderer: this.formatDate,
        header: 'Au', dataIndex: 'enddate', flex: 3, renderer: this.formatDate,
        header: 'Exercice', dataIndex: 'year', align: 'center', flex: 1,
        header: 'Statut', xtype:'templatecolumn', tpl:'<img src="../images/status-statusid.png"  title="status" />', align: 'center', flex: 1,
        header: 'Type d\'absence', dataIndex: 'absencetype', align: 'center', flex: 2,
        header: 'Commentaires', dataIndex: 'comment', flex: 6
      ],
      dockedItems: [
        xtype: 'toolbar',
        dock: 'top',
        items: [
           xtype: 'tbfill',
           xtype: 'button', text: 'Rafraichir', action: 'refresh', iconCls: 'item-rafraichir' 
        ]
      ]
    );

    this.callParent(arguments);
  ,

  formatDate: function(date) 
    if (!date) 
      return '';
    
    return Ext.Date.format(date, 'l d F Y - H:i');
  

);

我的控制器:

Ext.define('Cc.controller.Absences', 
  extend: 'Ext.app.Controller',

  models: ['Absence', 'AbsenceHistory'],

  views: [],

  refs: [
     ref: 'navigation', selector: 'navigation' ,
     ref: 'tabPanel', selector: 'tabpanel' ,
     ref: 'absencePanel', selector: 'absencepanel' ,
     ref: 'refreshButton', selector: 'absencepanel button[action=refresh]',
     ref: 'absenceGrid', selector: 'absencegrid' ,
     ref: 'absenceHistory', selector: 'absencehistory' ,
  ],

  init: function() 
    this.control(
      'absencepanel button[action=refresh]': 
        click: this.onClickRefreshButton
      ,
      'absencegrid': 
        selectionchange: this.viewHistory
      ,
      'absencegrid > tableview': 
        refresh: this.selectAbsence
      ,
    );
  ,

  selectAbsence: function(view) 
    var first = this.getAbsenceGrid().getStore().getAt(0);
    if (first) 
      view.getSelectionModel().select(first);
    
  ,

  viewHistory: function(grid, absences) 
    var absence = absences[0],
      store = this.getAbsenceHistory().getGrid().getStore();
    if(absence.get('id'))
      store.getProxy().url = '/absence/' + absence.get('id') +'/history';
      store.load();
    
  ,

  onClickRefreshButton: function(view, record, item, index, e)
    var store = this.getAbsenceGrid().getStore();
    store.load();
  ,
);

仅创建一个缺席实例的其他控制器。面板:

Ext.define('Cc.controller.Tools', 
  extend: 'Ext.app.Controller',

  stores: ['Tools', 'User' ],

  models: ['Tool'],

  views: [],

  refs: [
     ref: 'navigation', selector: 'navigation' ,
     ref: 'tabPanel', selector: 'tabpanel' ,
     ref: 'toolList', selector: 'toollist' ,
     ref: 'toolData', selector: 'toollist dataview' 
  ],

  init: function() 
    this.control(
      'toollist dataview': 
        itemclick: this.loadTab
      ,
    );
  ,

  onLaunch: function() 
    var dataview = this.getToolData(),
        store = this.getToolsStore();

    dataview.bindStore(store);
  ,

  loadTab: function(view, record, item, index, e)
    var tabPanel = this.getTabPanel();
    switch (record.get('tab')) 
      case 'absences':
        if(Ext.getCmp('absence-panel'))
          tabPanel.setActiveTab(Ext.getCmp('absence-panel'));
        
        else 
          var panel = Ext.create('Cc.view.absence.Panel',
            id: 'absence-panel'
          ),
              store = panel.getGrid().getStore();
          panel.enable();
          tabPanel.add(panel);
          tabPanel.setActiveTab(panel);
          store.getProxy().url = '/person/' + this.getUserId() +'/absences';
          store.load();
        
      break;

      default: 
      break;
    
  ,

  getUserId: function()
    var userStore = this.getUserStore();
    var id = userStore.first().get('id')
    return id;
  
);

另一个创建很多缺席实例的控制器。面板:

Ext.define('Cc.controller.Agents', 
  extend: 'Ext.app.Controller',

  stores: ['Agents'],

  models: ['Agent', 'Absence', 'AbsenceHistory'],

  views: ['agent.List', 'agent.Window', 'absence.Panel', 'absence.Grid', 'absence.History'],
  refs: [
     ref: 'agentList', selector: 'agentlist' ,
     ref: 'agentData', selector: 'agentlist dataview' ,
     ref: 'agentWindow', selector: 'agentwindow' ,
  ],

  init: function() 
    this.control(
      'agentlist dataview':
        itemclick: this.loadWindow
      ,
    );
  ,

  onLaunch: function() 
    var dataview = this.getAgentData(),
        store = this.getAgentsStore();
    dataview.bindStore(store);
  ,

  loadWindow: function(view, record, item, index, e)
    if(!Ext.getCmp('agent-window'+record.get('id')))
      var window = Ext.create('Cc.view.agent.Window', 
        title: 'À propos de '+record.get('firstname')+' '+record.get('lastname'),
        id: 'agent-window'+record.get('id')
      ),
          tabPanel = window.getTabPanel();
          absencePanel = window.getAbsencePanel();
          store = absencePanel.getGrid().getStore();

      absencePanel.enable();
      tabPanel.add(absencePanel);
      tabPanel.setActiveTab(absencePanel);
      store.getProxy().url = '/person/' + record.get('id') +'/absences';
      store.load();
    
    Ext.getCmp('agent-window'+record.get('id')).show();
  
);

以及缺席.Panel 视图,缺席.Grid 的容器:

Ext.define('Cc.view.absence.Panel', 
    extend: 'Ext.panel.Panel',
    alias: 'widget.absencepanel',

  title: 'Mes absences',
  iconCls: 'item-outils',
  closable: true,
  border: false,
  disabled: true,
  layout: 'border',

  initComponent: function() 
    this.grid = Ext.create('Cc.view.absence.Grid', 
      region: 'center'
    );
    this.history = Ext.create('Cc.view.absence.History', 
      region: 'south',
      height: '25%'
    );
        Ext.apply(this, 
      items: [
        this.grid,
        this.history
      ]
    );

        this.callParent(arguments);
    ,

  getGrid: function()
    return this.grid;
  ,

  getHistory: function()
    return this.history;
  
);

【问题讨论】:

听起来您需要在用户单击项目时创建每个窗口的唯一实例?现在我认为您只是在打开重复项? 同一商店链接到所有窗口。因此,如果我将数据加载到新窗口中,所有其他打开的窗口也会重新加载。但我只想在点击刷新按钮时重新加载数据。 【参考方案1】:

是的。这是我在做什么的更多详细信息。我希望你已经完全阅读了论坛。还有一个我们不清楚的信息。这就是在控制器中使用“stores”属性的确切用法。恕我直言,Sencha 团队应该通过复杂的示例更详细地解释 MVC。

是的,您新发布的内容是正确的。创建新视图后,创建商店的新实例!现在,从论坛的讨论中,人们开始争论MVC。我肯定会选择 steffenk 。我们在这里所做的是向我的视图注入一个新的 store 实例。我忽略了控制器的stores 属性。

这是一个例子:

这是我的看法。它是我在标签面板上显示的面板(带有用户个人资料信息):

Ext.define('Dir.view.profile.View' ,
    extend: 'Ext.panel.Panel',
    alias : 'widget.profileview',
    title : 'Profile',
    profileId: 1,   // default and dummy value
    initComponent: function() 

        // configure necessary stuff, I access my store etc here..       
        // console.log(this.profileStore);
        this.callParent(arguments);         
    ,
    // Other view methods goes here
);

现在,看看我的控制器:

Ext.define('Dir.controller.Profile', 
    extend: 'Ext.app.Controller',
    //stores: ['Profile'],   --> Note that I am NOT using this!
    refs: [     
        ref:'cp',selector: 'centerpane'
    ],
    views: ['profile.View'],
    init: function() 
        // Do your init tasks if required
    ,  
    displayProfile: function(selectedId) 

            // create a new store.. pass your config, proxy url etc..
        var store = Ext.create('Dir.store.Profile',profileId: selectedId);

        console.log('Display Profile for ID ' + selectedId);    

        // Create instance of my view and pass the new store
        var view = Ext.widget('profileview',profileId: selectedId,profileStore: store);

        // Add my new view to centeral panel and display it...
        this.getCp().add(view);
        this.getCp().setActiveTab(view);
    
);

我的displayProfile() 是从一些事件监听器(菜单、树等)调用的,它传递了一个 id。我的控制器使用这个 id 来设置一个新的商店和视图。我希望上面的代码能让你清楚地了解我昨天所说的内容。

在您的控制器中,您必须添加 Ext.require('Dir.store.Profile'); 以便 ExtJS 知道您有这样的商店。这是因为我们没有使用stores 属性。

现在,您可以想在其他地方重复使用这些创建的商店,您可以将它们添加到StoreManager。有了这个,您可以在任何地方访问您创建的商店,添加和删除商店。但这会带来您管理商店实例的开销。


为什么你用不同的视图共享同一个 store 实例?当您有新视图时,请创建商店的新实例。当一个窗口被刷新时,这将防止更新的数据出现在其他窗口上。

【讨论】:

我不知道你是否真的了解我。这里的问题完全相同:link。你将如何解决这个问题? 我在第一篇文章中定义了一个视图(编辑)。所以当我打电话给Ext.create('Cc.view.absence.Panel') 时,我会创建与商店一样多的视图? 是的,看看更新后的答案。我希望这有助于您理解我试图解释的内容。 好的,谢谢,但我真的不明白为什么在我的版本中,如果我使用Ext.create(...),store 与我的视图无关 "商店独立于我的观点" --> 我没明白。你能解释一下它的行为是如何独立的吗?

以上是关于ExtJS 4 - 在 MVC 架构中重用组件的主要内容,如果未能解决你的问题,请参考以下文章

Ext JS MVC 中的可重用操作

在 extjs MVC 架构中如何将参数传递给函数

ExtJS 4(MVC)商店不使用我的自定义模型类

Ext JS 4 架构你的应用 第2节 (官方文档翻译)

带有 Ext JS 的 MVC 架构中 store.load() 的动态代理 URL

一种高效的跨进程MVC架构