Click here to Skip to main content
15,879,474 members
Articles / Web Development / ASP.NET

PleaseWaitButton ASP.NET Server Control 2.0

Rate me:
Please Sign up or sign in to vote.
3.60/5 (4 votes)
18 Aug 2006CPOL5 min read 58.8K   551   22   21
Modified PleaseWaitButton (originaly written by Mike Ellison) for ASP.NET 2.0.

Introduction

The original version of the PleaseWaitButton control was written by Mike Ellison for ASP.NET 1.1 (thanks Mike): http://www.codeproject.com/KB/webforms/PleaseWaitButton.aspx.

I just addressed the JavaScript rendering in version 2.0.

It is often useful upon a form submission in a web application to display a "please wait" message, or an animated GIF image, particularly if the submission process lasts a few seconds or more. The best way of preventing the user from clicking on the button twice is to make sure the state of the button is changed upon initial click.

Using the control

As its descendent, the PleaseWaitButton functions just like a regular Button control. It exposes three additional properties to govern the display of the "Please Wait" message or image once the button is clicked.

  • PleaseWaitText: This is the client-side text message to display, if any, in place of the button when clicked.
  • PleaseWaitImage: This is the image file (typically an animated GIF) to display, if any, in place of the button when clicked. This property serves as the src attribute for the resulting <img> tag.
  • PleaseWaitType: One of the PleaseWaitTypeEnum values – TextOnly, ImageOnly, TextThenImage, or ImageThenText – which governs the layout of the message and/or image.

Here is an example .aspx file demonstrating a PleaseWaitButton with both PleaseWaitText and PleaseWaitImage set:

ASP.NET
<%@ Page language="C#" %>
<%@ Register TagPrefix="cc1" Namespace="JavaScriptControls" 
                             Assembly="PleaseWaitButton" %>

<script runat="server">
    private void PleaseWaitButton1_Click(object sender, System.EventArgs e)
    {
       // Server-side Click event handler; 

       // simulate something that could take a long time,
       // like a file upload or time-consuming server processing

       DateTime dt = DateTime.Now.AddSeconds(5);
       while (DateTime.Now < dt)
       {
         // do nothing; simulate a 5-second pause
       }

       // at the end of the loop display a success message
       // and hide the submit form 
       panelSuccess.Visible = true;
       PleaseWaitButton1.Visible = false;
    }
</script>

<html>
    <head>
        <title>Testing PleaseWaitButton</title>
    </head>
    <body>
        <form id="Form1" method="post" runat="server">

            <P>Testing the PleaseWaitButton control.</p>

            <cc1:PleaseWaitButton id="PleaseWaitButton1" runat="server" 
                     Text="Click me to start a time-consuming process"
                     PleaseWaitText="Please Wait... "
                     PleaseWaitImage="pleaseWait.gif" 
                     OnClick="PleaseWaitButton1_Click" />

            <asp:Panel id="panelSuccess" runat="server" 
                       visible="false">
                Thank you for submitting this form.  You are truly 
                the coolest user I've ever had the pleasure of serving.
                No, really, I mean it.  There have been others, sure,
                but you are really in a class by yourself.  
            </asp:Panel>
  
        </form>
    </body>
</html>

How it works

The PleaseWaitButton control renders a standard ASP.NET Button within a <div> tag. It also renders an initially empty <div> tag for the message/image. JavaScript functions (shown below under Client-side functions) control the hiding of the button and display of a "please wait" message when clicked. For convenience, the PleaseWaitButton server control handles the rendering of all necessary JavaScript client code.

Because PleaseWaitButton renders its own JavaScript onclick handler, additional measures are necessary to preserve existing onclick handlers and to allow the control to work cleanly with client-side validation code. To accomplish this, the base Button is first rendered to a string buffer, which is then manipulated to include our custom onclick code.

C#
protected override void Render(HtmlTextWriter output)
{
    // Output the button's html (with attributes)
    // to a dummy HtmlTextWriter
    StringWriter sw = new StringWriter();
    HtmlTextWriter wr = new HtmlTextWriter(sw);
    base.Render(wr);
    string sButtonHtml = sw.ToString();
    wr.Close();
    sw.Close();
    // now modify the code to include an "onclick" handler
    // with our PleaseWait() function called appropriately
    // after any client-side validation.
    sButtonHtml = ModifyJavaScriptOnClick(sButtonHtml);
    
    // before rendering the button, output an empty <div>
    // that will be populated client-side via javascript
    // with a "please wait" message"
    output.Write(string.Format("<div id='pleaseWaitButtonDiv2_{0}'>",
                                this.ClientID));
    output.Write("</div>");

    // render the button in an encapsulating <div> tag of its own
    output.Write(string.Format("<div id='pleaseWaitButtonDiv_{0}'>",
                                this.ClientID));
    output.Write(sButtonHtml);
    output.Write("</div>");
}

This technique of rendering the button to a string buffer and then manipulating its onclick contents is certainly a hack. It does however allow us to render our PleaseWait() JavaScript call after the standard validation code rendered by the parent Button class. Unless we were willing to completely rewrite the rendering of attributes of the parent's Button class, the best we can do without this hack is to render our PleaseWait() function call before the validation code in the onclick attribute. This creates the undesirable effect of hiding the button and displaying a "please wait" message even if there are data entry errors on the page. The calling of our client-side PleaseWait() function must be forced to occur after client-side page validation in the onclick handler.

The modification of the onclick attribute occurs in the ModifyJavaScriptOnClick() function. This takes the rendered HTML string for the button and inspects it to see if there is an existing onclick attribute. If so, the function also checks to see if client-side validation code is in use. In this case, our custom PleaseWait() JavaScript function call is added to the end of the existing onclick code, following a check of the client-side boolean variable Page_IsValid. This variable is present when validation controls are in use. If Page_IsValid is false on the client side, the "please wait" message is stalled. If Page_IsValid is true on the client side, the "please wait" message displays.

C#
private string ModifyJavaScriptOnClick(string sHtml)
{
    // Thanks to CodeProject member KJELLSJ (Kjell-Sverre Jerijaervi)
    // for code ideas to allow the button to work with client-side validation

    string sReturn = "";
    string sPleaseWaitCode = GeneratePleaseWaitJavascript();

    // is there an existing onclick attribute?
    Regex rOnclick = new Regex("onclick=\"(?<onclick>[^\"]*)");
    Match mOnclick = rOnclick.Match(sHtml);
    if (mOnclick.Success)
    {
        // there is an existing onclick attribute;
        // add our code to the end of it; if client-side
        // validation has been rendered, make sure
        // we check to see if the page is valid;
        string sExisting = mOnclick.Groups["onclick"].Value;
        string sReplace = sExisting 
                 + (sExisting.Trim().EndsWith(";") ? "" : "; ");
        
        if (IsValidatorIncludeScript() && this.CausesValidation)
        {
            // include code to check if the page is valid
            string sCode = "if (Page_IsValid) " + sPleaseWaitCode 
                            + " return Page_IsValid;";
            // add our code to the end of the existing onclick code;
            sReplace = sReplace + sCode;
        }
        else
        {
            // don't worry about the page being valid;
            sReplace = sReplace + sPleaseWaitCode;
        }

        // now substitute our onclick code
        sReplace = "onclick=\"" + sReplace;
        sReturn = rOnclick.Replace(sHtml, sReplace);
    }
    else
    {
        // there isn't an existing onclick attribute;
        // add ours
        int i = sHtml.Trim().Length - 2;
        string sInsert = " onclick=\"" + sPleaseWaitCode + "\" ";
        sReturn = sHtml.Insert(i, sInsert);             
    }
    
    return sReturn;
}

The function IsValidatorIncludeScript() referenced above checks to see if a standard JavaScript block used by ASP.NET validation controls has been registered with the page. This serves as a convenient way of testing whether or not validation code and variables like Page_IsValid will be available.

C#
private bool IsValidatorIncludeScript()
{
    // return TRUE if this page has registered javascript
    // for client-side validation; this code may not be registered
    // if ASP.NET detects what it thinks (correctly or incorrectly)
    // is a down-level browser.
        

     //Changed from origianl code to .NET 2.0 format
     return this.Page.ClientScript.IsStartupScriptRegistered("ValidatorIncludeScript");

}

The function GeneratePleaseWaitJavascript() constructs the PleaseWait() JavaScript function call that is included in the onclick attribute. Control properties are inspected to determine the desired layout.

C#
private string GeneratePleaseWaitJavascript()
{
    // create a JavaScript "PleaseWait()" function call
    // suitable for use in an onclick event handler

    string sMessage = "";
    string sText = _pleaseWaitText;
    string sImage = (_pleaseWaitImage != String.Empty 
        ? string.Format(
        "<img src=\"{0}\" align=\"absmiddle\" alt=\"{1}\"/>"
        , _pleaseWaitImage, _pleaseWaitText )
        : String.Empty);

    // establish the layout based on PleaseWaitType
    switch (_pleaseWaitType)
    {
        case PleaseWaitTypeEnum.TextThenImage:
            sMessage = sText + sImage;
            break;
        case PleaseWaitTypeEnum.ImageThenText:
            sMessage = sImage + sText;
            break;
        case PleaseWaitTypeEnum.TextOnly:
            sMessage = sText;
            break;
        case PleaseWaitTypeEnum.ImageOnly:
            sMessage = sImage;
            break;
    }

    // return the final code chunk
    string sCode = string.Format(
        "PleaseWait('pleaseWaitButtonDiv_{0}', 
                    'pleaseWaitButtonDiv2_{1}', '{2}');"
        , this.ClientID, this.ClientID, sMessage);
    sCode = sCode.Replace("\"", "&quot;");

    return sCode;
}

If a PleaseWaitImage has been specified, an additional block of JavaScript is included, instructing the client to pre-load the image. The registration of this script occurs in the overridden OnPreRender method. The registration key is based on the image name; if multiple buttons are used on a page with the same image, the preload script is rendered only once for the image. A regular expression is used to create the JavaScript image variable, ensuring non-alphanumeric characters (such as a slash in a file path) are converted to underscores.

C#
protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender (e);

    // If we're using an image, register some javascript
    // for client-side image preloading
    if (_pleaseWaitImage != String.Empty 
        && _pleaseWaitType != PleaseWaitTypeEnum.TextOnly)
        RegisterJavascriptPreloadImage(_pleaseWaitImage);
}

private void RegisterJavascriptPreloadImage(string sImage)
{
    Regex rex = new Regex("[^a-zA-Z0-9]");
    string sImgName = "img_" + rex.Replace(sImage, "_"); 

    StringBuilder sb = new StringBuilder();
    sb.Append("<script language="'JavaScript'">");
    sb.Append("if (document.images) { ");
    sb.AppendFormat("{0} = new Image();", sImgName);
    sb.AppendFormat("{0}.src = \"{1}\";", sImgName, sImage);
    sb.Append(" } ");
    sb.Append("</script>");

    //Changed from origianl code to .NET 2.0 format  
    this.Page.ClientScript.RegisterStartupScript(this.GetType(), sImgName + 
              "_PreloadScript",sb.ToString());
}

Client-side functions

The embedded text file javascript.txt contains the client-side code to hide the button's <div> and display the "please wait" message/image. This code is loaded in the overridden OnInit() method with a call to the private method RegisterJavascriptFromResource(). This method calls the more generic method GetEmbeddedTextFile() which loads a text file embedded as a resource and returns the contents as a string.

C#
protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    
    // the client-side javascript code is kept
    // in an embedded resource; load the script
    // and register it with the page.
    RegisterJavascriptFromResource();
}


private void RegisterJavascriptFromResource()
{   
    // load the embedded text file "javascript.txt"
    // and register its contents as client-side script
    string sScript = GetEmbeddedTextFile("javascript.txt"); 

    //Changed from origianl code to .NET 2.0 format 
   this.Page.ClientScript.RegisterStartupScript(this.GetType(), 
             "PleaseWaitButtonScript", sScript);
}

private string GetEmbeddedTextFile(string sTextFile)
{
    // generic function for retrieving the contents
    // of an embedded text file resource as a string

    // we'll get the executing assembly, and derive
    // the namespace using the first type in the assembly
    Assembly a = Assembly.GetExecutingAssembly();
    String sNamespace = a.GetTypes()[0].Namespace;

    // with the assembly and namespace, we'll get the
    // embedded resource as a stream
    Stream s = a.GetManifestResourceStream(
                string.Format("{0}.{1}", sNamespace, sTextFile)
            );
    
    // read the contents of the stream into a string
    StreamReader sr = new StreamReader(s);
    String sContents = sr.ReadToEnd();

    sr.Close();
    s.Close();

    return sContents;
}

The javascript.txt embedded resource contains the client-side method PleaseWait() which is executed in the JavaScript onclick handler for the button. This code calls the client method HideDiv() to hide the button's containing <div>, then populates the previously empty <div> tag with the message/image by setting its innerHTML property. The helper function GetDiv(), attempting to maintain cross-browser compatibility, inspects document.getElementById, document.all, and document.layers to return a <div> object given its ID. The complete client-side code in javascript.txt follows:

JavaScript
<script language="JavaScript">
function GetDiv(sDiv)
{
    var div;
    if (document.getElementById)
        div = document.getElementById(sDiv);
    else if (document.all)
        div = eval("window." + sDiv);
    else if (document.layers)
        div = document.layers[sDiv];
    else
        div = null;

    return div;
}

function HideDiv(sDiv)
{
    d = GetDiv(sDiv);
    if (d)
    {
        if (document.layers) d.visibility = "hide";
        else d.style.visibility = "hidden";
    }
}

function PleaseWait(sDivButton, sDivMessage, sInnerHtml)
{
    if (!ValidatorOnSubmit()) return; //Added to original code

    HideDiv(sDivButton);
    var d = GetDiv(sDivMessage);
    if (d) d.innerHTML = sInnerHtml;
}
</script>

Summary

The ASP.NET server control PleaseWaitButton presented here renders a standard Button within <div> tags with companion client-side JavaScript to present users with a "please wait" message or image when clicked. Such a message can provide users with a useful visual cue for time-consuming form processing and prevent accidental multiple clicks. Though special considerations are necessary for the control to function cleanly with client-side validators, this complexity can be encapsulated in a server control, maintaining convenience for the control user.

Acknowledgements

Thanks to CodeProject member Mike Ellison for the original code; I just copied and pasted more than 98% of his article and made a little modification to it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Working as software engineer for almost 10 years and during these years have develop several type of projects including ASP, ASP.NET, Widows form, Windows services, Web service and speech enable applications.

Comments and Discussions

 
GeneralError with validator - Solved Pin
fravelgue15-Oct-09 3:02
fravelgue15-Oct-09 3:02 
GeneralRe: Error with validator - Solved Pin
Ahmad Asgharzadeh15-Oct-09 6:39
Ahmad Asgharzadeh15-Oct-09 6:39 
QuestionGif is not animated Pin
RodneyBackwards26-Jul-07 15:18
RodneyBackwards26-Jul-07 15:18 
AnswerRe: Gif is not animated Pin
Ahmad Asgharzadeh27-Jul-07 3:40
Ahmad Asgharzadeh27-Jul-07 3:40 
GeneralRe: Gif is not animated Pin
RodneyBackwards27-Jul-07 18:30
RodneyBackwards27-Jul-07 18:30 
QuestionValidatorOnSubmit is not defined Pin
LNHockey21-Jun-07 9:24
LNHockey21-Jun-07 9:24 
AnswerRe: ValidatorOnSubmit is not defined Pin
Ahmad Asgharzadeh26-Jun-07 16:17
Ahmad Asgharzadeh26-Jun-07 16:17 
GeneralRe: ValidatorOnSubmit is not defined Pin
LNHockey5-Jul-07 10:36
LNHockey5-Jul-07 10:36 
GeneralRe: ValidatorOnSubmit is not defined Pin
Member 214467924-Apr-09 8:02
Member 214467924-Apr-09 8:02 
QuestionNot working as expected..am I doing any mistake? Pin
anju1-Mar-07 15:06
anju1-Mar-07 15:06 
AnswerRe: Not working as expected..am I doing any mistake? Pin
Ahmad Asgharzadeh1-Mar-07 16:28
Ahmad Asgharzadeh1-Mar-07 16:28 
GeneralRe: Not working as expected..am I doing any mistake? Pin
anju1-Mar-07 19:35
anju1-Mar-07 19:35 
AnswerRe: Not working as expected..am I doing any mistake? Pin
markus778816-Mar-07 11:58
markus778816-Mar-07 11:58 
GeneralNice Work! Pin
cobrasss8-Nov-06 10:15
cobrasss8-Nov-06 10:15 
GeneralControl submits after validation in Firefox Pin
Sjuustah13-Sep-06 14:21
Sjuustah13-Sep-06 14:21 
GeneralRe: Control submits after validation in Firefox Pin
Ahmad Asgharzadeh14-Sep-06 4:40
Ahmad Asgharzadeh14-Sep-06 4:40 
GeneralRe: Control submits after validation in Firefox [modified] Pin
Sjuustah15-Sep-06 3:10
Sjuustah15-Sep-06 3:10 
GeneralButton within a masterpage and formview in contentarea Pin
Sjuustah12-Sep-06 2:41
Sjuustah12-Sep-06 2:41 
QuestionRe: Button within a masterpage and formview in contentarea Pin
Ahmad Asgharzadeh14-Sep-06 4:38
Ahmad Asgharzadeh14-Sep-06 4:38 
AnswerRe: Button within a masterpage and formview in contentarea Pin
Sjuustah17-Sep-06 3:18
Sjuustah17-Sep-06 3:18 
AnswerRe: Button within a masterpage and formview in contentarea Pin
Ahmad Asgharzadeh20-Sep-06 7:29
Ahmad Asgharzadeh20-Sep-06 7:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.