I've got an application that is heavy on field usage. I noticed that adding new fields can be fairly expensive, even when using suspend/resumelayouts. Observing the timeline in Chrome, I can see quite a lot of recalculation of styles and forced layouts (seems like one per fields) for the panel div.
The code below is a simple representation of what I'm doing.
util = {
createTextField: function(myItemId) {
return Ext.create('Ext.form.field.Text', {
fieldLabel: 'Field' + myItemId + ':',
name: 'field',
itemId: myItemId,
autofocus: true,
enableKeyEvents: true,
labelAlign: 'left',
labelWidth: 50,
labelStyle: 'font-size: 16px;',
width: 500
});
}
}
Ext.onReady(function() {
Ext.create('Ext.Button', {
text: 'Click me',
renderTo: Ext.getBody(),
handler: function() {
for(i=0; i<100; i++)
{
Ext.suspendLayouts();
formPanel.add(util.createTextField(i));
Ext.resumeLayouts(true);
}
}
});
var formPanel = Ext.create('Ext.form.Panel', {
frame: true,
title: 'Form Fields',
width: 340,
height: 600,
bodyPadding: 5,
autoScroll: true,
fieldDefaults: {
labelAlign: 'left',
labelWidth: 90
}});
formPanel.render('form-ct');
});
The page itself is fairly straightforward:
<body>
<div id="form-ct"></div>
</body>
Right now pressing the button takes roughly ~2 seconds in Chrome and almost 4 in IE11. My question is whether this can be somehow optimized. Note that the fields must be rendered dynamically. I'm using ExtJS 4.1.
Start with moving suspendLayout/resumeLayout pair outside of the loop:
Ext.suspendLayouts();
for(i=0; i<100; i++)
{
formPanel.add(util.createTextField(i));
}
Ext.resumeLayouts(true);
Calling these inside the loop basically defeats the whole purpose of suspending layouts because you are forcing a relayout no less than 100 times in a row.
The add method is firing two events, add and beforeadd. You can instead using an array with components to add all at ones. Besides that you can use defaults and defaultType, but that will not do much I guess.
util = {
createTextField: function(myItemId) {
return Ext.create('Ext.form.field.Text', {
fieldLabel: 'Field' + myItemId + ':',
name: 'field' + myItemId // names are unique, we will use this to query components
});
}
}
Ext.onReady(function() {
Ext.create('Ext.Button', {
text: 'Click me',
renderTo: Ext.getBody(),
handler: function() {
// array to hold all components
var components = new Array();
// optimize the for loop and introduce y
for(var i = 0, y = 100; i < y; i++)
components.push(util.createTextField(i));
// add all components at ones to prevent multiple events fired
Ext.suspendLayouts();
formPanel.add(components);
Ext.resumeLayouts(true);
}
});
var formPanel = Ext.create('Ext.form.Panel', {
frame: true,
title: 'Form Fields',
width: 340,
height: 600,
bodyPadding: 5,
autoScroll: true,
// use defaultType and defaults to clean the code
defaultTypes: 'textfield',
defaults: {
autofocus: true,
enableKeyEvents: true, // this is heavy, consider if it is required
labelAlign: 'left',
labelWidth: 50,
labelStyle: 'font-size: 16px;',
width: 500
}
});
formPanel.render('form-ct');
});
Related
I am facing problem in adding text area to container dynamically.
Initial creation of container:
xtype: 'container',
layout: 'form',
width: 400,
ref: 'form',
layoutConfig: {
labelSeparator: ' ',
trackLabels: true
},
items: [{
xtype: 'textarea',
value: 'test',
fieldLabel: 'label',
anchor: '100%',
submitValue: false,
readOnly: true,
ref: '../field_1',
id: 'field_1'
}]
}
Dynamic code:
for (i = 4; i < obj.length; i++) {
var id = i + 12;
id = 'field_' + id;
var field = newTextArea(id);
field.setValue(obj[i].value);
field.setVisible(true);
this.form.add(field);
}
Function to create text area:
function newTextArea(id) {
var text_Area = new Ext.form.TextArea({
fieldLabel: 'Test',
height: 30,
width: 250,
submitValue: false,
readOnly: true,
autoScroll: true,
id: id
});
return text_Area;
}
Problem:
When i debug and see form, textarea is added in form items but its not displayed in the browser. Can someone suggest what to do?
Regards,
Raj
Check this simple fiddle.
Not sure what is wrong with your code, you dont mention what is obj and I think that this.form is wrong reference to the container. I think you can to use Ext.ComponentQuery.query or something similar (like up and down methods for queryable components).
In extjs 3.x, you have to call doLayout after adding items to a container.
for (i = 4; i < obj.length; i++) {
var id = i + 12;
id = 'field_' + id;
var field = newTextArea(id);
field.setValue(obj[i].value);
field.setVisible(true);
this.form.add(field);
}
this.form.doLayout();
I'm struggling mightily to get the height of an ExtJS panel and the only reason I can think it's any different is that it's the panel defined as a border region, because I've not had this problem otherwise (maybe I need to report a bug to them). I'll begin with some code that demonstrates how I'm trying to get the height, with results demonstrating what the height really is, what ExtJS says it is, and how it's not an issue with a panel using a different layout (one of the panels inside the border region):
'cmBuildTab #cmProductExplorer': {
afterrender: function(productExplorer) {
var domNode = Ext.getDom(productExplorer.el),
savedSearchesPanel = productExplorer.down('#savedSearchesPanel');
console.log('productExplorer.getHeight(): ' + productExplorer.getHeight());
console.log(productExplorer.componentLayout);
console.log(productExplorer.componentLayout.lastComponentSize);
console.log(domNode);
console.log('domNode.style.height: ' + domNode.style.height);
console.log('savedSearchesPanel.getHeight(): ' + savedSearchesPanel.getHeight());
}
},
I was initally encouraged I might have a couple of alternate ways to get the height, via the dom node, or via componentLayout.lastComponentSize but neither of these are accessible to Javascript code, only the chrome console. My final try then would be to parse the returned dom string but that is a horrible hack. Suggestions appreciated; below the console.log results is the pertinent portion of my view config.
console.log results:
productExplorer.getHeight(): 2
Ext.Class.newClass
autoSized: Object
borders: Object
calculateDockBoxes_running: false
childrenChanged: true
frameSize: Object
id: "dock-1155"
info: Object
initialized: true
initializedBorders: true
lastComponentSize: Object
height: 787
width: 254
__proto__: Object
layoutBusy: false
layoutCancelled: false
onLayout_running: false
owner: Ext.Class.newClass
previousComponentSize: undefined
targetInfo: Object
__proto__: Class.registerPreprocessor.prototype
undefined
<div id="panel-1049" class="x-panel x-box-item x-panel-default" role="presentation" aria-labelledby="component-1198" style="margin: 0px; width: 254px; height: 787px; left: 0px; top: 0px; ">…</div>
domNode.style.height:
savedSearchesPanel.getHeight(): 151
View config (partial):
},{
region: 'west',
collapsible: true,
title: 'Product Explorer',
itemId: 'cmProductExplorer',
split: true,
width: '20%',
minWidth: 100,
layout: {
type: 'vbox',
pack : 'start',
},
items: [{
collapsible: true,
width: '100%',
border: false,
title: 'Search',
itemId: 'savedSearchesPanel',
items: [{
border: false,
xtype: 'cmSearchTermAccordionPanel'
},{
border: false,
margin: '5 20 0 20',
html: 'Saved Searches:<hr>'
}]
},{
afterrender is not the event you want to use to determine the heights of components, it fires when the elements have been rendered not after they have been sized. If you are using 4.1 you can use the boxready event which fires only once the the container is intially sized http://docs.sencha.com/ext-js/4-1/#!/api/Ext.AbstractComponent-event-boxready . If not you can use the afterlayout event to determine size but that event fires every layout.
Also fyi widths as percentages are not documented as supported in 4.0, I've seen them work and I've seen them fail.
Here is some code I hijacked from the Viewport example thanks to a court ruling this is ok.
4.1
Ext.create('Ext.container.Viewport', {
layout: 'border',
items: [{
region: 'north',
html: '<h1 class="x-panel-header">Page Title</h1>',
border: false,
margins: '0 0 5 0'
}, {
region: 'west',
collapsible: true,
title: 'Navigation',
width: 150,
//ADD LISTENERS
listeners: {
afterrender:function(){console.log( 'afterrender ' +this.getHeight())},
boxready:function(){console.log( 'boxready ' +this.getHeight())}
}
// could use a TreePanel or AccordionLayout for navigational items
}, {
region: 'south',
title: 'South Panel',
collapsible: true,
html: 'Information goes here',
split: true,
height: 100,
minHeight: 100
}, {
region: 'east',
title: 'East Panel',
collapsible: true,
split: true,
width: 150
}, {
region: 'center',
xtype: 'tabpanel', // TabPanel itself has no title
activeTab: 0, // First tab active by default
items: {
title: 'Default Tab',
html: 'The first tab\'s content. Others may be added dynamically'
}
}]
});
Output :
afterrender 2
boxready 276
This code is working in Extjs 4.0.2a
but when converted to 4.1 it no longer works and gives an error
Uncaught TypeError: Cannot call method 'query' of undefined
Ext.onReady(function() {
var panel = new Ext.Panel({
renderTo: divtag,
draggable: {
insertProxy: false,
onDrag: function(e) {
var el = this.proxy.getEl();
this.x = el.getLeft(true);
this.y = el.getTop(true);
},
endDrag: function(e) {
this.panel.setPosition(this.x, this.y);
}
},
title: 'Panel',
width: 200,
height: 100,
x: 20,
y: 20
});
});
Apparently there is a bug in this version of Ext. It wont work even if you try default D'n'D for panel like this:
Ext.onReady(function() {
var panel = new Ext.Panel({
renderTo: 'divtag',
draggable: true,
title: 'Panel',
width: 200,
height:100,
x: 20,
y: 20
}); //panel.show(); });
});
I menage to patch the code to work the way you want it, this code should work:
Ext.onReady(function() {
var panel = new Ext.Panel({
renderTo: 'divtag',
draggable: {
insertProxy: false,
onDrag: function(e) {
var el = this.proxy.getEl();
this.x = el.getX();
this.y = el.getY();
},
endDrag: function(e) {
panel.setPosition(this.x,this.y);
},
alignElWithMouse: function() {
panel.dd.superclass.superclass.alignElWithMouse.apply(panel.dd, arguments);
this.proxy.sync();
}
},
title: 'Panel',
width: 200,
height:100,
x: 20,
y: 20
}); //panel.show(); });
});
As a side note I should probably advice you not to do this anyway, because you can define your own DD for panel that you can use, and even better Ext already have one defined, so you can just override Ext panel to use default Ext.util.ComponentDragger, or in code, I advice you to do this:
Ext.override(Ext.panel.Panel, {
initDraggable: function() {
var me = this,
ddConfig;
if (!me.header) {
me.updateHeader(true);
}
if (me.header) {
ddConfig = Ext.applyIf({
el: me.el,
delegate: '#' + me.header.id
}, me.draggable);
// Add extra configs if Window is specified to be constrained
if (me.constrain || me.constrainHeader) {
ddConfig.constrain = me.constrain;
ddConfig.constrainDelegate = me.constrainHeader;
ddConfig.constrainTo = me.constrainTo || me.container;
}
me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
}
}
});
var panel = Ext.create('Ext.panel.Panel', {
id: 'test',
renderTo: 'divtag',
draggable: true,
floating: true,
title: 'Panel',
width: 200,
height:100,
x: 20,
y: 20
});
Code for a initDraggable function in panel override is taken from current stable version of Ext.window.Window.initDraggable method.
I was able to get it working in 4.1: you have to add quotes around the id of the renderTo element, like:
renderTo : 'divtag',
Without quotes it was looking for an undefined variable named divtag.
Once I ran that I got no errors, and then I just did panel.show() to render it.
Just a suggestion: a Window component is a specialized Panel that has a floating mixin - might be all you need.
I using Ext JS 2.3.0 and have a GridPanel that looks like this:
I want to expand the width of the column such that the scroll bar is pushed over the extreme right of the panel, thus eliminating the empty space to the right of the scroll bar.
The relevant code is shown below:
var colModel = new Ext.grid.ColumnModel([
{
id: 'name',
header: locale['dialogSearch.column.name'],
sortable: true,
dataIndex: 'name'
}
]);
var selModel = new Ext.grid.RowSelectionModel({singleSelect: false});
this._searchResultsPanel = new Ext.grid.GridPanel({
title: locale['dialogSearch.results.name'],
height: 400,
layout: 'fit',
stripeRows: true,
autoExpandColumn: 'name',
store: this._searchResultsStore,
view: new Ext.grid.GridView(),
colModel: colModel,
selModel: selModel,
hidden: true,
buttonAlign: 'center',
buttons: [
{
text: locale["dialogSearch.button.add"],
width: 50,
handler: function () {
}
},
{
text: locale["dialogSearch.button.cancel"],
width: 50,
handler: function () {
entitySearchWindow.close();
}
}
]
});
You should use the forceFit config for the grid view:
this._searchResultsPanel = new Ext.grid.GridPanel({
title: locale['dialogSearch.results.name'],
height: 400,
layout: 'fit',
viewConfig: {
forceFit: true
}, ....
I'm not sure if this isn't redundant so you can remove this part view: new Ext.grid.GridView(),
The problem is not the column but the grid itself which does not stretch to fill the window body completely.
Put the layout: 'fit' property onto the window config instead of onto the grid config (where it needs to be removed). You should also remove the height property because the grid height will determined by the window's size.
Add
flex: 1,
to one of the the columns config
I have a some class
AddOrgWindowUI = Ext.extend(Ext.Window, {
title: 'form',
width: 400,
height: 198,
layout: 'form',
padding: 5,
initComponent: function() {
this.items = [
{
xtype: 'textfield',
fieldLabel: 'parapapa',
anchor: '95%',
value: m,
emptyText: 'perapapa'
}
];
AddOrgWindowUI.superclass.initComponent.call(this);
}});
when I create an object var AddOrgWindowForm = new AddOrgWindowUI('aaa'); I want to get arg ('aaa') to my new form value (value m). How get it?
Im trying initComponent: function(m) { and thats not working.
The initComponent function is called internally on one of the base classes of Ext.Window. You shouldn't try to call it directly. That is why it won't handle your own parameters.
So I recommend you to use the standard form parameters when extending ExtJS classes.
It is as simple as initializing the object with the property or methods you want to override (or insert in case the property is not in there already). And then just using the this keyword to access them.
This is possible because for every Ext.Component and its subclasses, the first parameter passed to the constructor should be an object, and every member in that object will be copied to the new object constructed. And most ExtJS classes extend directly or indirectly from Ext.Component, and you are extending from Ext.Window which extends from Ext.Component too.
Here you have your example fixed:
var AddOrgWindowUI = Ext.extend(Ext.Window, {
title: 'form',
width: 400,
height: 198,
layout: 'form',
padding: 5,
initComponent: function() {
this.items = [
{
xtype: 'textfield',
fieldLabel: 'parapapa',
anchor: '95%',
value: this.initialValue,
emptyText: 'perapapa'
}
];
AddOrgWindowUI.superclass.initComponent.call(this);
}
});
function test() {
var AddOrgWindowForm = new AddOrgWindowUI({initialValue:'aaa'});
AddOrgWindowForm.show();
}
pass m as an arg of initComponent:
edit:
AddOrgWindowUI = function(input) {
var m = input;
return Ext.extend(Ext.Window, {
title: 'form',
width: 400,
height: 198,
layout: 'form',
padding: 5,
initComponent: function() {
this.items = [
{
xtype: 'textfield',
fieldLabel: 'parapapa',
anchor: '95%',
value: m,
emptyText: 'perapapa'
}
];
AddOrgWindowUI.superclass.initComponent.call(this);
}
});
}