Someone on the Atlas forums was interested in an Atlas progress bar. So, I went ahead and wrote a basic, client-side Atlas progress bar. If you want, you can also download the source.

So, what is involved to build such a control? Not a hell'u'valot, really.

Step 1: Derive from Web.UI.Control
First of all, you will want to derive from the base Atlas control class, Web.UI.Control. It contains some plumbing for you, such as associating itself with an HTML element. Additionally, you should register your type to make it possible to instantiate it declaratively. In this case, we will register our type with the namespace 'script' and the tagname 'progressBar'. You should also register your class, so Atlas can do its thing, such as describing what the base type is, etc.

JavaScript:
1 
2 
3 
4 
5 
6 
7 

Web.UI.ProgressBar = function(associatedElement) {
    Web.UI.ProgressBar.initializeBase(this, [associatedElement]);
    
    
}
Type.registerSealedClass('Web.UI.ProgressBar', Web.UI.Control);
Web.TypeDescriptor.addType('script','progressBar', Web.UI.ProgressBar);

Step 2: Make it configurable
Secondly, you will want to add some properties to let a user configure the control. In our case, we will add properties like an interval, the url to the service, and a method that we will call on this service to get the progress. Properties have to follow an exact naming convention: the getter of the property should be a function prefixed with 'get_', and the setter should be prefixed with 'set_' and expect 1 parameter. Additionally, we should add these properties to our control's descriptor. Please see step 4 on how this should be done.

JavaScript:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 

// Define a 'private field'.
var _serviceURL;

this.get_serviceURL = function() {
    return _serviceURL;
}

this.set_serviceURL = function(value) {
    _serviceURL = value;
}

Step 3: Add a timer that will query the service on every tick
Since we want to query the service n milliseconds, we could just re-use the Web.Timer internally. We should define a delegate to represent the function that we want the timer to invoke on each tick. To be safe, we should make sure we clean up after ourselves when our control is disposed.
Please note that we prevent our control from querying the service multiple times when we are still waiting for a response.

JavaScript:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 

var _responsePending;
var _timer;
var _tickHandler;

this.initialize = function() {
    Web.UI.ProgressBar.callBaseMethod(this, 'initialize');

    _tickHandler = Function.createDelegate(this, this._onTimerTick);

    // Add our event handler to the tick event of the timer.
    _timer.tick.add(_tickHandler);

    this.set_progress(0);
}

this.dispose = function() {
    if (_timer) {
        // Remove the reference of the timer's tick event to our event handler.
        _timer.tick.remove(_tickHandler);
        _tickHandler = null;
        _timer.dispose();
    }

    // Just to be safe we should explicitly set this to null to make 
    // sure we aren't still referencing the timer.
    _timer = null;

    Web.UI.ProgressBar.callBaseMethod(this, 'dispose');
}

this._onTimerTick = function(sender, eventArgs) {
    if (!_responsePending) {
        _responsePending = true;

        // Asynchronously call the service method. Pass a reference to 
        // this control as the context, so we can use that in our 
        // '_onMethodComplete' callback function.
        Web.Net.ServiceMethodRequest.callMethod(_serviceURL, _serviceMethod, 
            null, _onMethodComplete, null, null, this);
    }
}

function _onMethodComplete(result, response, context) {
    // Get a reference to the control.
    var behavior = context;

    // Update the progress bar.
    behavior.set_progress(result);
    _responsePending = false;
}

Step 4: Add a few methods to control our progress bar
Finally, we should add a few methods to start/stop our progress bar. All we need to do is add a start/stop function that enables/disables the timer. Since we want these to be callable by other objects, we should describe these methods in our control's descriptor.

JavaScript:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 

this.getDescriptor = function() {
    var td = Web.UI.ProgressBar.callBaseMethod(this, 'getDescriptor');
    td.addProperty('interval', Number);
    td.addProperty('progress', Number);
    td.addProperty('serviceURL', String);
    td.addProperty('serviceMethod', String);
    td.addMethod('start');
    td.addMethod('stop');
    return td;
}

this.start = function() {
    _timer.set_enabled(true);
}

this.stop = function() {
    _timer.set_enabled(false);
}

Step 5: Test our control
Now we're done with our control, we should be able to test it. Once you added a reference to your new script control's file, you should be good to go and use it. Since our progress bar needs to be started explicitly, and since we will want to simulate a task, we should add a few more controls to put together a proper demo.

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 

[...]
<div class="progressBarContainer">
  <!-- This div is associated with our progress bar control. -->
  <div id="pb1" class="progressBar"></div>
</div>
[...]

<script type="text/xml-script">
    <page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
        <components>
            <!-- This component can be invoked to actually start a 
                 simulated time-consuming task. -->
            <serviceMethod id="taskService1" url="TaskService.asmx" 
                methodName="StartTask1" />

            <!-- Our progress bar's id should refer to a valid associated 
                 HTML element. -->
            <progressBar id="pb1" interval="500" serviceURL="TaskService.asmx" 
                serviceMethod="GetProgressTask1" />

            <!-- Our button should both start the task and the progress bar. -->            
            <button id="start1">
                <click>
                    <invokeMethod target="taskService1" method="invoke" />
                    <invokeMethod target="pb1" method="start" />
                </click>
            </button>
        </components>
        <references>
            <add src="ScriptLibrary/AtlasUI.js" />
            <add src="ScriptLibrary/AtlasControls.js" />
            <!-- Add a reference to our control. -->
            <add src="ScriptLibrary/ProgressBar.js" />
        </references>
    </page>
</script>

As you can see in this code, we invoke a method 'StartTask1' in our service to start the task, and we invoke 'GetProgressTask1' to get the progress for this task. It is really up to you on how you implement these. For example, if you had a file upload scenario, you could implement 'GetProgressTask1' by checking how many bytes were uploaded already and how big the file is.
Since I don't really want you to upload stuff to my server, I have decided to simulate a time-consuming task instead. This task pretty much sleeps and registers progress every 100/200 milliseconds. The GetProgressTask1 method just returns the registered progress.

C#:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 

[WebMethod]
public int GetProgressTask1()
{
    // Get the registered progress for the user's running task.
    string processKey = this.Context.Request.UserHostAddress + "1";
    object savedState = this.Context.Cache[processKey];
    if (savedState != null)
    {
        return (int)savedState;
    }
    return 0;
}

[WebMethod]
public void StartTask1()
{
    string processKey = this.Context.Request.UserHostAddress + "1";

    // Create a lock object to prevent 1 user from running task 1 
    // multiple times.
    string threadLockKey = "thread1" + this.Context.Request.UserHostAddress;
    object threadLock = this.Context.Cache[threadLockKey];
    if (threadLock == null)
    {
        threadLock = new object();
        this.Context.Cache[threadLockKey] = threadLock;
    }

    // Only allow 1 running task per user.
    if (!Monitor.TryEnter(threadLock, 0))
        return;

    // Simulate a time-consuming task.
    for (int i = 1; i <= 100; i++)
    {
        // Update the progress for this task.
        this.Context.Cache[processKey] = i;
        Thread.Sleep(100);
    }

    // The task is done. Release the lock.
    Monitor.Exit(threadLock);
}

Hopefully this gives you an idea of what is involved to create a basic client-side Atlas control.

Could your progress bar be implemented with a dataSource? To show the progress of the dataSource getting data?

That would be cool, but I don't know how to do it. Nice work.
Keegan
To use this progress bar with file upload, I think that the file must uploaded with Atlas...

Do you know if is possible to upload files using Atlas?

Thanks!
Solari
get this error:
Unrecognized attribute 'appliesTo'. Note that attribute names are case-sensitive.

in the web config file
Great stuff you've got here. Any chance you could help me out understanding how to implement it into my VB upload site? I'm sure it would do the trick, but C# and Javascript is very unfamiliar to me..

Thanks for any response!
Do you know if is possible to upload files using Atlas?
Can this be done using Atlas Modal Dialog, to show progress bar in the modal dialog instead?
Got this error:

Server Error in '/AtlasProgressBarDemo' Application.
--------------------------------------------------------------------------------

Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: Unrecognized attribute 'appliesTo'. Note that attribute names are case-sensitive.

Source Error:


Line 50: <compilation debug="true">
Line 51: <buildProviders>
Line 52: <add extension=".script" type="Microsoft.Web.Compilation.ScriptBuildProvider" appliesTo="Web"/>
Line 53: </buildProviders>
Line 54: </compilation>


Source File: C:\Documents and Settings\RAFAL\My Documents\development\atlas\AtlasProgressBarDemo\web.config Line: 52
I have tried this, but it does not work. the webservice does not wrok as well, it complains something about ICollection... Is there an update for the new Atlas?
I have tried this, everything compiles just nothing happens. I've run it in debug mode and it doesn't even call the service. I also tried an Atlas Microsoft sample with the Hello-World and that works. I did get an error at first w/the Microsoft sample and it said something about a missing reference to the components namespace I commented out the reference and it worked. Any ideas?
xcvxcv
Hi, I am trying to make Asynchronous File Upload using ATLAS but its not working in <atlas:updatepanel>. If i am putting the control outside the panel then its working fine. Please suggest.
Tried this implementation and it is brokeass...
ffjkhjgjkghjkghjkghjk
Your message will be encoded/formatted when it is displayed. If you want to post code, please put the code inside [code=X][/code] tags, where X is the language of your code (C#, ASPX, SQL, etc).
Name:
Email:
(will be encoded using JavaScript to keep it functional and prevent it from being picked up by spammers)
Url:
 
Message:
3 + 3 =