PDA

View Full Version : [CLOSED] Custom component ID creation difference with v 1.x



anup
Jul 29, 2012, 9:25 PM
Hi,

In v 1.x of Ext.NET if I created a subclass of an Ext.NET component, I was able to do this:



private static int _instanceCount;

public override string ID
{
get { return base.ID ?? (base.ID = "MyId" + Interlocked.Increment(ref _instanceCount)); }
set { base.ID = value; }
}


The above is quite handy because if a user did not set an explicit ID for an instance, it would default to a reasonably predictable one (overriding IDMode to be Explicit made it even more easier to predict).

The other reason I like this is that if my custom component has some listeners then I can make those listeners be implemented as client side instance methods (e.g. Fn = "#{" + ID + "}.myMethod" ).

If I do this in v2.0 latest (or 2.1 latest) the id does not get created if the user of the component does not set an explicit id so I cannot call instance methods on the client side if needed.

Is it possible to have the 1.x behaviour in 2.x, or is there a suitable alternative that can be recommended?

Vladimir
Jul 29, 2012, 10:31 PM
The above is quite handy because if a user did not set an explicit ID for an instance, it would default to a reasonably predictable one (overriding IDMode to be Explicit made it even more easier to predict).

I am not sure how that ID will be more predictable than standard autogenerated ID, how do you predict the index?



The other reason I like this is that if my custom component has some listeners then I can make those listeners be implemented as client side instance methods (e.g. Fn = "#{" + ID + "}.myMethod" ).

If you set IDMode="Legacy" then you can use that approach without ID overriding. And '"#{" + ID + "}' it is better replace by 'ClientID'



If I do this in v2.0 latest (or 2.1 latest) the id does not get created if the user of the component does not set an explicit id so I cannot call instance methods on the client side if needed.

By default, autogenerated id is not rendered. If ID getter is never invoked before serialization then ID will not be rendered.



Is it possible to have the 1.x behaviour in 2.x, or is there a suitable alternative that can be recommended?

Override IDMode and use Legacy mode (or set it in the ctor). Or override ForceIdRendering property and return true

anup
Jul 30, 2012, 12:18 AM
Hi,

It actually worked, my full example had a copy/paste error...!

Also, to answer some of your questions, I probably didn't explain very well. In my custom components I don't necessarily set ids when I make use of them (either when I create them in code-behind or in markup).

At the same time, the component itself may set up listeners that call instance methods. I do this to try and keep most of my JavaScript code encapsulated (somewhat). When creating the component on the server side, the listener cannot always use the "this" keyword when referencing the instance (sometimes I can - example below).

I find that using ClientID doesn't work when an ID isn't set by the caller/user of the component, and I would prefer not to force it. So I find that using an Explicit id helps, but then I have to ensure id is made unique.

This is achieved by using the Interlocked.Increment method so I can generate default ids like "MyId1", "MyId2" etc. It is just easier to look at when debugging compared to ASP.NET's auto generated ones which are quite long (though understandable why they need to do that).

Here is an example of a custom window class



public class CustomWindow : Window
{
private static int _instanceCount;

public override string InstanceOf
{
get { return "MyApp.CustomWindow"; }
}

public override string XType
{
get { return "customwindow"; }
}

public override string ID
{
get { return base.ID ?? (base.ID = "CustomWindow" + Interlocked.Increment(ref _instanceCount)); }
set { base.ID = value; }
}

public override IDMode IDMode
{
get { return IDMode.Explicit; }
}

protected override void OnInit(EventArgs e)
{
Title = "Custom window";

Listeners.Show.Handler = "this.customFn();";

BottomBar.Add(new Toolbar
{
Items =
{
new Button
{
Text = "Button",
Listeners =
{
Click =
{
Handler = "#{" + ID + "}.onButtonClicked();"
}
}
}
}
});

base.OnInit(e);
}
}


Notice for the show handler I can just use "this". But, for the button click handler, if I want to call an instance method on the custom window, not the button, so I use "ID". (And because ID is auto incremented (maybe IDMode = Explicit is optional) I can get unique ids so I can use Handler = "#{" + ID + "}.onButtonClicked();".

The client side JavaScript is:



Ext.define('MyApp.CustomWindow', {
extend: 'Ext.Window',

alias: 'customWindow',

customFn: function() {
console.log('customFn called');
},

onButtonClicked: function() {
console.log('onButtonClicked called');
}
});


In some markup I can then create something like this



<body>
<ext:ResourceManager runat="server" />

<ext:CustomWindow runat="server" Width="300" Height="100" />
</body>


The generated JavaScript (pretty printed) is this:



Ext.net.ResourceMgr.init({
id: "ctl00",
theme: "gray"
});
Ext.onReady(function () {
Ext.create("MyApp.CustomWindow", {
id: "CustomWindow1",
height: 100,
hidden: false,
renderTo: Ext.getBody(),
width: 300,
bbar: {
xtype: "toolbar",
items: [{
id: "ctl03",
text: "Button",
listeners: {
click: {
fn: function (item, e) {
App.CustomWindow1.onButtonClicked();
}
}
}
}]
},
title: "Custom window",
listeners: {
show: {
fn: function (item) {
this.customFn();
}
}
}
});
});


Notice the button click listener has "App.CustomWindow1.onButtonClicked()" which is fine.

If I now change to using ClientID (regardless of whether I remove the ID and IDMode override or keep them) I get this in the generated JavaScript for the button click handler:



id: "ctl01", // id of component even if ID property is left in place
// all the other properties are as above -unchanged
items: [{
id: "ctl03",
text: "Button",
listeners: {
click: {
fn: function (item, e) {
Ext.get("App").onButtonClicked(); // this is not right
}
}
}
}]


And that therefore throws an error when clicking the button. (I also change OnInit and base.OnInit calls to be OnLoad instead but same problem.)

I guess what I should really be doing is setting up all the event handling in the JavaScript side, and relaying them or catching them in the right place - so the Custom Window on the JavaScript side will catch the button click event and call the local method as needed. I should perhaps also not be creating the button like this but really that should be in the JavaScript custom window class. However, in some of my real world components it is not as easy as that I think; sometimes I auto generate a large number of components contained inside my component so at the time the Javascript class is written/defined it is not easy to do this (I'd have to pass lots of custom configs etc from the C# component to the JavaScript which then needs a lot more intelligence and logic on what to create when, whereas sometimes that is easier and more readable/maintainable to set a few handlers like this encapsulated in the .NET control).

anup
Jul 30, 2012, 9:36 AM
Forgot to add - another difference I notice with 1.x is that in 1.x in the above code (the first example that works), I could override OnLoad rather than OnInit. However, in 2.x if I do that I don't get the id coming through. So, if I explicitly override ForceIdRendering to true it works but the id generated is the auto-generated id (ctl01 etc) instead of what is defined in the ID property. In 1.x I didn't need to override ForceIdRendering and it would generate the id based on the ID property.

I don't think this is perhaps too significant for me, as overriding OnInit is fine (initializing a custom control with those various properties seems to make sense to me unless you think that is bad practise), and using OnLoad + ForeceIdRendering = true but without needing to override ID/IDMode is perhaps okay too.

Vladimir
Jul 30, 2012, 1:25 PM
In my custom components I don't necessarily set ids when I make use of them (either when I create them in code-behind or in markup).

Do you mean to set ids by yourself?



At the same time, the component itself may set up listeners that call instance methods. I do this to try and keep most of my JavaScript code encapsulated (somewhat). When creating the component on the server side, the listener cannot always use the "this" keyword when referencing the instance (sometimes I can - example below).

Well, good practice is avoing any ids (in the Examples Explorer samples we use explicit ids extensively for code simplification only). In real applications, id using must be using if it is required only. ID must be changed by MessageBus events (a widget sends any info and another widget listens the event) or use ComponentQuery (like in your case because MessageBus is too complicated to call parent window method)


Handler = "this.up('window').onButtonClicked();"




I find that using ClientID doesn't work when an ID isn't set by the caller/user of the component, and I would prefer not to force it. So I find that using an Explicit id helps, but then I have to ensure id is made unique.

When you use ClientID in OnInit then ID property was not read yet (getter is not executed and your ID is not set). Therefore ASP.NET thinks that need to generate ID. If you call the following code before ClientID using then your generated ID will be used


string id = this.ID;
BottomBar.Add(new Toolbar
...
Handler = "#{" + ID + "}.onButtonClicked();"
...




Forgot to add - another difference I notice with 1.x is that in 1.x in the above code (the first example that works), I could override OnLoad rather than OnInit. However, in 2.x if I do that I don't get the id coming through. So, if I explicitly override ForceIdRendering to true it works but the id generated is the auto-generated id (ctl01 etc) instead of what is defined in the ID property.

ASP.NET already generates ID before OnLoad therefore 'base.ID' is never null



In 1.x I didn't need to override ForceIdRendering and it would generate the id based on the ID property.

Don't forget that 1.x uses .NET 3.5 but 2.x uses .NET 4
.NET has serious changes regarding ID and ClientID

Finally, if you need to set your own ID then do it in the constructor. If a developer set ID in markup or after component creation then your generated id will be overriden. Setting it in the getter is not good idea because ASP.NET can generate ID before your getter calling

anup
Jul 30, 2012, 5:33 PM
Thanks for that reply.

Yes, I meant when I use a component in any of my pages, I avoid setting id if I can, so in my own component itself I set the id internally - but as you explain I don't need to do it (or if I do think I do, then I should do it in the constructor).

Using "this.up" is a great suggestion.

Out of curiosity, I could not see the same .up method in Ext JS 3.x (it had an up method but only on Ext.Element). I had been using the Id approach since 1.x to help tie components together. I was about to ask if you knew the "up" equivalent in Ext JS 3.x but I see now on Component there is findParentByType, etc... So I will switch to using that, too for Ext.NET 1.x/Ext JS 3.x.

Many thanks! You can mark this as closed.

Daniil
Jul 30, 2012, 7:11 PM
Hi Anup,



Using "this.up" is a great suggestion.

Out of curiosity, I could not see the same .up method in Ext JS 3.x (it had an up method but only on Ext.Element). I had been using the Id approach since 1.x to help tie components together. I was about to ask if you knew the "up" equivalent in Ext JS 3.x but I see now on Component there is findParentByType, etc... So I will switch to using that, too for Ext.NET 1.x/Ext JS 3.x.

It appears in ExtJS 4.
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.ComponentQuery (http://docs.sencha.com/ext-js/4-1/#%21/api/Ext.ComponentQuery)

And it's extremely useful.

And yes, you can use the ExtJS 3 findParentByType method. Though there is no power and flexibility of ExtJS 4 ComponentQuery.