什么';ExtJS视图组件拥有自己的私有控制器是一种很好的模式吗?
我有一些ExtJS 4 MVC应用程序,它们共享某些自定义视图组件。共享组件有其自己的名称空间,并且通常非常复杂(例如,有一个必须管理的大型子组件树),因此它需要自己的控制器。我希望此“特定于组件的控制器”正确封装在组件内(专用)(即,组件外的任何内容都不必知道或关心组件正在使用嵌入式控制器)。当然,应用程序应该可以有多个组件实例(每个实例封装其组件控制器的一个单独实例) 为此,我开发了以下mixin(此版本适用于ExtJS 4.1),但我很想知道是否有人更优雅地解决了相同的问题:什么';ExtJS视图组件拥有自己的私有控制器是一种很好的模式吗?,extjs,extjs4,extjs-mvc,Extjs,Extjs4,Extjs Mvc,我有一些ExtJS 4 MVC应用程序,它们共享某些自定义视图组件。共享组件有其自己的名称空间,并且通常非常复杂(例如,有一个必须管理的大型子组件树),因此它需要自己的控制器。我希望此“特定于组件的控制器”正确封装在组件内(专用)(即,组件外的任何内容都不必知道或关心组件正在使用嵌入式控制器)。当然,应用程序应该可以有多个组件实例(每个实例封装其组件控制器的一个单独实例) 为此,我开发了以下mixin(此版本适用于ExtJS 4.1),但我很想知道是否有人更优雅地解决了相同的问题: /** *
/**
* A mixin that provides 'controller' style facilities to view components.
*
* This is for 'view' components that also serve as the 'controller' for the component.
* In such situations where an embedded controller is required we would have preferred to use a separate
* Ext.app.Controller derived controller but it looks like Ext.app.Controllers are global things,
* i.e. their refs can't be scoped to within the component (allowing multiple instances on the component to be used,
* each with their own controller each with its own set of refs).
*
* Usage:
* - Declare a 'refs' config just as in an Ext.app.Controller.
* - Call this.setupControllerRefs() from your initComponent() template function.
* - Call this.control(String/Object selectors, Object listeners) just as you would in a Ext.app.Controller.init()
* template function.
* - Any events fired from within a Window need special treatment, because Ext creates Windows as floated
* (top-level) components, so our usual selector scoping technique doesn't work. The trick is to give each Window
* an itemId prefixed with this component's itemId, e.g. itemId: me.itemId + '-lookup-window'
* Then, in the 'this.control({...})' block, define selectors as necessary that begin with "#{thisItemId}-", e.g.
* '#{thisItemId}-lookup-window aux-filter-criteria': ...
*
* It is also recommended to keep the 'view' aspect of the component minimal. If there is a significant proportion of
* view code, push it down into a new component class. Ideally, the component/controller should be just a Container.
*/
Ext.define('Acme.CmpController', {
setupControllerRefs: function() {
var me = this,
refs = me.refs;
// Copied from Ext.app.Controller.ref
refs = Ext.Array.from(refs);
Ext.Array.each(refs, function(info) {
var ref = info.ref,
fn = 'get' + Ext.String.capitalize(ref);
if (!me[fn]) {
me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
}
});
},
/** @private (copied from Ext.app.Controller.ref) */
getRef: function(ref, info, config) {
this.refCache = this.refCache || {};
info = info || {};
config = config || {};
Ext.apply(info, config);
if (info.forceCreate) {
return Ext.ComponentManager.create(info, 'component');
}
var me = this,
selector = info.selector,
cached = me.refCache[ref];
if (!cached) {
//me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
/**** ACME ****/ me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector, this)[0];
if (!cached && info.autoCreate) {
me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
}
if (cached) {
cached.on('beforedestroy', function() {
me.refCache[ref] = null;
});
}
}
return cached;
},
control: function(selectors, listeners) {
var me = this,
selectorPrefix,
thisIemIdPrefix = '#{thisItemId}',
newSelectors = {};
if (listeners)
throw "Support for the optional 'listeners' param (which we had thought was rarely used) has not yet been coded.";
// Since there could be multiple instances of the controller/component, each selector needs to be
// prefixed with something that scopes the query to within this component. Ensure each instance has
// an itemId, and use this as the basis for scoped selectors.
me.itemId = me.itemId || me.id;
if (!me.itemId)
throw "We assume the component will always have an 'id' by the time control() is called.";
selectorPrefix = '#' + me.itemId + ' ';
Ext.Object.each(selectors, function(selector, listeners) {
if (selector.indexOf(thisIemIdPrefix) === 0)
selector = '#' + me.itemId + selector.substring(thisIemIdPrefix.length);
else
selector = selectorPrefix + selector;
newSelectors[selector] = listeners;
});
selectors = newSelectors;
// Real Controllers use the EventBus, so let's do likewise.
// Note: this depends on a hacked EventBus ctor. See ext-fixes.js
Ext.app.EventBus.theInstance.control(selectors, listeners, me);
}
});
如最后一条评论所述,ExtJS必须按如下方式进行修补:
Ext.override(Ext.app.EventBus, {
/**
* Our CmpController mixin needs to get a handle on the EventBus, as created by the Ext.app.Application instance. Analysis
* of the ExtJS source code shows that only one instance of EventBus gets created (assuming there's never more than one
* Ext.app.Application per app). So we hack the ctor to store a reference to itself as a static 'theInstance' property.
*/
constructor: function() {
this.callOverridden();
/**** ACME ****/ this.self.theInstance = this;
},
/**
* Had to patch this routine on the line labelled **** ACME ****. Events intercepted by Pv were being received by the Pv
* instance first created by appPv. appPv created a new Pv instance every time a 'to.AcmeViewer.View' message is received.
* Even though the old Pv had isDestroyed:true, the routine below was dispatching the event to it.
*
* It's possible this surprising behaviour is not unconnected with our (mis?)use of EventBus in Acme.CmpController.
*
* This patched function is from ExtJS 4.1.1
*/
dispatch: function(ev, target, args) {
var bus = this.bus,
selectors = bus[ev],
selector, controllers, id, events, event, i, ln;
if (selectors) {
// Loop over all the selectors that are bound to this event
for (selector in selectors) {
// Check if the target matches the selector
if (selectors.hasOwnProperty(selector) && target.is(selector)) {
// Loop over all the controllers that are bound to this selector
controllers = selectors[selector];
for (id in controllers) {
if (controllers.hasOwnProperty(id)) {
// Loop over all the events that are bound to this selector on this controller
events = controllers[id];
for (i = 0, ln = events.length; i < ln; i++) {
event = events[i];
/**** ACME ****/ if (!event.observable.isDestroyed)
// Fire the event!
if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
return false;
}
}
}
}
}
}
}
return true;
}
});
Ext.override(Ext.app.EventBus{
/**
*我们的CmpController mixin需要获得由Ext.app.Application.Analysis创建的EventBus上的句柄
*ExtJS源代码的示例显示,只创建了一个EventBus实例(假设不存在多个实例)
*因此我们破解了ctor,将对自身的引用存储为静态“theInstance”属性。
*/
构造函数:函数(){
this.callOverrided();
/****ACME****/this.self.theInstance=this;
},
/**
*必须在标记为****ACME****的行上修补此例程。Pv正在接收Pv截获的事件
*appPv首先创建的实例。每次收到“to.AcmeViewer.View”消息时,appPv都会创建一个新的Pv实例。
*尽管旧Pv已被销毁:正确,但下面的例程正在将事件分派给它。
*
*这种令人惊讶的行为可能与我们在Acme.CmpController中(错误地?)使用EventBus有关。
*
*此修补函数来自ExtJS 4.1.1
*/
调度:功能(ev、目标、参数){
var bus=此.bus,
选择器=总线[ev],
选择器、控制器、id、事件、事件、i、ln;
如果(选择器){
//循环遍历绑定到此事件的所有选择器
用于(选择器中的选择器){
//检查目标是否与选择器匹配
if(selectors.hasOwnProperty(选择器)和&target.is(选择器)){
//循环所有绑定到此选择器的控制器
控制器=选择器[选择器];
用于(控制器中的id){
if(controllers.hasOwnProperty(id)){
//循环此控制器上绑定到此选择器的所有事件
事件=控制器[id];
对于(i=0,ln=events.length;i
虽然我目前使用的是ExtJS 4.1,但我也有兴趣了解依赖于4.2的解决方案,因为这可能有助于激励我迁移。虽然这并不能真正解决您对4.x的问题,但组件的视图控制器已经在5.x测试版中实现,如果您愿意尝试的话。这很好,Evan,但我的产品负责人暂时不会批准升级到版本5(必须经过充分验证)。但多亏了你的评论,我现在可以用不同的措辞回答这个问题:如何在ExtJS 4中接近ExtJS 5 ViewController?通过升级。。。。目前还没有类似的产品。