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.
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.
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.
var _responsePending;
var _timer;
var _tickHandler;
this.initialize = function() {
Web.UI.ProgressBar.callBaseMethod(this, 'initialize');
_tickHandler = Function.createDelegate(this, this._onTimerTick);
_timer.tick.add(_tickHandler);
this.set_progress(0);
}
this.dispose = function() {
if (_timer) {
_timer.tick.remove(_tickHandler);
_tickHandler = null;
_timer.dispose();
}
_timer = null;
Web.UI.ProgressBar.callBaseMethod(this, 'dispose');
}
this._onTimerTick = function(sender, eventArgs) {
if (!_responsePending) {
_responsePending = true;
Web.Net.ServiceMethodRequest.callMethod(_serviceURL, _serviceMethod,
null, _onMethodComplete, null, null, this);
}
}
function _onMethodComplete(result, response, context) {
var behavior = context;
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.
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.
[...]
<divclass="progressBarContainer="pb1"class="progressBar"></div>
</div>
[...]
<scripttype="text="http://schemas.microsoft.com/xml-script/2005">
<components>
<serviceMethodid="taskService1="TaskService.asmx"
methodName="StartTask1"/>
<progressBarid="pb1="500"serviceURL="TaskService.asmx"
serviceMethod="GetProgressTask1"/>
<buttonid="start1="taskService1"method="invoke"/>
<invokeMethodtarget="pb1="start"/>
</click>
</button>
</components>
<references>
<addsrc="ScriptLibrary="ScriptLibrary/AtlasControls.js"/>
<addsrc="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.
[WebMethod]
publicint GetProgressTask1()
{
string processKey = this.Context.Request.UserHostAddress + "1";
object savedState = this.Context.Cache[processKey];
if (savedState != null)
{
return (int)savedState;
}
return 0;
}
[WebMethod]
publicvoid StartTask1()
{
string processKey = this.Context.Request.UserHostAddress + "1";
string threadLockKey = "thread1" + this.Context.Request.UserHostAddress;
object threadLock = this.Context.Cache[threadLockKey];
if (threadLock == null)
{
threadLock = newobject();
this.Context.Cache[threadLockKey] = threadLock;
}
if (!Monitor.TryEnter(threadLock, 0))
return;
for (int i = 1; i <= 100; i++)
{
this.Context.Cache[processKey] = i;
Thread.Sleep(100);
}
Monitor.Exit(threadLock);
}
Hopefully this gives you an idea of what is involved to create a basic client-side Atlas control.