Build a Simple Control to Refresh UpdatePanel from Client Side
(It's the post I wrote one year ago in my previous blog which has been cancelled now.)
Daron Yondem provided a solution to refresh UpdatePanels from client side. He built an Extender control (with AjaxControlTookit) and use an invisible textbox as an trigger to raise an async postback. The extender model in ASP.NET AJAX is an light-weight solution to build extensions for existing controls, but these extenders based on AjaxControlToolkit is realy heavy. Using a single extender control build upon AjaxControlToolkit will cause all the base scripts to load on the page. It's probably not a good idea to use such a heavy model and some simple solution is just enough for some simple objectives.
All we should do for the current feature is just to generate JavaScript proxies in the page. Actually I've also build an simple control to help the devs to raise async postbacks from client side, encapsulated the way described in this post. It can be used as following:
<ajaxExt:JavaScriptUpdater ID="Updater1" runat="server" MethodName="Refresh">
<TriggeredPanels>
<ajaxExt:TriggeredPanel UpdatePanelID="up1" />
<!-- other UpdatePanels need to be updated -->
</TriggeredPanels>
</ajaxExt:JavaScriptUpdater>
The MethodName property in this control indicates the name of the proxy method generated in the client. Each control will be provided a collection of references to the UpdatePanels on the page. And now, devs can raise an async postback to refresh those UpdatePanels by executing the following method:
UpdatePanels.Refresh();
First of all, we should define a class as the element for the collection of UpdatePanel would be triggered. It can't be easier:
public class TriggeredPanel
{
public string UpdatePanelID { get; set; }
}
Of course we'll expose the collection in the control:
[PersistChildren(false)]
[ParseChildren(true)]
[NonVisualControl]
public class JavaScriptUpdater : Control
{
...
private List<TriggeredPanel> m_triggeredPanels = new List<TriggeredPanel>();
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<TriggeredPanel> TriggeredPanels
{
get
{
return m_triggeredPanels;
}
}
...
}
Please note that the MethodName can only be modified during initialize period. It's the way which has been widely used in ASP.NET AJAX to keep the consistency of an control's settings in the lifecycle. Please see the following code:
private bool m_pageInitialized = false;
private string m_methodName;
public string MethodName
{
get
{
return this.m_methodName;
}
set
{
if (this.m_initialized)
{
throw new InvalidOperationException("...");
}
this.m_methodName = value;
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.Page.InitComplete += (sender, ev) =>
{
this.m_pageInitialized = true;
};
this.Page.Load += new EventHandler(OnPageLoad);
if (!ScriptManager.GetCurrent(this.Page).IsInAsyncPostBack)
{
this.Page.PreRenderComplete += new EventHandler(OnPagePreRenderComplete);
}
}
Each helper control on the page will put a invisible button on the page as the trigger. The following code will dynamically generate an LinkButton control on the page and handle its Click event:
private string m_triggerUniqueId = null;
private void OnPageLoad(object sender, EventArgs e)
{
LinkButton button = new LinkButton();
button.Text = "Update";
button.ID = this.ID + "_Trigger";
button.Style[HtmlTextWriterStyle.Display] = "none";
button.Click += new EventHandler(OnTriggerClick);
this.Page.Form.Controls.Add(button);
this.m_triggerUniqueId = button.UniqueID;
ScriptManager.GetCurrent(this.Page).RegisterAsyncPostBackControl(button);
}
Before page's rendering, we should register the proxy to the page:
private static readonly string BaseScripts =
@"if (!window.UpdatePanels) window.UpdatePanels = {};
UpdatePanels.__createUpdateMethod = function(triggerUniqueId)
{
return function()
{
__doPostBack(triggerUniqueId, '');
}
}";
private const string RegisterMethodTemplate =
"\nUpdatePanels['{0}'] = UpdatePanels.__createUpdateMethod('{1}');";
private void OnPagePreRenderComplete(object sender, EventArgs e)
{
this.Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
"BasicScripts",
JavaScriptUpdater.BaseScripts,
true);
this.Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
this.m_triggerUniqueId,
String.Format(
JavaScriptUpdater.RegisterMethodTemplate,
this.MethodName,
this.m_triggerUniqueId),
true);
}
When the page received an async postback raised by the proxy, the Click event of the dynamic button would be fired. As you can see later, I defined three events in the contorl. The ResolveTriggeredPanel event is to help the control to find the specified UpdatePanels. In addition, the other two events, Updating and Updated will be fired before and after refreshing the UpdatePanels:
public event EventHandler<ResolveTriggeredPanelEventArgs> ResolveTriggeredPanel;
public event EventHandler Updating;
public event EventHandler Updated;
private void OnTriggerClick(object sender, EventArgs e)
{
if (this.Updating != null)
{
this.Updating(this, EventArgs.Empty);
}
foreach (TriggeredPanel panel in this.TriggeredPanels)
{
UpdatePanel updatePanel = this.FindTriggeredPanel(panel.UpdatePanelID);
if (updatePanel != null && updatePanel.UpdateMode != UpdatePanelUpdateMode.Always)
{
updatePanel.Update();
}
}
if (this.Updated != null)
{
this.Updated(this, EventArgs.Empty);
}
}
private UpdatePanel FindTriggeredPanel(string id)
{
UpdatePanel triggeredPanel = null;
if (id != null)
{
triggeredPanel = this.NamingContainer.FindControl(id) as UpdatePanel;
}
if (triggeredPanel == null)
{
ResolveTriggeredPanelEventArgs e = new ResolveTriggeredPanelEventArgs(id);
if (this.ResolveTriggeredPanel != null)
{
this.ResolveTriggeredPanel(this, e);
}
triggeredPanel = e.TriggeredPanel;
}
return triggeredPanel;
}
Oh, I've missed the ResolveUpdatePanelEventArgs class. Here it is:
public class ResolveTriggeredPanelEventArgs : EventArgs
{
public string ID { get; private set; }
public UpdatePanel TriggeredPanel { get; set; }
public ResolveTriggeredPanelEventArgs(string id)
{
this.ID = id;
}
}
Here we have finished the control. Let's see a simple demo of it. The following is the declarative code in aspx file:
<asp:UpdatePanel runat="server" ID="up1">
<ContentTemplate>
<%= DateTime.Now.ToString() %>
</ContentTemplate>
</asp:UpdatePanel>
<ajaxExt:JavaScriptUpdater ID="Updater1" runat="server" MethodName="Refresh">
<TriggeredPanels>
<ajaxExt:TriggeredPanel UpdatePanelID="up1" />
</TriggeredPanels>
</ajaxExt:JavaScriptUpdater>
<input type="button" onclick="UpdatePanels.Refresh()" value="Refresh" />
When we click the button, the UpdatePanel will be refresh without updating the whole page. Let's see the proxy code generated in client side:
<script type="text/javascript">
if (!window.UpdatePanels) window.UpdatePanels = {};
UpdatePanels._createUpdateMethod = function(triggerUniqueId)
{
return function()
{
$get(triggerUniqueId).click();
}
}
UpdatePanels['Refresh'] = UpdatePanels._createUpdateMethod('Updater1_Trigger');
</script>
...
<a id="UpdaterButton" href="javascript:__doPostBack('UpdaterButton','')"
style="display:none;">Update</a>
The attachment is the source code of the JavaScriptUpdater control.