PDA

View Full Version : Ext.ux.InterfaceViewport - Viewport with Interface for Component Communication



santosh.rajan
25 Sep 2008, 10:07 PM
Latest Version here
http://www.extjs.com/forum/showthread.php?p=230976#post230976

This extension came about because I wanted to create an application without further polluting the global namespace. ie I wanted to avoid creating new namespaces and 'vars' in the global namespace. Also I wanted all functions that modify a component to be within the component itself rather than 'floating' around elsewhere. I believe this will create a 'clean' application layout. The solution was to have
1) An interface that allowed Ext Components to modify other components in an application directly.
2) The obvious choice to locate such an interface was the Viewport. Because you can have only one instance of the viewport and it is the grand daddy of all the components.
3) Also every Component should have access to the Interface Viewport from within.
These objectives were achieved by
1) Extending Ext.Viewport to create Ext.ux.InterfaceViewport.
2) Each component can access the InterfaceViewport internally via its this.iviewport.
3) Each Component can allow other components, to modify itself by registering functions with the Interface Viewport.
So if you have a treepanel that needs to add a tab in a tabpanel you can do it like this inside the treepanel.
this.iviewport.addMainPanelTab();
addMainPanelTab is a function registered with the viewport by the tabpanel and is called in the scope of the tabpanel.
Here is a complete application that illustartes all this.

<html>
<head>
<title>Ext.uxInterfaceViewport Usage</title>
<link rel="stylesheet" type="text/css" href="../ext-2.2/resources/css/ext-all.css" />
</head>
<body>
<script type="text/javascript" src="ext-2.2/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext-2.2/ext-all.js"></script>
<script type="text/javascript" src="Ext.ux.InterfaceViewport.js"></script>
<script type="text/javascript">

Ext.BLANK_IMAGE_URL = 'ext-2.2/resources/images/default/s.gif';

Ext.onReady(function() {
new Ext.ux.InterfaceViewport({
layout: 'border',
items: [{
region: 'west',
xtype: 'treepanel',
width:220,
root: new Ext.tree.TreeNode({
text: 'Testing Ext.ux.InterfaceViewport',
expanded: true
}),
listeners: {
click: function(node) {
if (node.isLeaf()) {
switch(node.id) {
case 'Sales':
this.iviewport.addSalesTab(); //Calling addSales in the InterfaceViewport.
break;
case 'Expense':
this.iviewport.addExpenseTab();//Calling addExpense in the InterfaceViewport.
break;
}
}
},
render: function() {
this.root.appendChild([
new Ext.tree.TreeNode({id:'Sales', text:'Sales', leaf:true}),
new Ext.tree.TreeNode({id:'Expense', text:'Expense', leaf:true})
]);
}
},
scope: this
},{
region: 'center',
xtype: 'tabpanel',
iviewportRegister: { // Register these functions with the
addSalesTab: function() { // Interface Viewport.
if (!this.salestab) {
this.salestab = this.add({
title: 'Sales Tab'
});
}
this.setActiveTab(this.salestab);
},
addExpenseTab: function() {
if (!this.expensetab) {
this.expensetab = this.add({
title: 'Expense Tab'
});
}
this.setActiveTab(this.expensetab);
}
}
}]
});
});

</script>
</body>
</html>

Important! The registered functions must be unique within the application. They must be added for every object instance via the config option. If you want to add for a 'class' or 'preconfigured class' then the iviewportRegister: must be in the config of the class.

Here is the source Code

///////////////////////////////// Class Ext.ux.InterfaceViewport /////////////////////////////////////
// Author: Santosh Rajan
// Filename: Ext.ux.InterfaceViewport.js
// Email: santrajan@gmail.com
// Version: 0.9 beta
// License: BSD Licence - http://extgooglevisdemo.myofiz.com/license.txt

Ext.ux.InterfaceViewport = function() {
Ext.apply(this, {
iviewport: this,
listeners: {
add: this.onAdd
},
scope: this
});
Ext.ux.InterfaceViewport.superclass.constructor.apply(this, arguments);
};

Ext.extend(Ext.ux.InterfaceViewport, Ext.Viewport, {
onAdd: function(cont, comp, i) {
comp.iviewport = this;
if (comp.iviewportRegister)
this.register(comp, comp.iviewportRegister);
if (comp.items) {
comp.on('add', this.onAdd, this);
comp.items.each(function(item, idx) {
this.onAdd(comp, item, idx);
}, this);
}
},
register: function(comp, obj) {
for (var i in obj)
this[i] = obj[i].createDelegate(comp);
}
});


Please let me know of any bugs or modifications required.

danh2000
25 Sep 2008, 10:17 PM
Each Component can allow other components, to modify itself by registering functions with the Interface Viewport.
So if you have a treepanel that needs to add a tab in a tabpanel you can do it like this inside the treepanel.
this.iviewport.addMainPanelTab();


Doesn't this just created tightly coupled components though? and goes against reuse.

I prefer components that have as little knowledge about the rest of the application as possible.

I believe the container of 2 seperate components should manage the comms between them and they should ideally not be communicating directly.

I'm not knocking your work I just think that this piece goes against good OO design priniciples.

Did you consider the interdependency issues and how do you handle them?

santosh.rajan
25 Sep 2008, 10:31 PM
I understand your point of view, but this is about instance objects communicating via an interface, and these are not actual classes communicating. Instance of a class is tightly coupled to the application anyway, unlike the class itself which is what you are talking about. The above does not make any modification to the class itself.
I hope you have noticed above that the Extension acts on an instance of treepanel and tabpanel. NOT the Ext.tree.Treepanel or Ext.TabPanel classes.

danh2000
26 Sep 2008, 2:54 AM
Hi Santosh,

I understand that they are instances and your component does suit a nested architecture like in your example, I just find that Ext's in built method of inter-component communication (everything being observable) provides everything that I need and doesn't impose any restrictions.

A container registers a listener with a TreePanel and creates an instance of a SalesTab when a node is clicked, therefore the container (which already knows about both components) is the only part that is coupled.

In your example the TreePanel must know explicitly about the SalesTab (or the method to instantiate it anyway) which tightly couples it to that use case and prevents you from reusing it in another part of the application.

As I said, your component could be useful for the small nested sample that you demonstrate (even though anything declared with var there wouldn't be global anyway as it's in a function), but I'm sorry, I just fail to see the benefit otherwise.

Am I missing something? You say you've used it in an application - how was this structured?

Thanks,

santosh.rajan
26 Sep 2008, 3:28 AM
Hi Santosh,


A container registers a listener with a TreePanel and creates an instance of a SalesTab when a node is clicked, therefore the container (which already knows about both components) is the only part that is coupled.



Hi Danh,
Do you have an example of what you mentioned. Because I did consider your approach but I figured that would involve components firing events. I thought that this approach was simpler. The application I am using this is a fairly large accounting application, I cant give any more details here, but so far I have no problems with this approach. Because what would have been other external functions are in the respective config options of the components now.
Thanks Santosh

tof
27 Sep 2008, 5:55 AM
I prefer a global event listener for intercomponents communication :
Like :

In a grid :

AppEvents.on('showgrid1', function() { this.show(); })

Anywhere else :

var b = new Button({
text : "Display the first grid",
handler: function() { AppEvents.fireEvent('showgrid1'); }
});


But your idea is good anyway :)
You should look at something like this in your InterfaceViewport, to automatically register "iwieport" (not tested) :


var self = this;
Ext.Component.prototype.render = Ext.Component.prototype.render.createSequence(function() {
this.iviewport = self;
});

santosh.rajan
27 Sep 2008, 6:23 AM
I did consider your first option. As I mentioned in the a earlier post I chose my current way because I thought it was simple. Creating and calling functions is simpler than creating and firing events (especially for users who are not advanced users for Ext).
Your second idea is something that didnt cross my mind but definitely I am going to test it and incorporate in my next version. Thanks Tof.

santosh.rajan
29 Sep 2008, 12:48 AM
Based on a suggestion from Christophe Badoit here is the simplified version 0.91 beta.

///////////////////////////////// Class Ext.ux.InterfaceViewport /////////////////////////////////////
// Author: Santosh Rajan
// Filename: Ext.ux.InterfaceViewport.js
// Email: santrajan@gmail.com
// Version: 0.91 beta
// License: BSD Licence - http://extgooglevisdemo.myofiz.com/license.txt

Ext.ux.InterfaceViewport = function() {
var viewport = this;
Ext.Component.prototype.render = Ext.Component.prototype.render.createSequence(function() {
this.iviewport = viewport;
if (this.iviewportRegister)
viewport.register(this, this.iviewportRegister);
});
Ext.ux.InterfaceViewport.superclass.constructor.apply(this, arguments);
};

Ext.extend(Ext.ux.InterfaceViewport, Ext.Viewport, {
register: function(comp, obj) {
for (var i in obj)
comp.iviewport[i] = obj[i].createDelegate(comp);
}
});

jay@moduscreate.com
30 Sep 2008, 6:12 AM
No offense, but I'd rather stick to event-based UI development.

santosh.rajan
30 Sep 2008, 6:53 PM
Yes I understand jgarcia, it is a matter of choice, this is an option for beginners or those not comfortable with with adding and firing new events. What I have done here is replaced the "event model" with "API model". In effect each component (or component instance) creates an "API" (so to speak) and registers it with the viewport. Any other component can access the api via its own (this.iviewport).

jay@moduscreate.com
1 Oct 2008, 3:07 AM
Understood. I would like to point out that this does not play in the spirit of Ext, which is an event-driven framework.

santosh.rajan
1 Oct 2008, 4:05 AM
Any user interface is 'event driven', hence Ext too is event driven. So is a browser or any other application that has a user interface. However that does not mean your software architecture is or has to be always event driven. I will explain this with an example.

You have an instance of a tabpanel, for which you want to add tabs based on some event, and you need a function called addTab. A very common situation while developing in Ext. Question is "where do i locate this function?". Options are
1) In the global namespace.
2) In your own namespace. (MyApp.addTab).
3) Or add the addTab functionality inside an 'event sink' (the listener) in an event driven architecture.

However there is a fourth option where you have a service oriented architecture complementing an event driven architecture. You can locate addTab within the tabpanel definition (I firmly believe that is where it rightfully belongs, and it should be called within the scope of the tabpanel instance), and register it to the service oriented architecture provider, in this case the InterfaceViewport.