Classes in Ext JS 4: Under the hood

Last week we unveiled a the brand new class system coming in Ext JS 4. If you haven’t seen the new system in action I hope you’ll take a look at the blog post on sencha.com and check out the live demo. Today we’re going to dig a little deeper into the class system to see how it actually works.

To briefly recap, the new class system enables us to define classes like this:

Ext.define('Ext.Window', {
    extend: 'Ext.Panel',
    requires: 'Ext.Tool',
    mixins: {
        draggable: 'Ext.util.Draggable'
    },
    
    config: {
        title: "Window Title"
    }
});

Here we’ve set up a slightly simplified version of the Ext.Window class. We’ve set Window up to be a subclass of Panel, declared that it requires the Ext.Tool class and that it mixes in functionality from the Ext.util.Draggable class.

There are a few new things here so we’ll attack them one at a time. The ‘extend’ declaration does what you’d expect – we’re just saying that Window should be a subclass of Panel. The ‘requires’ declaration means that the named classes (just Ext.Tool in this case) have to be present before the Window class can be considered ‘ready’ for use (more on class readiness in a moment).

The ‘mixins’ declaration is a brand new concept when it comes to Ext JS. A mixin is just a set of functions (and sometimes properties) that are merged into a class. For example, the Ext.util.Draggable mixin we defined above might contain a function called ‘startDragging’ – this gets copied into Ext.Window to enable us to use the function in a window instance:

//a simplified Draggable mixin
Ext.define('Ext.util.Draggable', {
    startDragging: function() {
        console.log('started dragging');
    }
});

When we create a new Ext.Window instance now, we can call the function that was mixed in from Ext.util.Draggable:

var win = Ext.create('Ext.Window');
win.startDragging(); //"started dragging"

Mixins are really useful when a class needs to inherit multiple traits but can’t do so easily using a traditional single inheritance mechanism. For example, Ext.Windows is a draggable component, as are Sliders, Grid headers, and many other UI elements. Because this behavior crops up in many different places it’s not feasible to work the draggable behavior into a single superclass because not all of those UI elements actually share a common superclass. Creating a Draggable mixin solves this problem – now anything can be made draggable with a couple of lines of code.

The last new piece of functionality I’ll mention briefly is the ‘config’ declaration. Most of the classes in Ext JS take configuration parameters, many of which can be changed at runtime. In the Ext.Window above example we declared that the class has a ‘title’ configuration, which takes the default value of ‘Window Title’. By setting the class up like this we get 4 methods for free – getTitle, setTitle, resetTitle and applyTitle.

  • getTitle – returns the current title
  • setTitle – sets the title to a new value
  • resetTitle – reverts the title to its default value (‘Window Title’)
  • applyTitle – this is a template method that you can choose to define. It is called whenever setTitle is called.

The applyTitle function is the place to put any logic that needs to be called when the title is changed – for example we might want to update a DOM Element with the new title:

Ext.define(‘Ext.Window’, {
    //..as above,
    
    config: {
        title: 'Window Title'
    },
    
    //updates the DOM element that contains the window title
    applyTitle: function(newTitle) {
        this.titleEl.update(newTitle);
    }
});

This saves us a lot of time and code while providing a consistent API for all configuration options: win-win.

Digging Deeper

Ext JS 4 introduces 4 new classes to make all this magic work:

  • Ext.Base – all classes inherit from Ext.Base. It provides basic low-level functionality used by all classes
  • Ext.Class – a factory for making new classes
  • Ext.ClassLoader – responsible for ensuring that classes are available, loading them if they aren’t on the page already
  • Ext.ClassManager – kicks off class creation and manages dependencies

These all work together behind the scenes and most of the time you won’t even need to be aware of what is being called when you define and use a class. The two functions that you’ll use most often – Ext.define and Ext.create – both call Ext.ClassManager under the hood, which in turn utilizes the other three classes to put everything together.

The distinction between Ext.Class and Ext.Base is important. Ext.Base is the top-level superclass for every class ever defined – every class inherits from Ext.Base at some point. Ext.Class represents the class itself – every class you define is an instance of Ext.Class, and a subclass of Ext.Base. To illustrate, let’s say we created a class called MyClass, which doesn’t extend any other class:

Ext.define('MyClass', {
    someFunction: function() {
        console.log('Ran some function');
    }
});

The direct superclass for MyClass is Ext.Base because we didn’t specify that MyClass should extend anything else. If you imagine a tree of all the classes we’ve defined so far, it will look something like this:

Sample-inheritance-tree

This tree bases its hierarchy on the inheritance structure of our classes, and the root is always Ext.Base – that is, every class eventually inherits from Ext.Base. So every item in the diagram above is a subclass of Ext.Base, but every item is also an instance of Ext.Class. Classes themselves are instances of Ext.Class, which means we can easily modify the Class at a later time – for example mixing in additional functionality:

//we can define some mixins at definition time
Ext.define('MyClass', {
    mixins: {
        observable: 'Ext.util.Observable'
    }
});

//it’s easy to add more later too
MyClass.mixin('draggable', 'Ext.util.Draggable');

This architecture opens up new possibilities for dynamic class creation and metaprogramming, which were difficult to pull off in earlier versions.

In the next episode, we’ll look at how the class definition pipeline is structured and how to extend it to add your own features.

