2012/05/01

How to open RadWindow without RadWindowManager


I liberated my RadWindows from the yoke of the RadWindowManager. Now the question is how to open such free window.
Some parts are well described in Telerik documentation.

Client side
Function window.radopen is not usable for manager-less windows. RadWindow client side API should be employed instead:
$find('FreeWindow').show();

It is worth noting that the same API can be used for RadWindow placed inside RadWindowManager.

From code behind
The only one relevant opening tool on server-side is property VisibleOnPageLoad. When the property is true, then the window is automatically shown after the page is loaded on the client. VisibleOnPageLoad is ViewState property so it has to be reset to the false after OnInit phase otherwise the window will open at all sequent postbacks (partial and full).

Is there any other possibility?

From code behind via startup script
It is the same approach as on client side but the script is registered in code behind as a startup script. Startup script registration is quite basic topic but when it comes to dealing with GETs/postbacks and partial postbacks within the same application, things are getting a little bit tricky.

A universal way how to open a RadWindow from code behind is:
function openWindowOnPageLoad(windowId) {
    var fn = function () {
        var window = $find(windowId);
        window.show();

        Sys.Application.remove_load(fn);
    };

    Sys.Application.add_load(fn);
}

Code behind:
ScriptManager.RegisterStartupScript(this, this.GetType(), "openWindow", "openWindowOnPageLoad('FreeWindow');", true);

What an odd code! Complexity is the price for robustness. Reasons are explained in this blog post.

2012/04/24

Script registration labyrinth – startup scripts and $find (alternative)

I described a universal way how to register startup script that invokes $find javascript function. This solution is taken from AJAX Control Toolkit and looks like this:
(function () {
    var fn = function () {
        var control = $find('someControl');
        control.doSomething();
        Sys.Application.remove_load(fn);
    };

    Sys.Application.add_load(fn);
})();
There are other possibilities. I found a solution proposed on Telerik’s forum: use setTimeout javascript function to postpone the execution of $find until all controls are instantiated by $create functions.
The trick is used by Telerik ASP.NET Ajax controls, particularly for scripts registered into RadAjaxManager.ResponseScripts collection. If you try this fragment:
string script = string.Format("alert($find('{0}'));", Control1.ClientID);
AjaxManager1.ResponseScripts.Add(script);

It will result into this output:
setTimeout(function(){alert($find('Control1'));}, 0);

It works well for partial postbacks but not for full postbacks. No surprise there because RadAjaxManager is inherently tied to partial postbacks. It can be used for full postbacks as well anyway. It is a pitty because there is no unified way how to register startup scripts for both postback types in Telerik ASP.NET AJAX controls (at least in current version – 2012.1.411). You have to check the request type (GET/full postback vs. partial postback) and decide how to register the startup script.
It is so easy to fix this approach. Just wrap the setTimeout function to init event handler:
Sys.Application.add_init(function () {
    setTimeout(function () {
        alert($find('Control1'));
    }, 0);
});

Here is full markup and code-behind (Telerik ASP.NET AJAX controls are required).

2012/04/14

Script registration labyrinth – startup scripts and $find

You are an ASP.NET developer working for surrealistic corporation. You have an ASP.NET timer on your ASPX page and it should be disabled when a particular condition is met. This condition can be evaluated only on the server. There is Enabled property in Timer class that can be easily used to fulfill customer’s requirement but it is against corporate identity. You are expected to use client side API instead.

OK. You have a timer:
<asp:Timer runat="server" ID="BretonTimer" OnTick="BretonTimer_Tick"
    Interval="2000" />

To use client side API of ASP.NET AJAX controls $find method has to be utilized to find the control instance - it is an client-side control instance not DOM element. Timer’s client-side API is not well documented but there are some clues on the Internet. So let’s choose _stopTimer function for this purpose:
string script = string.Format("$find('{0}')._stopTimer();", BretonTimer.ClientID);

Create a button just to test this undocumented function:
<asp:Button runat="server" ID="StopBreton" Text="Stop!"
    OnClientClick="$find('BretonTimer')._stopTimer(); return false;" />

Click on the button to test that API works well.

