简体   繁体   中英

ExtJs best practices

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 layouts / applications with ExtJS. And how to write them without creating an unreadable mess...

I've no specific application in mind, I'm just wondering how I can "beatify" my ExtJS code. So if you need an example, a "regular" application for item management (library) or something like a job board would best describe what I was thinking of.

So can anyone share some good links or advices on how to structure such applications when I want to build them with ExtJS for the client side code?

Thanks in advance, Cheers

In my company main code reviewer enforcing:

  • As component always talk to PAPA - never to you sibling (own children offcourse allowed)
  • Try to avoid bubblling
  • If you follow #1 you do not have need to use Ext.getCmp () it too expensive, so DO NOT
  • Consider each component reusable by someone else in your team
  • Use proper hierarchy in namespaces (and use namespaces)

As only a few main rules on top of follow documentation... :)

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

  • Name collisions
  • 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:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM