Click here to Skip to main content
Click here to Skip to main content

A Custom UpdateProgress Control

By , 15 Sep 2009
Rate this:
Please Sign up or sign in to vote.

Contents

Introduction

In this article, I'd like to show you how to build a custom update progress control.

I prepared the live demo here. Just click on the link to see the result or look at the following picture:

pic1

Pic 1

As you can see, there is the yellow box approximately in the middle of the page. The rest of the page is covered by a shadow and does not allow the users to click the buttons.

The article is divided into two parts. The first "Example of Use" and the second "Theory".

Example of Use

Consider the situation where you are using an UpdatePanel control for a partial rendering in your page. If you want to use the UpdateProgress control described in this article, then you have to follow these steps:

  • Add a reference to the library DotNetSources.Web.UI.dll.
  • Add a link to the CSS styles into your page like:
  • <link href="UpdateProgress.css" rel="stylesheet" type="text/css" />
  • Register the control. Into the web.config file section pages/controls, add the following line:
  • <add tagprefix="dns"
      namespace="DotNetSources.Web.UI"
      assembly="DotNetSources.Web.UI" />
  • Define the control in your page:
  • <dns:customupdateprogress  runat="server"
      id="progress" progressimage="ajax-loader2.gif" text="Updating..." />

And that's it. I think it's very simple, and the result will be as in Pic 1. This example is available in the source code related with this article and the name of the project is CustomUpdateProgressExample. Just open the project using Visual Studio, run it, and enjoy the demo.

Theory

Let's do the steps to create a control like that and describe some of the theory.

The first step is to create the control itself. Let's call it CustomUpdateProgress and derive from WebControl and IScriptControl. The control will have the following properties:

  • Delay - How long to wait before displaying the progress bar. In milliseconds.
  • ProgressImage - The path to the GIF displaying the progress.
  • Text - The text displayed during the progress.

Add these values as properties of the control like follows:

private long delay = 3000;

///<summary>
/// Delay. Default: 3000 (3 seconds).
/// </summary>
public long Delay {
    get {
        return delay;
    }
    set {
        delay = value;
    }
}

private string progressImage;

///<summary>
/// Get or set progress image URL.
/// </summary>
public string ProgressImage {
    get {
        return progressImage;
    }
    set {
        progressImage = value;
    }
}

private string text = "Updating...";

///<summary>
/// Text to display. Default: "Updating...".
/// </summary>
public string Text {
    get {
        return text;
    }
    set {
        text = value;
    }
}

The control itself will be rendered as DIV element. That is easy to specify in the base constructor like:

public CustomUpdateProgress()
    : base(HtmlTextWriterTag.Div) {
}

The important part of the control is creating the child controls. Let's think about the layout of the control and stuff needed inside. Look at picture 2 below to understand the base elements inside. Basically there are two DIV elements. One is covering the page for disabling users to click buttons and another DIV as popup.

pic2

Pic 2

Let’s discuss the layout inside the popup. See picture 3 for understanding. It's no big deal, just three DIV elements sorted vertically.

pic2

Pic 3

Let’s see how to create DIVs like that dynamically in C#. The green panel from picture 3:

// panel
pnlMain = new Panel();
pnlMain.ID = "pnlMain";
pnlMain.CssClass = "uProg_modalPopup";
pnlMain.Style.Add("display", "none");
Controls.Add(pnlMain);

Red DIV from picture 3:

// div message
pnlMessage = new Panel();
pnlMessage.ID = "pnlMessage";
pnlMessage.CssClass = "uProg_messageDiv";
lblMessage = new Label();
pnlMessage.Controls.Add(lblMessage);
pnlMain.Controls.Add(pnlMessage);

Green DIV from picture 3:

// div progress
 pnlProgress = new Panel();
 pnlProgress.ID = "pnlProgress";
 pnlProgress.CssClass = "uProg_progressDiv";
 image = new Image();
 pnlProgress.Controls.Add(image);
 pnlMain.Controls.Add(pnlProgress);

Orange DIV from picture 3:

// div abort
pnlAbort = new Panel();
pnlAbort.ID = "pnlAbort";
pnlAbort.CssClass = "uProg_abortDiv";
cmdAbort = new Button();
cmdAbort.UseSubmitBehavior = false;
cmdAbort.Text = "Abort";
cmdAbort.OnClientClick =
	string.Format("$find('{0}').Abort();return false;", ClientID);
pnlAbort.Controls.Add(cmdAbort);
pnlMain.Controls.Add(pnlAbort);