Synchronous postback
So as the last step this script should be registered as a startup script and today’s work will be done:
string script = string.Format("$find('{0}')._stopTimer();", BretonTimer.ClientID);
ScriptManager.RegisterStartupScript(this, this.GetType(), "key1", script, true);

The timer is really off after page reload. There is a last annoyance, a javascript error: $find("BretonTimer") is null

Why $find returns an instance when the script is executed within click event handler and fails when the same script is run during page load? Let’s see how page source looks like.

Startup script is near the page end as expected:
$find('BretonTimer')._stopTimer();Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(Sys.UI._Timer, {"enabled":true,"interval":2000,"uniqueID":"BretonTimer"}, null, null, $get("BretonTimer"));

But what is on the next line? It seems like something what is intended to create client-side instance of the timer control. ASP.NET AJAX design does not follow idiosyncratic philosophy of your corporation, so it is not possible to find an instance of control that was not created yet.

Add_init attaches event handler to the client-side init event. It reminds server-side page lifecycle. And really there is the client-side load event as well. Load event handlers are executed after init handlers and it is exactly what you need (see Ajax Client Life-Cycle Events for more information):
string script = string.Format("Sys.Application.add_load(function() {{ $find('{0}')._stopTimer(); }});", BretonTimer.ClientID);
ScriptManager.RegisterStartupScript(this, this.GetType(), "key1", script, true);

It works! Timer is not running anymore and there is no javascript error.
But your sixth sense warns you that there is something wrong with this solution... What about partial postback?

Partial postback
To avoid reloads of whole page you are forced to use an UpdatePanel. You modify the markup in that way:
<asp:UpdatePanel runat="server" ID="UpdatePanel1">
    <ContentTemplate>
        <asp:Timer runat="server" ID="BretonTimer" OnTick="BretonTimer_Tick" Interval="2000" />
        <asp:Button runat="server" ID="StopBretonButton" Text="Stop!" OnClick="StopBretonButton_Click" />
        <asp:Button runat="server" ID="JustAnotherPostbackButton" Text="Just another postback" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="BretonTimer" />
        <asp:AsyncPostBackTrigger ControlID="StopBretonButton" />
    </Triggers>
</asp:UpdatePanel>

and code behind:
protected void StopBretonButton_Click(object sender, EventArgs e)
{
    string script = string.Format("Sys.Application.add_load(function() {{ $find('{0}')._stopTimer(); }});", BretonTimer.ClientID);
    ScriptManager.RegisterStartupScript(this, this.GetType(), "key1", script, true);
}

When “Stop!” button is clicked, timer is stopped properly. But there is a strange feeling in your guts. Something is wrong.
Add an alert message to make sure what is really happing:
string script = string.Format("Sys.Application.add_load(function() {{ alert('load event handler');$find('{0}')._stopTimer(); }});", BretonTimer.ClientID);

Alert message “load event handler” is displayed after pushing “Stop!” button. But when it is pushed for second time, two message boxes are displayed. Push “Just another postback” button and you will get two message boxes as well. It means load event handler remains attached to the event after partial postback.

For init events it would be a catastrophic behavior. They are used by ASP.NET AJAX framework heavily to run $create functions. When a new event handler would be attached to init event after every partial postback then the page would be unusable after a while because browser would spend a lot of time executing many init identical event handlers. It can be hardly true.

Let’s do an experiment:
string script = string.Format("Sys.Application.add_init(function() {{ alert('init event handler'); }});", BretonTimer.ClientID);

Only one message box is shown regardless how many times the button is clicked. So there is a fundamental difference between load and init events. Why?

It is difficult to find an answer (MSDN keep silent). If you find one, please let me know, I would love to hear any reason. It is pretty surreal and well suited to the corporate identity.

Load event solution is well crafted by AJAX Control Toolkit authors. The handler is unregistered after it is executed for the first time:
(function () {
    var fn = function () {
        $find('BretonTimer')._stopTimer();
        Sys.Application.remove_load(fn);
    };

    Sys.Application.add_load(fn);
})();

The beauty of this solution is in the fact that you don’t need to generate a unique function name for each event handler.

Here is the final markup and code behind.

