You are hereBlogs / rahul's blog / Maintaining Component References in ExtJs/Ext.Net applications - Part II

Maintaining Component References in ExtJs/Ext.Net applications - Part II


rahul's picture

By rahul - Posted on 13 July 2010

In the first of this series of couple of blog posts (available here), I have discussed the problem of maintaining reference to ExtJs components on the client-side in javascript, and the motivation for creating a new solution to it, than what ExtJs/Ext.Net provide out-of-the box. Well, here's how I have settled down on resolving the issue.

In a nut-shell, I use a dedicated namespace for maintaining Component references for all Components that are logically associated and which need to be accessible in each other's event listeners. Now, follow the implementation details.

It all starts with creating a new namespace for maintaining references for all logically associated components. Here's how:

 

Ext.ns('Rahul.Ext');

Rahul.Ext.allocateControlNamespace = function(parentNamespace) {
    var fn = function() {
        if (Rahul.Ext.allocateControlNamespace.counter == undefined) {
            Rahul.Ext.allocateControlNamespace.counter = 0;
        }
        return (Rahul.Ext.allocateControlNamespace.counter++);
    }

    var ns, fqn;
    if (Ext.isEmpty(parentNamespace)) {
        parentNamespace = Ext.ns('Rahul.Ext.Controls');
        ns = Rahul.Ext.Controls;
        fqn = 'Rahul.Ext.Controls';
    } else if (typeof (parentNamespace) == 'string') {
        if (!parentNamespace.endsWith('.Controls')) {
            parentNamespace = parentNamespace + '.Controls';
        }
        fqn = parentNamespace;
        Ext.ns(parentNamespace);
        ns = eval(parentNamespace);
    } else {
        throw 'parentNamespace should be specified as a string.';
    }

    var i = fn();
    ns['ns' + i] = {};
    fqn += '.ns' + i;

    ns = ns['ns' + i];
    ns.reg = function(name, ctl) {
        //Please take note that reg can either be called automatically in iniComponent, or you can call it explicitly, like ns.reg('ctl', yourControl);
        //Therefore, you need to register references both ways.
        ctl.ns = ns;
        ns[name] = ctl;

        return (ns);
    }
    ns.getFullName = function() {
        return (fqn);
    }

    return (ns);
}


String.prototype.endsWith = function(str) {
    var lastIndex = this.lastIndexOf(str);
    return (lastIndex != -1) && (lastIndex + str.length == this.length);
}

The parentNamespace argument to the method is optional above, and if specified, should be a string. The method generates a unique namespace for storing Component references and returns the namespace generated. The namespace has 2 methods, reg which should be used to register a Component reference with this namespace, and getFullName, which returns the fully qualified name of the namespace as a string (something on the lines of Rahul.Ext.Controls.ns0 if parentNamespace is not specified). endsWith is a utility method added to the String class.

Next at the beginning of instantizing the controls, which are logically related, I get allocated a new namespace as follows:

 

var ns = Rahul.Ext.allocateControlNamespace();

Then, in the config options for any ExtJs components, I specify 2 custom attributes, i) ns which corresponds to the namespace we just got allocated, and ii), nsName, the name with which the component should be registered with the namespace. For example:

 

var ns=Rahul.Ext.allocateControlNamespace();
var pn = new Ext.Panel({
    title: 'Login',
    layout: 'form',
    listeners: { show: pnlLoginShown },
    ns: ns,
    nsName: 'pnlLogin',
    items: [{ xtype: 'textfield', fieldLabel: 'Username', ns: ns, nsName: 'txtUsername', listeners: {specialkey:txtLoginSpecialKey} },
    { xtype: 'textfield', fieldLabel: 'password', ns: ns, nsName: 'txtPassword', listeners: { specialkey: txtLoginSpecialKey} }
    ]
});

Now, we need to hook into ExtJs' component instantization process, so that the components get registered with the namespace when they are instantized. To do this, we create a sequence method for Ext.Component.initComponent method:

 

Ext.Component.prototype.initComponent = Ext.Component.prototype.initComponent.createSequence(function() {
    if (this.ns && this.nsName) {
      if (Ext.isFunction(this.ns.reg)) {
        this.ns.reg(this.nsName, this);
      } else {
        this.ns[this.nsName] = this;
      }
    }
});

Well, that's it, we have done majority of the work needed. Now you can easily reference any component in an event listener for any other components, provided they got registered to the same namespace while instantization. e.g.

 

function pnlLoginShown(el) {
    var ns = el.ns;

    var username = ns.txtUsername.getValue();
    var password = ns.txtPassword.getValue();
    //Your processing here
}

