ASP.NET Callback Panel
Continuing with my quest for reusable, no dependencies, Web Forms AJAX controls, this time I wanted a replacement for the venerable UpdatePanel control. Specifically, I wanted to address the following issues:
-
Allow the partial update of a region on my page, including all of its controls;
-
Being able to cause an update through JavaScript (tricky with UpdatePanel);
-
Have the ability to only send data for controls living inside my control, not everything else on the page (read, __VIEWSTATE);
-
Support JavaScript events at the beginning/end of a callback or in the occurrence of an error (yes, I know about PageRequestManager, but I wanted something different).
I ended up with a CallbackPanel control, which is what I am going to talk about. Here is its declaration:
1: <web:CallbackPanel runat="server" ID="callback" SendAllData="false" OnBeforeCallback="onBeforeCallback" OnAfterCallback="onAfterCallback" OnCallbackError="onCallbackError" OnCallback="OnCallback">
2: <!-- some controls -->
3: <asp:Label runat="server" ID="time"/>
4: <asp:TextBox runat="server" ID="text"/>
5: <asp:Button runat="server" ID="button" Text="Button"/>
6: </web:CallbackPanel>
The CallbackPanel control supports some properties:
-
SendAllData: if all the data in the form should be sent, including viewstate, or just the data for the controls inside the CallbackPanel (default is false);
-
OnAfterCallback: the name of a JavaScript function to be called after the callback terminates;OnBeforeCallback: the optional name of a JavaScript function that gets called before a callback; if we so want, we can return false on this function to cancel the callback;
-
OnCallbackError: the name of a JavaScript function that is called in the event of an error.
Some examples of the JavaScript functions:
1: <script type="text/javascript">
1:
2:
3: function onCallbackError(error, context)
4: {
5: }
6:
7: function onBeforeCallback(arg, context)
8: {
9: //return false to cancel
10: }
11:
12: function onAfterCallback(result, context)
13: {
14: }
15:
</script>
For causing an update, we call its callback function, passing it a parameter and an optional context:
1: document.getElementById('callback').callback('test', null);
The most important property in CallbackPanel is the server-side event, OnCallback: this is raised whenever the callback function is called:
1: protected void OnCallback(Object sender, CallbackEventArgs e)
2: {
3: this.time.Text = e.Parameter + ": " + DateTime.Now.ToString();
4: }
This event receives a CallbackEventArgs argument, which is nothing more than:
1: [Serializable]
2: public sealed class CallbackEventArgs : EventArgs
3: {
4: public CallbackEventArgs(String parameter)
5: {
6: this.Parameter = parameter;
7: }
8:
9: public String Parameter { get; private set; }
10: }
And finally, the code for the CallbackPanel itself:
1: public class CallbackPanel : Panel, INamingContainer, ICallbackEventHandler
2: {
3: public CallbackPanel()
4: {
5: this.OnAfterCallback = String.Empty;
6: this.OnBeforeCallback = String.Empty;
7: this.OnCallbackError = String.Empty;
8: this.SendAllData = true;
9: }
10:
11: public event EventHandler<CallbackEventArgs> Callback;
12:
13: [DefaultValue("")]
14: public String OnBeforeCallback { get; set; }
15:
16: [DefaultValue("")]
17: public String OnAfterCallback { get; set; }
18:
19: [DefaultValue("")]
20: public String OnCallbackError { get; set; }
21:
22: [DefaultValue(true)]
23: public Boolean SendAllData { get; set; }
24:
25: protected override void OnInit(EventArgs e)
26: {
27: var sm = ScriptManager.GetCurrent(this.Page);
28: var reference = this.Page.ClientScript.GetCallbackEventReference(this, "arg", String.Format("function(result, context){{ document.getElementById('{0}').innerHTML = result; {1} }}", this.ClientID, (String.IsNullOrWhiteSpace(this.OnAfterCallback) == false) ? String.Concat(this.OnAfterCallback, "(result, context);") : String.Empty), "context", String.Format("function(error, context){{ {0} }}", ((String.IsNullOrWhiteSpace(this.OnCallbackError) == false) ? String.Concat(this.OnCallbackError, "(error, context)") : String.Empty)), true);
29: var script = String.Concat("\ndocument.getElementById('", this.ClientID, "').callback = function(arg, context){", ((this.SendAllData == true) ? "__theFormPostCollection.length = 0; __theFormPostData = ''; WebForm_InitCallback(); " : "__theFormPostCollection.length = 0; __theFormPostData = ''; WebForm_InitCallback(); for (var i = 0; i < __theFormPostCollection.length; ++i) { if (__theFormPostCollection[i].name.indexOf('" + this.UniqueID + "$') == -1) { __theFormPostCollection[i].value = '' } }; "), (String.IsNullOrWhiteSpace(this.OnBeforeCallback) == true ? String.Empty : String.Concat("if (", this.OnBeforeCallback, "(arg, context) === false) return; ")), reference, ";};\n");
30:
31: if (sm != null)
32: {
33: this.Page.ClientScript.RegisterStartupScript(this.GetType(), String.Concat("callback", this.ClientID), String.Format("Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function() {{ {0} }});\n", script), true);
34: }
35: else
36: {
37: this.Page.ClientScript.RegisterStartupScript(this.GetType(), String.Concat("callback", this.ClientID), script, true);
38: }
39:
40: base.OnInit(e);
41: }
42:
43: protected virtual void OnCallback(CallbackEventArgs e)
44: {
45: var handler = this.Callback;
46:
47: if (handler != null)
48: {
49: handler(this, e);
50: }
51: }
52:
53: #region ICallbackEventHandler Members
54: String ICallbackEventHandler.GetCallbackResult()
55: {
56: var builder = new StringBuilder();
57:
58: using (var writer = new StringWriter(builder))
59: using (var htmlWriter = new HtmlTextWriter(writer))
60: {
61: this.Render(new HtmlTextWriter(writer));
62:
63: return (builder.ToString());
64: }
65: }
66:
67: void ICallbackEventHandler.RaiseCallbackEvent(String eventArgument)
68: {
69: this.OnCallback(new CallbackEventArgs(eventArgument));
70: }
71: #endregion
72: }
Again, it is implementing ICallbackEventHandler, for client callbacks, but this time it is inheriting from Panel, which is a nice container for other controls. The rest should be self-explanatory, I guess. If you have questions, do send them to me!
As always, hope you like it!