Conclusion
The example with Timer control is quite absurd and may seem useless. But frameworks like AJAX Control Toolkit or Telerik are based on $create and $find functions and you have to register some startup scripts using $find function on and off. I chose the Timer control as something that is known to all users of those ASP.NET AJAX frameworks.
Registering startup script that uses $find function is not as straightforward as it seems for the first time. AJAX Control Toolkit solution is safe for synchronous and partial postbacks:
(function () {
    var fn = function () {
        var ajaxControl = $find('AJAX control');
        // do something usefull
    };

    Sys.Application.add_load(fn);
})();
It is based on client side page life cycle which is not very commonly known. It handles a little bit shocking load event behavior that is not documented on MSDN. It is good to know about these ASP.NET AJAX parts.

2012/03/11

Script registration labyrinth in ASP.NET WebForms


I'm always lost when trying to register a javascript fragment that must be executed on the client side after the postback. There are several usable methods in ASP.NET WebForms and I never know which one to choose.

Page.ClientScript
Page.ClientScript.RegisterStartupScript – registers the startup script. What does it mean “startup”? It is something that is executed when the browser finishes page loading, which is guaranteed by the fact that “startup” scripts are rendered just before closing <form> tag. Almost all elements on the page are processed by the browser at this point and can be referenced from that “startup” script.
Page.ClientScript.RegisterClientScriptBlock – registers the script block, so it is rendered somewhere near after the <form> element opening. This block is executed before the DOM is fully processed and before any “startup” scripts are executed. “ClientScript” blocks are good place for javascript functions.

Page
Page.RegisterStartupScript and Page.RegisterClientScriptBlock are marked as obsolete and they do nothing but Page.ClientScript call. Just ignore them.

ScriptManager
Now what about ScriptManager class? RegisterClientScriptBlock and RegisterStartupScript are there as well. What is the justification for such duplication? The only difference, according to the documentation (here and here), is that the scripts are “included every time that an asynchronous postback occurs”. This formulation is slightly confusing. Scripts registered by ScriptManager’s methods are rendered by non-asynchronous postback as well. ClientScript’s methods are never rendered by asynchronous-postbacks.

Script registration after asynchronous postback
The javascript block is not executed if it is a part of markup in AJAX response. Browser simply doesn’t process such block. Consider such silly ASPX:
<asp:Button runat="server" ID="Button1" Text="Do postback" OnClick="Button1_Click" />
<asp:Literal runat="server" ID="Literal1" />

and C# code behind with a script:
protected void Button1_Click(object sender, EventArgs e)
{
    Literal1.Text = "<script>alert('Button1 clicked')</script>";
}

Button1 causes postback (when clicked) and event handler fills Literal1 with the javascript block. This javascript block is execeuted and the alert message is shown when browser loads the (synchronous) postback response.

Let’s move to asynchronous postback:
<asp:UpdatePanel runat="server" ID="UpdatePanel1">
    <ContentTemplate>
        <asp:Button runat="server" ID="Button1" Text="Do AJAX call" OnClick="Button1_Click" />
        <asp:Literal runat="server" ID="Literal1" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="Button1" />
    </Triggers>
</asp:UpdatePanel>

Code behind remains unchanged.

No message is shown after the Button1 is clicked. Body of the response should look something like this:
139|updatePanel|UpdatePanel1|
        <input type="submit" name="Button1" value="Do AJAX call" id="Button1" />
        <script>alert('Button1 clicked')</script>
|152|hiddenField|__VIEWSTATE|/wEPDwUKLTUxODY2NjI0NQ9kFgICAw9kFgICAw9kFgJmD2QWAgI
DDxYCHgRUZXh0BSk8c2NyaXB0PmFsZXJ0KCdCdXR0b24xIGNsaWNrZWQnKTwvc2NyaXB0PmRk88Fv/gu
MXM8Xq/lT5WC+dG66354=|48|hiddenField|__EVENTVALIDATION|/wEWAgL/oouVBgKM54rGBmox+
1LEg7FJSnLOTrjRf5eL73gK|7|asyncPostBackControlIDs||Button1|0|postBackControlIDs|
||13|updatePanelIDs||tUpdatePanel1|0|childUpdatePanelIDs|||12|panelsToRefreshIDs|
|UpdatePanel1|2|asyncPostBackTimeout||90|13|formAction||WebForm8.aspx|

