About Mitchell Simoens

Mitchell is the Senior Forum Manager at Sencha Inc.

Going Remote with Sencha Command

With the introduction of Sencha Touch 2.0.0 GA we got the use of Sencha Command which allows developers to create an app and build an app. Well, not as simple as that as there is more to it than simply creating and building but that’s for another blog post maybe. One thing that I try to emphasize is that you not edit index.html. The only exception to my rule is to edit the CSS and HTML for the loading screen but nothing else! No adding <link> elements, <script> elements, nothing! Of course your application may require it but try not to do it.

app.json

app.json is the configuration file that the builder and microloader uses to know really how to work and what to load. What to load, like CSS and JavaScript files? Exactly! There are both js and css properties that tell the builder to copy over to the build folder and setup the microloader to load and possibly save to localStorage. However, there is a flaw with this and is noted in the comment above the js and css properties that the path must be relative to the app.json file. Many apps require the loading of remote sources and one of the more popular ones is the Google Maps API. To load a Google Map you must have a <script> element to load the API but you shouldn’t edit index.html and app.json‘s js property only loads relative files which both mean that there is no way to handle remote files.

Hack

The URL that you need to load for the Google Maps API is likely http://maps.google.com/maps/api/js?sensor=true and if you look at this file you can plainly see that it bootstraps their API and creates a <script> element to load the actual API. You could, in theory (I haven’t tested it) create a local file that has this source and it would work but it’s a hack and I wouldn’t recommend it. Yeah, I have see someone do this and the reason was because they really didn’t have any other way to load the Google Maps API before their app was launched which I couldn’t blame them for trying it.

Savior

I haven’t dug into the source for Sencha Command and the microloader (Ext.blink) so I took it upon myself to jump head first to implement a way to load remote files using a simple config in app.json. I wanted to add a simple and descriptive config to the config objects you can put into the js array and the “remote” config was born. This config would mark that resource as being a remote file and accepts true or false (defaults to false, well, undefined really but it’s falsey so the same in this instance).

Sencha Touch 2.0.2

First thing I notice while digging into Sencha Command was that it copies the files to the build folder in a couple places. A few if statements and solved that issue. Tested and things were working great! I didn’t do enough testing admittedly and this was released for Sencha Touch 2.0.2. @themightychris (Chris Alfano from Jarv.us Innovations) tweeted about how to load remote files in Sencha Touch 2 and as a proud father of what I thought worked I told him about the “remote” config. He replied with errors… oh boy. I should have known it was too easy to throw a couple if statements at a problem and expect it to work fully. The next day I didn’t do any work on the Forums until I fixed it, my fault, my problem, my fix.

What it does now is when Sencha Command builds the index.html it will create <script> elements for your remote JavaScript files and <link> elements for your remote CSS files. The microloader, the system responsible for actually loading the resources, submits Ajax calls to load the assets and once all the assets have been loaded it then kicks off your application’s launch method. So why create <script> files in the builder and not let the microloader do the loading? The issue I came across is I couldn’t accurately determine when the Google Maps API was loaded due to it creating it’s own <script> tag as mentioned before (see why I explained that now?). I could determine when the <script> element that I created would load but not the <script> element the Google Map bootstrap element I created would create so the launch method would fire before the Google Maps API was actually loaded causing issues with my Ext.Map test. So not knowing what people will use I felt creating <script> elements in the index.html the safest solution. So once you do a production build, you can see the <script> elements in index.html that Sencha Command created. Not the prettiest solution but sometimes you just don’t have the control you want. This fix will be part of the next release (2.0.3 I believe it will be) but for you premium users it should be in SVN and part of the nightly builds you can get from http://support.sencha.com/

Example

Enough backstory, I know you are craving some code! Diving into the Sencha Touch 2.0.3 directory I created a simple app:

cd ~/Sites/sencha-touch-2.0.3
sencha app create MyApp ../MyApp

Simple to create an app isn’t it? Hell yeah it is (I was a doubter but now I’m a promoter)! I then edited the MyApp.view.Main class to add an Ext.Map item as the 2nd item so I opened the ~/Sites/MyApp/view/Main.js in my editor (IntelliJ IDEA if you were wondering) and added this code as index 1:

{
    xtype   : 'map',
    title   : 'Map',
    iconCls : 'maps'
},

and of course added Ext.Map to the requires array at the top of the file. So now we have our view using Ext.Map which will use the Google Maps API so we need to add the JavaScript file in app.json so I opened app.json and made 2 edits. First I added this code after the sencha-touch.js asset in the js property array:

{
    "path": "http://maps.google.com/maps/api/js?sensor=true",
    "remote": true
},

and then at the bottom to get around a current error I changed the logger config to false:

"buildOptions": {
    "product": "touch",
    "minVersion": 3,
    "debug": false,
    "logger": false
},

So now app.json is going to tell Sencha Command to include the Google Maps’ remote JavaScript file and the display the map in my MyApp.view.Main tab panel. First I need to test to make sure everything is working so I do a testing build:

cd ~/Sites/MyApp
sencha app build testing

Launched http://localhost/MyApp/build/testing/ in Chrome (my browser of choice) and everything displayed just fine, my map shows correctly (centered South-East of San Jose California because I didn’t tell it to center anywhere and that’s default). More importantly I checked out the console and there were not errors! Great, so from everything I know thus far my app is working perfectly so now I can deploy with a production build:

sencha app build production

Launched http://localhost/MyApp/build/production/ in Chrome (yup, still my favorite browser) and everything works perfectly!

Summary

So we learned some backstory on the issue at hand and the little bumpy road to the fix that is the “remote” config for your JavaScript and CSS assets in app.json. We then looked at building a simple example all using Sencha Command.

Happy coding!

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

var me = this; convention

Once in a while the topic of using the var me = this; convention is brought up. Some people love it, some people hate it and some people don’t understand it. Usually the people who love it or hate it understand the convention (or may not fully understand it) but there seems to be quite a few people who do not understand it. I use the variable me as an example but you could name it anything and it won’t change it, even to the beloved var that = this; and what I will say will still hold true. The decision on whether to use var me = this; is purely for minification and therefore saving bytes. However, it could also cost you bytes if you don’t realize what you are doing or maybe think you will need to use it when you start coding and don’t go back to clean your code up. Let’s start experimenting!

