In a typical real world application there might be quite a number of Views and Controllers similar to each other but doing different things, e.g., operating on different data sets. It is inefficient to repeat the same code over and over in different classes, and some code sharing technique is required. For Views, we can easily abstract most of the code in a base View class and add the required configuration in subclasses:
Ext.define('MyApp.view.AbstractPanel', { extend: 'Ext.grid.Panel', alias: 'widget.mypanel', bbar: [{ xtype: 'button', itemId: 'okButton', text: 'OK' }, { xtype: 'button', itemId: 'cancelButton', text: 'Cancel' }], ... }); Ext.define('MyApp.view.FooPanel', { extend: 'MyApp.view.AbstractPanel', alias: 'widget.foopanel', // We are overriding buttons in this view bbar: [{ xtype: 'button', itemId: 'yesButton', text: 'Yes' }, { xtype: 'button', itemId: 'noButton', text: 'No' }], ... }); Ext.define('MyApp.view.BarPanel', { extend: 'MyApp.view.AbstractPanel', alias: 'widget.barpanel', // This view has the same buttons as base class, // and also an informational side bar, so we need to // accommodate for it dockedItems: [{ xtype: 'panel', itemId: 'sidebar' }], ... });
What about Controllers then? In fact, nothing prevents us from taking the same approach, albeit we will have to do it in slightly less automated way:
Ext.define('MyApp.controller.AbstractController', { extend: 'Ext.app.Controller', // Matches any MyApp.view.AbstractPanel descendant by xtype viewSelector: 'mypanel', okSelector: 'button#okButton', cancelSelector: 'button#cancelButton', constructor: function(config) { Ext.apply(this, config); var tplRefs = [{ ref: 'view', selector: this.viewSelector }, { ref: 'okButton', selector: this.viewSelector + ' > ' + this.okSelector }, { ref: 'cancelButton', selector: this.viewSelector + ' > ' + this.cancelSelector }]; this.refs = this.refs ? Ext.merge(tplRefs, this.refs) : tplRefs; this.callParent(arguments); }, ... }); Ext.define('MyApp.controller.Foo', { extend: 'MyApp.controller.AbstractController', // Now we only have to provide selectors, and voila - // the refs will be created automagically viewSelector: 'foopanel', okSelector: 'button#yesButton', cancelSelector: 'button#noButton', ... }); Ext.define('MyApp.controller.Bar', { extend: 'MyApp.controller.AbstractController', // Buttons are the same so we only provide view selector viewSelector: 'barpanel', // We can add some refs that are unique to this View - // in our case, it is the sidebar. The refs should be as // precise as it is feasible, to avoid collisions with // other Views. refs: [ { ref: 'sidebar', selector: 'barpanel > panel#sidebar' } ], ... });
In the above example, we have abstracted a lot of duplicating functionality in our base Controller, greatly simplifying children Controller classes. A similar approach can be used with `control` component selectors and `listen` event domain selectors, respectively.
Les wrote:
The problem with the Abstract/Base controller approach is that you end up with a long list controllers that you need to specify in the Application.
I take a different approach. Instead of creating an Abstract controller, I have a regular controller and then store all controlling logic into multiple mixins that the controller uses.
This way I have fewer controllers and the code is still logically divided.
Here’s an example of such a controller:
Ext.define(‘Risk.controller.center.drilldown.DrilldownController’, {
extend: ‘Ext.app.Controller’,
mixins: {
drilldownglobal: ‘Risk.controller.center.drilldown.mixin.DrilldownGlobal’,
drilldownpanel: ‘Risk.controller.center.drilldown.mixin.DrilldownPanel’,
drilldownstore: ‘Risk.controller.center.drilldown.mixin.DrilldownStore’
},
init: function (application) {
application.initMixins(this);
this.queryCache = {};
}
});
initMixins is located in the application.js file:
initMixins: function (controller) {
Ext.Object.each(controller.mixins, function (key, mixin) {
if (mixin.init) {
mixin.init(controller);
}
});
},
Link | February 11th, 2014 at 10:00 AM
Les wrote:
Here’s an example of a mixin used by the controller:
/*jslint browser: true, vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, todo: true, sloppy: true, unparam: true */
/*global Ext, window, Risk */
Ext.define(‘Risk.controller.center.measure.mixin.MeasureMenu’, {
uses: [
‘Risk.view.center.measure.MeasureMenu’
],
init: function (controller) {
this.addRefs(controller);
this.addListeners(controller);
},
addRefs: function (controller) {
controller.addRef([{
ref: ‘measureMenu’,
xtype: ‘measuremenu’,
autoCreate: true
}, {
ref: ‘guarantyfundsdropDown’,
selector: ‘guarantyfundsdropDown’
}]);
},
addListeners: function (controller) {
var me = this;
controller.listen({
component: {
‘measurepanel>header’: {
click: me.onHeaderClick
},
‘measuremenu’: {
click: me.onMenuClick
}
}
});
},
onHeaderClick: function (header, evt) {
var panels = Ext.ComponentQuery.query(‘measurepanel’),
items = Risk.view.center.measure.MeasureMenu.prototype.items,
guarantyFundDropdown = this.getGuarantyfundsdropDown();
if (panels.length < items.length) {
this.getMeasureMenu().showMenu(header.ownerCt, guarantyFundDropdown);
}
},
onMenuClick: function (menu, item, evt) {
var panel = menu.selectedPanel,
container = panel.ownerCt,
index = panel.getPanelIndex() + 1,
text = item.text,
title = text + ' ▼', // arrow down
config = {
xtype: 'measurepanel',
titleText: text,
title: title
};
Ext.copyTo(config, item.initialConfig, 'itemId,measureId,toleranceId');
container.addSplitter(index);
container.addPanel(index + 1, config);
container.scrollTargetEl();
}
});
Link | February 11th, 2014 at 10:15 AM