Alert script is rendered into markup but it is not executed by the browser. How is it possible that script registration via ScriptManager works?

This code behind modification could discover the secret:
protected void Button1_Click(object sender, EventArgs e)
{
    ScriptManager.RegisterStartupScript(this, this.GetType(), "script", "<script>alert('Button1 clicked')</script>", false);
}

The alert message box is shown this time. Here is the response:
98|updatePanel|UpdatePanel1|
        <input id="Button1" name="Button1" type="submit" value="Do AJAX call" />
    |52|hiddenField|__VIEWSTATE|/wEPDwUKLTUxODY2NjI0NWRkwPvseEH2ojph8EJh0MFS5L+TksA
=|48|hiddenField|__EVENTVALIDATION|/wEWAgKaoIz3DwKM54rGBr7RMEQMtuoxxWPUsRRwWxmrCH
/Z|7|asyncPostBackControlIDs||Button1|0|postBackControlIDs|||13|updatePanelIDs||t
UpdatePanel1|0|childUpdatePanelIDs|||12|panelsToRefreshIDs||UpdatePanel1|2|asyncP
ostBackTimeout||90|13|formAction||WebForm8.aspx|45|scriptStartupBlock|
ScriptContentWithTags|{"text":"alert(\u0027Button1 clicked\u0027)"}|

There are two significant changes in the response: the script is not rendered (unsurprisingly) in the Literal control and there is something more in the response: scriptStartupBlock.  ASP.NET AJAX library ensures that script from this “section” is executed after receiving the response. ScriptManager.RegisterClientScriptBlock is very similar but it uses scriptBlock “section”.

It is quite easy to navigate through the labyrinth when it is clear how things work under the hood.

2012/01/08

I apologize for my ignorance, dear RadWindow

I use RadWindow and RadWindowManager for a while and yet I know hardly anything about them. This StackOverflow question about RadWindow in ajax scenario has initiated manifestation of my ignorance. For me it was always clear that RadWindow must be declared inside RadWindowManager and there can be only one RadWindowManager instance on a page. Perverse consequences of this combination are obvious when you use WebForms – ASPX and ASCX entanglement through RadWindow instances. ASPX must contain RadWindowManager and RadWindow that is used on the ASCX as well. What a festival of ignorance!

First of all, what is RadWindowManager useful for? I managed to find 3 reasons from the documentation:
  1. It is a convenient way how to declare property values shared among several RadWindow instances.
  2. It adds radopen, radalert, radconfirm and radprompt functions to the global window obect.
  3. Window managment API (tile, cascade, etc...).
Documentation also says that it is possible to instantiate RadWindow without manager participation. And both ways are viable: either declaratively in markup or imperatively from code behind:

var window1 = new RadWindow();
window1.NavigateUrl = "http://www.google.com";
window1.VisibleOnPageLoad = true;
window1.ID = "RadWindow1";
window1.Width = 500;
window1.Title = "Google";
window1.Height = 300;
Panel1.Controls.Add(window1);


The last line is the most important one from my perspective. Window instance is added into normal asp Panel and no manager is involved.  The window is displayed even after partial postback when the panel is ajaxified. It is due to fact that RadWindow is control like any others and it is rendered in the same way as any other controls are. The same fact is valid in the case when the window is placed within RadWindowManager. RadWindow is rendered at the same place as the RadWindowManager. In ajax scenario you have to enclose the RadWindowManager into panel that is also updated in given partial postback. It sounds weird in connection with the fact that there can be only one RadWindowManager.

Fortunately this fact is not a fact but delusion. A page could easily contain more than one manager instance. In such case radopen and similar functions always use the first manager instance.

What about performance? Other managers like RadStyleSheetManager and RadScriptManager improve Telerik-based application performance. It seems to be not true for RadWindowManager according to this site.

What is the moral of this sad story? It is not worth to be afraid to work directly with RadWindow instances. As an application does not use tons of windows it would be better to use RadWindowManager only as support for radopen & Co. functions.