Common Pitfalls and Suggestions (Sencha Touch / Ext JS)



I would like to take this opportunity to explain a few pitfalls that arise when in development with Sencha. This article is designed to help develop Sencha applications faster and more robust.

If you should find that something important is missing or something is wrong, do not hesitate to contact me.

Loading Sencha Framework and external Resources

It is recommended that the Sencha framework is loaded via the Microloader.

The "index.html" should be kept as lightweight as possible to avoid negative side effects. It seems Sencha has problems, for example, when trying to place child elements within the body region.

Below is an example of linking against the Sencha Touch framework:
<!DOCTYPE HTML>
<html manifest="" lang="en-US">
<head>
    <meta charset="UTF-8">
    <title>Your Application</title>
    <script id="microloader" type="text/javascript" src="touch/microloader/development.js"></script>
</head>
<body></body>
</html>
If additional JavaScript files required, these should not be included in the "index.html". Additional JavaScript files and CSS should always defined in the file "app.json".

The defintion of the files in the "app.json ' has the following advantages:
  • Included files are automatically compressed during the Sencha Build process. 
  • Included files can be loaded depending on the target platform. 
  • The application loads faster. 
  • index.html is lightweight.

Styling your Application

When it comes to styling your application, Sencha has a few ways to do this directly in JavaScript. However, this should be avoided. All style information should be defined via CSS or better SASS.
This results in the following advantages:
  • The actual source code is lightweight. 
  • All style information are defined centrally. 
  • The application can be easily edit at the same time by several developers. 
  • The design can be flexibly adapted at any time.

The app.js File

All changes to this file should be as minimal as possible. The reason for this, as soon as a Sencha update is performed, this file must be manually merged with the update.

Using Ext.getCmp()

The use of the function Ext.getCmp() should be largely avoided. The use of this function makes the source code hard to read and maintainable. By using the MVC principle, the function should no longer be needed.
See also MVC in depth Part 1 and MVC in depth Part 2.

In short, if Ext.getCmp() is used, the developer has missed the object-oriented approach.

In many applications, this function is still used to prototype faster or because the programmer does not know better.
When using Ext.getCmp() is essential to make sure that the object exists. I.e. the object must not be 'undefined' when its function is called. Otherwise, the interpreter throws an exception.

Using Ext.Viewport.removeAll() to destroy components

In the life cycle of an application it often happens that components are no longer needed. A good example to log out in an application. It is tempting to destroy all components on the Viewport with the Ext.Viewport.removeAll (http://docs.sencha.com/touch/2.2.1/#!/api/Ext.Container-method-removeAll) command:
Ext.Viewport.removeAll(true, true);
At first glance, all components were destroyed which were located on the Viewport. But what happens if the user now wants to re-login? Furthermore, we assume that the user enters his password incorrectly, and we show via Ext.Msg an error message.
Unfortunately, the following error message is displayed instead Ext.Msg:
Uncaught TypeError: Cannot read property 'dom' of null
Ext.Msg is a singleton instance of Ext.MessageBox. When you do the removeAll(true, true) the second argument is to destroy everything, inner items, docked items and floating items. Ext.Msg is a floating item so you have destroyed Ext.Msg, but you are then trying to use it. Now, if the component is destroyed it ends with the error message above.
It is recommended to destroy all the components separately. Not with "Ext.Viewport.remove" but directly through "myObj.destroy();"

Naming Model, Store and View

In the naming of models, views, and stores should pay attention to the following:
Basically, the name always begins with a capital letter, all the following words also with a capital letter.
  • Model, in the singular. Example: "TrainStation.js" 
  • Store in the plural. Example: "TrainStations.js" 
  • View, in the singular. Example: "TrainStation.js" 
Following this convention, the structure of the application is easier to read and quicker to learn.

JavaScript === versus == (Comparison Operators)

The identity "===" operator behaves identically to the equality "==" operator except no type conversion is done, and the types must be the same to be considered equal.

If values ​​are compared with each other it is recommended to use "===" instead of "==". This makes the program less error prone.

This is also the case for "!==" and "!=".

See also http://www.c-point.com/javascript_tutorial/jsgrpComparison.htm

Stop over nesting the View

When creating new views, make sure that they are as lightweight as possible. I.e. you should not go deeper than it is absolutely necessary in the structure. The use of unnecessary panels is a prime example of this. Is a view nested too deeply, it will have a negative impact on the performance of the application.

Is a view defined with "Ext.View", the view should not contain any action listener. These are defined in the relevant controller. This has the advantage that the view is more legible.

In short, in the view there is no business logic! All business logic should always be to find in the respective controller.

Code Completion

If the application is developed in Eclipse there are two ways to obtain a code completion. The first way is to use Spket. How this works can be found here. The second alternative is to purchase Sencha Complete. This version includes the necessary Eclipse plugins.

JSLINT for better source code

For the development of JavaScript applications, it is recommended to use JSLint. Who JSLint does not know yet, thus it is possible to validate your code against best practices. How to configure Eclipse with JSLint is described here.

Icon fonts instead of the classic icons

One element of themes in Sencha Touch 2.2 is the new use of icons as fonts. This is an awesome feature, because this offers the following new features:
  • size can be easily changed. 
  • color can be easily changed. 
  • shadow can be easily added to their shape. 
  • transparency nearly everywhere, unlike transparent png's in ie6. 
  • smaller in file size 
  • infinitely scaleable (because vector)
The goal of using icon fonts is to replace the need to render icons on a web page using images.

Click here to continue...

Using ComponentQueries instead of Id Config

Avoid using the id config, let ComponentQuery resolve the component using xtype with a property.

For those who are not familiar with ComponentQueries yet, they are used to retrieve pointers to one or many components in the application using a syntax similar to that of CSS selectors.

Let‘s compare the following cases. With Id Config, we define the following Sencha controller:
Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            myButton: '#buttonId'
        },
        control: {
            myButton: {
                tap: 'onButtonTap',
                change: 'onButtonChange'
            }
        }
    },
});

