PDA

View Full Version : [OPEN] [#403] UserControlRendererConfig does not render some controls



michaeld
Dec 02, 2013, 1:07 AM
..........

michaeld
Dec 02, 2013, 3:01 AM
Is there a way to get the reference to the UserControl rendered from UserControlRendererConfig? I attempted to get it from the ComponentAddedEventArgs.Control but it isn't the same. I use the actual UserControl's ClientID to bind objects.

michaeld
Dec 02, 2013, 3:54 AM
public static void AddDirectControl( this ComponentBase ctnr, string ctlPath, string ctlName, BindFn bindFn = null ) {
UserControlRendererConfig cfg = new UserControlRendererConfig {
UserControlPath = ctlPath,
UserControlId = ctlName,
UserControlClientIDMode = System.Web.UI.ClientIDMode.Static,
Mode = RenderMode.AddTo,
Element = ctnr.ClientID
};
if( bindFn!=null ) {
cfg.BeforeRender += delegate( ComponentAddedEventArgs eCmp ) {
bindFn( ((Container)eCmp.Control).ContentControls[0] );
};
}
X.DirectRendering = true;
UserControlRenderer.Render( cfg );
X.DirectRendering = false;
}

Daniil
Dec 02, 2013, 4:36 AM
Hi @michaeld,

It will work only if:

if (config.ControlIdToRender.IsEmpty() && !config.SingleControl)
where "config" - a UserControlRendererConfig instance.

It is an excerpt from the UserControlRenderer's Build method (in UserControlScriptBuilder.cs).

Please note that in your case even if

Mode = RenderMode.AddTo
an intermediary Container is created and the user control's content is rendered to that Container as Content (not as Items). Then that Container is rendered to the Element using RenderMode.AddTo.

UserControlRenderer doesn't extract Ext.NET components from a user control, as UserControlLoader does.

Maybe, it is better to use a UserControlLoader instead of UserControlRenderer? There is the UserControlAdded event in UserControlLoader.

Also, we are considering a possibility to pass a user control into a UserControlRenderer's BeforeRender handler.

michaeld
Dec 02, 2013, 12:09 PM
It will work only if:

if (config.ControlIdToRender.IsEmpty() && !config.SingleControl)
where "config" - a UserControlRendererConfig instance.

I found that if you set SingleControl on a UserControl, it ONLY renders the first item. This was actually the issue I was having that originated this thread. I figured that out and couldn't understand under any condition that might be appropriate to just render the first item in a UserControl (unless you know for sure it's just a single item). Honestly, I don't know where this class is documented well enough to understand all all the variations and how it can be properly used. I've come up with an extension above that ensures, at least so far, the user control is the only part that's rerendered without its parent as well. It handles autoloaders as well.



It is an excerpt from the UserControlRenderer's Build method (in UserControlScriptBuilder.cs).

Please note that in your case even if

Mode = RenderMode.AddTo
an intermediary Container is created and the user control's content is rendered to that Container as Content (not as Items). Then that Container is rendered to the Element using RenderMode.AddTo.

UserControlRenderer doesn't extract Ext.NET components from a user control, as UserControlLoader does.

Maybe, it is better to use a UserControlLoader instead of UserControlRenderer? There is the UserControlAdded event in UserControlLoader.

I'm not sure I'm following exactly how your posing a better solution. Please post a rewrite of a better implementation of the extension method that handles items. I just need one reliable way for content and one for items that only renders the user control and adds it to a parent.

Daniil
Dec 10, 2013, 1:19 PM
Re: SingleControl

By default, UserControlRenderer creates an intermediary Container and put a user control to its ContentControls.

Let's consider an example.

Page

<%@ Page Language="C#" %>

<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>

<script runat="server">
protected void RenderUserControl(object sender, DirectEventArgs e)
{
UserControlRenderer.Render(new UserControlRendererConfig()
{
Element = this.Panel1.ClientID,
UserControlPath = "TestUC.ascx",
Mode = RenderMode.AddTo
});
}
</script>

<!DOCTYPE html>
<html>
<head runat="server">
<title>Ext.NET v2 Example</title>
</head>
<body>
<form runat="server">
<ext:ResourceManager runat="server" />

<ext:Button runat="server" Text="Render User Control" OnDirectClick="RenderUserControl" />

<ext:Panel
ID="Panel1"
runat="server"
Title="Panel"
Height="200"
Layout="FitLayout" />
</form>
</body>
</html>


User Control

<%@ Control Language="C#" %>

<ext:Container runat="server" Html="User Control" StyleSpec="background-color: yellow;" />

If you run it, you will see that the Panel1's FitLayout doesn't affect the Container inside the user control. It is because there is an intermediary Container between them. So, the Panel1's FitLayout affects that intermediary Container which has no layout itself.

A developer can set up SingleControl="true" (or ControlIdToRender) to avoid that intermediary Container. In other words, a developer can define an intermediary Container himself if there are several controls to render, not just the only one.

But an intermediary Container is required in a general case, because a user control can look like this one:

User Control

<%@ Control Language="C#" %>

<script>
alert("Hello!");
</script>

<asp:Panel runat="server">
ASP.NET Panel
</asp:Panel>

<ext:Container runat="server" Html="User Control" StyleSpec="background-color: yellow;" />

Generally speaking, the SingleControl option is quite out-of-date, becase now there is this setting.

Items = true

With this setting the UserControlRender will extract all the Ext.NET controls (top level) from the user control and render it without an intermediary Container.

Though, we keep SingleControl to avoid making a breaking change just removing that.

Re: UserControlRenderer vs UserControlLoader

I said that the UserControlRenderer cannot retrieve Ext.NET controls from a user control, but I just forgot about the "Items = true" option. So, it is not the benefit of UserControlLoader and you can use UserControlRenderer reliably.

Re: getting the user control in the BeforeRender handler


void cfg_BeforeRender(ComponentAddedEventArgs args)
{
UserControl uc = ((args.Control as Container).ContentControls[0]) as UserControl;
}


Honestly, I don't know where this class is documented well enough to understand all all the variations and how it can be properly used.

The lack of good docs is one of our main problems. We apologize for the inconveniece. Hopefully, my explanation helps a bit.

michaeld
Dec 11, 2013, 10:47 AM
If I could use UserControlLoader with a render and find a way to add it to a parent, that would be ideal? If this is possible, please let me know with an example. Or are you saying the better way is to set items=true on a UserControlRenderer to render just the control and it will attach to items to become part of the parent layout? Again, the goal is to only render the new control and attach it to the parent in a DirectEvent without rerendering the parent too (which clobbers the parent's other control values).

As for the lack of documentation, hopefully this detail you supply will help others too now or in the future. What vladimir did in the other thread was pretty awesome showing variations and examples.

michaeld
Dec 12, 2013, 10:32 AM
So I'm finding this model doesn't work either if the user control being loaded has its own DirectEvents.

Vladimir
Dec 12, 2013, 12:30 PM
So I'm finding this model doesn't work either if the user control being loaded has its own DirectEvents.

It is true for any dynamic ASP.NET control which has event handler and which is not recreated on next requests (for example, ASP.NET button with Click event handler)

michaeld
Dec 12, 2013, 11:43 PM
It is true for any dynamic ASP.NET control which has event handler and which is not recreated on next requests (for example, ASP.NET button with Click event handler)

I do reload it on postback.
aspx


<%@ Page Language="C#" EnableViewState="false" %>


<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>


<script runat="server">
protected void Page_Load(object sender, EventArgs e) {
if( X.IsAjaxRequest && ucLoaded.Text == "1" ) {
var uc = Page.LoadControl( "Test39.ascx" );
uc.ID = "uc1";
uc.ClientIDMode = System.Web.UI.ClientIDMode.Predictable;
Form.Controls.Add(uc);
}
}
protected void CreateTest( object sender, DirectEventArgs e ) {
UserControlRendererConfig cfg = new UserControlRendererConfig {
UserControlPath = "Test39.ascx",
UserControlId = "uc1",
UserControlClientIDMode = System.Web.UI.ClientIDMode.Predictable,
Mode = RenderMode.AddTo,
Element = RP.ClientID
};
UserControlRenderer.Render( cfg );
ucLoaded.Text = "1";
}
</script>


<!DOCTYPE html>
<html>
<head id="Head1" runat="server">
<title>Ext.NET Example</title>
</head>
<body>
<form id="Form1" runat="server">


<ext:ResourceManager ID="ResourceManager1" runat="server" ScriptMode="Development" SourceFormatting="true" />
<ext:Viewport ID="vp" runat="server" Layout="HBoxLayout">
<Items>


<ext:Container ID="LP" runat="server" Border="true" Padding="5" Flex="1" Layout="FitLayout">
<Items>
<ext:Button runat="server" Text="Test Right Panel Load">
<DirectEvents>
<Click OnEvent="CreateTest" />
</DirectEvents>
</ext:Button>
<ext:Hidden ID="ucLoaded" runat="server" Text="0" />
</Items>
</ext:Container>


<ext:Container ID="RP" runat="server" Flex="1" Layout="VBoxLayout">
<Items>
</Items>
</ext:Container>


</Items>
</ext:Viewport>
</form>
</body>
</html>



aspx


<%@ Control Language="C#" ClassName="Test39" %>


<script runat="server">
protected void Page_Load(object sender, EventArgs e) {
}
protected void MsgTest( object sender, DirectEventArgs e ) {
HeadL.InnerHtml = "This text was changed";
Button1.Text = "Proves button changed in directEvent";
ASPButton.Text = "Changed aspbutton";
Button1.Listeners.Render.Handler = "alert('Test');";
Button1.Render();
}
</script>


<ext:Panel ID="EventP" runat="server" Flex="1" Padding="5" Title="TestRightPanel">
<Content>


<div class="TitlePnl">
<h1 id="TitleL" runat="server" class="Title">Test Title</h1>
<div class="HeadPnl">
<span id="HeadL" runat="server" class="Head">Test Span</span>
</div>
<asp:Button ID="ASPButton" runat="server" Text="AspButton" />
</div>


</Content>
<Items>
<ext:Button ID="Button1" runat="server" Text="Test DirectEvent in Loaded Control" IDMode="Static">
<DirectEvents>
<Click OnEvent="MsgTest" />
</DirectEvents>
<Listeners>
<AfterRender Handler="" />
</Listeners>
</ext:Button>
</Items>
</ext:Panel>


This renders the following:


{script:"Ext.net.append(Ext.getBody(),[\"<div id=\\\"uc1_ct_Content\\\" class=\\\"x-hidden\\\"><div id=\\\"App.EventP_Container\\\"><div id=\\\"uc1_EventP_Content\\\" class=\\\"x-hidden\\\">\",\"<div class=\\\"TitlePnl\\\">\",\"<h1 id=\\\"uc1_TitleL\\\" class=\\\"Title\\\">Test Title</h1>\",\"<div class=\\\"HeadPnl\\\">\",\"<span id=\\\"uc1_HeadL\\\" class=\\\"Head\\\">Test Span</span>\",\"</div>\",\"<input type=\\\"submit\\\" name=\\\"uc1$ASPButton\\\" value=\\\"AspButton\\\" id=\\\"uc1_ASPButton\\\" />\",\"</div>\",\"</div></div>\",\"</div>\"].join(''));Ext.net.ResourceMgr.destroyCmp(\"App.uc1_ct\");Ext.create(\"Ext.panel.Panel\",{id:\"EventP\",padding:5,renderTo:\"App.EventP_Container\",flex:1,contentEl:\"uc1_EventP_Content\",items:[{id:\"Button1\",xtype:\"button\",text:\"Test DirectEvent in Loaded Control\",directEvents:{click:{fn:function(item,e){Ext.net. directRequest({control:this});}}}}],title:\"TestRightPanel\"});App.RP.add({id:\"uc1_ct\",xtype:\"container\",contentEl:\"uc1_ct_Content\"});App.ucLoaded.setValue(\"1\");"}


The issue is that despite all IDModes set to Predictable, EventP.IDMode=Static is not rendered with the Predictable naming format. I can get this whole thing working if I set modes to Static and set EventP to Static but I shouldn't have to do that. I think there is a bug.

michaeld
Dec 13, 2013, 10:21 AM
So I stepped through the code and the IDMode is correct when the controls are in UserControlScriptBuilder.Build() when it loads uc, but they change to IDMode.Static when it retrieves childControls = this.FindControls() in DefaultScriptBuilder.Build(). Inside FindControls() it calls Control.EnsureChildControls() which is causing the loss of IDMode. This calls LifeCycle.CreateClientControls. IDMode changes immediately after this:
this.Controls.Add(((IContent)this).ContentContaine r). this, in this case is the ContentContainer and it was explicitly set to IDMode.Static at the top in UserControlScriptBuilder.Build() [why I do not know). I'm wondering if the IDMode is cascading to the children, but I cannot step through further. At this point I'm hoping this can clue you in on the issue.

michaeld
Dec 16, 2013, 1:16 PM
So I'm completely dead in the water until this item gets looked into.

Daniil
Dec 16, 2013, 4:35 PM
Sorry for the delay.

There are a few points.

1. The EventP's IDMode has been switching to Static, because the UserControlRenderer creates an intermediary Panel with IDMode="Static" and the EventP inherits that (IDMode is Inherit by default). It is why you are seeing that switch to Static. That hardcoded IDMode="Static" for an intermediary Panel and no way to change it is definitely a problem. We created an Issue and will try to address it. Thank you for bringing that problem to us.
https://github.com/extnet/Ext.NET/issues/403

2. Currently, to fix your test case you can set up IDMode="Legacy" or IDMode="Explicit" for the EventP.

3. Also it is worth to use "Items = true" for the UserControlRendererConfig in your sample. In this case an intermediary Panel won't be rendered, and the LP's VBoxLayout will manage the EventP. Otherwise, it will manage the intermediary Panel.

michaeld
Dec 17, 2013, 4:18 AM
1. The EventP's IDMode has been switching to Static, because the UserControlRenderer creates an intermediary Panel with IDMode="Static" and the EventP inherits that (IDMode is Inherit by default). It is why you are seeing that switch to Static. That hardcoded IDMode="Static" for an intermediary Panel and no way to change it is definitely a problem. We created an Issue and will try to address it. Thank you for bringing that problem to us.
https://github.com/extnet/Ext.NET/issues/403

As soon as you've got a fix for this, I can share some work I've taken on a stab at this:
http://forums.ext.net/showthread.php?27574-UserControl-Auto-Loader-amp-Manager


2. Currently, to fix your test case you can set up IDMode="Legacy" or IDMode="Explicit" for the EventP.

I understand and I tried, but the problem identified here makes this solution prohibitive: http://forums.ext.net/showthread.php?27573-UserControlRendererConfig-RenderModes
Also, the test case in that example there does not have a parent container that I can do this.


3. Also it is worth to use "Items = true" for the UserControlRendererConfig in your sample. In this case an intermediary Panel won't be rendered, and the LP's VBoxLayout will manage the EventP. Otherwise, it will manage the intermediary Panel.
It's actually very strange, but in the last sample I provided, I've found the exact opposite to be true. If Items=true, EventP doesn't expand to the length of the right panel despite having Flex=1. But with Items=false, it does. Maybe you can explain why that is because I can't.

Daniil
Dec 18, 2013, 4:31 AM
As soon as you've got a fix for this, I can share some work I've taken on a stab at this:
http://forums.ext.net/showthread.php?27574-UserControl-Auto-Loader-amp-Manager


Nice! I have seen the thread already, but have not had a good chance to read thoroughly




2. Currently, to fix your test case you can set up IDMode="Legacy" or IDMode="Explicit" for the EventP.
I understand and I tried, but the problem identified here makes this solution prohibitive: http://forums.ext.net/showthread.php?27573-UserControlRendererConfig-RenderModes
Also, the test case in that example there does not have a parent container that I can do this.


Could you, please, clarify why?



It's actually very strange, but in the last sample I provided, I've found the exact opposite to be true. If Items=true, EventP doesn't expand to the length of the right panel despite having Flex=1. But with Items=false, it does. Maybe you can explain why that is because I can't.

VBoxLayout uses Flex of its items to calculate its height, not width. To get the EventP stretched you should set up

<LayoutConfig>
<ext:VBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
for the "RP" Container.

I am not quite sure why it stretches without "Items = true", but I would not rely on that.

michaeld
Dec 18, 2013, 3:13 PM
The fruits of this thread and others can be found here:
http://forums.ext.net/showthread.php?27574-UserControl-Auto-Loader-amp-Manager&p=122875#post122875