Control

Let’s look at a control method that you may have somewhere in your code:

saveSomething : function(foo, status, scope) {
    this.foo = foo;

    this.myObj = {
        mitch : status
    };

    this.onSave.call(scope);
}

If we look at this, we use this 4 times. All looks great so far! We have a method that saves the first two arguments and then calls a method.

New Feature!

So our control saveSomething method has been working quite well but we realize that we should do some error checking, we can make the scope argument optional and we decide we need to pass a method in the onSave method call. Let’s look at an example of how we can do it and then circle back to see if we can do a little better:

saveSomething : function(foo, status, scope) {
    var myObj = this.myObj;

    scope = scope || this;

    if (!myObj) {
        myObj = this.myObj = {};
    }

    this.foo = foo;

    myObj.mitch = status;

    this.onSave.call(scope, this.saveSomethingCallback);
}

So here we created a variable myObj to equate to this.myObj. However, this.myObj may be undefined so we added an if statement to check and if it is undefined (or falsey) set the myObj variable and this.myObj property to an Object. Also, we made scope default to this with the scope = scope || this; so if the scope argument is undefined (or falsey) it will then equate to this making it an optional argument (IMO a little more robust). Lastly we pass the saveSomethingCallback method in the onSave call as an argument to be executed later in our code.

Minification

So now we have a little better of a method but it can get better! When we deploy our code we should always minify our code so it’s as small as can be reducing what the client has to download but still maintain code functionality. This does many things but what I want to talk about is how it minifies variables. This small example:

var foo = 'bar';

can get minified to

var a='bar';

which saves 4 bytes as foo got renamed to a and the spaces before and after the equal sign got trimmed. Thinking back to our saveSomething method we can think that this will get minified to something like a just like the foo variable got renamed. Unfortunately, this (and other JavaScript keywords like delete or new) will not get minified. To combat this, we can create a variable to cache this and that variable can then get minified. This is where the var me = this; convention comes into play as we are going to cache this to the me variable and me will get minified:

saveSomething : function (foo, status, scope) {
    var me    = this,
        myObj = me.myObj;

    scope = scope || me;

    if (!myObj) {
        myObj = me.myObj = {};
    }

    me.foo = foo;

    myObj.mitch = status;

    me.onSave.call(scope, me.saveSomethingCallback);
}

We put the me = this in the variable block at the beginning of the method and replaced all this instances with me. We can now minify it and save 3 bytes per this instance for a total of 18 bytes (this was used 6 times in the saveSomething method before we applied the me = this convention and 6 x 3 = 18). However me = this also costs us some bytes, in fact in this example it cost us 8 bytes not including the spaces between me and this as they will get trimmed when it’s minified. So in total we saved 10 bytes after the var me = this convention which doesn’t account for much but if you have a sizable app it can add up and if you ask me, every bit saved counts.

Trouble!

This convention doesn’t come without a dark side. Applying this convention without counting the bytes that the me = this takes up and what you will save after minification will cost you bytes. If we go way back to the first saveSomething method and applied the var me = this; convention we will have costed some bytes:

saveSomething : function(foo, status, scope) {
    var me = this;

    me.foo = foo;

    me.myObj = {
        mitch : status
    };

    me.onSave.call(scope);
}

The var me = this; would cost us 11 bytes (it would be var a=this; after minification). For every instance of this we replaced we save 3 bytes and we had 3 instances so we save 9 bytes but the me variable line cost us 11 so we saved -2 bytes which is bad. We are trying to save bytes not add them.

Two Types

In these examples you saw the me variable get created by itself and in a variable block. If you have me = this in a variable block where there are other variables it costs less as we don’t have to add the var keyword. Let’s look at the byte cost:

var me = this;
var a=this; //minified

The minified version costs 11 bytes so you would have to use this 4 or more times to save bytes in the long run.

var me  = this,
    foo = 'bar';
var a=this,b='bar'; //minified

a=this, costs 7 bytes so you would have to use this 3 or more times to save bytes in the long run.

Review

Using the var me = this; convention is purely to save some byte because this cannot be minified but me can. The savings won’t be drastic but every little bit helps but you have to be cautious as it could cost you bytes. If you ask me, this is a code style issue also and code style (quality) should be just as high of a priority as what your code is doing. I take my code style very seriously so lining colons and equal signs up and also applying me = this wherever it will save a byte is very important to me.

A downside to this, which is just a visual annoyance, is in your IDE you probably have syntax highlighting and keywords usually get a blue (or other color depending ont he IDE) highlight but me will not so you can’t easily visually see the scope. My eyes simply have been trained to see me easily but I do miss seeing this highlighted. The syntax highlighting in the above examples show this.

Results may vary.

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

Build configs using Sencha Command

Many times when developing a Sencha Touch 2 app I often have to call remote data with a Store to fill Lists. Simple thing to build a Store with a Proxy pointing to some server side script or JSON file. Simple stuff. Lately I have been using Node.js for many of my servers but this can still be used if you are using Apache + PHP or SilkJS or whatever. I test the server code locally but of course when I deploy I will have the remote server setup and want to use the remote server for production and local server for development as I’m sure many are also doing. Before starting, this is assumed you have a fresh app created using Sencha Command via:

cd /path/to/sencha-touch-2.0.1.1/
sencha app create Test ../Test

Since I have different environments with different a different base url for each environment I create a Config utility class to easily change the base url for when I deploy. This utility class looks like this in a simple form:

Ext.define('Test.util.Config', {
    singleton : true,

    config : {
        baseUrl : 'http://www.mitchellsimoens.com/'
    },

    constructor : function(config) {
        this.initConfig(config);

        this.callParent([config]);
    }
});

So this will create a Test.util.Config singleton class which we can execute the Test.util.Config.getBaseUrl() in your Stores like so:

Ext.define('Test.store.Users', {
    extend : 'Ext.data.Store',

    requires : [
        'Test.model.User'
    ],

    config : {
        autoLoad : true,
        model    : 'Test.model.User',
        proxy    : {
            type  : 'ajax',
            url   : Test.util.Config.getBaseUrl() + 'get/users'
        }
    }
});

When the Users.js file is evalutaed by the browser it will execute the Test.util.Config.getBaseUrl() and so the url that is on the prototype is actually ‘http://www.mitchellsimoens.com/get/users’ which is fantastic. To make this work your app.js file should look like (removed some code):

Ext.Loader.setPath('Test' : 'app');

Ext.require([
    'Test.util.Config'
]);

Ext.application({
    //..

    stores : [
        'Users'
    ]
});

So we have an application with a Users Store and a Config utility class and the Users store is autoLoading from http://www.mitchellsimoens.com/get/users but we are still in development so we want to hit localhost not www.mitchellsimoens.com. We could just change the baseUrl config in Test.util.Config but that’s manual and there is an automatic way for when you do a build and that is using //<debug> and //</debug>. When you do any build:

cd /path/to/Test
sencha app build [testing|production|package|native]

Anything between the //<debug> and //</debug> will automatically get removed. Get where I am going? So instead of manually changing the baseUrl, just do something like:

Ext.define('Test.util.Config', {
        singleton : true,

        config : {
            baseUrl : 'http://www.mitchellsimoens.com/'
        },

        constructor : function (config) {
            this.initConfig(config);

            this.callParent([config]);
        }
//<debug>
},
function () {
    this.setConfig({
        baseUrl : 'http://localhost/'
    });
//</debug>
});

What this will do is when the Ext.define is finished creating the class definition and creating the singleton class it will execute the callback function which we are using to set the baseUrl config to ‘http://localhost/’ which is overriding the ‘http://www.mitchellsimoens.com/’. And since we wrapped the new code in //<debug> and //</debug> it will remove this override when we do any of the builds automatically switching the baseUrl for us not manually.

Further more, when you use requires and uses properties when creating class definitions you can remove those lines saving some space as they should all be within the built app.js file. So our Users Store we have above could be:

Ext.define('Test.store.Users', {
    extend : 'Ext.data.Store',

    //<debug>
    requires : [
        'Test.model.User'
    ],
    //</debug>

    config : {
        autoLoad : true,
        model    : 'Test.model.User',
        proxy    : {
            type  : 'ajax',
            url   : Test.util.Config.getBaseUrl() + 'get/users'
        }
    }
});

And that will remove the requires property when you do a build saving 3 lines (plus the two for the debug comments) as it’s not needed in a production build.

This is a trick of the trade that I found when developing Sencha Touch 2 apps using Sencha Command and one trick that I am really loving!

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

Friends and life

I’m a very open and honest person; I don’t try to be something I’m not. I will always admit I don’t know everything which is actually one of the values of myself that I hold dear, it helps me always push myself.

Some of you may have noticed that I got engaged recently and am trying to plan a wedding which looks to be end of April 2013 so Danielle and I have plenty of time to plan. First up is location and picking guest list and the wedding party. All this planning is both fun and overwhelming, I knew there would be a lot of things but dealing with a woman who has been thinking about ideas for last 25 or so years quite a lot to keep up with.

Back when I was a teenager I was a very sensitive person which I allowed for things to hurt me. So as I matured I tried to grow away from that and have successfully been able to control my emotions a lot more. Hell, controlling what I think about has become pretty easy minus the big life things. So as I am planning the wedding and picking my wedding party I realized something that, to be honest, bothers me… friends.

When I was young, talking before I was 16, I had friends that I would hang out with. Once I started working I lost those friends and gained new ones which is fine. I’ve gone through a couple evolutions in my life and in each evolution I lost all my friends as they weren’t able or willing to be ok with my changes. They were friends but not good friends. There is one friend that has stuck with me for last 10 years and no doubt will be (and has accepted) to be my best man but I can’t have a wedding party of one. In my current evolution, my “geek” evolution I have made many new friends and am very happy with where I am with these friends but it still bothers me that I don’t have more friends from back when I was a kid or a teenager. I have pretty much chosen who is in my wedding party (haven’t asked everyone yet) and couldn’t be more thrilled for the group of guys I will be having don’t get me wrong, I have been in a couple wedding recently and see them having friends that have been around for a long time.

I guess the moral of this is growing up sucks. You want to hold on to what was but still move towards what will be. It’s a struggle, it’s a personal struggle. I have told Danielle that you can’t live in the past, all you can do is learn and move on. But your past is what made you, what sculpted you.

Disclaimer – I know this is personal and not really tech related but it is therapeutic to write things.

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

JSONPLint.com is Here!

Hopefully you have used both JSLint.com or JSONLint.com to validate and maybe just for some formatting of your code. If you haven’t, shame!

I work at Sencha providing crazy fast support on the forums and a lot of times I need to look at the response people are using. Either it’s JSON, JSONP or on a bad day it’s XML. I use JSONLint.com to make the JSON or JSONP readable and also tell me if it’s even valid. JSONLint.com does a fantastic job for me but when I have JSONP I have to manually remove the callback function to only have the JSON or else JSONLint.com will yell at me.

So I decided to do something about it and registered JSONPLint.com just so I can make sure the actual JSON is valid and also see if the JSONP is actually valid. I found out Zach Carter actually does the pure JavaScript JSON validation behind JSONLint.com and has the code up on GitHub (here) which is fantastic and makes my life easier. All I have to do is remove the callback function and validate the actual JSON which is easy. So I modified Zach Carter’s example to do just that and did some coloring to fit my own needs for a first draft.

What’s next? First, need to clean it up. Got the first draft done but there is a better way to strip out the callback function. After that I would like to support url calling where you can pass params and headers so you can lint remote JSONP right from JSONPLint.com

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

Fullscreen Madness!

Sencha Touch 2 is a fantastic framework, my favorite to work with and most likely will be until Sencha Touch 3 comes out (no public dates yet :) ). I’m a mobile developer at heart I guess or maybe I just like that I don’t have to worry about IE. Either case Sencha Touch 2 is fantastic and by looking at the popularity in the Sencha Touch 2 forums I’m not the only one loving it! Helping people out a lot, I’m noticing a lot of people using the fullscreen config option where they shouldn’t which makes me think that the function of the fullscreen config isn’t explained very well. Today, I hope to change this.

Ext.Viewport

Before we dive into what the fullscreen config does, I want to make you aware of a critical piece of the puzzle. When you launch a Sencha Touch 2 app, the framework creates a component that will act as the viewport for your app. The instance is saved to Ext.Viewport. Quick explanation of what Ext.Viewport is is it’s an Ext.Container using card layout. Bit more info is you may have noticed the classes in the Ext.viewport namespace (remember JavaScript is case-sensitive so Ext.Viewport !== Ext.viewport). Ext.viewport.Android and Ext.viewport.Ios both extend Ext.viewport.Default and help setup the viewport based on what platform is currently viewing your application. Ext.viewport.Viewport acts as a factory for creating the correct viewport class. Each platform has it’s own quirks and issues and this helps navigate around these to provide better performance.

You can configure the Ext.Viewport by using the viewport config in Ext.setup or the viewport config in Ext.application. Each link links to the online API Docs and there are examples for this so I don’t have to show that code.

To sum Ext.Viewport up… Ext.Viewport is an Ext.Container and by default it uses card layout.

fullscreen Config!

First, let’s look at a small code snippet and then break it down:

new Ext.Container({
    fullscreen : true,
    html       : 'Do you seem my size?',
    items      : [
        {
            xtype  : 'toolbar',
            docked : 'top',
            title  : 'Top Toolbar'
        },
        {
            xtype  : 'toolbar',
            docked : 'bottom',
            title  : 'Bottom Toolbar'
        }
    ]
});

I added the two toolbars so you can visually see the size of the Ext.Container we created. The important part we need to talk about is that fullscreen config. So you noticed that the Ext.Container took 100% of the height and width but how did it do that? Remember Ext.Viewport? When you create a component (using the new keyword like I did or Ext.create) with the fullscreen config set to true (defaults to false) it actually fires a fullscreen event on itself. Within Ext.viewport.Default, it has a listener for fullscreen events and when it captures one, it will take that component and add it as an item of Ext.Viewport. Since by default Ext.Viewport uses card layout the Ext.Container that was just added as an item will take up 100% of the height and width because that’s what card layout does.

To recap this, Ext.Viewport listens for fullscreen events and adds that Ext.Component as an item.

Multiple fullscreens

What happens when we have two or more components using fullscreen set to true? Well, it keeps adding those components and adds them as a child item but you will only be able to see one at a time as that is how card layout works. You can use the setActiveItem method on Ext.Viewport to navigate through your “fullscreen” components:

new Ext.Container({
    fullscreen : true,
    html       : 'Do you seem my size?',
    items      : [
        {
            xtype  : 'toolbar',
            docked : 'top',
            title  : 'Top Toolbar',
            items  : [
                {
                    text    : 'Go 2nd',
                    ui      : 'confirm',
                    handler : function() {
                        Ext.Viewport.setActiveItem(1);
                    }
                }
            ]
        },
        {
            xtype  : 'toolbar',
            docked : 'bottom',
            title  : 'Bottom Toolbar'
        }
    ]
});

new Ext.Container({
    fullscreen : true,
    html       : 'Second One'
});

So we have two Ext.Containers using fullscreen set to true. The active item will be the first container as Ext.Viewport won’t change the active item, it will just add it as an item. I added a button to the top toolbar of the first container that simple executes Ext.Viewport.setActiveItem(1) which will switch to the item at index 1 of Ext.Viewport which is the second container.

Summary

We quickly learned what Ext.Viewport is and how to configure it. We then dove into how the fullscreen config works behind the scenes and then saw how we can use multiple items with the fullscreen config set to true.

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

ActionColumn and MVC

Being full-time on the Sencha forums gives me direct access to the community which gives me an idea of some frequently asked questions that I hope to blog more to answer.

Mission

Building applications with Ext JS 4 is very easy with the MVC architecture but sometimes it’s not always easy to know how to use MVC with certain widgets or features of Ext JS 4. Today I would like to discuss how I went about using ActionColumns and MVC specifically how to control when you click on an ActionColumn item within a controller.

Setting the Stage

Let’s set the stage! I’m going to use the Array Grid example that comes with every Ext JS release but turn it from an ordinary example into a more MVC example. Here are the controller, model, view and application code that we can start with:

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

    init : function() {
        //this.control will go here
    }
});

Ext.define('Mitch.model.Company', {
    extend : 'Ext.data.Model',

    idProperty : 'company',
    fields     : [
       { name : 'company'                                            },
       { name : 'price',      type : 'float'                         },
       { name : 'change',     type : 'float'                         },
       { name : 'pctChange',  type : 'float'                         },
       { name : 'lastChange', type : 'date', dateFormat : 'n/j h:ia' }
    ]
});

Ext.define('Mitch.view.Viewport', {
    extend : 'Ext.grid.Panel',
    xtype  : 'mitch-viewport',

    multiSelect : true,
    height      : 350,
    width       : 600,
    title       : 'Array Grid',

    initComponent : function() {
        var me = this;

        me.columns    = me.buildColumns();
        me.store      = me.buildStore();
        me.viewConfig = {
            stripeRows          : true,
            enableTextSelection : true
        };

        Mitch.view.Viewport.superclass.initComponent.call(me);
    },

    buildStore : function() {
        return new Ext.data.Store({
            model : 'Mitch.model.Company',
            data  : [
                ['3m Co',                               71.72, 0.02,  0.03,  '9/1 12:00am'],
                ['Alcoa Inc',                           29.01, 0.42,  1.47,  '9/1 12:00am'],
                ['Altria Group Inc',                    83.81, 0.28,  0.34,  '9/1 12:00am'],
                ['American Express Company',            52.55, 0.01,  0.02,  '9/1 12:00am'],
                ['American International Group, Inc.',  64.13, 0.31,  0.49,  '9/1 12:00am'],
                ['AT&T Inc.',                           31.61, -0.48, -1.54, '9/1 12:00am'],
                ['Boeing Co.',                          75.43, 0.53,  0.71,  '9/1 12:00am'],
                ['Caterpillar Inc.',                    67.27, 0.92,  1.39,  '9/1 12:00am'],
                ['Citigroup, Inc.',                     49.37, 0.02,  0.04,  '9/1 12:00am'],
                ['E.I. du Pont de Nemours and Company', 40.48, 0.51,  1.28,  '9/1 12:00am'],
                ['Exxon Mobil Corp',                    68.1,  -0.43, -0.64, '9/1 12:00am'],
                ['General Electric Company',            34.14, -0.08, -0.23, '9/1 12:00am'],
                ['General Motors Corporation',          30.27, 1.09,  3.74,  '9/1 12:00am'],
                ['Hewlett-Packard Co.',                 36.53, -0.03, -0.08, '9/1 12:00am'],
                ['Honeywell Intl Inc',                  38.77, 0.05,  0.13,  '9/1 12:00am'],
                ['Intel Corporation',                   19.88, 0.31,  1.58,  '9/1 12:00am'],
                ['International Business Machines',     81.41, 0.44,  0.54,  '9/1 12:00am'],
                ['Johnson & Johnson',                   64.72, 0.06,  0.09,  '9/1 12:00am'],
                ['JP Morgan & Chase & Co',              45.73, 0.07,  0.15,  '9/1 12:00am'],
                ['McDonald\'s Corporation',             36.76, 0.86,  2.40,  '9/1 12:00am'],
                ['Merck & Co., Inc.',                   40.96, 0.41,  1.01,  '9/1 12:00am'],
                ['Microsoft Corporation',               25.84, 0.14,  0.54,  '9/1 12:00am'],
                ['Pfizer Inc',                          27.96, 0.4,   1.45,  '9/1 12:00am'],
                ['The Coca-Cola Company',               45.07, 0.26,  0.58,  '9/1 12:00am'],
                ['The Home Depot, Inc.',                34.64, 0.35,  1.02,  '9/1 12:00am'],
                ['The Procter & Gamble Company',        61.91, 0.01,  0.02,  '9/1 12:00am'],
                ['United Technologies Corporation',     63.26, 0.55,  0.88,  '9/1 12:00am'],
                ['Verizon Communications',              35.57, 0.39,  1.11,  '9/1 12:00am'],
                ['Wal-Mart Stores, Inc.',               45.45, 0.73,  1.63,  '9/1 12:00am']
            ]
        });
    },

    buildColumns : function() {
        return [
            {
                text      : 'Company',
                flex      : 1,
                sortable  : false,
                dataIndex : 'company'
            },
            {
                text      : 'Price',
                width     : 75,
                sortable  : true,
                renderer  : 'usMoney',
                dataIndex : 'price'
            },
            {
                text      : 'Change',
                width     : 75,
                sortable  : true,
                renderer  : 'usMoney',
                dataIndex : 'change'
            },
            {
                text      : '% Change',
                width     : 75,
                sortable  : true,
                renderer  : function(v) { return v + '%'; },
                dataIndex : 'pctChange'
            },
            {
                text      : 'Last Updated',
                width     : 85,
                sortable  : true,
                renderer  : Ext.util.Format.dateRenderer('m/d/Y'),
                dataIndex : 'lastChange'
            },
            {
                xtype        : 'actioncolumn',
                menuDisabled : true,
                sortable     : false,
                width        : 50,
                items        : [
                    {
                        icon    : '../SDK/extjs/examples/shared/icons/fam/delete.gif',
                        tooltip : 'Sell stock',
                        handler : function(grid, rowIndex, colIndex) {
                            var rec = store.getAt(rowIndex);
                            alert("Sell " + rec.get('company'));
                        }
                    },
                    {
                        getClass : function(v, meta, rec) {
                            if (rec.get('change') < 0) {
                                this.items[1].tooltip = 'Hold stock';
                                return 'alert-col';
                            } else {
                                this.items[1].tooltip = 'Buy stock';
                                return 'buy-col';
                            }
                        },
                        handler  : function(grid, rowIndex, colIndex) {
                            var rec = grid.getStore().getAt(rowIndex);
                            alert((rec.get('change') < 0 ? "Hold " : "Buy ") + rec.get('company'));
                        }
                    }
                ]
            }
        ];
    }
});

Ext.application({
    name        : 'Mitch',
    controllers : [ 'Main' ],

    launch : function() {
        new Mitch.view.Viewport({
            renderTo : document.body
        });
    }
});

Bunch of code but very easy to follow. Currently the Main controller doesn’t do anything and you can see we have two items under the ActionColumn but it’s not very MVCish. I know you can use the ComponentQuery ‘mitch-viewport actioncolumn’ to resolve the ActionColumn so in the Main controller I just decided to test if I can listen to the a click event on the ActionColumn in a controller and it actually worked! Here is how to capture the click event:

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

    init : function() {
        this.control({
            'mitch-viewport actioncolumn' : {
                click : this.handleActionColumn
            }
        });
    },

    handleActionColumn : function(gridview, el, rowIndex, colIndex, e, rec, rowEl) {
        console.log(arguments);
    }
});

So if you click on an item in the ActionColumn the controller will console.log the arguments out so you can inspect them. You could actually stop here and use this as is but upon inspecting the different arguments (there are quite a few) I saw a problem… there isn’t an easy way to distinguish which icon in the ActionColumn was actually clicked on. You could add some logic in to look look at the actual target to see which icon was clicked on but there is an easier and less expensive way than parsing the DOM.

Custom Events

I would hope we all know that you don’t have to stick with the default events that are fired within the framework, we can actually fire custom events using fireEvent but I know I didn’t have that thought when I first started off. So why not use fireEvent within the handler of each item in the ActionColumn so that we don’t have to dig into the DOM? Why have a catch-all click event that is on the column? We can have a general event that is fired but easily distinguishable from one another and name it ‘itemclick’!

Fire itemclick

So we chose to fire a custom event call ‘itemclick’, first we need to decide what kind of arguments we want to fire this event with. The scope of the handler is that of the ActionColumn which is where we are going to fire the event on. We would maybe want the ActionColumn, grid, rowIndex, colIndex, record, eventObject, the node clicked on and since we are firing the event, we can make it easy on ourselves and put a custom argument to tell the action we should take. Let’s look at the code:

            {
                xtype        : 'actioncolumn',
                menuDisabled : true,
                sortable     : false,
                width        : 50,
                items        : [
                    {
                        icon    : '../SDK/extjs/examples/shared/icons/fam/delete.gif',
                        tooltip : 'Sell stock',
                        handler : function(grid, rowIndex, colIndex, node, e, record, rowNode) {
                            this.fireEvent('itemclick', this, 'sell', grid, rowIndex, colIndex, record, node);
                        }
                    },
                    {
                        getClass : function(v, meta, rec) { /* */ },
                        handler  : function(grid, rowIndex, colIndex, node, e, record, rowNode) {
                            var action = record.get('change') < 0 ? 'hold' : 'buy';
                            this.fireEvent('itemclick', this, action, grid, rowIndex, colIndex, record, node);
                        }
                    }
                ]
            }

So you can see how we can use the fireEvent and fired the ‘itemclick’ event onto this which is the ActionColumn. Now we can update the Main controller like this:

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

    init : function() {
        this.control({
            'mitch-viewport actioncolumn' : {
                itemclick : this.handleActionColumn
            }
        });
    },

    handleActionColumn : function(column, action, grid, rowIndex, colIndex, record, node) {
        console.log(action);
    }
});

If you update the view and the controller now and click on an icon in the ActionColumn, you would see the action console.log into the developer tools console being one of these options: ‘sell’, ‘hold’ or ‘buy’. Now based on that action we can then do whatever application and business logic that is required. Since we have the power of firing our own event you could actually fire the event on the grid itself so that your control method in your controller is easily managable and not have too many different ComponentQuery selectors but I would prefix the ‘itemclick’ event with ‘action’ so that the event name means something and doesn’t collide with any existing events.

Recap

First we took the Array Grid example and turned it into a simple MVC application. We then tested to see what events fire on the ActionColumn that we could use and found the click event was fired on the ActionColumn even though that particular event is documented in the API docs (I looked at the source for the ActionColumn and saw the possibility, don’t be afraid to look at the source!). We inspected the arguments that are fired with the click event and saw that we could use it but would have to query the DOM to see which item in the ActionColumn was actually clicked on. We decided that there could be a less expensive way to accomplish what we wanted to do than to play with the DOM as doing anything with the DOM (read/write) has a high performance hit. We found out we can fire our own events which should be faster than the DOM and so we modified our code to do this and found out it was very simple to do! Now we can go forth and do actual actions with our custom ‘itemclick’ event!

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

Ext.define and listeners as a property… bad!

Recently I have noticed a lot of people on the forums using the listeners, in my opinion, the wrong way. Being event driven is a great tactic in application development, one that I often use. Template methods are great and have their uses but for things that are reacting to user interactions, events are going to be your best friend.

Let’s look at a quick piece of code. Grids are used a lot and I often create an abstract class to handle all my grids as most of the stuff in grids can be abstracted out instead of having tons of duplicate code.

Ext.define('MyApp.view.grid.Abstract', {
    extend : 'Ext.grid.Panel',
    alias  : 'widget.myapp-grid-abstract',

    initComponent : function() {
        Ext.apply(this, {
            dockedItems : this.buildDocks(),
            selModel    : this.buildSelModel()
        });

        this.callParent(arguments);
    },

    buildDocks : function() {
        var docks = Ext.Array.from(this.dockedItems);

        docks.push({
            xtype : 'toolbar',
            dock  : 'top',
            items : [
                {
                    text     : 'Delete',
                    disabled : true,
                    action   : 'delete'
                }
            ]
        });

        return docks;
    },

    buildSelModel : function() {
        return {
            type : 'rowmodel',
            mode : 'SIMPLE'
        };
    }
});

So you can see from this code (which usually isn’t the entire abstract class) that we are adding a top docked Ext.Toolbar with a disabled delete button and defining a selection model with mode set to ‘SIMPLE’. The delete button should enable and disable based on the number of rows selected, disabled if the number of rows selected is zero, otherwise enable it. Usually you would extend an abstract class but for this blog we will just instantiate the abstract class like this:

new MyApp.view.grid.Abstract({
    renderTo : Ext.getBody(),
    width    : 400,
    height   : 400,
    title    : 'Test Grid',
    columns  : [
        {
            header    : 'Row',
            flex      : 1,
            dataIndex : 'row'
        }
    ],
    store    : new Ext.data.Store({
        fields : ['row'],
        data   : [
            { row : 'One'   },
            { row : 'Two'   },
            { row : 'Three' },
            { row : 'Four'  },
            { row : 'Five'  }
        ]
    })
});

So now we have a grid rendering as expected, time to handle the user interaction using the selectionchange event on the grid panel. This is where the problem lies. I have seen many developers add listeners like this:

Ext.define('MyApp.view.grid.Abstract', {
    extend : 'Ext.grid.Panel',
    alias  : 'widget.grid-abstract',

    listeners : {
        selectionchange : function(selModel, selected) {
            var view = selModel.view,
                grid = view.up('gridpanel'),
                btn  = grid.down('button[action=delete]');

            btn.setDisabled(selected.length === 0);
        }
    },

    initComponent : function() {
        /**/
    },

    buildDocks : function() {
        /**/
    },

    buildSelModel : function() {
        /**/
    }
});

Notice the listeners property we just set on the class’ prototype. And this works but I have always been told just because it works doesn’t mean it’s right; to me, this is an invalid use of listeners. listeners is listed as a config option so what happens when you instantiate the class like so:

new MyApp.view.grid.Abstract({
    /**/
    listeners : {
        afterrender : function() {
            console.log('my grid has rendered!');
        }
    }
});

Now, try to enable that delete button by selecting a row. Did it work? The problem here is anything you pass in the config object will overwrite anything you set on the prototype in Ext.define so the listeners you set to listen for the selectionchange event is now overwritten by the listeners config object listening for the afterrender when we instantiated. You now easily broke your abstract class. This is why you should never use listeners when using Ext.define.

So how do we go about to rectify this situation so that we can have our config listeners not overwrite our class listeners? The simple usage of on() will allow this, the only cavet is that we have to clean up the listener but that’s simple. Let’s delete the listeners from the Ext.define and put the on() usage into initComponent and use un() in beforeDestroy:

