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
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.
$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.
(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.
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.