Using ExtJs with TypeScript

Since the release of TypeScript there has been an explosion of JavaScript libraries that have had TypeScript definition files written for them.  A significant number can be found on borisjankov’s github repository  ( DefinatelyTyped ).

Unfortunately, there are currently no definition files for ExtJs in this repository.

Kudos to Mike Aubury (zz9pa) who has been able to use jsduck to reverse-engineer an ExtJs definition file from the ExtJs documentation, and load the project into his github repository ( extjsTypescript ).  For the purpose of this blog, I have had to modify Mike’s ExtJs.d.ts file generation slightly, just to mark each property and function as optional.  Read on to find out why.

This blog is the results of my initial findings attempting to use zz9pa’s ExtJs definitions with TypeScript.

ExtJs Class Structure

Lets have a look at a simple ExtJs Application :

Ext.application(
    {
        name: 'SampleApp',
        appFolder: '/code/sample',
        controllers: ['SampleController'],
        launch: () =>  {

            Ext.create('Ext.container.Viewport', {
                layout: 'fit',
                items: [{
                    xtype: 'panel',
                    title: 'Sample App',
                    html: 'This is a Sample Viewport'
                }]
            });

        }

    }
);

Note that the structure of ExtJs javascript is to instantiate objects with a configuration block as follows:

Ext.application(
    { 
        // Ext.application config block
    } 
);
Ext.create('Ext.container.Viewport', 
    {
        // viewport config block
    }
);

Compile time type-casting

The only way to utilize the powerful TypeScript benefits (i.e. type safety) is to manually type-cast these configuration blocks to the correct type by using compile time type casting as follows:

Ext.application(
    <Ext_app_Application>{ 
        // Ext.application config block
        // now has intellisense and type casting
    } 
);

With the above modification to the config block, we now have full intellisense, and type checking within our code.

A simple ExtJs Application with type-casting.

Let’s have a look at our sample application now utilizing the type-casting method as described above:

Ext.application(
    <Ext_app_Application> { // config block cast
        name: 'SampleApp',
        appFolder: '/app/sampleapp',
        controllers: ['SampleController'],
        launch: () =>  {

            Ext.create('Ext.container.Viewport', 
                <Ext_container_Viewport>{ // config block cast 
                layout: 'fit',
                items: [<Ext_panel_Panel>{
                    xtype: 'panel',
                    title: 'Sample App',
                    html: 'This is a Sample Viewport'
                }]
            });
        }
    }
);

ExtJs_Intellisense

Modifications to ExtJs.d.ts

In order to use the module definition for ExtJs ( extjsTypescript ) with this style of coding, we will need to modify the generated definition file to mark each property and function as optional :

Instead of this ( as an example)

interface Ext_AbstractPlugin extends Ext_Base {
   pluginId : String;
}

We need this:

interface Ext_AbstractPlugin extends Ext_Base {
   pluginId ? : String;
}

Note the ? optional flags for each of the properties and methods.

I have made some quick modifications to zz9pa’s code in order to make each property and function optional.  The Ext.d.ts file is included in the source download accompanying this blog.

The Ext namespace

The final modification that we need for the ExtJs definition file is for the Ext namespace itself.  This is where the majority of the work will be required to add further properties and function definitions for the Ext namespace.

Putting together this blog, and getting some samples up and running, I have defined only a tiny sub-set of the Ext namespace – mostly what I have needed to build a very simple application, and to start writing unit tests in Jasmine and Siesta.  So far, I have the following:

var Ext: IExt;
interface IExt {
    application(config: Ext_app_Application);
    Window: Ext_WindowManager;
    create(name: string, viewport: Ext_container_Viewport);
    getVersion();
    get (name: string): Ext_dom_AbstractElement;
    removeNode(name: Ext_dom_AbstractElement);
    DomHelper: Ext_DomHelper;
    getBody(): Ext_dom_AbstractElement;

    define(name: string, controller: Ext_app_Controller);
    define(name: string, controller: Ext_app_Application);

    ComponentManager: Ext_ComponentManager;
    require(name: string);
    onReady(call: Function);
}

Note that there are two define() functions, each with a different controller cast – as TypeScript will allow for function overloading.

ExtJs and TypeScript GOTCHA’s

Scope of this in config blocks and closures.

TypeScript uses the closure and module patterns extensively for it’s generated javascript.   One of the major advantages of these patterns is to correctly control scope, particularly for the ubiquitous this keyword.

In the following TypeScript code, init() will NEVER be called by ExtJs !

Note that init is a function returning this.control ( { … } ), and is using standard TypeScript syntax for specifying init as a function – the  () => { } syntax.

Ext.define('SampleApp.controller.FaultyController', <Ext_app_Controller>{
    extend: 'Ext.app.Controller',
    init: () => { 
        this.control({});
    },
});

The compiled code looks like this:

var _this = this;
Ext.define('SampleApp.controller.FaultyController', {
    extend: 'Ext.app.Controller',
    init: function () {
        _this.control({
        });
    }
});

