Ext JS 4: The Class Definition Pipeline
January 25, 2011 27 Comments
Last time, we looked at some of the features of the new class system in Ext JS 4, and explored some of the code that makes it work. Today we’re going to dig a little deeper and look at the class definition pipeline – the framework responsible for creating every class in Ext JS 4.
As I mentioned last time, every class in Ext JS 4 is an instance of Ext.Class. When an Ext.Class is constructed, it hands itself off to a pipeline populated by small, focused processors, each of which handles one part of the class definition process. We ship a number of these processors out of the box – there are processors for handling mixins, setting up configuration functions and handling class extension.
The pipeline is probably best explained with a picture. Think of your class starting its definition journey at the bottom left, working its way up the preprocessors on the left hand side and then down the postprocessors on the right, until finally it reaches the end, where it signals its readiness to a callback function:
The distinction between preprocessors and postprocessors is that a class is considered ‘ready’ (e.g. can be instantiated) after the preprocessors have all been executed. Postprocessors typically perform functions like aliasing the class name to an xtype or back to a legacy class name – things that don’t affect the class’ behavior.
Each processor runs asynchronously, calling back to the Ext.Class constructor when it is ready – this is what enables us to extend classes that don’t exist on the page yet. The first preprocessor is the Loader, which checks to see if all of the new Class’ dependencies are available. If they are not, the Loader can dynamically load those dependencies before calling back to Ext.Class and allowing the next preprocessor to run. We’ll take another look at the Loader in another post.
After running the Loader, the new Class is set up to inherit from the declared superclass by the Extend preprocessor. The Mixins preprocessor takes care of copying all of the functions from each of our mixins, and the Config preprocessor handles the creation of the 4 config functions we saw last time (e.g. getTitle, setTitle, resetTitle, applyTitle – check out yesterday’s post to see how the Configs processor helps out).
Finally, the Statics preprocessor looks for any static functions that we set up on our new class and makes them available statically on the class. The processors that are run are completely customizable, and it’s easy to add custom processors at any point. Let’s take a look at that Statics preprocessor as an example:
//Each processor is passed three arguments - the class under construction, //the configuration for that class and a callback function to call when the processor has finished Ext.Class.registerPreprocessor('statics', function(cls, data, callback) { if (Ext.isObject(data.statics)) { var statics = data.statics, name; //here we just copy each static function onto the new Class for (name in statics) { if (statics.hasOwnProperty(name)) { cls[name] = statics[name]; } } } delete data.statics; //Once the processor's work is done, we just call the callback function to kick off the next processor if (callback) { callback.call(this, cls, data); } }); //Changing the order that the preprocessors are called in is easy too - this is the default Ext.Class.setDefaultPreprocessors(['extend', 'mixins', 'config', 'statics']);
What happens above is pretty straightforward. We’re registering a preprocessor called ‘statics’ with Ext.Class. The function we provide is called whenever the ‘statics’ preprocessor is invoked, and is passed the new Ext.Class instance, the configuration for that class, and a callback to call when the preprocessor has finished its work.
The actual work that this preprocessor does is trivial – it just looks to see if we declared a ‘statics’ property in our class configuration and if so copies it onto the new class. For example, let’s say we want to create a static getNextId function on a class:
Ext.define('MyClass', { statics: { idSeed: 1000, getNextId: function() { return this.idSeed++; } } });
Because of the Statics preprocessor, we can now call the function statically on the Class (e.g. without creating an instance of MyClass):
MyClass.getNextId(); //1000 MyClass.getNextId(); //1001 MyClass.getNextId(); //1002 ... etc
Finally, let’s come back to that callback at the bottom of the picture above. If we supply one, a callback function is run after all of the processors have run. At this point the new class is completely ready for use in your application. Here we create an instance of MyClass using the callback function, guaranteeing that the dependency on Ext.Window has been honored:
Ext.define('MyClass', { extend: 'Ext.Window' }, function() { //this callback is called when MyClass is ready for use var cls = new MyClass(); cls.setTitle('Everything is ready'); cls.show(); });
That’s it for today. Next time we’ll look at some of the new features in the part of Ext JS 4 that is closest to my heart – the data package.
Sorry for being picky: shouldn’t the first call to MyClass.getNextId() return 1001 instead of 1000?
@Scott – try it! You’ll get 1000. Now replace idSeed++ with ++idSeed and it’ll start with 1001. These are known as pre and post increment – http://www.hunlock.com/blogs/The_Complete_Javascript_Number_Reference is a good reference
It would be very interesting to see implementation of “Loader” in new Class system of ExtJs, or to read about it one of your articles
Ed, are there any args or return value in the Ext.define or Ext.require callback?
@Les no arguments given to the require callback (we may add some later). The define callback is given a single argument – a reference to the newly-defined class (e.g. MyClass in the last example above).
The Ext.define callback is also called in the scope of the newly-defined class (e.g. this === MyClass in the final example above)
But don’t all these preprocessors slow things down? The loading and initialization routines now seem to be split into much more stages involving a lot of copying properties between objects, calling callbacks… Although it’s clear that the new system is more flexible and has less requirements for the end developer.
Ed, can Ext.require load a resource other than an Ext class, e.g. a JavaScript or an html file or an Ext template? Is there a way to define a “templated” class that would depend on an external template?
@Dmitriy there are a few more function invocations but the enhanced flexibility is well worth it
@Les Only classes at the moment, but the separate Loader can load any file. I’ll post something about that soon, either here or on the sencha.com blog
Ed, how a component class would dynamically load plugins?
>>> Ed, how a component class would dynamically load plugins?
I believe I can answer my own question:) Class is ready before the plugins that it can use are loaded. This because plugins are needed per instance of the class. So, I’d need to require the plugins before an instance of the class is created.
Ed,
Will the object´s superClass have an alias in Ext4? I don´t know if I´m just too lazy but something like “this.super.initComponent();” would be so much better than “Ext.Panel.superclass.initComponent.call(this)”
@Rafael yea, just call this.parent(). It’s the same as doing My.NewClass.superclass.prototype.theMethodName() – much better
Can a preprocessor or a postprocessor be used to automatically modify the created object?
@Blacktiger which object are you talking about? The processors can modify the Ext.Class instance which represents the new class. If you want to have some code that automatically modifies every created instance of that class, you can do it in the class’ constructor
Sorry, dumb question. I thought about it afterward and realized you can do most anything either by mucking with the class or adding a constructor.
Pingback: extjstutorial.org
This is very nice! Just a thought, would building a ‘Aspect’ preprocessor be something worthwhile to consider? This would allow a sort of aspect oriented programming capabilities added to classes.
@halcwb give it a go! It’s really easy to write your own processors and hook them in
last example gives me ‘TypeError: Expecting a function in instanceof check, but got [object Object]’ , why? :
Ext.define(‘MyClass’, {
extend: ‘Ext.Window’
}, function() {
//this callback is called when MyClass is ready for use
var cls = new MyClass();
cls.setTitle(‘Everything is ready’);
cls.show();
});
I do not find a default preprocessor in Ext JS 4.0.2 source code (I look in Class.js).
Also, I found the way Ext JS 4 processing preprocessors and postprocessors is strange. I think there are bugs in it. http://www.sencha.com/forum/showthread.php?143668-when-configuration-data-for-Ext.ClassManager.create-overwrite-postprocessor&p=636782#post636782 .
just a quick update. I saw the “loader” preprocessor is registered in loader.js
Honestly i have no idea why ext has such a drastic changes that broke everything?
this.parent.methodname() will not work.
Yes, this.callParent(arguments) will work, however you will have to keep your method name the same.
What should be the alternative?
Great post Ed!
I would like to add a piece of info that can save some headaches, for people reading this topic, as bc the documentation regarding this matter is lacking some info. Remember that in ExtJS v4.1.3 (haven’t tried others) the preprocessors (Ext.Class.registerPreprocessor) needs to :
**have the name of an existing config, let say for a store class, “proxy” and “model” are valid names to use, therefore Ext.Class.registerPreprocessor(“proxy”, function() { //do stuff });
**The function passed in, has 4 arguments and not 3 as states in the doc. Therefore it one should use the function as: Ext.Class.registerPreprocessor(“proxy”, function(cls, data, ***hooks***, callback) { //do stuff });
Hooks is the 3rd arg and callback will be the 4th.
Hope this helps someone that lands in this page for the same purpose as me.
Cheers!
Tiago Teixeira
Picture’s broken.
@rijkvanwel unfortunately I lost a bunch of images when I moved my blog a few months back. If I find another copy I’ll restore it
Here’s the image: http://web.archive.org/web/20130821165451im_/https://edspencer.net/wp-content/uploads/2011/01/Processors.png
I do have a question for you though. I have always been confused by the config preprocesser in Ext Js. In fact I never use it. It seems to work fine in simple examples not extending anything, but if you want to use it on an Ext class it behaves oddly. For instance, if you wanted it to match Sencha Touch by wrapping the fields property, it would not play nice and your model won’t have any fields. Should it never be used on non-user declared properties? It is often required in Sencha Touch. I do hope it gets changed to at least be consistent between the frameworks in 5.x.
Good thinking 🙂 Updated the post with that image
Agreed, that doesn’t make any sense. I wrote it up here mainly so I could figure out how it works! But yea, none of it should exist (in fact I believe they’re heading back to an Ext.extend-like single function for performance and other reasons).
By complete chance I’m actually writing a new framework at my new company, where I’ve learned that having a single architect design the whole thing results in a far more consistent whole. We live and learn I guess