[CLOSED] JavaScript error when a component is a property of another component that is generated during a DirectMethod request

  1. #1

    [CLOSED] JavaScript error when a component is a property of another component that is generated during a DirectMethod request

    Hi,

    I am finding that I get JavaScript errors when I generated a component during a DirectMethod request. The nature of the component is significant though.

    It is only if my custom component itself has its own custom component that is also generated during the DirectMethod request and added as a property of a containing component.

    In the example below, I have a custom button. What is custom about it is that it has a property to hold a custom window (the idea being you press the button and the window is shown).

    When the button is used via markup (or code-behind), there is no problem. However, when the button (and its custom window) is generated via a DirectMethod request, then I get JavaScript errors noted further below.

    Here is an example.

    First, example of a custom window:

    public class CustomWindow : Window
    {
        private Panel _panel;
    
        public override string InstanceOf
        {
            get { return "MyApp.CustomWindow"; }
        }
    
        public override string XType
        {
            get { return "customwindow"; }
        }
    
        protected override void OnLoad(EventArgs e)
        {
            Title = "Custom window";
            Cls += " custom-window";
            Layout = "Fit";
            Listeners.Show.Handler = "this.customFn();";
    
            BottomBar.Add(new Toolbar
            {
                Items =
                {
                    new Button
                    {
                        Text = "Button",
                        Listeners =
                        {
                            Click =
                            {
                                Handler = "this.findParentByType('customwindow').onButtonClicked();"
                            }
                        }
                    }
                }
            });
    
            Items.Add(GetNewPanel());
    
            CustomConfig.Add(new ConfigItem("panel", _panel.ClientID, ParameterMode.Value));
    
            base.OnLoad(e);
        }
    
        private Component GetNewPanel()
        {
            _panel = new Panel { Html = "Panel html", Border = false };
    
            return _panel;
        }
    }
    Next, an example of a custom button using that window

    public class ButtonWithCustomWindow : Button
    {
        private ItemsCollection<CustomWindow> _customWindows;
    
        public override string InstanceOf { get { return "MyApp.ButtonWithCustomWindow"; } }
        public override string XType { get { return "buttonwithcustomwindow"; } }
    
        [Meta]
        [ConfigOption]
        [NotifyParentProperty(true)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual ItemsCollection<CustomWindow> CustomWindow
        {
            get { return _customWindows ?? (_customWindows = new ItemsCollection<CustomWindow>(true)); }
        }
    
        [XmlIgnore]
        [JsonIgnore]
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override ConfigOptionsCollection ConfigOptions
        {
            get
            {
                ConfigOptionsCollection list = base.ConfigOptions;
    
                list.Add("customWindow", new ConfigOption("customWindow", new SerializationOptions("customWindow", typeof(ItemCollectionJsonConverter)), null, CustomWindow));
    
                return list;
            }
        }
    
        protected override void OnLoad(System.EventArgs e)
        {
            Cls += " custom-button";
    
            if (CustomWindow.Count > 0)
            {
                CustomWindow[0].Hidden = true;
                Controls.Add(CustomWindow[0]);
                LazyItems.Add(CustomWindow[0]);
            }
    
            base.OnLoad(e);
        }
    }
    JavaScript for the two components (embedded into a sample HTML)

    <%@ Page Language="C#" %>
    <!DOCTYPE html>
    <html>
    <head runat="server">
        <title>Example</title>
        <ext:ResourcePlaceHolder runat="server" />
        
        <style>
            body { padding:10px; }
        </style>
    
        <script type="text/javascript">
            Ext.ns("MyApp");
    
            MyApp.CustomWindow = Ext.extend(Ext.Window, {
                constructor: function(config) {
                    config = config || {};
    
                    MyApp.CustomWindow.superclass.constructor.call(this, config);
    
                    if (this.panel) {
                        this.panel = Ext.getCmp(this.panel);
                    }
                },
    
                customFn: function() {
                    console.log('customFn called', this.id, this);
                },
    
                onButtonClicked: function() {
                    console.log('onButtonClicked called', this.id, this);
                }
            });
    
            Ext.reg("customwindow", MyApp.CustomWindow);
    
            MyApp.ButtonWithCustomWindow = Ext.extend(Ext.Button, {
                constructor: function (config) {
                    config = config || {};
    
                    MyApp.ButtonWithCustomWindow.superclass.constructor.call(this, config);
    
                    this.on('click', function () {
                        this.customWindowInstance.show();
                    }, this);
                },
    
                initComponent: function () {
                    MyApp.ButtonWithCustomWindow.superclass.initComponent.call(this);
    
                    this.customWindowInstance = Ext.create(Ext.apply({ ownerCt: this, animateTarget: this.el }, this.customWindow));
    
                    delete this.customWindow;
                }
            });
    
            Ext.reg("buttonwithcustomwindow", MyApp.ButtonWithCustomWindow);
        </script>
    </head>
    <body>
        <ext:ResourceManager runat="server" />
        
        <p>Button With Custom Window via markup</p>
    
        <ext:ButtonWithCustomWindow runat="server" Text="Show custom window">
            <CustomWindow>
                <ext:CustomWindow runat="server" />
            </CustomWindow>
        </ext:ButtonWithCustomWindow>
        
        <p>Button With Custom Window component generated via ASHX</p>
        
        <ext:Button runat="server" Text="Generate button">
            <Listeners>
                <Click Handler="
                    Ext.net.DirectMethod.request({
                        url: 'BuildButtonWithCustomWindow.ashx',
                        cleanRequest: true,
                        params: { container: '#{Panel1}' },
                        complete: function() { console.log('done'); }
                    });
                " />
            </Listeners>
        </ext:Button>
        
        <ext:Panel ID="Panel1" runat="server" Title="Dynamic button" />
    </body>
    </html>
    The ASHX that is invoked to dynamically create an instance of the ButtonWithCustomWindow:

    public class BuildButtonWithCustomWindow : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            string buttonScript = BuildButton().ToScript(RenderMode.RenderTo, context.Request["container"]);
    
            new DirectResponse(buttonScript).Return();
        }
    
        private XControl BuildButton()
        {
            return new ButtonWithCustomWindow
            {
                ID = "id" + Guid.NewGuid().ToString("N"),
                Text = "Created from server",
                CustomWindow =
                {
                    new CustomWindow
                    {
                        Width = 400,
                        Height = 400
                    }
                }
            };
        }
    
        public bool IsReusable
        {
            get { return false; }
        }
    }
    When I press the button for the ButtonWithCustomWindow that has been added via markup to the page, this is part of the initialization script that I get:

    new MyApp.ButtonWithCustomWindow({
        id : "ctl02",
        cls : " custom-button",
        renderTo : "ctl02_Container",
        text : "Show custom window",
        customWindow : {
            id : "ctl09",
            panel : "ctl15",
            xtype : "customwindow",
            cls : " custom-window",
            hidden : true,
            height : 100,
            width : 200,
            items : {
                id : "ctl15",
                html : "Panel html",
                border : false
            },
            layout : "fit",
            bbar : {
                id : "ctl11",
                xtype : "toolbar",
                items : [{
                        id : "ctl12",
                        text : "Button",
                        listeners : {
                            click : {
                                fn : function (item, e) {
                                    this.findParentByType('customwindow').onButtonClicked();
                                }
                            }
                        }
                    }, {
                        id : "ctl18",
                        xtype : "nettbspacer"
                    }
                ]
            },
            title : "Custom window",
            listeners : {
                show : {
                    fn : function (item) {
                        this.customFn();
                    }
                }
            }
        }
    });
    When I look at the initialization script created by the ASHX, it is very similar, but has some crucial differences:

    Ext.net.ResourceMgr.destroyCmp("idd5f79582b75945cd8e6ed53ed45cafb3");
    new MyApp.ButtonWithCustomWindow({
        id : "idd5f79582b75945cd8e6ed53ed45cafb3",
        cls : " custom-button",
        renderTo : "Panel1",
        text : "Created from server",
        customWindow : {
            panel : "ctl08",
            xtype : "customwindow",
            cls : " custom-window",
            hidden : true,
            height : 400,
            width : 400,
            items : {
                html : "Panel html",
                border : false
            },
            layout : "fit",
            bbar : {
                xtype : "toolbar",
                items : [{
                        id : "id748da190ac874f56a6e8c4f65e83ff4f",
                        text : "Button",
                        listeners : {
                            click : {
                                fn : function (item, e) {
                                    this.findParentByType('customwindow').onButtonClicked();
                                }
                            }
                        }
                    }, {
                        xtype : "nettbspacer"
                    }
                ]
            },
            title : "Custom window",
            listeners : {
                show : {
                    fn : function (item) {
                        this.customFn();
                    }
                }
            }
        }
    });
    ctl02.setTitle("Custom window");
    ctl02.addClass(" custom-window");
    Notice the last two lines:

    ctl02.setTitle("Custom window");
    ctl02.addClass(" custom-window");
    Also note that it doesn't even match the ID that I set explicitly (to a new Guid). Whether I set or don't set the ID, I get these two lines that do not match any id (if any) for the button.

    Hence, I always get a JavaScript error.

    Note, functionally the button still works and the Window gets its declared properties, because they are also through the config as well as these method calls; but it just has the error as well.

    The error also prevents the success and complete handlers from running.

    I am sure I have missed something obvious, as I am sure this has worked in the past... If it helps, for a long time I've been running an october 16 build, and only recently updated to latest 1.x branch, and still seeing the issue.
    Last edited by Daniil; Feb 07, 2013 at 4:05 AM. Reason: [CLOSED]
  2. #2
    Hi Anup,

    Glad to see you on the forums. Thank you the excellent explanation and a sample to reproduce. As usual.

    These code lines in the CustomWindow's OnLoad generate that script.
    Title = "Custom window";Cls += " custom-window";
    The condition is to generate a script or not is:
    if ((control.GenerateMethodsCalling) || (RequestManager.IsAjaxRequest && (control.AllowCallbackScriptMonitoring && (!control.IsDynamic || control.IsProxy))))
    So, IsAjaxRequest is true and control.IsDynamic is false. The first is correct, but the second is wrong.

    The IsDynamic property is:
    protected internal bool IsDynamic
    {
        get
        {
            return this.isDynamic || this.TopDynamicControl;
        }
        set
        {
            this.isDynamic = value;
        }
    }
    We will look into a possibility to auto recognize that the CustomWindow's IsDynamic should be true in your case. But not sure there is a good solution.

    For now I can suggest this solution.
    this.SuspendScripting();
    Title = "Custom window";
    Cls += " custom-window";
    this.ResumeScripting();
  3. #3
    All properties should be set up before putting a control into a Controls collection, i.e. before this code line.
    Controls.Add(CustomWindow[0]);
    But the OnLoad of the CustomWindow is executed after.

    You can try to move the block of properties initialization into a constructor or OnInit (before base.OnInit()).

    You can also look how a Button's Menu property is implemented in Ext.NET.

    But, it would be simpler to use a Bin feature. Just put a CustomWindow to the Button's Bin collection.
    Last edited by Daniil; Feb 05, 2013 at 11:44 AM.
  4. #4
    Quote Originally Posted by Daniil View Post
    Glad to see you on the forums.
    Lol. Thanks. Been away for a bit, and side tracked on other stuff a bit longer than I hoped... lot of catching up still!

    Quote Originally Posted by Daniil View Post
    We will look into a possibility to auto recognize that the CustomWindow's IsDynamic should be true in your case. But not sure there is a good solution.

    For now I can suggest this solution.
    this.SuspendScripting();
    Title = "Custom window";
    Cls += " custom-window";
    this.ResumeScripting();
    Thanks for the explanation. Look forward to a good solution, but in the meanwhile, the workaround works well from my initial tests; and useful to know about too, so many thanks for that.

    (I have not had a chance to test this in 2.x yet. I will try later this week and let you know if any problems or not.)
  5. #5
    Quote Originally Posted by Daniil View Post
    All properties should be set up before putting a control into a Controls collection, i.e. before this code line.
    Controls.Add(CustomWindow[0]);
    But the OnLoad of the CustomWindow is executed after.

    You can try to move the block of properties initialization into a constructor or OnInit (before base.OnInit()).

    You can also look how a Button's Menu property is implemented in Ext.NET.

    But, it would be simpler to use a Bin feature. Just put a CustomWindow to the Button's Bin collection.
    I replied to your first reply before I saw your second one :)

    I was going to use the excellent Bin feature, but as a component I wanted to support the ability to have a custom component as a property for easier use by other users of the component. I'll look into setting the control properties before adding to the controls collection if possible (the real code is a bit more complex, spread over some subclasses, and other types dynamically loaded at run time etc, so not sure how easy that will be, but it looks like I have many options here though, so thanks again!)
  6. #6
    For a user of the custom control you could return a control from the Bin collection, can't you? I mean organizing a property to easily access a required control.
  7. #7
    Quote Originally Posted by Daniil View Post
    For a user of the custom control you could return a control from the Bin collection, can't you? I mean organizing a property to easily access a required control.
    I think the issue with that then is they can't do this, can they?

        <ext:ButtonWithCustomWindow runat="server" Text="Show custom window">
            <CustomWindow>
                <ext:CustomWindow runat="server" />
            </CustomWindow>
        </ext:ButtonWithCustomWindow>
    If we use Bin, then they can only use code-behind option, not markup option?
  8. #8
    I meant something like this.

    Example ButtonWithCustomWindow class
    using System;
    using System.ComponentModel;
    using System.Web.UI;
    using Ext.Net;
    
    namespace Work
    {
        public class ButtonWithCustomWindow : Ext.Net.Button
        {
            private CustomWindow _customWindow;
    
            public override string InstanceOf { get { return "MyApp.ButtonWithCustomWindow"; } }
            public override string XType { get { return "buttonwithcustomwindow"; } }
    
            [NotifyParentProperty(true)]
            [PersistenceMode(PersistenceMode.InnerProperty)]
            public virtual CustomWindow CustomWindow
            {
                get 
                {
                    return _customWindow ?? (_customWindow = new CustomWindow()); 
                }
                set
                {
                    _customWindow = value;
                }
            }
    
            protected override void OnInit(System.EventArgs e)
            {
                Cls += " custom-button";
    
                if (this.CustomWindow != null)
                {
                    this.CustomWindow.Hidden = true;
                    this.Bin.Add(CustomWindow);
                }
    
                base.OnInit(e);
            }
        }
    }
    Example usage
    <ext:ButtonWithCustomWindow runat="server" Text="Show custom window">
        <CustomWindow runat="server" Title="Markup Title" />
    </ext:ButtonWithCustomWindow>
    Also you will need to adjust JavaScript. For example, the Button's initComponent should look:
    initComponent: function () {
        MyApp.ButtonWithCustomWindow.superclass.initComponent.call(this);
        this.customWindowInstance = this.bin; // if there are two or more components then "bin" is an array 
    }
  9. #9
    I see what you mean. That is interesting... that could work too...

    Lots of options and possibilities so that is good to know. Thanks!

Similar Threads

  1. Replies: 0
    Last Post: Oct 30, 2012, 9:25 AM
  2. [CLOSED] How to add Guid[] property to the component?
    By pil0t in forum 1.x Legacy Premium Help
    Replies: 3
    Last Post: Apr 22, 2011, 2:23 PM
  3. Add Ext JavaScript Component to Component
    By geoffrey.mcgill in forum Examples and Extras
    Replies: 3
    Last Post: Mar 10, 2010, 12:38 PM
  4. Replies: 1
    Last Post: May 22, 2009, 7:38 AM
  5. Replies: 3
    Last Post: Jul 29, 2008, 6:31 PM

Posting Permissions