function txtLoginSpecialKey(el) {
    var ns = el.ns;

    var pnlLogin = ns.pnlLogin;
    var username = ns.txtUsername.getValue();
    var password = ns.txtPassword.getValue();
    //Your processing here
}

As you can see, accessing other components is as easy as referencing them from the associated namespace, no need to traverse along parent/child container axis, or needing to manually maintain unique ids. You just need to pass a couple of extra config options for Components, which need to be referenced together.

Please note Ext.Component.initComponent needs to be sequenced only once (and not each time you get allocated a new control namespace).

That was for maintaining references while creating components in javascript. What about if you are using Ext.Net/Coolite. How do you maintain references then. Well, you again have 2 easy ways to do so. But' let's first see a small sample of Ext.Net markup:

 

<ext:Panel runat="server" ID="pnlLogin" Title="Login" Layout="Form">
	<Items>
		<ext:TextField runat="server" ID="txtUsername" />
		<ext:TextField runat="server" ID="txtPassword" />
	</Items>
</ext:Panel>

There are 2 ways you can maintain references for your Ext.Net controls inside the same page, or same UserControl. The first one uses CustomConfig available on each control, that I do not necessarily recommend. My other approach is to add an extra <ext:Hidden> control to the Page or the UserControl, and manually register references in its Render handler. Again an example is in order:

 

<ext:Hidden runat="server">
	<Listeners>
		<Render Handler="
	var ns = MB.allocateControlNamespace(null, true);
	
	ns.reg('pnlLogin', #{pnlLogin});
	ns.reg('txtUsername', #{txtUsername});
	ns.reg('txtPassword', #{txtPassword});
		" />
	</Listeners>
</ext:Hidden>

Please take care that this Hidden Control is always towards the bottom of the Page or UserControl. There's no change on how you reference components in the javascript event listeners for your Ext.Net/Coolite components, and that remains the same as demonstrated above.

Is is interesting to take note that you can indeed use any javascript object while specifying the ns custom config option (and not necessarily the retirn value of the allocateControlNamepsace method). If the ns passed has a reg method, the initComponent method registers the control through the reg method, otherwise it assigns the reference directly.

The above approach allows you to maintain component references pretty easily and access all registered components easily in any event listener, or else wise, whenever you have reference to any single component which was registered with others to a common namespace.

All your ASP.NET UserControls and Pages remain independent and you can choose any Control Id independently from what has been chosen on other Pages or UserControls for the components. Just ensure that you have not specified one of the Explicit options for your Ext.Net controls' IDMode property as that forces you to manually ensure unique ids across UserControls.

For communication between UserControls, I have well-defined server and client-side interfaces, that allow Components in one UserControl to access Components in other UserControls. However, as these interfaces are highly specific to the particular application they have been developed for, they are not suitable for discussion here.

 

Very usefull, thank you for postig this! Indispensable, even for smaller projects.


Great post I must say.. Simple but yet entertaining and engaging.. Keep up the good work!

With many fewer lines the same effect can be achieved:

Ext.Component.prototype.initComponent = Ext.Component.prototype.initComponent.createSequence(function(){
    if(this.refO && this.itemId ){
            this.refO[this.itemId] = this;
    }
});

In the component add the property 'refO' pointing to an object where you want a reference to be added as a property with the name of the property 'itemId'. (as per the discussion on the forums)

I see this is already in there, but why do you have all the other code? What is 'reg' for when you can just add a reference by adding an object property.

It is best to not introduce any unnecessary function calls to every call to initComponent because anything in the Ext component initialization/rendering pipeline will slow down the page. Crashes due to bad references passed can be handled in the coding phase.

rahul's picture

Hi Andrew, thanks for your feedback. Well yes, you can easily omit the function call and have the reference registration in initCompnent itself. How you choose to do it is largely a matter of taste.

I have actually come up with an even better method that allows you to specify the namespace higher in the component tree without repeating it for every component. There is a small, but powerful, change made. I detailed my approach on my blag and I would very much like your opinion.

http://jsjoy.com/blog/123/dealing-with-references-to-components

 

rahul's picture

I saw your blog post Andrew, and the approach is good (saves time and increases productivity). Kudos!!!

Hi everybody else !!! The blog was totally fantastic! you all do such a fantastic job at such Concepts..., for one appreciate all you do!

Post new comment

The content of this field is kept private and will not be shown publicly.
Type the characters you see in this picture. (verify using audio)
Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated. Not case sensitive.

Recent comments