Ext.define('MyApp.view.grid.Abstract', {
    extend : 'Ext.grid.Panel',
    alias  : 'widget.grid-abstract',

    initComponent : function() {
        var me = this;

        Ext.apply(me, {
            dockedItems : me.buildDocks(),
            selModel    : me.buildSelModel()
        });

        me.callParent(arguments);

        me.on('selectionchange', me.handleSelectionChange, me);
    },

    beforeDestroy: function() {
        var me = this;

        me.un('selectionchange', me.handleSelectionChange, me);

        me.callParent(arguments);
    },

    buildDocks : function() {
        /**/
    },

    buildSelModel : function() {
        /**/
    },

    handleSelectionChange : function(selModel, selected) {
        var view = selModel.view,
            grid = view.up('gridpanel'),
            btn  = grid.down('button[action=delete]');

        btn.setDisabled(selected.length === 0);
    }
});

Can you see where I used on() and un()? Now, when you render the grid, the listeners you placed in the config object that has an afterrender listener gets executed and if you select a row, the delete button will now enable.

Awesome! Now we have a full functional abstract class that has event listeners that cannot be affected when instantiating a class.

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

About me

I thought I would start this blog off by introducing myself. Many have talked to me but probably don’t know how I became the man I am today. It was a rough road but here it is, I won’t spend years talking about it, I promise.

Early Years

Born in Iowa City, Iowa USA on February 2, 1984 (on a farm), I moved down to Saint Louis, Missouri USA in 1988 where I have called home ever since. I was a shy, quiet yet adventurous child. I never had lots of friends but kept a small group close. I was into sport, soccer being my favorite and devoted lots of my time practicing which didn’t leave me much time to spend inside playing video games.

Teen Years

Like I said, I was an adventurous child I would go places where I shouldn’t go. I lived in the suburbs of Saint Louis so it’s not like I could get into much trouble. There used to be lots of trails and creeks where I used to ride my bike doing tricks… trying to at least. I stopped playing soccer and decided to tear appart my parent’s $4,000 Compaq. My mother came home with everything spread around the room. Little later I got everything put back together and told her I wasn’t too sure I’d get that back together; this is what started my whole nerdness. I started learning by dissecting so when I got a truck when I turned 16, installing a head unit was a fun experiment. Using the Compaq, I started playing online games and joined an online group. This is where I fell into web design as they needed someone to help and I took on that challenge. Of course it was using Frontpage and Geocities but had to use existing Perl scripts but ditched that for PHP. For the next couple years I would spend 100% of my time within PHP. When I turned 16, I also started working at QuikTrip (convenient store chain around the USA) and thought that was what I was going to do for the rest of my life. It took priority over everything, school included. Growing up the way I did, my work ethic was top notch. Working 15 hours wouldn’t be a problem to me.

In the middle of the teen years, I tried escaping a problem I had. My father and I were close; hunting, fishing, sports were things that we did together a lot. The problem I felt is that we would never express our feelings toward each other. He would just push me to be better and if I didn’t live up to what he expected me to be, I felt he was disappointed in me. My family has a history of depression and I look back and I wish I would have seen this and prevented myself going down that road. I tried to work more and keep myself active but I would have terrible mood swings. The smallest thing would set me off. Alcohol would also play a large roll in all of this. This would go on for the better part of a decade.

Adult Years

After high school, I tried going to Maryville University here in Saint Louis going for a degree in eCommerce. I didn’t mind going to school but QuikTrip always trumped it so I bailed out after half a semester. I would then focus on my career at QuikTrip keeping my hobby of web developing on the side. My father also works at QuikTrip and has been very successful and for the first time I felt like he was proud of my accomplishments at following him in his footsteps. I started moving up and taking on projects but keeping him in the dark about my drinking and depression. Going to work drunk was fine as long as I had cologne to mask the smell and energy drinks to keep me awake as I wouldn’t sleep. I stayed away from pills or marijuana and have never tried that; I take pride in this actually.

I haven’t talked much about females in my life as they weren’t really my priority. Yes, I dated and lost my virginity at age 16 but I just didn’t really care about them. After high school they played more of a role. I always seemed to get good looking girls but they would use me because working at QuikTrip I made great money. I built a house, had a truck, car (1994 Ford Mustang 5.0), and a motorcycle (Kawasaki Ninja 600R) all before I could legally drink. Started building my house when I was 19. I would do anything for the girl in my life but looking back, it seems they took advantage of me. Maybe not their fault as I let them. This would not help my depression and confidence. This eventually would teach me about myself and help me later on.

When I was 22, one night waking up in my own vomit, I decided to stop drinking. After that decision, I would only drink socially. I still got drunk once in a while but wouldn’t even be close to drinking like I used to. I went from drinking almost every waking minute to only having a couple here and there. This self control really made myself start taking a long look at what kind of man I was growing up to be. I started taking more action to improve myself and I would have to hit rock bottom and rebuild myself. I quit QuikTrip, foreclosed on my house, moved back in with my parents. Was out of work for 2 months before I started working at Sam’s Club. I was seasonal but after 2 weeks turned full-time. When I start a new job, I work very hard to make myself a crucial asset to my employer. Started making my way up but saw some questionable things being done by management (TVs went missing, favoritism, etc) so I stepped down and kind of was just living day by day. I turned to my hobby of web developing and started contracting. This was just a side job and never really took off.

Love of my life :)

Early 2008 my mother went to visit one of her long time friends. They used to work next door to each other for many many years at the U.S. Department of Veterans Affairs but in 2007 my mother’s friend got a divorce and moved to Florida to pursue a different job within the U.S. Department of Veterans Affairs and her daughter moved down a couple months later. As my mother was down there, her daughter (Danielle Collins) started texting me and we hit it off right away. 2 months later on May 3rd, over the phone and hundreds of miles away, I asked Danielle if she would be my girlfriend. About every other month, one of us would fly to the other to actually see each other, when we were away from each other, Skype had to do. Long distance isn’t easy but I do believe it allowed us to really connect and become each other’s best friend. Fast forward to August 2011, after over 3 years of dating we finally decided to make the hard decision of having her quit her job and move in with me. This was a huge and scary step in our relationship but enough was enough with long distance. We have been living together for a few months now and things couldn’t be going better. Sure we have our little arguments ehre and there but the transition from being a thousand miles away from each other to seeing each other each day hasn’t been as hard as I was afraid. I would do anything for her and hope to get engaged this next year as soon as I find a ring that I am proud to give her.

Ext JS/Sencha Years

Around 2004/2005 I started pushing myself to learn more about web developing. I was using Prototype, Scriptaculous and whatever else I could get my hands on. My thirst for knowledge was driving me in a million directions. It wasn’t till very late in 2006 till I heard of this Ext JS and dropped everything and started trying to wrap my head around this. Early 2007 I jumped on the forums asking for help… I was very green. Next couple years my thirst for knowledge really pushed me to learn a lot about Ext JS and people like Jay Garcia, Nige “Animal” White, Shea “VinylFox” Fredricks, Condor really helped me out. It wasn’t till 2010 until I started getting some traction after helping with the book Ext JS in Action authored by Jay Garcia and some projects. People started coming to me for work so things were looking up. October 2010 I had a full-time opportunity as a web developer so I quit Sam’s Club to pursue web developing as a career. The guy I was working for didn’t like my coding style so he made me code like him. After 3 months of working for him and not liking it, I had another opportunity and took it. I like the guy I was working for, I can work from home, and the pay was great compared to what I was making. In a matter of about 4 months, my pay quadrupled! I thought I would work there for a long time but then I got an email from Ed Spencer at Sencha. At first, I was reluctant because I felt quite loyal to my current boss but I decided to give it a shot so I was flown out to Redwood City, CA USA for my interview. I then talked to Jeff Hartley who seemed very happy to speak with me and decided to offer me a job. I went back home and struggled to make a decision. After talking to family and friends, I decided that it was in my best interest to accept Sencha’s offer and handed in a one month notice to my current boss so that I could finish up things and he could find my replacement.

Joining Sencha in March of 2011 as part of the Professional Services team, I quickly became a bit of a star (self proclaimed). Not being cocky but I could now show my talents off on a larger scale and for many peers who actually understand what is coded. My first assignment in Sencha was working at a certain client (cannot be named) working with Doug Hendricks and Jack Ratcliff who also work at Sencha on the Professional Services team. Doug was traveling a lot but Jack worked locally with me so I became close to working with him… enjoyed bouncing ideas off him and just talking with him on a daily basis. Jack moved on to another project so it was just Doug and I so I was by myself onsite. As of October 21st, I moved on to be the Senior Forum Manager (I got to pick my own title :) ).

That brings us up to today! You probably have started seeing me spam the forums! My mission is to help people learn, not just give answers. I want to guide people to the answer so I’m sorry if you expect example code but that’s not really going to help you grow as a Sencha developer.

Sorry if it was a long post but I appreciate you reading it and getting to know me. I hope to talk to you via Twitter or on the Forums but my true wish is to be able to attend meetups and conferences to meet you in person.

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS

Help with cleanup

We are only in the developer preview stages of Sencha Touch 2 but we are already seeing so many great things. Performance in Android is superb. Scrolling is so native feeling that users won’t really be able to tell it’s really just a web page. Orientation changing is faster than the actual browser. The list goes on and on but I just stumbled on something that will help you clean up after your defined components.

Lots of times, we as developers set references onto components to keep track of things. We now can use the config Object with the getters and setters which is very useful I have to admit. But sometimes that’s just a bit much for what we need. We just want to set a reference and refer to it whenever we want but the problem with this is it can leak memory. So what we would do is either listen for the destroy event or use one of the destroy methods and call the superclass. But what if Sencha Touch as a framework can take care of it? Enter the referenceList.

Every subclass of Ext.AbstractComponent gets an Array placed as referenceList. All this is is an Array of Strings which are references on the component. You will see that the referenceList Array will have ‘element’ and sometimes ‘innerElement’ (depending on if it is an Ext.Container or subclass). When the component is destroyed, the destroy method will iterate over the referenceList Array, execute the destroy method on that reference and then delete it:

        // Destroy all element references
        for (i = 0,ln = referenceList.length; i &lt; ln; i++) {
            reference = referenceList[i];
            this[reference].destroy();
            delete this[reference];
        }

Now you can see the comment says that it will destroy all element references but technically you can put anything in there that has a destroy method. Very simple to clean up your references. Let’s look at an example…

Say you want a fullscreened Ext.Container but you need to detect when that Ext.Container‘s size changes. By default, Ext.Container doesn’t monitor it’s own size so you have to initialize your own Ext.util.SizeMonitor in order to do this. I will caution you, Ext.util.SizeMonitor adds in overhead so don’t go crazy adding it to everything. This is an example of a subclass that does just this:

Ext.define('MyContainer', {
    extend : 'Ext.Container',

    config : {
        fullscreen : true
    },

    initialize: function() {
        var me      = this,
            refList = me.referenceList;

        me.sizeMonitor = Ext.create('Ext.util.SizeMonitor', {
            element  : me.element,
            callback : me.onSizeChange,
            scope    : me
        });

        me.on('painted', 'onPainted', me, { single : true });

        me.callParent();
    },

    onPainted: function(cmp) {
        cmp.sizeMonitor.refresh();
    },

    onSizeChange: function() {
        this.fireEvent('sizechanged', this);
    }
});

Couple things I want to point out here. First, you can see we are placing a reference to the Ext.util.SizeMonitor instance to this.sizeMonitor. Second, you have to execute the refresh method on the Ext.util.SizeMonitor instance when the component has been painted, this is what will make the size change be picked up. This may be better suited for the config Object to get the getter and setter but it’s just an example for this blog post. So right now we have a fully working example with a reference that could be a memory leak, we could listen for the ‘destroy’ event and clean it up ourselves but there is a built in way to do it as mentioned before, the referenceList Array. So after you set the reference, we just need to push a String to the referenceList Array and it will automatically be cleaned up for us:

Ext.define('MyContainer', {
    //..

    initialize: function() {
        var me      = this,
            refList = me.referenceList;

        me.sizeMonitor = Ext.create('Ext.util.SizeMonitor', {
            element  : me.element,
            callback : me.onSizeChange,
            scope    : me
        });
        refList.push('sizeMonitor');

        me.on('painted', 'onPainted', me, { single : true });

        me.callParent();
    },

    //...
});

That simple! But remember, it must have that destroy method or else you will get a JavaScript error as there is no check if a destroy method is present. Kind of wish there was for other references but it’s not really what this was meant for.

Enjoy all the Sencha Touch 2 goodness!

Share and Enjoy

  • Facebook
  • Twitter
  • Delicious
  • LinkedIn
  • StumbleUpon
  • Add to favorites
  • Email
  • RSS