PDA

View Full Version : [FIXED][3.0.0] List View Columns grow 100 times wider each instance



JoeAtTrends
29 Jul 2009, 3:31 PM
Ext version tested:
Ext 3.0.0
IIRC Also earlier versions
Adapter used:
ext
css used:
only default ext-all.css


Browser versions tested against:

IE7
FF3 (firebug 1.3.0.10 installed)
Operating System:

WinXP Pro
Description:
In my case I was using a customized Ext.ux.Form.ComboGrid, and had extended it further to create a custom component (just to package default values into it)
When using this custom component the columns will grow 100 times with each instance of the custom component.

Test Case:
Sorry for the mess, I am looking into a hosted example but this should be droppable into the examples folder (in its own folder)



<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title id='title'>ListView column sizing issue Example</title>

<!-- ** CSS ** -->
<!-- base library -->
<link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />

<!-- overrides to base library -->


<!-- ** Javascript ** -->
<!-- base library -->
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-all-debug.js"></script>


<!-- overrides to base library -->

<!-- extensions -->
<!-- Using Ext.ux.Form.ComboGrid http://extjs.com/forum/showthread.php?t=68618
Removed all combogrid specific comments for appearance,
left all customizations I had made (such as using list
view instead of grids)
-->
<script type="text/javascript">
Ext.namespace('Ext.ux.Form');

Ext.ux.Form.ComboGrid = Ext.extend(Ext.form.ComboBox, {
selectEvent: 'click',
valueTitle: '',
view: null,
defaultTpl: function()
{
return '<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>';
},
initComponent: function()
{
this.lazyInit = false
this.inEditMode = false;
Ext.ux.Form.ComboGrid.superclass.initComponent.call(this);
},

// private - this will generate default config for view
// Modified to use ListView instead of the Grid
defaultView: function()
{
var cols = [
{id: this.valueField, dataIndex: this.valueField, hidden: true, sortable: false},
{id: this.displayField, dataIndex: this.displayField, sortable: true, header: this.valueTitle, tpl: this.tpl}
];

return {
xtype: 'listview',
applyTo: this.innerList,
store: this.store,
loadingText: this.loadingText,
singleSelect: true,
columns: (Ext.isEmpty(this.columns)? cols : this.columns),
viewConfig: {
forceFit: true,
headersDisabled: Ext.isEmpty(this.valueTitle),
emptyText: this.listEmptyText,
scrollOffset: 0
},
//Removed since it isn't a grid
//sm: new Ext.grid.RowSelectionModel({moveEditorOnEnter: false}),
frame: false,
//Removed to make height dynamic
//height: (this.minHeight + 5),
autoHeight: true
};
},
bindStore : function(store, initial)
{
if (this.store && !initial)
{
this.store.un('beforeload', this.onBeforeLoad, this);
this.store.un('load', this.onLoad, this);
this.store.un('loadexception', this.collapse, this);
if (this.store !== store && this.store.autoDestroy)
{
this.store.destroy();
}

if (!store)
{
this.store = null;
}
}
if (store)
{
if (!initial)
{
this.lastQuery = null;
if (this.pageTb)
{
this.pageTb.bindStore(store);
}
}

this.store = Ext.StoreMgr.lookup(store);
this.store.on('beforeload', this.onBeforeLoad, this);
this.store.on('load', this.onLoad, this);
this.store.on('loadexception', this.collapse, this);

if (this.view)
{
this.view.refresh();
}
}
},

initList: function()
{
var cls = 'x-combo-list';

if (!this.list)
{
this.list = new Ext.Layer({
parentEl: this.getListParent(),
shadow: this.shadow,
cls: [cls, this.listClass].join(' '),
constrain:false
});

var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
this.list.setSize(lw, 0);
this.list.swallowEvent('mousewheel');
this.assetHeight = 0;
if(this.syncFont !== false){
this.list.setStyle('font-size', this.el.getStyle('font-size'));
}
if(this.title){
this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
this.assetHeight += this.header.getHeight();
}

this.innerList = this.list.createChild({cls:cls+'-inner'});
this.mon(this.innerList, 'mouseover', this.onViewOver, this);
this.mon(this.innerList, 'mouseout', this.onViewOut, this); // this one is mine
this.mon(this.innerList, 'mousemove', this.onViewMove, this);
this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));

if(this.pageSize){
this.footer = this.list.createChild({cls:cls+'-ft'});
this.pageTb = new Ext.PagingToolbar({
store: this.store,
pageSize: this.pageSize,
renderTo:this.footer
});
this.assetHeight += this.footer.getHeight();
}

if (Ext.isEmpty(this.tpl)) this.tpl = this.defaultTpl.call(this);
if (Ext.isEmpty(this.view)) this.view = {};
if (!(this.view instanceof Ext.Component))
{
this.view = Ext.applyIf(this.view, this.defaultView());
this.view = Ext.ComponentMgr.create(this.view);
}
if (this.view instanceof Ext.grid.EditorGridPanel)
{
this.view.on('beforeedit', this.onViewBeforeEdit, this);
}

this.mon(this.view, this.selectEvent, this.onViewClick, this);
this.view.on('keydown', this.onViewKeyDown, this);

this.bindStore(this.store, true);

if(this.resizable){
this.resizer = new Ext.Resizable(this.list, {
pinned:true, handles:'se'
});
this.mon(this.resizer, 'resize', function(r, w, h){
this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
this.listWidth = w;
this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
this.restrictHeight();
}, this);

this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
}

}
},

onBeforeLoad : function(){
if(!this.hasFocus){
return;
}
this.view.fireEvent('onBeforeLoad',arguments);
this.restrictHeight();
this.selectedIndex = -1;

},

onLoad : function(){
if (this.view && this.view.view)
{
this.innerList.update('');
this.view.view.refresh();
this.view.body.appendTo(this.innerList);
}
Ext.ux.Form.ComboGrid.superclass.onLoad.call(this);
},

/* Removed (don't think its needed, combo's methods work)
// private - overrided, updated
onViewOver : function(e, t)
{
if(this.inKeyMode || this.inEditMode) // prevent key nav and mouse over conflicts
{
return;
}
var v = this.view;
var index = v.findRowIndex(t);
var row = v.findRow(t);
var r, rs;
var cls = 'x-grid3-cell-selected';

if (index !== false)
{
rs = this.view.getEl().query('.'+cls);
Ext.each(rs, function(i){Ext.get(i).removeClass(cls);});
r = Ext.get(row);
if (!r.hasClass(cls)) {
r.addClass(cls);
this.selectedIndex = index;
}
}
},

// private - new handler
onViewOut: function()
{
if (!this.inEditMode)
{
var v = this.view;
var cls = 'x-grid3-cell-selected';
var rs;

rs = this.view.getEl().query('.'+cls);
Ext.each(rs, function(i){Ext.get(i).removeClass(cls);});
this.select(this.selectedIndex, false, this.hasFocus);
}
},

// private
onViewClick : function(doFocus)
{
var r = this.store.getAt(this.selectedIndex);

if (r)
{
this.onSelect(r, this.selectedIndex);
}
if (doFocus !== false)
{
this.el.focus();
}
},
*/
// private - if view got focus, emulates original navigation.

onViewKeyDown: function(e)
{
switch (e.getKey())
{
case e.ENTER:
{
this.keyNav['enter'].call(this, e);
break;
}
case e.ESC:
{
this.keyNav['esc'].call(this, e);
break;
}
case e.TAB:
{
this.keyNav['tab'].call(this, e);
break;
}
}
e.stopEvent();
return false;
}

/**
* Removed (don't think it is needed, combo's works and no sm for listview)

select : function(index, scrollIntoView, focus)
{
var sm = this.view.getSelectionModel();

this.selectedIndex = index;

if (sm instanceof Ext.grid.RowSelectionModel)
{
sm.selectRow(Ext.isArray(index)? index[0] : index);
}
else if (sm instanceof Ext.grid.CellSelectionModel)
{
sm.select(
Ext.isArray(index)? index[0] : index
,
Ext.isArray(index)? index[1] : 1
&nbsp;
}

if (scrollIntoView !== false)
{
var el = this.view.getView().getRow(index);
if (el)
{
this.innerList.scrollChildIntoView(el, false);
}
}

if (focus !== undefined && focus === true) this.focus();
}
*/
});

Ext.reg('combogrid', Ext.ux.Form.ComboGrid);

var dataStore = new Ext.data.JsonStore({
fields: [
{name:'userID',dataIndex:'userID',header:'User Id'},
{name:'firstName',dataIndex:'firstName',header:'First Name'},
{name:'lastName',dataIndex:'lastName',header:'Last Name'},
{name:'display',convert:function(v,r){ return r.lastName + ((r.lastName.replace(/\s/g,"") == '' || r.firstName.replace(/\s/g,"") == '')?'':', ') + r.firstName; }}
],
data:[
{userID:'jSmith',firstName:'John',lastName:'Smith'},
{userID:'gBurdell',firstName:'George',lastName:'Burdell'},
{userID:'jDoe',firstName:'John',lastName:'Doe'},
{userID:'mcLovin',firstName:'McLovin',lastName:''}
]
});
/**
* Extending above custom comboGrid to have default User specific values
*
*/

Ext.ux.UserCombo = Ext.extend(Ext.ux.Form.ComboGrid,{
fieldLabel:'User',
hiddenName:'selUser',
valueField:'userID',
displayField:'display',
triggerAction: 'all',
typeAhead:true,
width: 100,
listWidth: 400,
height: 300,
lazyInit: false,
mode:'local',
lazyRender: false,
listAlign: 'tl-bl?',
store: dataStore,
columns:[{dataIndex:'userType',width:0},
{dataIndex:'userID',header:'User Id',width:.25},
{dataIndex:'firstName',header:'First Name',width:.25},
{dataIndex:'lastName',header:'Last Name',width:.25}]

});

Ext.reg('usercombo', Ext.ux.UserCombo);

</script>
<!-- page specific -->

<script type="text/javascript">
Ext.BLANK_IMAGE_URL = '../../resources/images/default/s.gif';
//global for better debugging
var form, win,customCombo;
Ext.onReady(function(){

//Test Form
form = new Ext.FormPanel({
id: 'form',
bodyStyle: 'padding: 10px',
border: false,
buttons: [{
text: 'Login',
scope: this,
handler: function() {
Ext.getCmp('form').getForm().submit({ url: 'noscript.php' });
}
}],
items: [{xtype:'usercombo'
},{xtype:'textfield',
fieldLabel:'Password',
inputType:'password'
},{xtype:'usercombo',
fieldLabel:'Assume Identity'
},{xtype:'usercombo',
fieldLabel:'Guess which columns are which'
}]
});
win = new Ext.Window({
title: 'User Login Example',
width: 400,
height: 250,
layout: 'fit',
items: form
});
win.show();

}); //end onReady
</script>

</head>
<body>
</body>
</html>

Steps to reproduce the problem:

Extend column information into a component (or the store I think)
Use this component more than once.
The result that was expected:
Columns in list view should be the specified widths.
The result that occurs instead:

The column widths extend well past the list width, and far off the page.

Debugging already done:
Searched for the problem and found that in List View's initComponent method when the columns are calculated they are converted from float percents to whole percent numbers ( width *= 100; )
It seems that when the columns are included in the extended components the variables persist in prototype and will be multipled out for each instance.Here is the code as is:


if(Ext.isNumber(c.width)){
c.width *= 100;
allocatedWidth += c.width;
colsWithWidth++;
}
Possible fix:
Checking that the width is < 1 before multiplying out by 100
Doing this could also benefit users who have personal issues with floating point numbers and would like to begin with whole percents instead
Proposed change to above code:



if(Ext.isNumber(c.width)){
if(c.width < 1){
c.width *= 100;
}
allocatedWidth += c.width;
colsWithWidth++;
}
BTW, this was my first bug post, so tear me apart and let me know if I have done anything wrong here... Don't want to make your lives harder, I am just happy to have found something wrong where I can then contribute to the project.
Thanks.

mjlecomte
29 Jul 2009, 5:27 PM
Thanks for the report.

That modification seems reasonable to me, at first glance doesn't seem like it would cause problems and would be more flexible to allow either expressing the width as a percent or a whole number.

But, that said, any reason the ux you mention couldn't also be modified to force it to use column widths < 1. I'm not familiar nor did I study the other ux.

evant
29 Jul 2009, 5:44 PM
Thanks for the detailed report. The fix you've provided would work, but it's not really addressing the root of the problem. As you've suggested, since you're putting the column definitions in the object prototype they will be shared across all instances. In general, Ext tries not to modify configuration objects, so in this instance we should probably extend this to the columns as well. As such, I'm going to add a fix that copies the column definitions.

At this point in time you'll still need to specify < 1 values for column widths.

Marking this one as fixed.

JoeAtTrends
30 Jul 2009, 6:08 AM
Thank you for your help. That will save me the trouble of digging into the code and adding these changes each version.

My problem wasn't really with specifying widths < 1. And putting a system in the ux to limit the widths to < 1 would only cause it to error on the second and third instance. Maybe a better explaination of the problem is that upon the second use of the custom component the widths would grow and grow more with each instance after that. I found this when using the ComboGrid, modified as seen above to use a list view, in a dynamic form.

Another take on reproducing this is if list view config for the combo was created by using columns: this.columns || this.store.fields,. The advantage to using this is that the combo config stays simple and the specifics of how the columns are shown are determined by the store. (I haven't used this for the ovbious reason that it locks your store into being shown a single way) But it was one more place to look for column details so I kept the failover.

The only other question I have in regards to this was where can I find the revision number of this change. Since it was added to the trunk should just grab the head revision? or can I have more specifics so I might be able to understand the corrections better.

Again many thanks.

mjlecomte
30 Jul 2009, 2:41 PM
Looks like rev 4931 is what you're looking for. If you look at the more recent log entries you can reference a svn log to a forum entry with a #t for thread number or #p for a post number.

JoeAtTrends
31 Jul 2009, 11:45 AM
Thanks for the tip on the revision, I hadn't even looked at the SVN yet but knew there had to be a way to tie these two together.

I unfortunately have found that the fixes made here has opened up a pandora's box of trouble. It does correct the problem here, but unlike the fix I had tried this one creates issues with Ext.ListView.ColumnResizer and also the textual alignment in columns.

How would one post a request to remove a bug fix? I wasn't sure if it would be best to rethink the fix here, or fix anything else this fix broke.

mjlecomte
2 Aug 2009, 5:09 AM
Marking this as OPEN pending further investigation.

evant
2 Aug 2009, 4:40 PM
Already fixed in this thread: http://extjs.com/forum/showthread.php?t=76300

Marking as fixed again.

JoeAtTrends
3 Aug 2009, 5:00 AM
Did you fix the resizing?

The alignment I was talking about here was actually left or right text alignment.(:|

I will go ahead and create a brand new bug report, one for each. The resizing and the text alignment. With good simple examples for each. I will also reference this bug fix, as I believe it was the cause of those bugs.

Thank you very much.

JoeAtTrends
3 Aug 2009, 6:19 AM
Bug reports can be found here..

http://extjs.com/forum/showthread.php?p=367699#post367699
http://extjs.com/forum/showthread.php?p=367701#post367701

They reference back so I figured it only best to reference forward in case someone is later searching on these problems.

Thanks.

EDIT: I also found that what had originally appeared to be textual alignment issues was actually the column width issues (a column only as wide as the text, makes the text look left aligned when it is really right aligned) That was my bad, but the bug reports filed above reflect that new knowledge.