1. Introduction
This article presents the construction of a templated ASP.NET control, working as a PleaseWait button. Using C# with ASP.NET 2.0, you will see how to create a templated control and add specific functionality.
Some times, you need to perform some time consuming tasks during, say, a button click event, when submitting your page. Some times, the form's validation can request some call to a database, or a remote call, Web Services ... To inform the user that the server is doing something time consuming, it's a good idea to display a waiting message.
I've seen some articles that re-create a button with added properties, that display a message. But the presentation never suits to me. So I decided to create a complete one, customizable with a template functionality.
2. The result to obtain
What I want to have is something like this in my ASP.NET page:
<Test:PleaseWaitButton ID="PWB" runat="server">
<PleaseWaitMessageTemplate>
<div style="width:500px;color:green;border:1px solid red;text-align:center">
<div>Please wait a moment, </div>
<div>we are checking your informations ...</div>
<asp:Image runat="server" ImageUrl="wait.gif" />
</div>
</PleaseWaitMessageTemplate>
</Test:PleaseWaitButton>
Then, we have the button, and the place to define the graphic visualization of the please wait message.
To look like a real button, we need to define an event handler on the button click. We want to be able to customize the text of the button, with the Text
property too.
In a form, usually we want to use ASP.NET validation. Then, the button needs to check this validation and define, if needed, a ValidationGroup
property.
<Test:PleaseWaitButton ID="PWB" runat="server"
OnClick="ClickButton" ValidationGroup="myGroup"
Text="Please Click">
<PleaseWaitMessageTemplate>
<div style="width:500px;color:green;border:1px solid red;text-align:center">
<div>Please wait a moment, </div>
<div>we are checking your informations ...</div>
<asp:Image runat="server" ImageUrl="wait.gif" />
</div>
</PleaseWaitMessageTemplate>
</Test:PleaseWaitButton>
3. Creating the template button
First, you have to inherit from Control
and implement INamingContainer
. You have to define the ParseChildren(true)
attribute too.
[System.Security.Permissions.PermissionSet(
System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
[ParseChildren(true)]
[DefaultProperty("Text")]
public class PleaseWaitButton : Control, INamingContainer
{
}
3.1. The ITemplate property
This is all you need: have a template property. PersistenceMode
is used to tell the parser that the property persists as an inner property. TemplateContainer
is unused here, it's for databinding typing.
private ITemplate _pleaseWaitMessageTemplate = null;
[Browsable(false), DefaultValue(null),
PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(TemplateItem))]
public ITemplate PleaseWaitMessageTemplate
{
get { return _pleaseWaitMessageTemplate; }
set { _pleaseWaitMessageTemplate = value; }
}
We need to define a TemplateItemp
class too.
[ToolboxItem(false)]
public class TemplateItem : Control, INamingContainer
{
}
4.2. Creation of the controls
We'll override the CreateChildControls
method to create a Panel
that will contain our control. In this Panel
, if the user defines a template, we instantiate it (as a TemplateItem
). If no template is defined, we create a default LiteralControl
with a default message.
And just after that, we add a button, with a Click
handler defined, and with the Text property set to our Text
property.
With CSS styles, we set the Panel
to display none. The button, outside the panel, is always visible.
protected override void CreateChildControls()
{
Controls.Clear();
Panel panelMessage = new Panel();
panelMessage.Attributes["style"] = "display:none";
if (PleaseWaitMessageTemplate != null)
{
TemplateItem templateItem = new TemplateItem();
PleaseWaitMessageTemplate.InstantiateIn(templateItem);
panelMessage.Controls.Add(templateItem);
}
else
{
panelMessage.Controls.Add(new LiteralControl("Plesae Wait ..."));
}
Button boutonValidation = new Button();
boutonValidation.Text = Text;
boutonValidation.Click += b_Click;
Controls.Add(panelMessage);
Controls.Add(boutonValidation);
}
4.3. Creation of the properties
As seen, we need to hold a ValidationGroup
property and a Text
property. If no text is defined, we use the default "OK" value. We define a Click
event too. To ensure control creation, let's override the Controls
property and call EnsureChildControls()
(that will call CreateChildControls
, if needed).
[Bindable(true), Category("Behavior"), Description("Validation group")]
public string ValidationGroup
{
get { return (string)ViewState["ValidationGroup"] ?? string.Empty; }
set { ViewState["ValidationGroup"] = value; }
}
[Bindable(true), Category("Appearance"),
DefaultValue("OK"), Description("Button's text")]
public string Text
{
get { return (string)ViewState["Text"] ?? "OK"; }
set { ViewState["Text"] = value; }
}
private event EventHandler _clickHandler;
public event EventHandler Click
{
add { _clickHandler += value; }
remove { _clickHandler -= value; }
}
public override ControlCollection Controls
{
get { EnsureChildControls(); return base.Controls; }
}
4.4. Validation
We have to ask for the validation of the page (with the Page.Validate
method). If a ValidationGroup
is defined, we ask for the validation of this group.
private void b_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(ValidationGroup))
Page.Validate(ValidationGroup);
else
Page.Validate();
if (_clickHandler != null)
_clickHandler(sender, e);
}
4.4. Client-side JavaScript
On the client-side, we have to do two things:
- Client validation
- And display/hide the please wait message
This is done with JavaScript. We will play with the display style of the panel (represented as a div
in HTML), if validation is OK
or NOK
.
So let's add some JavaScript in the rendering phase, to call a specific JavaScript function on the client button click event (see comments).
But first, we have to find the clientId
of the Panel
.
protected override void OnPreRender(EventArgs e)
{
Panel panelMessage = null;
foreach (Control control in Controls)
{
if (control is Panel)
{
control.ID = ID + "_waiting";
panelMessage = (Panel) control;
}
}
if (panelMessage != null)
{
foreach (Control control in Controls)
{
if (control is Button)
{
((Button) control).OnClientClick =
string.Format("checkForm('{0}')", panelMessage.ClientID);
}
}
}
base.OnPreRender(e);
}
On render, create the java script functions that will manage the display, depending on the result of the client validation (client validation is done with the Page_ClientValidate
method).
protected override void Render(HtmlTextWriter writer)
{
string validationGroupParameters = string.IsNullOrEmpty(ValidationGroup) ?
string.Empty : string.Format("'{0}'", ValidationGroup);
string script = @"function getObj(id)
{
var o;
if (document.getElementById)
{
o = document.getElementById(id).style;
}
else if (document.layers)
{
o = document.layers[id];
}
else if (document.all)
{
o = document.all[id].style;
}
return o;
}
function setDisplay(id)
{
var o = getObj(id);
if (o)
{
o.display = 'block';
}
}
function unsetDisplay(id)
{
var o = getObj(id);
if (o)
{
o.display = 'none';
}
}
function checkForm(divWaiting)
{
try
{
if (!Page_ClientValidate(" + validationGroupParameters + @"))
{
unsetDisplay(divWaiting);
return false;
}
}
catch (e) {}
setDisplay(divWaiting);
}";
Page.ClientScript.RegisterStartupScript(GetType(),
"javascriptButton", script, true);
base.Render(writer);
}
4. Create a default page that uses this button
4.1. With no validation
Just define the template control and the template property. Add if needed, a button click handler.
<Test:PleaseWaitButton ID="PWB" runat="server" OnClick="ClickButton">
<PleaseWaitMessageTemplate>
<div style="width:500px;color:green;border:1px solid red;text-align:center">
<div>Please wait a moment, </div>
<div>we are checking your informations ...</div>
<asp:Image runat="server" ImageUrl="wait.gif" />
</div>
</PleaseWaitMessageTemplate>
</Test:PleaseWaitButton>
In the code-behind, simulate the time consuming task to perform ...
protected void ClickButton(object sender, EventArgs e)
{
Thread.Sleep(2000);
}
4.2. With validation
4.2.1. Without validation group
Let's define a TextBox
and add client-side validation with a RequiredFieldValidator
control and server-side validation with a CustomValidator
control.
<asp:TextBox runat="server" ID="aTextBox" />
<asp:RequiredFieldValidator runat="server" ControlToValidate="aTextBox"
ErrorMessage="Field should have a value" Display="dynamic"/>
<asp:CustomValidator runat="server" OnServerValidate="ValidateFunction"
Display="dynamic" ErrorMessage="Value should be ABC" />
protected void ClickButton(object sender, EventArgs e)
{
if (Page.IsValid)
{
Thread.Sleep(2000);
Response.Write("<br/>Informations are OK<br/>");
}
}
protected void ValidateFunction(object source, ServerValidateEventArgs args)
{
args.IsValid = aTextBox.Text == "ABC";
}
4.2.2. With validation group
You can try with a validation group too, like this:
<asp:TextBox runat="server" ID="aTextBox" />
<asp:RequiredFieldValidator runat="server" ControlToValidate="aTextBox"
ErrorMessage="Field should have a value"
Display="dynamic" ValidationGroup="myGroup"/>
<asp:CustomValidator runat="server" OnServerValidate="ValidateFunction"
Display="dynamic" ErrorMessage="Value should be ABC"
ValidationGroup="myGroup"/>
<Test:PleaseWaitButton ID="PWB" runat="server"
OnClick="ClickButton" ValidationGroup="myGroup">
<PleaseWaitMessageTemplate>
<div style="width:500px;color:green;border:1px solid red;text-align:center">
<div>Please wait a moment, </div>
<div>we are checking your informations ...</div>
<asp:Image runat="server" ImageUrl="wait.gif" />
</div>
</PleaseWaitMessageTemplate>
</Test:PleaseWaitButton>
protected void ClickButton(object sender, EventArgs e)
{
if (Page.IsValid)
{
Thread.Sleep(2000);
Response.Write("<br/>Informations are OK<br/>");
}
}
protected void ValidateFunction(object source, ServerValidateEventArgs args)
{
args.IsValid = aTextBox.Text == "ABC";
}
5. Preview
6. Conclusion
I hope you'll find this template button useful and maybe learn how to create a template control in ASP.NET with C#. Validation is a critical point and is very useful in an ASP.NET page. Knowing how to take advantage of validation with the validation API can be very powerful too.
If you see an error in this article, in the source code, or for any other information, send me a comment.