As seen in the above example, we refer to the button with "#buttonId". The code would work for now. But what happens if we destroy the view with the button on it and then re-create the view?

The result is, the "tap" handler fires, but not the "change" listener. The controller reference do not point to the button anymore. The reason being is, the button has been referenced via the config id. As you can clearly see Id Config is lack of flexibility.

Here is the same case, but using the ComponentQuery:

Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            myButton: 'button[id=buttonId]'
        },
        control: {
            myButton: {
                tap: 'onButtonTap',
                change: 'onButtonChange'
            }
        }
    },
});
There is no difference between quoted and unquoted references. myButton and also "myButton" are valid references. 

More about ComponentQueries

ComponentQueries can be built with: 
  • The XTYPE-property
  • Attributes in brackets, i.E. 'button[action=submitForm]' 
  • Functions which return true/false values in curly braces '{isValid()}'
  • The itemId property '#itemId'
  • The 'not' operator. Used to retrieve all components that are not the pointed component. I.E. 'not textfields'
  • Like in css '>' can used for parent and child relationships ('panel > button'). 
  • Multiple queries can be separated by using ','. I.E. 'button, textfield'

Testing ComponentQueries

ComponentQueries are mostly defined in controllers (ref section), but it is also possible to use the singleton object Ext.ComponentQuery (http://docs.sencha.com/touch/2.2.1/#!/api/Ext.ComponentQuery). To run and test a ComponentQuery,  there is a handy option in the JavaScript console. I.E Ext.ComponentQuery.query('panel > button')

Using itemId instead of id

I recommend you not to use the "id" attribute for the identification of components. The problem is that the "id" must be unique throughout the entire application. Replace "id" with "itemId" will avoid this conflict. The itemId property requires uniqueness of value only among child components of a container.

Caution: If the itemId used in a ComponentQuery, the itemId must either pre-qualified via a parent item and/or you could put its xtype#myItemId.

Short example. We  define a button with the itemId="navButton" and the parent container of our button has the xtype="navigation". So our config could look like this:
Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            myButton: 'button#navButton'
        },
        control: {
            myButton: {
                tap: ...
            }
        }
    },
});

While itemIds are not unique throughout the entire application, it is a good advice to put the xtype into the query too. So instead of 'button#navButton' we use the following query: 'xtype button#navButton' to gain more precision:
Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            myButton: 'navigation button#navButton'
        },
        control: {
            myButton: {
                tap: ...
            }
        }
    },
});

Using GLOBAL Variables

It is generally advised not to use global variables. First problem, there is no namespace for these variables. Second problem, the debugging of these variables often proves to be very difficult.
messageText = 'My message text.';
timeout = 5000;
In the above example, the variable "messageText" and "timeout" was defined as global. A better solution is to define these variables in a separate class.
Ext.define('MyApp.util.Config', {
    singleton : true,
    alternateClassName : ['Config'],

    config : {
        app : {
            messageText = 'My message text.'
            //...
        },
        services : {
            timeout = 5000;
            //....
        }
    },
    constructor: function () {
        return this.config;
    }
});
The new class "Config" is included in the "app.js" file via require:
Ext.application({
    name : 'MyApp',

    requires: [
        'MyApp.util.Config'
        //...
    ],
    //...
});
Access to the variables is done with:
Config.app.messageText
Config.services.timeout
Alternatively setter and getter functions can be automatically generated by Sencha. The Config class looks like this:
Ext.define('MyApp.util.Config', {
    singleton : true,
    alternateClassName : ['Config'],
    config: {
        messageText = 'My message text.',
        timeout = 5000
    },

    constructor: function(config) {
        this.initConfig(config);
    }
});
Access to the variables is done with:
Config.getTimeout();
Config.getMessageText();

8 comments:

  1. Great stuff! Thank you! I didn't know about the config class concept. *bookmarked* :-)

    ReplyDelete
  2. Hi Andreas,

    thank you very much. By the way, I updated the ComponentQuery part of the article.

    ReplyDelete
  3. Thanks! Very interesting. One information conflict is: "View, in the singular. Example: "TrainStations.js""

    ReplyDelete
  4. Thanks for the tips.

    "myObj.destroy();" Assuming myObj is a view containing several xtypes and Ext.Creates, are the child containers/components from xtypes and Ext.Creates destroyed as well? Is there Memory leak?

    Thanks

    ReplyDelete
    Replies
    1. Sorry for my late reply. If "destroy" is called on a container, it will invoke "removeAll". This removes all items in the container. So the answer should be no.

      Delete
  5. This comment has been removed by the author.

    ReplyDelete