Take a notice of the JavaScript related to the button click event. The first step is to call:

$find('[component_name]').Abort();

I will discuss it later when I will describe the JavaScript part of this control. The second step is to call:

return false;

Why? Because we don't want to have this button as Submit and ASP.NET Button control always generates postback call as the last part of the button client click event. return false; stops the execution immediately after our code is executed and the postback won't be performed.

Finally create the background DIV from picture 2:

// background
pnlBackground = new Panel();
pnlBackground.ID = "pnlBackground";
pnlBackground.CssClass = "uProg_modalBackground";
pnlBackground.Style.Add("visibility", "hidden");
Controls.Add(pnlBackground);

The introduced content must be created in the Init phase of the page life cycle. So, I override the OnInit method and I put the code there.

Afterwards I need to implement IScriptControl interface. There are two methods: GetScriptDescriptor and GetScriptReferences. In GetScriptDescriptors, you should specify the values you want to give to the JavaScript part. I will need the Delay for working with the interval in the JavaScript and I will also need the ID of the background DIV and ID of popup DIV. So the GetScriptDescriptors will look like follows:

public IEnumerable<ScriptDescriptor> GetScriptDescriptors() {
    ScriptControlDescriptor descriptor =
	new ScriptControlDescriptor("DotNetSources.Progress", this.ClientID);
    descriptor.AddProperty("delay", this.Delay);
    descriptor.AddProperty("backgroundId",
	this.pnlBackground.UniqueID.Replace('$', '_'));
    descriptor.AddProperty("popupId", this.pnlPopup.UniqueID.Replace('$', '_'));
    return new ScriptDescriptor[] { descriptor };
}

The second method named GetScriptReference provides the JavaScript resources related to this control. I decided to release JavaScript as an embedded resource. The implementation can be like:

public IEnumerable<ScriptReference> GetScriptReferences() {
    // create reference to the JS
    ScriptReference jsReference = new ScriptReference();
    jsReference.Assembly = "DotNetSources.Web.UI";
    jsReference.Name = "DotNetSources.Web.UI.scripts.DotNetSources_Progress.js";
    return new ScriptReference[] { jsReference };
}

The server control is created. Now I need to create the JavaScript mirror of this control.

First, register the namespace:

Type.registerNamespace("DotNetSources");

Second, define the constructor:

//
// Constructor
//
DotNetSources.Progress = function(element) {
    DotNetSources.Progress.initializeBase(this, [element]);

    // properties
    this._delay = null;
    this._backgroundId = null;
    this._popupId = null;
    this._requestIsInProgress = 0;
    this._timeOutId = null;

    // delegates
    this._beginRequestDelegate = null;
    this._endRequestDelegate = null;
}

As you can see there is the one parameter: element. It's the ID of the tag in DOM. Remember about the server control the part I specified that the server control will be rendered as DIV tag. So, this input element is ID of this DIV tag. What's happened next in the constructor? The properties are initialized to null and the delegates are initialized to null.

Furthermore, I'm going to specify the prototype:

DotNetSources.Progress.prototype = {

}

Inside the prototype, I define the methods and properties of the JavaScript class.

Let's see the properties:

// get accessor for delay
get_delay: function() {
    return this._delay;
},

// set accessor for delay
set_delay: function(value) {
    this._delay = value;
},

// get accessor for extender ID
get_backgroundId: function() {
    return this._backgroundId;
},

// set accessor for extender ID
set_backgroundId: function(value) {
    this._backgroundId = value;
},

// get accessor for extender ID
get_popupId: function() {
    return this._popupId;
},

// set accessor for extender ID
set_popupId: function(value) {
    this._popupId = value;
},

These properties are related to properties created in GetScriptDescriptors method in the server control. The ASP.NET AJAX extensions itself will take care of initializing these properties to the right values from the server control. So this is the way the handover of values between the server control and the JavaScript works.

When the JavaScript object is created by Microsoft AJAX extension, the initialize method is called. In the initialize method, I register the events like this:

initialize: function() {
    DotNetSources.Progress.callBaseMethod(this, 'initialize');
    this.registerEvents();
},
registerEvents: function() {
    var manager = Sys.WebForms.PageRequestManager.getInstance();

    this._beginRequestDelegate = Function.createDelegate(this, this.Show);
    this._endRequestDelegate = Function.createDelegate(this, this.Hide);

    manager.add_beginRequest(this._beginRequestDelegate);
    manager.add_endRequest(this._endRequestDelegate);
},

Let's look at the code inside the registerEvents method. The first line...

var manager = Sys.WebForms.PageRequestManager.getInstance();

... is returning the instance of PageRequestManager. This is the key object in this control because using the PageRequestManager I can get the information that the asynchronous postback had begun and of course when it's finished. The next step is creating delegates using Function.createDelegate. It will also work if I put this.Show directly into the manager.add_beginRequest. However in a case like that, I will be missing the context of the right object. See that the current instance is needed in the Function.createDelegate call.

Now I will implement the public interface for the JavaScript class. It's about four methods:

  • Show - to display the progress, counting with delay
  • ShowInternal - to display the progress, without taking care of delay. Usually called by timer which is set in Show
  • Hide - to hide the progress
  • Abort - to cancel the asynchronous request and hide the progress

Let's code:

//
// Show progress panel including delay.
//
Show: function() {
    this._requestIsInProgress = 1;
    // delay
    this._timeOutId = setTimeout
	(Function.createDelegate(this, this.ShowInternal), this._delay);
},

//
// Show progress panel internal - do not count with delay.
//
ShowInternal: function() {
    if (this._requestIsInProgress == 1) {
        $get(this._backgroundId).style.visibility = 'visible';
        $get(this._popupId).style.visibility = 'visible';
    }
},

//
// Hide progress panel immediately
//
Hide: function() {
    this._requestIsInProgress = 0;
    clearInterval(this._timeOutId);
    $get(this._backgroundId).style.visibility = 'hidden';
    $get(this._popupId).style.visibility = 'hidden';
},

Abort: function() {
    clearInterval(this._timeOutId);
    var manager = Sys.WebForms.PageRequestManager.getInstance();
    if (manager.get_isInAsyncPostBack()) {
        manager.abortPostBack();
    }
    this.Hide();
},

Afterwards, it is good to implement the dispose method to free resources.

// dispose, clear Handlers
dispose : function() {
    clearInterval(this._timeOutId);
    DotNetSources.Progress.callBaseMethod(this, 'dispose');
}

Finally I derive this class from Sys.UI.Control:

DotNetSources.Progress.registerClass
	('DotNetSources.Progress', Sys.UI.Control);

Thank you for reading this article and I hope it was useful.

Links

History

  • 31 July 2009
    • Initial release
  • 13 September 2009
    • Code changes
      • JavaScript class derived from Sys.UI.Control
      • The server control is implementing the IScriptControl interface
    • Text changes
      • The link to the live demo added

License

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

About the Author

Petr Pechovic
Technical Lead
Czech Republic Czech Republic

Comments and Discussions

 
QuestionGreat Job, but I can't do work on Master Pages Pinmembero5k4r.m4dr1d15-Nov-12 13:00 
QuestionIt doesn't work inside a ContentPage [modified] PinmemberMember 42561147-Oct-11 8:33 
AnswerRe: It doesn't work inside a ContentPage PinmemberPetr Pechovic7-Oct-11 10:52 
Hi, thanks for comment Smile | :) .
 
Well, I guess you know better Wink | ;)
 
Cheers,
Petr.
GeneralElement Ids Duplication [modified] PinmemberAli Al Omairi(Abu AlHassan)15-Jun-11 0:36 
GeneralMy vote of 5 PinmemberAli Al Omairi(Abu AlHassan)28-May-11 21:53 
GeneralRe: My vote of 5 PinmemberPetr Pechovic29-May-11 8:15 
GeneralMy vote of 5 Pinmemberparveen_gkv19-Oct-10 22:21 
GeneralRe: My vote of 5 PinmemberPetr Pechovic26-Oct-10 22:29 
GeneralIt dosent work at host for first time Pinmembermohsen.karami11-Nov-09 20:55 
GeneralRe: It dosent work at host for first time PinmemberPetr Pechovic11-Nov-09 22:28 
GeneralRe: It dosent work at host for first time Pinmembermohsen.karami11-Nov-09 22:49 
GeneralRe: It dosent work at host for first time PinmemberPetr Pechovic11-Nov-09 23:39 
GeneralRe: It dosent work at host for first time [modified] Pinmembermohsen.karami11-Nov-09 23:47 
GeneralRe: It dosent work at host for first time PinmemberPetr Pechovic12-Nov-09 6:09 
GeneralNice one ;) PinmemberMario Majcica1-Oct-09 5:00 
GeneralRe: Nice one ;) PinmemberPetr Pechovic1-Oct-09 21:55 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 15 Sep 2009
Article Copyright 2009 by Petr Pechovic
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid