Out of the box Atlas comes with a few drag and drop (DND) behaviors that can be attached to Atlas controls. One of them is the DragDropList behavior which makes it possible to move or copy data from one place to another. I’ve demonstrated this behavior in the past.

If the built-in behaviors do not fit your needs, there is nothing that stops you from rolling your own. Atlas provides a generic DND API which is designed for extensibility.

At the core of the DND API there is the DragDropManager (DDM). It’s responsible for the actual DND operations. This includes moving the drag visual which represents the data being dragged and notifying drag sources and drop targets about each drag event (movement, valid drops, cancel drag, etc.). Technically, these event notifications are made by calling methods on the relevant drag source and drop targets. This means that all you really need to do to extend the DND API is implement these methods.

Drag Sources
Drag sources are objects that generally expose data which can be moved or copied to somewhere else. They need to implement the IDragSource interface, which is defined as follow:

JavaScript:
1 
2 
3 
4 
5 
6 
7 
8 

Sys.UI.IDragSource = function() {
    this.get_dataType = Function.abstractMethod;
    this.get_data = Function.abstractMethod;
    this.get_dragMode = Function.abstractMethod;
    this.onDragStart = Function.abstractMethod;
    this.onDrag = Function.abstractMethod;
    this.onDragEnd = Function.abstractMethod;
}

The data type can really be anything. If your drag source contains an array of appointments, it might make sense to return 'Appointments' from get_dataType(). The data type is used by the DDM to check in which drop targets the data may be dropped.

When an item is dropped, the DDM will call the drag source’s get_data(context) method to query for the actual data. This data will be passed to the drop target on which the item was dropped.

The other methods are self explanatory. When a user starts dragging, onDragStart is called. While the user is dragging, onDrag is called. Finally, when an item is dropped (in either a valid or invalid location), onDragEnd is called with a parameter specifying whether the drag operation was cancelled.

Generally, a drag source is responsible for initiating a drag operation. It may do so by, for example, handling an element’s click event and calling the startDragDrop(dragSource, dragVisual, context) method on the DDM. It would pass in a reference to itself (the drag source), an element that represents the data that is dragged and a context. The DDM will pass the context onto the get_data() call when an item is dropped, which makes it possible for you to get the right data based on the context.

Drop Targets
A drop target is an object which accepts data from a drag source. There may be multiple drop targets on one page, as long as each implements the IDropTarget interface. It’s defined as follow:

JavaScript:
1 
2 
3 
4 
5 
6 
7 
8 

Sys.UI.IDropTarget = function() {
    this.get_dropTargetElement = Function.abstractMethod;
    this.canDrop = Function.abstractMethod;    
    this.drop = Function.abstractMethod;
    this.onDragEnterTarget = Function.abstractMethod;
    this.onDragLeaveTarget = Function.abstractMethod;
    this.onDragInTarget = Function.abstractMethod;
}

The dropTargetElement property should return the DOM element which represents the drop target. It will be used by the DDM to wire the relevant events based on the browser.

A drop target can define what kind of data it accepts by implementing canDrop(dataType). The DDM will pass in the data type of the drag source to this method. If the drop target can only deal with an array of appointments, you probably want to return true only if the passed in dataType argument equals 'Appointment'.

When an item is dropped on a drop target, the DDM will call the drop(dragMode, dataType, data) method. This is the place where you will want to deal with the either movied or copied data. For example, the Atlas DragDropList behavior moves or copies the dropped data to the underlying data source.

Similarly to the IDragSource, the IDropTarget defines a number of methods related to the movement of a drag visual with relation to the drop target. When a drag visual enters the bounds of a potential drop target, onDragEnterTarget is called by the DDM. One of the things you can do here is provide the user with feedback that the item may be dropped now. The reverse goes for onDragLeavetarget. Finally, onDragInTarget is called as long as the user is dragging an item in the drop target. An example implementation of this method includes inserting a HTML element to provide a preview of what would happen if the user would drop the data.

Drop targets are supposed to register themselves with the DDM. They can do this by calling the registerDropTarget(dropTarget) method. Similarly, unregisterDropTarget(dropTarget) should be called by the drop target when it is disposed.

Practical example
Instead of writing a basic example to support this article, I’m going to try and refer to the FloatingBehavior implementation that ships with Atlas instead. It’s probably one of the most basic implementations of both the IDragSource and IDropTarget interfaces, and yet it’s useful.

Please let me know if this was useful, or if there is something you'd like me to cover in more detail.

It looks like the Atlas December CTP is available for download. Nikhil Kothari already gave a quick tour of this new CTP.

The first few things you will notice is that most of the previously available controls, such as ListView, are no longer there (server-side). Instead, you can now use regular ASP.NET controls inside an UpdatePanel (see Nikhil's quick tour for more information on this subject). This should mean that there's no longer this duplication of controls (read: Atlas now seems to properly integrate with ASP.NET). As a result, it should be a lot easier to take an existing application and start using Atlas to replace postbacks with 'AJAX' requests.
While I'm glad they decided to go down this path, I wonder what will happen with the previously introduced concepts such as bindings. Can we expect to see them again (server-side) in a future release?

Another thing I noticed is that there is now a debug and release version of the script files. This makes it a lot easier to actually read the javascript and understand what is going on. Atlas will reference either the debug of release version of the script files based on the debug flag in the web.config.

I have updated the Atlas Class Browser. It now references the debug files of the December CTP.

Someone asked me today how one could use the InvokeMethodAction to declaratively invoke a (global) javascript function. It appears it's as easy as this:

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 

<script language="javascript" type="text/javascript">
  function test() {
    // code here.
  }
</script>

<atlas:Label runat="server" Text="hello">
    <Behaviors>
        <atlas:ClickBehavior>
            <Click Handler="test" />
        </atlas:ClickBehavior>
    </Behaviors>
</atlas:Label>

Previously, this post was about a workaround which assumed this couldn't be done declaratively. Thanks to Bertrand Le Roy for pointing out that Atlas already makes it trivial to do this. While I'm dumbass for not looking properly in the first case, my excuse is that I'd expect to have a 'Click' attribute on objects instead/as well.

One of the things Atlas provides is a generic drag and drop (DND) API, which is used by a few behaviors such as the DragDropList and DataSourceDropTarget. Currently, Atlas implements 2 DND managers: a generic one that relies on 'the mouse API' (onmousedown, onmousemove, etc.), and an IE specific manager which abstracts the DND API that IE provides. When a page is loaded, Atlas will load the correct manager based on the browser that the client is using, so you don't need to specify which manager you want to use. Obviously, you don't need to write any browser specific code either.

I have put together a few examples that use the DND API. I put one of the demos online, which demonstrates what is involved to make controls draggable/rearrangeable. Please download the source to see a few more examples, to for example see how you can use DND to move data between data sources.

While you can already do some nifty things with it, the use of the DND API is sort of limited at this moment. One of the reasons is that the current drop targets (DragDropList and DataSourceDropTarget) don't raise any event when data is dropped.
Another reason is that DND is currently not integrated with some built-in controls such as ListView. While the current set of behaviors can be used fine (see other demos in the download) to _copy_ data using drag and drop, it isn't really possible to move data between ListViews. The reason for this is that the DragDropList expects a pretty strict structure of the control to which it is associated. For it to work properly, it would have to know more about the structure of its associated control. In the case of the ListView, it is really only the ListView which knows about the exact structure to make it possible to re-arrange elements.
Lastly, the DragDropList currently only allows to re-arrange elements when the datatype is set to HTML. This makes it impossible to have multiple 'regions', which would for example be useful if you wanted to do something similar to my.msn.com: re-arrange categories _and_ re-arrange webparts.

Hopefully this sheds some light on the DND API in Atlas.

Today, I have spent most of my time working on a new Virtual Earth Atlas control. I wanted to add support for drawing shapes on top of a map, to make it easy to for example build a driving directions app. While the Virtual Earth control doesn't support this stuff directly, unlike Google Maps, I saw that the VE API provides pretty much all of the plumbing. As always, you will want to look at the results. It should work in at least IE and Firefox (I only tested those). Don't forget to check out the link to the source-code behind that page to get a sense of how much code was involved in putting that page together.

During the next few days I will probably extend the control a bit to actually add proper pushpin support, add a fancy map controller and a few other things. I may even write a proper demo if I could get some useful data. If there is interest, I could talk a bit more on how I put this thing together (client-side and server-side). Either way, I will try to make the binaries and source available within a few days.

Update:
You can now download the demo app, which includes client-side and server-side source code. Instead of writing an article about the whole thing, I'd like to ask you to leave behind a comment to let me know what it is you would like to know. This may range from the (current) server-side Atlas infrastructure to extending the Virtual Earth control.

Nikhil Kothari put together a pretty awesome application, called Virtual Places, to show off how much (or little) is involved to take web applications to the next level. Take a look at the source code (HTML/XML script) to get a sense of how the demo was implemented.

By the way, it looks like Nikhil used the ASP.NET 'Atlas' October Technology Preview to put his demo together.

When an Atlas enabled page is loaded, several things need to be set up. In a nutshell, it goes like this:

  1. Based on the browser, load the first browser compatibility layer
  2. Load the Atlas core
  3. Based on the browser, load the second browser compatibility layer
  4. Process the Atlas markup
  5. Initialize all components

Step 1: Load the first browser compatibility layer
One of the key features of Atlas is to provide a unified library where you no longer have to worry about the capabilities of the browser when it comes to core features, such as attaching event handlers. Having such a unification allows for more compact and more readable code. Therefore, it makes sense to have this unification in place as soon as possible when the page is loaded, so that you (and Atlas) can rely on this practically everywhere. For this specific reason, the first compatibility layer is the first thing that is loaded. It is only loaded when the browser does not support certain "API's", such as HTMLElement.attachEvent, that is supported by IE and is commonly used. Since IE is still the most used (and targetted) browser, it probably makes sense to optimize for IE and provide compatibility layers for other, less used browsers instead. As a result, you don't see compatibility layers for IE in the current release (let's forget about the _loadIECompatLayer in AtlasCore.js, which really only determines which XMLHTTP object it should use).
The server-side ScriptManager control takes care of including the browser compatibility layers, by looking at what browser the client uses. If you don't use the server controls, you could use the Script server-control, which has a Browser property that you can set to conditionally include a script.

Step 2: Load the Atlas core
Another key feature of Atlas is that it brings certain object-oriented programming concepts to the client, such as classes, interfaces, delegates and events. If you think this allows for certain techniques to work in the same way as you may be used to in other languages (such as C#), you thought right. Right after the first level of unification is in place, the AtlasCore is loaded by the browser. Inside this script, Atlas will extend certain JavaScript objects, such as Function and String, to practically create a type system, and to add a level of convenience (for example strings get a format function which is similar to .NET's String.Format). The emulated type system is mostly built on top of JavaScript objects Function and Object, and includes functions for registering namespaces/classes and testing whether a type implements a certain interface.
Once the type system and other extensions are in place, the rest of the core library is loaded. This includes registering several classes, such as Web.Component and Web.Net.WebRequest (the core for all 'AJAX' related things in Atlas). It also includes instantiating several (singleton) objects that will be available until the page is unloaded, such as a debugger (with basic debugging/tracing capabilities) and an application which parses the markup once the page is completely loaded (i.e. window's onload event is raised).
Lastly, there is a TypeDescriptor that is used to keep track of all types that can be used declaratively.

Step 3: Based on the browser, load the second browser compatibility layer
In the current release of Atlas, there is another browser compatiblity layer, which is really only used for Safari at this point. This layer takes some 'side effects'/'features' of Safari into account. For example, Safari converts all tags to uppercase when a string is parsed into a DOM. Atlas (and JavaScript) is case sensitive, which means that Atlas markup would not work in Safari, unless a workaround would be implemented. In the second browser compatibility layer, the registration functions for registering declaratively usable types and properties are extended. Specifically, Atlas will start registering stuff twice, in both original casing and the casing that Safari converts to, to allow for Safari's intelligence.
After the extensions have been made, all types that were already registered will be registered again, to ensure that the new registration process is applied to previously registered types as well.

Step 4: Process the Atlas markup
After the core is loaded, and most browser features have been unified, the browser is practically done loading the page. It therefore will raise window's onload event. As I mentioned before, the application handles this event by parsing the Atlas markup.
It does so by finding the first Atlas script tag. When this script tag is found, all the declaratively defined script references are resolved. After all references have been resolved, the markup of the objects will be parsed.
The application will go through the children of the top level components element, and do a lookup in a table with mappings between tag names and type names. This table was populated with all types that called Web.TypeDescriptor.addType to allow themselves to be used in markup. For each type, Atlas will look if the type has a (static) 'parseFromMarkup' implementation. If it does not, it recursively does the same for the base type. Once it found a parse function, it will call it and provide the current xml element and the markup context. From there on, the function is responsible for processing the entire markup in this element.
The markup context stores all object references and is used to find objects within a specific scope. This is also the basis for resolving references between objects. At this moment, there is a top-level markup context (with all global objects), and a markup context for each template instance (for example in a ListView scenario). Because sometimes you want to be able to reference global objects from within a template, markup contexts can be parented. When an object inside an item template references a global object, Atlas will resolve this reference by first trying to find this object in the item template's markup context, and if it's not there, recursively try to find it in the parent markup context.
Generally, an implementation of 'parseFromMarkup' starts by instantiating the type based on the tag name of the xml node. When a type implements Web.ISupportBatchedUpdates, it will call its 'beginUpdate' function. This gives the instance a way of knowing if it is being initialized, so it can potentially delay certain logic in for example a property's setter. Right after that, all properties/events will be set. The attributes that were declared, will be parsed as property values or event handlers. Properties that can reference other objects in markup will not be set directly. Instead, the instance will be registered with the property in the markup context, and the reference will be resolved once all markup has been processed.
Properties that are declared as elements, will be parsed using logic similar to the way the application started parsing markup. Each property will be set to the instance (or instances if the property is an array) that are a result of parsing the child nodes of the property element.
Events will be parsed in a similar way, except that child nodes should result in action instances, that will be added as actions to the event.
After the instance has been (recursively) processed, the instance will be registered in the markup context as an object where the Web.ISupportBatchedUpdates's 'endUpdate' function still needs to be called. Most objects implement this function by actually initializing themselves. Therefore, the 'endUpdate' function of each object will be called after all markup has been parsed, and after all object references have been resolved.
Templates are not part of the initial parsing process. Controls such as ListView can use a template instance to create new instances of the types defined in the markup of the template. Ofcourse, the same parsing logic (and rules) applies when templates are parsed.
While this covers most of the markup parsing logic, there is one more thing I should mention. In some cases, there are non-user defined references between objects. For example, a behavior has a reference to its control, which it needs when it is initialized. To ensure that an object is guaranteed to have a reference to a different object, Atlas provides a component collection object. An instance of a component collection can be created by calling Web.Component's static 'createCollection' function. As an argument, you can pass an owner object. Web.UI.Control for example passes a reference to itself when it creates the behavior collection. Whenever an object is added to this collection, the owner object will be passed to the setOwner function of the added object.

Step 5: Initialize all components
In the previous step, all the objects were instantiated, and only references to elements were resolved. References to objects in markup were not resolved, because that would limit declaratively defined objects to only refer to objects that were previously declared. After all the objects have been instantiated, all references can be resolved.
Finally, the 'endUpdate' function will be called on all objects that implemented Web.ISupportBatchedUpdates, to actually initialize each object.

Hopefully this post gave you a better idea of how an Atlas page is constructed. Please leave a comment if you have feedback and/or questions.

Since there is currently a lack of documentation for each type, it may be hard to find out what kind of stuff you can actually do with certain types. For example, if you want to know to what properties you could bind when binding a Label, you really have to take a quick look at the source and find it out yourself.

I've put together a little Atlas Class Browser which lets you quickly find a type and display its member information. Hopefully this makes it a bit easier to understand the capabilities of the current types in Atlas.

Update: I have updated the class browser with support for viewing the source code as well. The formatting may be far from perfect, but I thought it would be better than nothing.

Atlas provides a binding model which differs from the data-binding model in ASP.NET. It is a flexible binding mechanism, somewhat similar to the one provided by WPF (Windows Presentation Foundation), that basically lets you bind any property of 1 component to any property of another object. Therefore we call them bindings, instead of data-bindings, since we can for example even bind the style of 1 control to a property of another control.

Let's first look at a basic example that binds the value of a textbox to a label. Whenever we change the text in the textbox, we want to update the label's text property accordingly.

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 

HTML:
[...]
<input id="nameTextBox" type="textbox" /><br />
<span id="nameLabel"></span>
[...]

Atlas:
<script type="text/xml-script">
  <page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
    <components>
      <!-- 'Upgrade' the HTML textbox to an Atlas textbox. -->
      <textBox id="nameTextBox" />

      <!-- 'Upgrade' the HTML span to an Atlas label. -->
      <label id="nameLabel">
        <bindings>
          <binding dataContext="nameTextBox" dataPath="text" property="text" />
        </bindings>
      </label>
    </components>
  </page>
</script>

So, what does Atlas do to make this this possible? First of all, Atlas needs a way to listen to property value changes (unless we don't use automatic bindings). There is an INotifyPropertyChanged interface, which is similar to the one provided by .NET. Objects can implement this interface to let other objects listen to property value changes. The Atlas component class (base class for components such as controls and behaviors) implements this interface. Additionally, the component class has a function 'raisePropertyChanged(propertyName)' which you should call in every property's setter, to raise the INotifyPropertyChanged.propertyChanged event.

With this knowledge, lets look at a few parts of the TextBox implementation to learn how we should raise the propertyChanged event when an event of the HTML element was raised.

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 

[...]
var _text;
var _changeHandler;

this.get_text = function() {
  return this.element.value;
}

this.set_text = function(value) {
  if (this.element.value != value) {
    _text = value;
    this.element.value = value;
    this.raisePropertyChanged('text');
  }
}
this.initialize = function() {
  Web.UI.TextBox.callBaseMethod(this, 'initialize');
  _text = this.element.value;
  _changeHandler = Function.createDelegate(this, this._onChanged);
  this.element.attachEvent('onchange', _changeHandler);
  [...]
}

// Handles changes in the underlying HTML control.
this._onChanged = function() {
  if (this.element.value != _text) {
    _text = this.element.value;
    this.raisePropertyChanged('text');
  }
}
[...]

As you can see, we raise the propertyChanged event whenever the text in our textbox changes. This makes it possible to actually bind against this property.

When we create a binding, the binding will look at both the 'source' object (the data context) and the target object (on which you defined the binding) to see if it implements INotifyPropertyChanged. If the source object does, the binding will handle its propertyChanged event by evaluating the 'incoming' binding, if the property you specified in the dataPath was the property that changed and the direction is set to In or InOut.
If the target object implements INotifyPropertyChanged, its propertyChanged event will be handled by evaluating the 'outgoing' binding, if it is the property you defined on the binding and if the direction is set to Out or InOut.

Lastly, lets have a look at the public properties and methods of a binding, and see what they are good for. I got this list by looking at the getDescriptor implementation of Web.Binding in AtlasCore.js.

  • Property 'automatic': Specifies whether the binding should automatically be evaluated whenever the property of the source object's property was changed (In, InOut), or the target object's property was changed (Out, InOut). Sometimes you want to control when the binding should be evaluated. For example, you only want to evaluate a binding when a ServiceMethodRequest completed the request so it actually has data. You can do this by setting automatic to false, and explictly invoke the evaluateIn method of the binding. The default value is 'true'.
  • Property 'dataContext': Specifies the source object which has a property that you want to bind to. You can set this to another object, such as a TextBox control. If you don't set this, the binding will call its owner's dataContext property. Controls implement this property by either returning the dataContext that was set, or by returning its parent control's dataContext. Controls such as the ListView take care of setting the dataContext to for example a DataRow object when it creates ListView items, which makes it possible to bind against data items.
  • Property 'dataPath': The source object's property that you want to bind to. You can bind against 'nested properties' as well, using the following syntax: 'sourceObjectProperty.nestedProperty.anotherNestedProperty'. The binding will split this path and run code similar to:
      'var value = mySourceObject.getProperty('sourceObjectProperty')
      .getProperty('nestedProperty')
      .getProperty(anotherNestedProperty)'
    The default value is nothing, which means you will actually bind to the source object itself.
  • Property 'direction': Lets you specify whether you want to listen for changes (In), propagate changes (Out), or both (InOut). The default value is 'In'.
  • Property 'property': The target object's property that you want to bind to. You should always set this property.
  • Property 'propertyKey': Sometimes you want to bind to a member of the property you specified in the 'property' property. For example, you may want to bind to the style's property 'width'. You can do so by setting the property 'property' to style, and set the property 'propertyKey' to 'width'.
  • Property 'transformerArgument': An argument that will be passed to a transformer.
  • Event 'transform': This event lets you apply a transformation during the evaluation of a binding. This can be useful if you for example want to display a bound value differently in the target object. There are several built-in transformers, such as Add, which can be useful to add a value (specified using the property 'transformerArgument') to the bound value. This is useful if you are binding to an index, which generally start at 0, and you want to display them starting at 1.
  • Method 'evaluateIn': Evaluates the 'incoming' binding, if the direction was set to In or InOut. It does so by actually getting the value of the source object's property (based on the dataContext and dataPath values), and call the target object property's setter with this value.
  • Method 'evaluateOut': Similar to evaluateIn, except that it works the other way round, and only works when the direction is set to Out or InOut.

Hopefully this answers some questions with relation to bindings. Please leave a comment with any questions that you may have, or leave behind a suggestion for a different aspect of Atlas that you want me to zoom in on.

I've put together a little KeyboardHandler component that let's you declaratively handle keyboard events (up/down/press). This should make it easy to for example allow a user to navigate through the details of listview items by using the keyboard. Please look at the working demo or download the source.

Today, someone asked a question on how to create a master-detail, where the detail was actually related information. Specifically, this person had a list of products, and you could drill-down by clicking on 'comments' to see the comments for that product.

To do this, you could create 2 data sources: the products datasource and the comments datasource. When someone clicks on a comment button of a product, we should first set a ProductID parameter on the comments datasource, which it will pass to the select method on the server. After this property is set, we should actually invoke the select method of that datasource. That's it, basically. The code for this should be something like this:

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 

<dataSource id="productDataSource" serviceURL="Products.asmx" />
<dataSource id="commentsDataSource" serviceURL="Comments.asmx" />

<listView id="productListView" ....>
  <bindings>
    <binding dataContext="productDataSource" dataPath="data" property="data" />
  </bindings>
  [...]
      <label id="nameLabel">
        <bindings>
          <binding dataPath="ProductName" property="text" />
        </bindings>
      </label>
      <button id="commentsButton">
        <click>
          <setProperty target="commentsDataSource" property="selectParameters" 
            propertyKey="ProductID">
            <bindings>
              <binding dataPath="sender.dataContext.ProductID" property="value" />
            </bindings>
          </setProperty>
          <invokeMethod target="commentsDataSource" method="select" />
        </click>
      </button>
  [...]
</listView>
[...]
<listView id="commentsListView">
  <bindings>
    <binding dataContext="commentsDataSource" dataPath="data" property="data" />
  </bindings>
  [...]
</listView>
[...]

The interesting bit is probably the dataPath in our binding between ProductID and value. Actions (such as setProperty and invokeMethod) have their own data context, to allow you to bind to for example event arguments for the event that was raised. However, we are interested in the data context that holds the product data. There are really at least two ways you can get to that data context: either by using the sender's data context (as shown above), or by explicitly setting the data context of our binding to for example our button control. The latter would look like this:

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 

[...]
      <button id="commentsButton">
        <click>
          <setProperty target="commentsDataSource" property="selectParameters" 
            propertyKey="ProductID">
            <bindings>
              <binding dataContext="commentsButton" dataPath="dataContext.ProductID" property="value" />
            </bindings>
          </setProperty>
          <invokeMethod target="commentsDataSource" method="select" />
        </click>
      </button>
[...]

Hopefully this answers some questions with relation to bindings and ways to create a drill-down scenario.

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.

Someone on the Atlas forums had a great idea to show customer data in a ListView and display a map which centers to the location of the customer's country. I slightly modified his idea and came up with a demo which demonstrates several concepts such as bindings and actions.

Before we go through the code, you probably should take a look at the results first. You may also be interested in the source code for this demo. Note: I use a modified version of AtlasUIMap.js which fixes a few bugs. Please use the AtlasUIMap.js from this download if you want to try and build this demo yourself (at least until Microsoft releases an update which fixes these issues).

Step 1: Build the service
Ok, so now you've seen what can be done, we can take a look at how we actually did it. The first is to create a simple data service which exposes our customer data. Since I find binding against a data source currently the easiest way to get data from the server in for example a ListView, I create a web service that derives from Microsoft.Web.Services.DataService.
The first method that we expose is a data-service method that the DataSource uses to select data. We have to decorate it with a DataObjectMethod attribute, so the Atlas proxy for the DataSource knows which method to call to select data. This method returns an array of Customer objects. We can define such an object simply by creating it, and decorate the properties that should be accessible on the client with the DataObjectField attribute.
The second method we expose is to return suggestions to the auto complete textbox. It is important to note that this signature needs to be pretty exact. Especially the parameters (prefixText and count).

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

public class Customer {
    private string name;

    // Make this property accessible for the client-side.
    [DataObjectField(false)]
    public string Name {
        get {
            return this.name;
        }
    }
}

// Specify that this method is the data-selection method that 
// the DataSource proxy call.
[DataObjectMethod(DataObjectMethodType.Select)]
[WebMethod]
public Customer[] GetData(string country) {
    //
}

Step 2: Query the service
The next thing we should do is query the service. We can do this by adding a data source control to our page. Then add a TextBox control and point the AutoCompletion method to the method that we added to our service. After this is done, we can set up the binding between the DataSource and the TextBox, to specify what argument we want to pass to the service when we search for customers. To actually query the service, we should call the DataSource's select method when we press our search button. We can do this declaratively by using actions. Specifically, we use an InvokeMethod action, which can invoke a method of a component on our page.

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 

<atlas:DataSource ID="customerDataSource" runat="server" ServiceUrl="CustomerService.asmx">
    <Bindings>
        <atlas:Binding DataContext="countryTextBox" DataPath="text" 
            Property="selectParameters" PropertyKey="country" />
    </Bindings>
</atlas:DataSource>

<atlas:TextBox ID="countryTextBox" runat="server" AutoCompletionServiceUrl="CustomerService.asmx" 
    AutoCompletionServiceMethod="GetSuggestions" AutoCompletionMinimumPrefixLength="1" />
<atlas:Button runat="server" Text="Search">
    <Click>
        <Actions>
            <atlas:InvokeMethodAction Target="customerDataSource" Method="select" />
        </Actions>
    </Click>
</atlas:Button>

Step 4: Add a ListView to display the customers
We're almost there. To display the results (ie. the contents of the DataSource), we add the ListView control and define how it should display its data. Additionally, we also bind the ListView to the DataSource.

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 

<atlas:ListView ID="customerListView" runat="server" ItemTemplateControlID="customer">
    <Bindings>
        <atlas:Binding DataContext="customerDataSource" DataPath="data" Property="data" />
    </Bindings>
    <LayoutTemplate>
        <div id="customerContainer" runat="server">
            <div id="customer" runat="server">
                <atlas:Label runat="server">
                    <Bindings>
                        <atlas:Binding DataPath="Name" Property="text" />
                    </Bindings>
                </atlas:Label>
            </div>
        </div>
    </LayoutTemplate>
    <EmptyTemplate>
    No customers to display.
    </EmptyTemplate>
</atlas:ListView>

Step 5: Add a VirtualEarthMap to display the customers
Lastly, we add another view of our customer data, by adding a VirtualEarth control to our page. Again, all we need to do is add a binding and define what it should look like. Note that some properties like DataLatitudeField and DataLongitudeField default to 'Latitude' and 'Longitude' respectively, so you don't have to specify those if those are the names that you use in your data source.

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 

<atlas:VirtualEarthMap ID="map" runat="server" Latitude="22" Longitude="-100" 
    MapStyle="Hybrid" ZoomLevel="9" CssClass="map" DataValueField="ID" 
    DataTextField="Name" PushpinCssClass="pushpin" PushpinActivation="Hover">
    <Bindings>
        <atlas:Binding DataContext="customerDataSource" DataPath="data" Property="data" />
    </Bindings>
    <PopupTemplate>
        <div style="background-color: white; border: 1px solid black">
            <atlas:Label runat="server">
                <Bindings>
                    <atlas:Binding DataPath="Name" Property="text" />
                </Bindings>
            </atlas:Label>
        </div>
    </PopupTemplate>
</atlas:VirtualEarthMap>

Bonus step: Connect the customer in the ListView with the customer on the map
If you want, you can add a 'locate on map' button to the ListView, which takes you to the selected customer on the map. All you need to do is invoke a method on the map, 'panTo', and add 2 bindings to specify what arguments should be passed to this method (latitude and longitude values).

ASP.NET:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 

<atlas:Button runat="server" Text="Locate on map">
    <Click>
        <Actions>
            <atlas:InvokeMethodAction Target="map" Method="panTo">
                <Bindings>
                    <atlas:Binding DataPath="sender.dataContext.Latitude" 
                        Property="parameters" PropertyKey="latitude" />
                    <atlas:Binding DataPath="sender.dataContext.Longitude" 
                        Property="parameters" PropertyKey="longitude" />
                </Bindings>
            </atlas:InvokeMethodAction>
        </Actions>
    </Click>
</atlas:Button>

Hopefully you will find this useful to get some idea of what kind of cool stuff you can do with Atlas already. If you feel that I went through this way too quick, please leave behind a comment and let me know if there is anything in particular that you would like me to explain in more depth.