ExtJs and Ext.Net - Creating Ext.Net server components for custom ExtJs javascript components

I think all of us would agree that Ext.Net provides too good (and a bit complex) ExtJs and ASP.NET integration. It exposes almost all ExtJs toolkit classes server-side in ASP.NET and many of the popular extensions too. In addition, it provides own custom components that are useful (e.g. MultiCombo, Linkbutton etc).

But as with all toolkits, a time would come when you would want to extend/enhance the functionality for your purpose. With Ext.Net, any enhancement you would like to perform to a component or any custom component you would like to create has 2 aspects: client-side and server-side.

Let's say I want to create a specialized Panel with some additional config options or other features. The first thing I would do is to create the corresponding ExtJs component class in javascript, and you would find tonnes to examples of doing so on sencha.com as well as hundreds of blogs (including mine).

But the topic of this blog post is how to expose such a custom component server-side that would enable you to use the component in markup as you would do for a regular Ext.Net component and manipulate its properties in code-behind. This is something for which I haven't found much information (almost no information) on web.

For the purpose of this blog post, we would be using the following custom Panel class and create a server-side Ext.Net component for it:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Ext.ux.BlockPanel = Ext.extend(Ext.Panel, { constructor: function (config) { Ext.applyIf(config, { addDefaultTools: true, preventTitle: false }); if (config.preventTitle) { delete config.title; } if (config.addDefaultTools) { config.tools = config.tools || []; config.tools.splice(config.tools.length, 0, { id: 'gear', qtip: 'Settings', handler: this.toolSettingsClicked }, { id: 'close', qtip: 'Close', handler: this.toolCloseClicked } ); } Ext.ux.BlockPanel.superclass.constructor.call(this, config); }, toolSettingsClicked: function (event, toolEl, panel, tc) { //Your code here }, toolCloseClicked: function (event, toolEl, panel, tc) { //Your code here } }); Ext.reg('ext.ux.blockpanel', Ext.ux.BlockPanel);{/syntaxhighlighter}

I have tried to keep the example simple. In one of my projects, I needed a Panel with pre-defined tools and no title at lots of places, and decided to do it with a custom Panel derived component. The above example is a simplified version of the same Panel class from my application.

Now we would create a custom server-side Ext.Net component corresponding to this BlockPanel that would allow us using it in ASP.NET markup as well as in code-behind to set its additional config options.

Here's the corresponding Ext.Net server component class:

 

{syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }namespace Rahul.UI.ExtNet { [Meta] public class BlockPanel : Ext.Net.Panel { #region Public Properties public override string XType { get { return "ext.ux.blockpanel"; } } public override string InstanceOf { get { return "Ext.ux.BlockPanel"; } } /// <summary> /// True to add the default tools (settings, close etc.) to the Block Header, false otherwise. Defaults to true. /// </summary> [Meta] [ConfigOption] [Category("6. Panel")] [DefaultValue(true)] [NotifyParentProperty(true)] [Description("True to add the default tools (settings, close etc.) to the Block Header, flase otherwise. Defaults to true.")] public virtual bool AddDefaultTools { get { object obj = this.ViewState["AddDefaultTools"]; return (obj == null) ? true : (bool) obj; } set { this.ViewState["AddDefaultTools"] = value; } } /// <summary> /// True to prevent Block Title from rendering. If AddDefaultTools is also false, the Block Header would not render at all. /// </summary> [Meta] [ConfigOption] [Category("6. Panel")] [DefaultValue(false)] [NotifyParentProperty(true)] public virtual bool PreventTitle { get { object obj = this.ViewState["PreventTitle"]; return (obj == null) ? false : (bool) obj; } set { this.ViewState["PreventTitle"] = value; } } [Meta] [ConfigOption] [Category("6. Panel")] [DefaultValue(true)] [NotifyParentProperty(true)] public int ColSpan { get { object obj = this.ViewState["ColSpan"]; return (obj == null) ? 2 : (int) obj; } set { this.ViewState["ColSpan"] = value; } } [Meta] [ConfigOption] [Category("6. Panel")] [DefaultValue(true)] [NotifyParentProperty(true)] public int RowSpan { get { object obj = this.ViewState["RowSpan"]; return (obj == null) ? 1 : (int) obj; } set { this.ViewState["RowSpan"] = value; } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [System.Xml.Serialization.XmlIgnore] [Newtonsoft.Json.JsonIgnore] public override ConfigOptionsCollection ConfigOptions { get { ConfigOptionsCollection list = base.ConfigOptions; list.Add("addDefaultTools", new ConfigOption("addDefaultTools", null, true, this.AddDefaultTools)); list.Add("preventTitle", new ConfigOption("preventTitle", null, false, this.PreventTitle)); list.Add("colspan", new ConfigOption("colspan", null, true, this.ColSpan)); list.Add("rowspan", new ConfigOption("rowspan", null, true, this.RowSpan)); return (list); } } #endregion } }{/syntaxhighlighter}

Now let's discuss each portion of the above class as well as my approach towards creating it that should provide enough pointers to enable you to create such (and more complex) Ext.Net components:

  1. I used Ext.Net's TabPanel and TabPanelBase as a reference. I haven't found any documentation for creating custom Ext.Net components and therefore resorted to analyzing the toolkit code itself to figure out how to do it.
  2. In Ext.Net, you would always find 2 classes for an ExtJs component, one a abstract base class and second the concrete component class (e.g. TabPanelBase and TabPanel classes for ExtJs' TabPanel).
    This relates to some ASP.NET parsing constraints that I had earlier discussed with the Ext.Net team here, you might find that thread useful.
  3. At a very minimum, you need to atleast override 2 properties for your custom Ext.Net component: InstanceOf and XType. The first is the fully-qualified javascript class name for yoour ExtJs component, and the second is its xtype registered with Extjs. InstanceOf is used during direct instantization of a component by Ext.Net, whereas XType is used for lazy instantization usually in layouts.
  4. My first approach was to just put in the other config options in the server-side class decorated with proper attributes. I had hoped this would automatically put the property and its value in the config options for the constructor in javascript when an instance is created for the component.

    {syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }[Meta] [ConfigOption] [Category("6. Panel")] [DefaultValue(false)] [NotifyParentProperty(true)] public virtual bool PreventTitle { get { object obj = this.ViewState["PreventTitle"]; return (obj == null) ? false : (bool) obj; } set { this.ViewState["PreventTitle"] = value; } }{/syntaxhighlighter}
    But it did not work out. When I set the value of this property either in markup or in code-behind, that value was not sent to the client in javascript.
  5. So I turned exploring other classes in the Ext.Net framework, and stumbled upon the Factory folder in Ext.Net project, which further contained Builder, Config and ConfigOptions folders.
    Each of these further had a associated code file for TabPanelBase and TabPanel classes. And interestingly enough, the corresponding code files in various folders contain the same partial class distributed across multiple files.

    So, in Ext.Net's project structure, the following files:
    Ext.Net\Ext\TabPanel.cs
    Ext.Net\Factory\Builder\TabPanelBuilder.cs
    Ext.Net\Factory\Config\TabPanelConfig.cs
    Ext.Net\Factory\ConfigOptions\TabPanelConfigOptions.cs

    all contained the same partial class split across multiple files. Also, there were separate DirectEvent and Listeners classes for TabPanel in:
    Ext.Net\Factory\ConfigOptions\TabPanelDirectEventsConfigOptions.cs
    Ext.Net\Factory\ConfigOptions\TabPanelListenersConfigOptions.cs
  6. You might want to explore each of these files atleast once, and also corresponding classes for the TabPanelBase class. This would give you a pretty good idea of the framework and foundation on which Ext.Net works.
    My own personal opinion is that I see lots of code duplicacy across multiple files. The same config option is repeated many times over across files. Ext.Net team would know their design better, but a reduction in redundancy should certainly help maintenance as well as understanding of the toolkit code.
  7. Anyways, in this code file:
    Ext.Net\Factory\ConfigOptions\TabPanelBaseConfigOptions.cs

    I noticed that Ext.Net registers all additional config options for a Component in a public Property called ConfigOptions, as you can see in the following code:

    {syntaxhighlighter brush: as3;fontsize: 100; first-line: 1; }public override ConfigOptionsCollection ConfigOptions { get { ConfigOptionsCollection list = base.ConfigOptions; list.Add("visibleIndex", new ConfigOption("visibleIndex", new SerializationOptions("activeTab"), -1, this.VisibleIndex )); list.Add("animScroll", new ConfigOption("animScroll", null, true, this.AnimScroll )); //More code lines return list; } }{/syntaxhighlighter}You would notice that this property collects the ConfigOptions from the base class in a list, and then adds its own options to this list.
  8. So I added the following property to my BlockPanel class:

    {syntaxhighlighter brush: csharp;fontsize: 100; first-line: 1; }[Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [System.Xml.Serialization.XmlIgnore] [Newtonsoft.Json.JsonIgnore] public override ConfigOptionsCollection ConfigOptions { get { ConfigOptionsCollection list = base.ConfigOptions; list.Add("addDefaultTools", new ConfigOption("addDefaultTools", null, true, this.AddDefaultTools)); list.Add("preventTitle", new ConfigOption("preventTitle", null, false, this.PreventTitle)); list.Add("colspan", new ConfigOption("colspan", null, true, this.ColSpan)); list.Add("rowspan", new ConfigOption("rowspan", null, true, this.RowSpan)); return (list); } }{/syntaxhighlighter}The first argment to the list.add method is the javascript property name with which your server property would be serialized, then a ConfigOption object with the property details (including its SerializationMode).

    And as soon as I did that and re-complied, I could see the config options set in markup and in code-behind being serialized to the client succesfully.

You would now register this component on the page using the ASP.NET's <% Register %> directive, give the namespace or assembly a TagPrefix (like you would do to Ext.Net) and you can then use it in markup. Here's an example:

 

 

<%@ Register Assembly="MyAssembly" Namespace="Rahul.UI.ExtNet" TagPrefix="rahul" %>


<rahul:BlockPanel runat="server" ID="pnlBlock" AutoWidth="225" Collapsible="true" AddDefaultTools="false" PreventTitle="true" />

 

 

In a nut-shell, to create custom ExtJs components and expose them as Ext.Net server components, you create your ExtJs Component class, the corresponding server component class derived from a suitable Ext.Net component class, add properties corresponding to your additional config options (and applying them with the attributes as in the above BlockPanel class), and register all those properties by overriding the ConfigOptions property. While overriding ConfigOptions, a very important thing to remeber is to add your options to base class options (and not replace them). So, they should be added to the list returned by a call to base.ConfigOptions.

Please note that I have covered creating custom components very much from the surface. There are many in-depth things I have left like invoking custom methods on your new component (which can be done in various ways like XControl.AddScript, XControl.Call and various other script registration methods available), adding custom DirectEvents or Listeners (for which this forum thread should provide a good understanding on how to do it and the gotchas involved), setting Properties in DirectEvents/DirectMethods etc.

A blog post covering all these aspects would stretch too long, and you might probably be better having a look at the above refeenced code files to see how Ext.Net does these things itself (you might need to create custom ConfigBuilders, nested Config classes and other such things if you want to exploit everything available out there in Ext.Net using your custom component).

I only tried to cover the basic features of adding Config Options to custom components which is what I do most of the time myself (very rarely do I need to provide a wrapper for invoking a custom javascript method on the component from server). And DirectEvent And DirectMethod related functionality is something I almost never incorporate in custom components because as a rule of thumb, I almost always avoid DirectEvents and DirectMethods in favor of javascript listeners and manual server interaction via web-service methods or .ashx handlers.

 

Comments

Lazy developers can use GenericComponent

See http://forums.ext.net/showthread.php?12861-CLOSED-TinyMCE&p=53043&viewfu...

About Factory folder, it is autogenerated folder, we use special utility to build Builder, Config and ConfigOptions files for each control in the toolkit (ConfigOptions allows us to avoid reflection using (reflection is used in 0.8.x version)). For example, Razor support (we are working on it right now) are based on Builder classes

rahul's picture

Hi vlad, thanks for the details. I was not aware that all that big Factory folder is auto-generated. I was pretty surprised at the duplicacy of various options in those related files, but now knowing that it is auto-generated, it does not look that much daunting for the Ext.Net team as it had looked to me earlier.

Hi rahul, your posts has been very helpful to me!.

I'm trying to create an Ext.Net componenet based on an ExtJs javascript extension.

This is the ExtJs Javascript extension that i'm trying to use: http://www.lubber.de/extjs/datepickerplus/

This is how the code looks: http://pastebin.com/kkUi9s6W

As you can see the MultipleSelection is by default true. But somehow it's not being passed to the client.

This is the ExtJs declaration of the component:

Ext.net.ResourceMgr.init({
    id: "ctl00$ContentPlaceHolder1$ResourceManager1",
    BLANK_IMAGE_URL: "/extjs/resources/images/default/s-gif/ext.axd",
    aspForm: "form1",
    theme: "blue"
});
Ext.onReady(function () {
    Ext.QuickTips.init();
    Ext.net.DirectEvent.on({
        ajaxrequestexception: {
            fn: function (response, result, el, eventType, action, extraParams, o) {
                RegisterError(response, result, el, eventType, action, extraParams);
            }
        }
    });
    new Ext.ux.DatePickerPlus({
        id: "ContentPlaceHolder1_date_ctl00",
        renderTo: "ContentPlaceHolder1_date_ctl00_Container",
        format: "d/m/Y"
    });
});

 

Any help or insight would be fairly appreciated!

Thanks

rahul's picture

Hi, on the surface it looks fine to me. If you are unable to get it to work, can you please create a simple app demonstrating the issue (with web.config, your server-side class and other required resources but without any dll in bin folder), zip it and attach it to your comment.

It looks fine to me also, that's why i'm so frustrated :P

I'm attaching the project to test... i didn't include the Ext.Net.dll because it was too heavy (12mb i think)... the version that we are using is the: 1.2.0.21945

Thanks in advance!!

rahul's picture

Hi sorry for the delayed reply, I was travelling first and then needed to clear up my work backlog. The problem seems to be this line:

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }list.Add("multiSelection", new ConfigOption("multiSelection", null, true, this.MultipleSelection));{/syntaxhighlighter}

Ext.Net won't render a config option if its value equals the default value. You are assinging true to MultipleSelection in Default.aspx.cs and in the above line, the default value is true. So, Ext.Net won't render it. Your default values should correspond to the defaults in your javascript component.

Alternatively you can pass null as default value above (instead of true) and Ext.Net would render both true/false values for MultipleSelection config option.

Great article! I was wondering if you have a complete solution or project available to download? I'm trying to create a custom grid (inherited from gridpanel) and I understand all the pieces in the article. I'm just curious about how everything is brought together in visual studio.

Thanks,
Amit

Your block panel works great.

But right now i'm having some trouble trying to read a property from the ExtJs control. Lets say that your blockpanel has a collapsed or expanded property.

How can you read that property from server side? I'm struggling with that...

rahul's picture

If you mean you are setting the collapsed property in javascript, and then attempting to read the updated value server-side in PostBack or DirectEvent, I am not aware of any way to do that. And this is not the correct way of using ExtJs also, the power of ExtJs is using it client-side and communicating with server using Ajax with explicitly serialized request and response data instead of relying on traditional ASP.NET postbacks.

If you absolutely need to do this, you would want to render an Hidden input control on the page in your BlockPanel (or whatever component you have). In javascript, for the property (collpased or expanded) setter, you would also update this hidden field's value.

This would would then be available server-side in PostBack or DirectEvent/DirectMethod.

The ext.reg is not working anymore....

With Ext.Net, any enhancement you would like to perform to a component or any custom component you would like to create has 2 aspects: client-side and server-side. - Weather Shield Windows