20 Responses to Classes in Ext JS 4: Under the hood

  1. Eugene says:

    Excellent post! Keep ‘em coming :)

  2. Jacques Fuentes says:

    Very nice features. I’m wondering if some of your 4.0 changes were influenced by Joose? The meta-programming technique will come in extremely handy with ext-js dev. Anyway, thanks for the heads up!

  3. Ed Spencer says:

    @Jacques not directly, though many frameworks follow a similar pattern for the basics that I’ve described so far. Joose seems to take things a little further with an interesting pseudo-typing system – a direction we’re not interested in at the moment.

  4. Pingback: ExtJS4: Las novedades « Aijoona

  5. Pingback: Ext JS 4: The Class Definition Pipeline : Ed Spencer

  6. Eugene says:

    Ed, I’m playing with this new stuff and I have a quick question. Here’s my code:

     
    Ext.define('MyClass', {
        config: {
          title: 'Some Title'
        }
    }, function() {
        var obj = Ext.create('MyClass');
        console.log(obj.getTitle());
    });
    

    I expect the console.log to print out ‘Some Title’, but it prints undefined. Why is that?

  7. Ed Spencer says:

    @Eugene looks like the call to initConfig happens later down the inheritance chain than Ext.Base… configs are initialized by the initConfig function, which isn’t getting called for your class. You can modify it like this to make it work:

     
    Ext.define('MyClass', {
        constructor: function() {
            this.initConfig();
        },
        
        config: {
            title: 'Some Title'
        }
    },
    function() {
        var obj = Ext.create('MyClass');
        console.log(obj.getTitle());
    });
    

    We’ll fix this for final – this is why we call it beta at the moment :)

  8. Eugene says:

    I see. Thanks a lot!

  9. Christian says:

    What if mixins would like to perform an action when an instance is created ? Do you provide a constructor like method for them or just call all their constructors in the class constructor ?

  10. Ed Spencer says:

    Currently you have to invoke the mixin’s constructor inside the class constructor. We may provide an autoInstantiateMixin config or something similar to do this automatically

  11. Pingback: extjstutorial.org

  12. numan says:

    hey Ed, regarding your comment to Eugene, why isn’t call to initConfig done in one of the parent constructors. Why make this call explicit in the subblass when it is the default behavior (can’t think of a counterexample)

    IOW, if you call this.callParent(arguments) in the subclass constructor (which you should anyway) that call should handle the initiConfig up the chain somewhere (perhaps in the Ext.Base?)

  13. halcwb says:

    I was writing some test code to learn from your post. When I create a simple class with a single config prop, name. I get a class with setName, getName, applyName, but not with a resetName method? Did I miss something or has this been left out in the final Ext 4 (latest version)?

  14. Martin Kunc says:

    Ed, how can I find which namespace is required to override specific built-in class ? Just from sources, or is there any convention for this ?

  15. Let me ask you a question. In my experience applyTitle must return the new value otherwise it will not set the value.
    In the code example you do not return the new value. I am using v4.02a.

    //updates the DOM element that contains the window title
    applyTitle: function(newTitle) {
    this.titleEl.update(newTitle);.
    }

  16. Vali says:

    There is a different behaivor when I create an object that extends Ext.util.Observable and when I create the same object but with a mixin Ext.util.Observable. When I extend Ext.util.Observable everything works fine as expected but then when I try to create the object with mix-in Ext.util.Observable, the open event is not fired.
    Can you explain what is the difference?

    Code1:
    [CODE]
    Ext.define(‘Beer’, {
    extend: ‘Ext.util.Observable’,
    constructor: function(config){
    this.name = config.name;
    this.addEvents(‘open’, ‘drink’);
    this.callParent(arguments);
    }
    });
    [/CODE]

    Code2(the open event is not fired):
    [CODE]
    Ext.define(‘Beer’, {
    mixins: {
    observable: ‘Ext.util.Observable’
    },
    constructor: function(config){
    this.name = config.name;
    this.addEvents(‘open’, ‘drink’);
    this.callParent(arguments);
    }
    });
    [/CODE]

    the object
    [CODE]
    Ext.define(‘Beer’, {
    extend: ‘Ext.util.Observable’,
    constructor: function(config){
    this.name = config.name;
    this.addEvents(‘open’, ‘drink’);
    this.callParent(arguments);
    }
    });
    [/CODE]

  17. Michael says:

    Hello Ed,

    I have inherited a web app that is built with Ext 2.0.2. My first requirement is to make the site 508 compliant.
    Can I do this with Ext 4.0? The app uses grids and windows. Here is the code on how the window is set up.
    Some direction on how to move forward with this would be helpful.
    Thanks!

    var cWin;
    this.cWindow = function () {
    if (!cWin) {
    cWin = new Ext.Window(
    {
    id: ‘cWindow’,
    layout:’anchor’,
    width:850,
    height:540,
    closable: false,
    closeAction:’hide’,
    plain: true,
    items: cForm,
    tbar: [cDeleteAction],
    monitorResize: true,
    buttons: cWinButtons
    });
    }
    cWin.doLayout();
    cWin.show(Ext.getBody());
    };

  18. Mixins sounds like the strategy pattern by GOF. Is a good software design pattern. I hope the Extjs take more strong giving real object oriented capacities to the framework. Javascript is good but native fails in this aspect.

  19. Hermes says:

    How can I make a multiple-instance app in Extjs 4 ,
    Can I have an Ext.app.Application instance with modules as Application too.
    for example:

    Ext.application({
    name:’Application Bundles’,
    bundles:[ 'App1' , 'App2' ],
    controllers:[...],

    })

    in App1.js

    Ext.create(‘Ext.app.Application’,{
    name: ‘App1′,
    appFolder:’app/bundles/app1′,
    controllers: ['Main'],
    launch:function(){
    console.log(‘App1 launched’);
    }
    })

    in App2.js

    Ext.create(‘Ext.app.Application’,{
    name: ‘App2′,
    appFolder:’app/bundles/app2′,
    controllers: ['Main2'],
    launch:function(){
    console.log(‘App2 launched too’);
    }
    })

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

Follow

Get every new post delivered to your Inbox.

Join 2,601 other followers

%d bloggers like this: