it seems that most ExtJS books and ExtJS introductions just focus on showing all the nice features but most of them don\'t really explain how to build stable, maintainable layou
Sencha blog article about top ten worst practices is worth reading.
Sencha Top 10 Worst Practices
A Summary from the blog post
**Please note that, all credit go to rightful owners of the original blog post.
1. Excessive or unnecessary nesting of component structures
Sometimes developers use redundant nesting components, which could result in unexpected unappealing aesthetics in the app with oddities like double borders or unexpected layout behavior.
BAD
items: [{
xtype : 'panel',
title: ‘My Cool Grid’,
layout: ‘fit’,
items : [{
xtype : 'grid',
store : 'MyStore',
columns : [{...}]
}]
}]
GOOD
layout: ‘fit’,
items: [{
xtype : 'grid',
title: ‘My Cool Grid’,
store : 'MyStore',
columns : [{...}]
}]
In the above example the nested panel is redundant because grid is an extension of panel. Moreover other elements like forms, trees, tab panels are extension of panel.
2. Memory leaks caused by failure to cleanup unused components.
This is one of the most important rules of all time. In any programming language it is very very important to make sure that components which are no longer in use are discarded properly, even in languages like Java, where GC is doing all cleanup for us, we should make sure that we are not holding to any objects after we are done with them.
BAD
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
columns : [{...}],
store: ‘MyStore’,
initComponent : function(){
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
}).showAt(event.getXY());
}
});
Every time user right clicks on a grid row, a new context menu is created. Which looks ok, because we only see the latest menu.
BAD(??)
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
store : 'MyStore',
columns : [{...}],
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
buildMenu : function(){
return Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
});
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
this.menu.showAt(event.getXY());
}
});
This is some what better than the initial one. It uses the same menu object everytime when user right clicks on a grid view. However it will keep the menu alive even if we kill the grid view, which is not what we need.
GOOD
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
store : 'MyStore',
columns : [{...}],
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
buildMenu : function(){
return Ext.create('Ext.menu.Menu',{
items : [{
text : 'Do Something'
}]
});
},
onDestroy : function(){
this.menu.destroy();
this.callParent(arguments);
},
onItemContextMenu : function(view,rec,item,index,event){
event.stopEvent();
this.menu.showAt(event.getXY());
}
});
In the above view, when the grid is destroyed, we destroy the menu as well.
3. Monster controllers
Some people code like monsters... Just kidding, but there are some big controllers(Not just controllers, other components as well :)) that consist of thousands of lines of code doing all those stuff that have no relation to each other at all.
It is very important to find a way to break down your application into different processing units at the beginning of the project so that you won't end up with a giant controller that handles all processes in your application.
Suggestion:
Breakup your application by different
APP FUNCTIONS (In an Order processing app --> Ordering, Delivering, Customer lookup...etc)
VIEWS (grids, forms,...etc)
In ExtJS controllers can talk to each other.
this.getController('SomeOtherController').runSomeFunction(myParm);
Also possible to fire an application level event that any controller can listen for.
MyApp.getApplication().fireEvent('myevent');
Also another controller listens for the app-level event.
MyApp.getApplication().on({
myevent : doSomething
});
4. Poor folder structure for source code
In any application good structure is very important, because it improves the readability and maintainability of a project. Instead of putting all controllers in one folder and all views in another folder, it is better to structure them logically according to their function.
5. Use of global variables
Why it is bad to use global variables? Sometimes it is not clear the actual value it holds, therefore it might lead to lot of confusions like
Hard to find bugs at runtime which are difficult to debug
What can we do about it? We could define a separate class for them and store them in it.
5.1 First we create a separate javascript file which holds the variables that needs to be changed as the app is used.
Runtime.js
5.2 Define a class to hold the globlly available data, in this case "myLastCustomer" variable
Ext.define(‘MyApp.config.Runtime’,{
singleton : true,
config : {
myLastCustomer : 0 // initialize to 0
},
constructor : function(config){
this.initConfig(config);
}
});
5.3 Then make the veriables available throughout the application
Ext.application({
name : ‘MyApp’,
requires : [‘MyApp.config.Runtime’],
...
});
5.4 Whenever you want to GET or SET the global variable value
5.4.1 To SET value
MyApp.config.setMyLastCustomer(12345);
5.4.2 To GET value
MyApp.config.getMyLastCustomer();
6. Use of ids in components is a bad idea?
Why?
6.1 Because every id that you define should be unique. In a large application this could lead to lot of confusions and problems.
6.2 It is easy to let the framework handles the naming of components
// here we define the first save button
xtype : 'toolbar',
items : [{
text : ‘Save Picture’,
id : 'savebutton'
}]
// somewhere else in the code we have another component with an id of ‘savebutton’
xtype : 'toolbar',
items : [{
text : ‘Save Order’,
id : 'savebutton'
}]
In the above sample, there are two buttons with the same name, which leads to name collisions. To prevent it, use "itemId" instead of id.
xtype : 'toolbar',
itemId : ‘picturetoolbar’,
items : [{
text : 'Save Picture',
itemId : 'savebutton'
}]
// somewhere else in the code we have another component with an itemId of ‘savebutton’
xtype : 'toolbar',
itemId: ‘ordertoolbar’,
items : [{
text : ‘Save Order’,
itemId: ‘savebutton’
}]
Now you can access the above components by their unique names as below
var pictureSaveButton = Ext.ComponentQuery.query('#picturetoolbar > #savebutton')[0];
var orderSaveButton = Ext.ComponentQuery.query('#ordertoolbar > #savebutton')[0];
// assuming we have a reference to the “picturetoolbar” as picToolbar
picToolbar.down(‘#savebutton’);
7. Unreliable referencing of components
It is not a good idea to use component positioning to get a reference to a component. Because someone might change the positon of a component without knowing that it is referenced by positioning in another part of the application.
var mySaveButton = myToolbar.items.getAt(2);
var myWindow = myToolbar.ownerCt;
How we can get the reference? Use the "ComponentQuery" or "up" / "down" methods.
var pictureSaveButton = Ext.ComponentQuery.query('#picturetoolbar > #savebutton')[0]; // Quering against the itemId
var mySaveButton = myToolbar.down(‘#savebutton’); // searching against itemId
var myWindow = myToolbar.up(‘window’);
8. Failling to follow upper/lower case naming conventions
It is important to use good naming conventions as a best practice, because it improves the consistance of your code and make it easy to read and understand. Also it is important to use meaningful names for all classes, variables and methods you define.
BAD
Ext.define(‘MyApp.view.customerlist’,{ // should be capitalized and then camelCase
extend : ‘Ext.grid.Panel’,
alias : ‘widget.Customerlist’, // should be lowercase
MyCustomConfig : ‘xyz’, // should be camelCase
initComponent : function(){
Ext.apply(this,{
store : ‘Customers’,
….
});
this.callParent(arguments);
}
});
GOOD
Ext.define(‘MyApp.view.CustomerList’,{ // Use of capitalized and then camelCase
extend : ‘Ext.grid.Panel’,
alias : ‘widget.customerlist’, // use of lowerCase
myCustomConfig : ‘xyz’, // Use of camelCase
initComponent : function(){
Ext.apply(this,{
store : ‘Customers’,
….
});
this.callParent(arguments);
}
});
9. Constraining a component to a parent components layout.
BAD
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
region : 'center',
......
});
this.callParent(arguments);
}
});
The "MyGrid" panel layout region is set as "center". Therefore it cannot be reused in another region like "west". So it is important to define your componets in such a way that it can be reused with any problem.
BAD(??)
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
......
});
}
});
Ext.create('MyApp.view.MyGrid',{
region : 'center' // specify the region when the component is created.
});
There is another way to define a component with defaults(in this case, the "region" property) and override the defaults when ever a change is required to defaults.
GOOD
Ext.define('MyApp.view.MyGrid',{
extend : 'Ext.grid.Panel',
region : 'center', // default region
initComponent : function(){
Ext.apply(this,{
store : ‘MyStore’,
......
});
}
});
Ext.create(‘MyApp.view.MyGrid’,{
region : ‘north’, // overridden region
height : 400
});
10. Making your code more complicated than necessary.
There are many ways to make a simple code complicated. One of the many ways is to load form data by accessing each field in the form individually.
BAD
// suppose the following fields exist within a form
items : [{
fieldLabel : ‘User’,
itemId : ‘username’
},{
fieldLabel : ‘Email’,
itemId : ‘email’
},{
fieldLabel : ‘Home Address’,
itemId : ‘address’
}];
// you could load the values from a record into each form field individually
myForm.down(‘#username’).setValue(record.get(‘UserName’));
myForm.down(‘#email’).setValue(record.get(‘Email’));
myForm.down(‘#address’).setValue(record.get(‘Address’));
GOOD
items : [{
fieldLabel : ‘User’,
name : ‘UserName’
},{
fieldLabel : ‘Email’,
name : ‘Email’
},{
fieldLabel : ‘Home Address’,
name : ‘Address’
}];
myForm.loadRecord(record); // use of "loadRecord" to load the entire form at once.
Useful links:
In my company main code reviewer enforcing:
As only a few main rules on top of follow documentation... :)