Note the var _this = this; line at the top of the code, and how the init: function() will call _this.control – here we have an example of how TypeScript is using closures to ensure that we are scoping this correctly.

The solution : use anonymous functions

To resolve this issue, we will need to use standard javascript syntax for declaring anonymous functions inside our configuration block as follows:

Ext.define('SampleApp.controller.WorkingController', <Ext_app_Controller>{
    extend: 'Ext.app.Controller',
    init: function () {
        this.control({});
    }
});

Note the very subtle difference between init: () => {} syntax, and init: function() { } syntax.

The compiled version of the above code now works correctly with ExtJs:

Ext.define('SampleApp.controller.WorkingController', {
    extend: 'Ext.app.Controller',
    init: function () {
        this.control({
        });
    }
});

Unit Testing

The sample source code that is attached to this blog entry contains two test runners, one build for Jasmine, and another built for Siesta.

As my unit-testing tool of choice is Jasmine, I have put together more unit tests for Jasmine than for Siesta, but am also compiling some Siesta tests through TypeScript, just to show how to use Siesta.

One and only one Application

Each ExtJs solution can have one and only one Application defined, and jasmine tests need to be launched during global Application initialization – specifically during the launch function as follows:

Ext.onReady(() => {
    Ext.create('Ext.app.Application', <Ext_app_Application> {
        name: 'TestAppBootStrapper',
        appFolder: '../app/sampleapp',
launch: () => {

            jasmine.getEnv().addReporter(new jasmine.HtmlReporter());
            jasmine.getEnv().execute();
            return true;
        }
    });
});

Unfortunately, this presents some problems in test coverage, as initialization routines in either the main Application, or initialization for Controllers cannot be tested – or at least I have not found a way to do so.

Consider the following test:

    it('has called init on SampleController', () => {
        expect(SampleApp.getController('SampleController')).toBeDefined();

        // cannot spy on init function, as it is called before the tests start
        var spyOnInit = spyOn(SampleApp, 'init');
        expect(spyOnInit).toHaveBeenCalled(); // this will always fail

    });

This test will ALWAYS fail – as the init method of the SampleController ( the default controller for our Application ) is called BEFORE we have a chance to set a spy on the method.

In Conclusion : ExtJs objects vs TypeScript objects

Unfortunately, TypeScript and ExtJs do not seem to work too well together.  This incompatibility is mainly due to the differences in object creation between the two approaches.

Where ExtJs uses config blocks and anonymous methods for object creation, TypeScript uses the closure pattern to bring an easier way to build object-oriented javascript.  Unfortunately these two approaches seem to be at odds with each other.

Consider ExtJs’s method of object creation:

Ext.create(
    'Ext.container.Viewport', // object name
    <Ext_container_Viewport>{ // config block
        launch: function () { // anonymous method
        }
});

Each object is created with an object name ( global namespace ), followed by a configuration block and anonymous methods.  Having to statically cast each config block to the required type is a work-around to get Intellisense and type-checking into ExtJs code.

If the ExtJs libraries were written in a more TypeScript friendly manner, then we would be able to code like this:

// possible implementation of Ext.container.Viewport
class ExtContainerViewport {
    constructor(objectName: string) {
    }
    launch() {
    }
}

// extending an ExtContainerViewport is now more object-oriented.
class MyViewPort extends ExtContainerViewport {
    constructor() {
        super('MyViewPort');
    }
    launch() {
        super.launch();
    }
}

Have fun,

Blorkfish

Source Code Download

The full source code for this blog can be found here.  Note that in order to run the application, you will need the latest version of ExtJs which can be downloaded from here, as well as the latest version of siesta, which can be downloaded from here.

Advertisements

7 Responses to Using ExtJs with TypeScript

  1. Thanks for this post. I’ve been sort of despairing of getting these two to play nice with each other, but this shows a nice, moderate way to get at least *some* benefit out of TS with ExtJs.

  2. Doug says:

    Thanks for the analysis. It was very helpful.

  3. Jeannine says:

    This is really interesting, You’re a very skilled blogger.
    I’ve joined your feed and look forward to seeking more of your magnificent post.
    Also, I have shared your website in my social networks!

  4. Gareth Smith says:

    There is another way of using Typescript and ExtJS together: a forked compiler that generates Javascript in a way more compatible with ExtJS.

    We have a project underway to provide such a compiler, see: https://github.com/fabioparra/TypeScript

    • blorkfish says:

      Hey Gareth,
      This project looks really good. From the code samples, this is just what TypeScript and ExtJs need. I’ll certainly give it a whirl when I get a chance.
      Great work.

  5. Thanh Pham says:

    Please go to find ExtTS – Premium TypeScript type definitions for Sencha Ext JS

    https://github.com/thanhptr/extts

    Every latest version of ExtJS 4.x, 5.x, 6.x is supported; currently latest version is 6.0.2.437.

    Everything you can find from ExtJS API document is in the type definitions with strongly-typed support (such as: Ext.dom.Element, ButtonConfig,..)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: