PDA

View Full Version : collapsible region title still shows up



mjlecomte
18 Jun 2008, 6:03 PM
For education I am altering the feedviewer demo (http://extjs.com/deploy/dev/examples/feed-viewer/view.html) code to the "Ext 2 way" of extending. I am doing something wrong, but don't see the problem.

The problem is when I collapse the west region. A picture is worth 1000 words, so see the attached. When I collapse the west region the title still shows up (the collapse icon changes even). Top 1/3 of picture is initial view, middle 1/3 of picture is the original working version, bottom 1/3 of picture is my ported problematic version.

The code below is what I believe to be just a straight port the alternate method to extend (no more, no less...so I thought, obviously it's less at the moment :"> ). You should be able to drop "my version" in the same spot (examples/feed-viewer/FeedPanel.js) and see my problem locally.

FeedPanel.js (My ported version)

FeedPanel = Ext.extend(Ext.tree.TreePanel, {

initComponent:function() {

Ext.apply(this, {

//stuff to apply
id:'feed-tree',
region:'west',
title:'Feeds',
split:true,
width: 225,
minSize: 175,
maxSize: 400,
collapsible: true,
margins:'0 0 5 5',
cmargins:'0 5 5 5',
rootVisible:false,
lines:false,
autoScroll:true,
root: new Ext.tree.TreeNode('Feed Viewer'),
collapseFirst:false,

tbar: [{
iconCls:'add-feed',
text:'Add Feed',
handler: this.showWindow,
scope: this
},{
id:'delete',
iconCls:'delete-icon',
text:'Remove',
handler: function(){
var s = this.getSelectionModel().getSelectedNode();
if(s){
this.removeFeed(s.attributes.url);
}
},
scope: this
}]

}); // end of apply

FeedPanel.superclass.initComponent.apply(this, arguments);

this.feeds = this.root.appendChild(
new Ext.tree.TreeNode({
text:'My Feeds',
cls:'feeds-node',
expanded:true
})
);

this.getSelectionModel().on({
'beforeselect' : function(sm, node){
return node.isLeaf();
},
'selectionchange' : function(sm, node){
if(node){
this.fireEvent('feedselect', node.attributes);
}
this.getTopToolbar().items.get('delete').setDisabled(!node);
},
scope:this
});

this.addEvents({feedselect:true});

this.on('contextmenu', this.onContextMenu, this);

}, // end of function initComponent

afterRender: function(){
//call parent
FeedPanel.superclass.afterRender.apply(this, arguments);

// prevent the default context menu when you miss the node
this.el.on('contextmenu', function(e){
e.preventDefault();
});

}, // end of function afterRender

/**
* Overrided/Extended methods
*/

addFeed : function(attrs, inactive, preventAnim){
var exists = this.getNodeById(attrs.url);
if(exists){
if(!inactive){
exists.select();
exists.ui.highlight();
}
return;
}
Ext.apply(attrs, {
iconCls: 'feed-icon',
leaf:true,
cls:'feed',
id: attrs.url
});
var node = new Ext.tree.TreeNode(attrs);
this.feeds.appendChild(node);
if(!inactive){
if(!preventAnim){
Ext.fly(node.ui.elNode).slideIn('l', {
callback: node.select, scope: node, duration: 0.4
});
}else{
node.select();
}
}
return node;
},

onContextHide : function(){
if(this.ctxNode){
this.ctxNode.ui.removeClass('x-node-ctx');
this.ctxNode = null;
}
},

onContextMenu : function(node, e){
if(!this.menu){ // create context menu on first right click
this.menu = new Ext.menu.Menu({
id:'feeds-ctx',
items: [{
id:'load',
iconCls:'load-icon',
text:'Load Feed',
scope: this,
handler:function(){
this.ctxNode.select();
}
},{
text:'Remove',
iconCls:'delete-icon',
scope: this,
handler:function(){
this.ctxNode.ui.removeClass('x-node-ctx');
this.removeFeed(this.ctxNode.attributes.url);
this.ctxNode = null;
}
},'-',{
iconCls:'add-feed',
text:'Add Feed',
handler: this.showWindow,
scope: this
}]
});
this.menu.on('hide', this.onContextHide, this);
}
if(this.ctxNode){
this.ctxNode.ui.removeClass('x-node-ctx');
this.ctxNode = null;
}
if(node.isLeaf()){
this.ctxNode = node;
this.ctxNode.ui.addClass('x-node-ctx');
this.menu.items.get('load').setDisabled(node.isSelected());
this.menu.showAt(e.getXY());
}
},

removeFeed: function(url){
var node = this.getNodeById(url);
if(node){
node.unselect();
Ext.fly(node.ui.elNode).ghost('l', {
callback: node.remove, scope: node, duration: 0.4
});
}
},

selectFeed: function(url){
this.getNodeById(url).select();
},

showWindow : function(btn){
if(!this.win){
this.win = new FeedWindow();
this.win.on('validfeed', this.addFeed, this);
}
this.win.show(btn);
}

}); // end of extend

Ext.ComponentMgr.registerType('feedpanel', FeedPanel); //convention is to use all lowercase for xtype

// end of file

FeedPanel.js (Original)


/*
* Ext JS Library 2.1
* Copyright(c) 2006-2008, Ext JS, LLC.
* [email protected]
*
* http://extjs.com/license
*/

FeedPanel = function() {
FeedPanel.superclass.constructor.call(this, {
id:'feed-tree',
region:'west',
title:'Feeds',
split:true,
width: 225,
minSize: 175,
maxSize: 400,
collapsible: true,
margins:'0 0 5 5',
cmargins:'0 5 5 5',
rootVisible:false,
lines:false,
autoScroll:true,
root: new Ext.tree.TreeNode('Feed Viewer'),
collapseFirst:false,

tbar: [{
iconCls:'add-feed',
text:'Add Feed',
handler: this.showWindow,
scope: this
},{
id:'delete',
iconCls:'delete-icon',
text:'Remove',
handler: function(){
var s = this.getSelectionModel().getSelectedNode();
if(s){
this.removeFeed(s.attributes.url);
}
},
scope: this
}]
});

this.feeds = this.root.appendChild(
new Ext.tree.TreeNode({
text:'My Feeds',
cls:'feeds-node',
expanded:true
})
);

this.getSelectionModel().on({
'beforeselect' : function(sm, node){
return node.isLeaf();
},
'selectionchange' : function(sm, node){
if(node){
this.fireEvent('feedselect', node.attributes);
}
this.getTopToolbar().items.get('delete').setDisabled(!node);
},
scope:this
});

this.addEvents({feedselect:true});

this.on('contextmenu', this.onContextMenu, this);
};

Ext.extend(FeedPanel, Ext.tree.TreePanel, {

onContextMenu : function(node, e){
if(!this.menu){ // create context menu on first right click
this.menu = new Ext.menu.Menu({
id:'feeds-ctx',
items: [{
id:'load',
iconCls:'load-icon',
text:'Load Feed',
scope: this,
handler:function(){
this.ctxNode.select();
}
},{
text:'Remove',
iconCls:'delete-icon',
scope: this,
handler:function(){
this.ctxNode.ui.removeClass('x-node-ctx');
this.removeFeed(this.ctxNode.attributes.url);
this.ctxNode = null;
}
},'-',{
iconCls:'add-feed',
text:'Add Feed',
handler: this.showWindow,
scope: this
}]
});
this.menu.on('hide', this.onContextHide, this);
}
if(this.ctxNode){
this.ctxNode.ui.removeClass('x-node-ctx');
this.ctxNode = null;
}
if(node.isLeaf()){
this.ctxNode = node;
this.ctxNode.ui.addClass('x-node-ctx');
this.menu.items.get('load').setDisabled(node.isSelected());
this.menu.showAt(e.getXY());
}
},

onContextHide : function(){
if(this.ctxNode){
this.ctxNode.ui.removeClass('x-node-ctx');
this.ctxNode = null;
}
},

showWindow : function(btn){
if(!this.win){
this.win = new FeedWindow();
this.win.on('validfeed', this.addFeed, this);
}
this.win.show(btn);
},

selectFeed: function(url){
this.getNodeById(url).select();
},

removeFeed: function(url){
var node = this.getNodeById(url);
if(node){
node.unselect();
Ext.fly(node.ui.elNode).ghost('l', {
callback: node.remove, scope: node, duration: 0.4
});
}
},

addFeed : function(attrs, inactive, preventAnim){
var exists = this.getNodeById(attrs.url);
if(exists){
if(!inactive){
exists.select();
exists.ui.highlight();
}
return;
}
Ext.apply(attrs, {
iconCls: 'feed-icon',
leaf:true,
cls:'feed',
id: attrs.url
});
var node = new Ext.tree.TreeNode(attrs);
this.feeds.appendChild(node);
if(!inactive){
if(!preventAnim){
Ext.fly(node.ui.elNode).slideIn('l', {
callback: node.select, scope: node, duration: 0.4
});
}else{
node.select();
}
}
return node;
},

// prevent the default context menu when you miss the node
afterRender : function(){
FeedPanel.superclass.afterRender.call(this);
this.el.on('contextmenu', function(e){
e.preventDefault();
});
}
});

mjlecomte
20 Jun 2008, 9:02 AM
I'll settle for any guesses what the problem might be. Something with the constructor sequence that renders something different? /:)

jsakalos
21 Jun 2008, 3:37 AM
Try to move region:'west' from initComponent to class variable.

mjlecomte
21 Jun 2008, 5:02 AM
Thanks for your thoughts.

I changed code to that shown below:

FeedPanel = Ext.extend(Ext.tree.TreePanel, {

region:'west',

initComponent:function() {

Ext.apply(this, {

//stuff to apply
id:'feed-tree',
//region:'west',
title:'Feeds',
split:true,

That made no apparent difference. I did spend some time comparing the css through firebug and I noticed that div id="feed-tree" has different css between 'my' version and the original.

For the working/original version the css within the div uses:

in the 'open' state:

position: absolute
visibility: visible

in the 'collapsed' state:

position: absolute
visibility: visible
display: none

In my version, the above css does not appear at all.

Not sure if that provides any more clues where I might check next.

mjlecomte
21 Jun 2008, 7:00 PM
Ok, I think Saki was on the right track. The problem appears to be in the construction.

If you're not familiar with the Feed Viewer example, in the FeedViewer.js file there's a line like this:

var feeds = new FeedPanel();

Using the original code, that line dumps you directly into the constructor for the FeedPanel class:

FeedPanel = function() {
FeedPanel.superclass.constructor.call(this, {
id:'feed-tree',
region:'west',
...
collapsible: true,
margins:'0 0 5 5',


Moving those configs inside initComponent doesn't work, that is the collapsible and margins properties do not get applied.

The only way I got those properties to take was to change FeedViewer.js file to specify those configs:

var feeds = new FeedPanel({
collapsible: true,
margins:'0 0 5 5'
});

If I do that all is well. Assigning those 2 properties outside initComponent as class properties didn't work either. Is there something different happening with the (earlier) direct call to superclass.constructor? As far as I can tell when code execution arrives at initComponent those properties ARE there (even if I assign as a class property).

Argh. :-?

dj
22 Jun 2008, 5:10 AM
That's a tricky one...

Normally you are perfectly right in setting the configs in initComponent. That should not matter. However, one thing is different when you apply your config in initComponent and not as a config object-literal in the constructor call: component.initialConfig will not be set.

Unfortunately initialConfig is used in BorderLayout to pass the config object-literal to the BorderLayout.Region that is created for each region of the BorderLayout (see widgets/layout/BorderLayout.js line 63f)


this[pos] = pos != 'center' && c.split ?
new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);


Since initialConfig is empty when you define your configs in initComponent, the Region gets created with the default config (i.e. not collapsible). That also explains the weird behavior/appearance, if you collapse the west-region: The override-code that BorderLayout.Region injects in the Panel gets not executed so the default Panel-collapse behaviour gets executed.

To fix this, just also set the initialConfig in your initComponent method:


initComponent:function() {

var cfg = {

//stuff to apply
id:'feed-tree',
region:'west',
title:'Feeds',
split:true,
width: 225,
minSize: 175,
maxSize: 400,
collapsible: true,
margins:'0 0 5 5',
cmargins:'0 5 5 5',
rootVisible:false,
lines:false,
autoScroll:true,
root: new Ext.tree.TreeNode('Feed Viewer'),
collapseFirst:false,

tbar: [{
iconCls:'add-feed',
text:'Add Feed',
handler: this.showWindow,
scope: this
},{
id:'delete',
iconCls:'delete-icon',
text:'Remove',
handler: function(){
var s = this.getSelectionModel().getSelectedNode();
if(s){
this.removeFeed(s.attributes.url);
}
},
scope: this
}]

};
Ext.apply(this, cfg);
Ext.apply(this.initialConfig, cfg);

FeedPanel.superclass.initComponent.apply(this, arguments);

this.feeds = this.root.appendChild(
new Ext.tree.TreeNode({
text:'My Feeds',
cls:'feeds-node',
expanded:true
})
);

this.getSelectionModel().on({
'beforeselect' : function(sm, node){
return node.isLeaf();
},
'selectionchange' : function(sm, node){
if(node){
this.fireEvent('feedselect', node.attributes);
}
this.getTopToolbar().items.get('delete').setDisabled(!node);
},
scope:this
});

this.addEvents({feedselect:true});

this.on('contextmenu', this.onContextMenu, this);

}, // end of function initComponent

mjlecomte
22 Jun 2008, 6:13 AM
Thanks, that does work.

Is this a limitation/shortcoming, just something to know about, or (unlikely) 'bug'?

Seems like the "old" way more reliably injects the config into Component so stuff gets applied to initialConfig. Or, I guess the equivalent with the "new" way is to just add the apply to initialConfig as part of the class definition.

Looking through the code I think this could potentially affect situations with:

BorderLayout (stated above)
AnchorLayout (onLayout method)
Tip (showAt method)
ComboBox (queryDelay and minChars properties)
FormPanel (possibly not, just deletes listeners)
GridPanel (loadMask property)
Component and Observable (cloneConfig method)


Edit: by the way, I haven't picked up on yet why specifying those properties in the class definition didn't get applied yet. Shouldn't class properties get passed through the same as if specified with new SomeClass({myConfigsHere})?

dj
22 Jun 2008, 2:18 PM
Is this a limitation/shortcoming, just something to know about, or (unlikely) 'bug'?

I think it's something to know about when you use the pre-configured classes method. Ext doesn't really anticipates this kind of configuration in some places.


Edit: by the way, I haven't picked up on yet why specifying those properties in the class definition didn't get applied yet. Shouldn't class properties get passed through the same as if specified with new SomeClass({myConfigsHere})?

Have a look at Ext.Component's constructor (src/widgets/Component.js) there you can see, that initialConfig gets copied before calling initComponent

mjlecomte
22 Jun 2008, 5:20 PM
Have a look at Ext.Component's constructor (src/widgets/Component.js) there you can see, that initialConfig gets copied before calling initComponent
Yes, I see that, but I defined those config properties as class properties, to clarify:

FeedPanel = Ext.extend(Ext.tree.TreePanel, {
region:'west',

initComponent:function() {
Ext.apply(this, {
//stuff to apply
id:'feed-tree',
//region:'west',
title:'Feeds',
split:true,

So, why wouldn't region be passed to Ext.Component's constructor? My understanding was that when this class is defined that region would be defined at runtime becoming a property of the FeedPanel class. So when code execution eventually ends up instanciating this class:

var myFeedPanel = new FeedPanel();
region was already defined as a class property...BEFORE initComponent. So shouldn't region exist when initialConfig was copied?
(Note region is just an example, I did this with the relevant configs used above).

dj
22 Jun 2008, 10:37 PM
If you do that, region is in the prototype of the object - not the object itself. So basically it is already there before you call the constructor. (Check with
console.dir(FeedPanel.protoype); in FireBug)


initialConfig explicitly only copies the config object-literal that you pass to the constructor, because that is the only thing that distinguishes objects of one kind from each other; calling two times
new FeedPanel(); gives you two objects that are identical. (BTW: they would have an empty initialConfig)


As far as I understand it, initialConfig was introduced mainly to allow cloning of objects. It is fine for that. But it is also used to copy the configuration of one class to a delegate of it (e.g. BorderLayout.Region) and that doesn't really work with pre-configured classes, because they configure the object in initComponent.