Roo.XComponent introduction

Published 2012-04-08 00:00:00
With a nice long Easter holiday, I finally got a chance to hack on some of those todo items that I'd been putting off for quite a while. One of the core mini tasks was to enable mtrack information inside of my main email/accounting do everything platform. 

The Pman codebase, as I've mentioned before forms the core of pretty much all applications I work on now, mostly intranet or extranet focused web applications, that work like desktop applications. All of these applications are built up of various componenents, for example the Accouting module has Components like general ledger, managing invoices, timesheet and tracking editing and a pricing management component. 

The whole premise of the Pman project codebase on the Javascript side, is to register all the components sometime after the page is loaded, then after the user has logged in, or authentication has been checked, it will create and render all these components in sequence, appending them to the container panels.

In the case of the Accounting module, there is a default top level module called 'Pman', which the Accounting 'Tab' is added to, then all the sub components are added to that. This whole design was done a couple of years ago, and has been working very well, it's reliable and adding extra componenets, is just a mater creating a file then saying what the parent is and in what sequence it will appear.

Read on to find out how this all works...

The code that is generated from the interface builder which supports this looked pretty much like this.

Pman.on('beforeload', function()
        modKey : '500-Pman.Tab.Cash', // the order to build it..
        module : Pman.Tab.Cash, 
        region : 'center',
        parent : Pman,   // where it should be added to..
        name : "Cash",
        disabled : false, 
        permname: '' 

This registration event on Pman's before load listener was done originally to handle the situation that Parent, and module, may have not been defined by the stage at which registration occured.

The body of the component looked like this

Pman.Tab.Cash = new Roo.util.Observable({
    panel : false,
    disabled : false,
    parentLayout:  false,
    add : function(parentLayout, region)
        var _this = this;
        this.parentLayout = parentLayout;
        this.panel = parentLayout.addxtype({
                ... the js tree that makes the interface goes here.
        this.layout = this.panel.layout;

This would be a typical tab on the interface, there was a method that looped through all the registered components and called that 'add' method with the correct parameters.

Having developed this for the Pman project, some time later I also needed to apply the similar concept to non-Pman projects, like the original web.mtrack code, and a few other projects where I wanted to use 'Component'ized code with plain old HTML. To solve this, I recreated most of the original logic from the Pman builder into a Class Called Roo.XComponent. The design implementation for this was considerably simpler, it learned the lessons from the first effort in Pman and improved on it.

Again the code is generally expected to be auto generated by an interface builder, but it can be hand coded..

The non-tree part looks pretty much like this..

Pman.Tab.MTrackTimeline = new Roo.XComponent({
    part     :  ["MTrack","Timeline"],
    order    : 300,
    region   : 'center',
    parent   : 'Pman.Tab.DocumentsTab',
    name     : "unnamed module",
    disabled : false, 
    permname : '', 
    _tree : function()
        var _this = this;
        var MODULE = this;
        return {
                ... the js tree that makes the interface goes here ...

In this design, the constructor of XComponent registers the new component in XComponent.modules[] along with doing the universal contructor technique of applying all properties to the new Object instance. In this example parent is the name of another component, however if you use '#some-div' as the parent, the code will be rendered to a id="some-div" component if it exists. 

This shows that by this time, the resolution of parent to a Javscript property is done a build time, so it does not matter what sequence these files are loaded.

If you are building a HTML application with Roo interface elements, you can now include all the Javascript files (compressed) on every page, marginally slower on first page load, but fast in the long run. Then if the element matching the '#id' is found it will render the required components into the page.

So to actually make the rendering occur you would do something like this.

Roo.onReady(function() {;

In the Pman project, this build occurs after the login has been checked, along with this, since the Pman project does a few things after the rendering occurs, it also registers a handler for the build complete event:

Roo.XComponent.on('buildcomplete', function() { ... do some more stuff. });

I have now added code to Pman which supports both methods, it actually converts the 'old style' components into new XComponents, when registred in the old system. So that all the real building code is now done in the core Roo.XComponent class. There is also some code in Pman, that is used to decide if the system should render the user interface based on the user permissions in the Pman system.

app.Builder.js the interface / application builder

Having got this far, one of the few remaining bastions of pre-XComponent code was the application builder. That as mentioned on the blog before, is a wrapper around the Gtk Webkit widget, that includes the Webkit introspection debugger (the one you get with Ctrl-Shift-J). And all the tools to manipulate the interface and write the javascript event handler code.

The actual building code in the builder however has been very dated, and relyed on passing the JSON array into the webkit browser and rebuilding on the fly. In some respects this code duplicated a large part the code that writes the Javascript from the JSON code. This technique had some serious drawbacks however.

Bugs in the code to be rendered was always difficult to track down. As there was no real source code (it was all injected as a method call), whenever a variable was undefined, you got the exception, without any idea of where in the codebase it may be occuring. You could see the backtrack in the inspector, but it did not have much information..

Although the components are frequently quite small, the end result was that if the interface failed to render, you would either end up running the full application in a browser (slow), or just quickly opening the generated Javascript file and searching for the problematic code. 

To solve all of this, I came up with the idea to actually generating a HTML page that includes the Roo.XComponent code above. This now means that any error that occurs shows you the exact line in the Introspector of where the error occurs, and shows you the source code that the builder generates.

The only downside is that it currently breaks the code that was originally in there to highlight selected nodes and any code that would have allowed drag drop into the rendered view. However since most of that code was already broken, and dragging in the tree has been the way I've been working for years now. It's not a great loss, and a project for another day..


Add a comment (requires javascript!)
Comment Title
I am a link spammer?