Dragging and dropping with ASP.Net 2.0 and Atlas
Introduction
This tutorial is intended to help readers understand how certain aspects
of Microsoft's new Atlas technology works. Atlas is intended to simplify
the development of Ajax-style functionality. As with all technology,
however, to use a tool well, it is important to understand the underlying
technology that Atlas abstracts. One of the key Atlas abstractions is
the new XML markup syntax developed to make coding with Atlas easier.
With XML markup, developers can modify their code declaratively.
However, there are times when a developer may want to be able to change her
code programmatically, and in order to accomplish this, she will need to
understand that underneath the markup abstraction, she is actually dealing
with good 'ol javascript and some custom javascript libraries developed by
Microsoft. In order to demonstrate the relationship between the Atlas
declarative model and the programmatic model, I will go through a series of
examples in which the same task will be accomplished both declaratively and
programmatically. I will be demonstrating how to use the
AtlasUIDragDrop library file to perform basic drag-drop operations as well
as setting up drop zones.
Background
As I write this, Atlas is still in beta and continues to change.
These examples apply to the April CTP of Atlas. Newer releases of Atlas may affect the accuracy of this tutorial.
I will attempt to update the code as new versions of Atlas become available.
Atlas only works with .Net 2.0.
I. Declarative Drag Drop
The first task is to use Atlas markup to add drag-drop behavior to a div tag. By drag and drop, I just mean the ability to drag and object and the have it stay wherever you place it. The more complicated behavior of making an object actually do something when it is dropped on a specified drop target will be addressed later in this tutorial. To configure your webpage to use Atlas, you will need to download the Microsoft.Web.Atlas.dll file from the Microsoft site into your bin folder and configure your web.config file with the following entry:
<system.web>
<pages>
<controls>
<add
namespace="Microsoft.Web.UI"
assembly="Microsoft.Web.Atlas"
tagPrefix="atlas"/>
<add
namespace="Microsoft.Web.UI.Controls"
assembly="Microsoft.Web.Atlas"
tagPrefix="atlas"/>
</controls>
</pages>
</system.web>
You will need to add an Atlas Script Manager control to your .aspx page and configure it to use the AtlasUIDragDrop library file:
<atlas:ScriptManager
ID="ScriptManager1"
runat="server">
<Scripts>
<atlas:ScriptReference
ScriptName="AtlasUIDragDrop"
/>
</Scripts>
</atlas:ScriptManager>
Add the div object you want to to make draggable, and make sure it has a
drag handle:
<div
style="background-color:Red;height:800px;width:600px;">
<div
id="draggableDiv"
style="height:100px;width:100px;background-color:Blue;">
<div
id="handleBar"
style="height:20px;width:auto;background-color:Green;">
</div>
</div>
</div>
Finally, add the markup script that will make your div draggable:
<script
type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<components>
<control id="draggableDiv">
<behaviors>
<floatingBehavior handle="handleBar"/>
</behaviors>
</control>
</components>
</page>
</script>
And with that, you should have a draggable div tag. The example demonstrates the simplicity and ease of using the declarative model with Atlas. In the terminology being introduced with Atlas, you have just used declarative markup to add the floating behavior to an html element.
II. Imperative Drag Drop
To accomplish the same thing using a programmatic model requires a bit more code, but not much more. It is important to understand that when you add an Atlas Script Manager component to your page, you are actually giving instructions to have the Atlas javascript library loaded into your page. The Atlas library, among other things, provides client-side classes that extend the DOM and provide you with tools that allow you to code in a browser agnostic manner (though there currently are still issues with Safari compatibility). These client-side classes also allow you to add behaviors to your html elements.
To switch to an imperative model, you will need to replace the xml markup with two javscript functions. The first one is generic script to add floating behavior to an html element. It leverages the Atlas client-side classes to accomplish this:
<script
type="text/javascript">
function
addFloatingBehavior(ctrl, ctrlHandle){
//create new floating behavior object
var
floatingBehavior = new
Sys.UI.FloatingBehavior();
//floating behavior class has a Handle
property
floatingBehavior.set_handle(ctrlHandle);
//convert object reference to Atlas
client control
var dragItem =
new Sys.UI.Control(ctrl);
//get behaviors collection from Atlas
control and add our floating behavior
dragItem.get_behaviors().add(floatingBehavior);
//run the floating behavior's internal
javascript
floatingBehavior.initialize();
}
</script>
The function takes two parameter values; the html element that you want to make draggable, and the html element that is the drag handle for the dragging behavior. Next, you instantiate a new Atlas client-side behavior object. The floating behavior has a property called "handle", to which you pass the handle html element. You then need to create a new client-side control object based on the control you want to make draggable. Turning your div tag into an Atlas client-side control enables you to add Atlas behaviors to it. You use the "get_behaviors()" method to return a collection of behaviors, and the add method to add a new behavior to your html object. Finally, you call the "initialize()" method of the behavior object to allow the behavior to configure itself internally. This utility function will be used throughout the rest of this tutorial.
Now you need to call the "addFloatingBehavior" function when the page loads. This, surprisingly, was the hardest part about coding this example. The Script Manager doesn't simply create a reference to the Atlas javascript libraries, and I have read speculation that it actually loads the library scripts into the DOM. In any case, what this means is that the libraries get loaded only after everything else on the page is loaded. The problem for us, then, is that there is no standard way to make our code that adds the floating behavior run after the libraries are loaded; and if we try to run it before the libraries are loaded, we simply generate javascript errors, since all of the Atlas methods we call can't be found.
There are actually a few workarounds for this, but the easiest one is to use a custom Atlas event called "pageLoad()" that actually only gets called after the libraries are loaded. To add the floating behavior to your div tag when the page is first loaded (but after the library scripts are loaded) you just need to write the following:
<script
type="text/javascript">
function pageLoad(){
addFloatingBehavior(document.getElementById('draggableDiv'),document.getElementById('handleBar'));
}
</script>
which in turn can be written this way, using an Atlas script shorthand that replaces "document.getElementById()" with "$()":
<script
type="text/javascript">
function pageLoad(){
addFloatingBehavior($('draggableDiv'),$('handleBar'));
}
</script>
And once again, you have a draggable div that behaves exactly the same as the draggable div you wrote using the declarative model.
III. Dynamic Drag Drop
Since the declarative model is much cleaner than the imperative model, why would you ever want to write your own javascript to handle Atlas behaviors? You might want to roll your own javascript if you want to add behaviors dynamically. One limitation of the declarative model is that you can only work with objects that are initially on the page. If you start adding objects to the page dynamically, you cannot add the floating behavior to them using the declarative model. With the imperative model, on the other hand, you can.
Building on the previous example, you will replace the "pageLoad()" function with a function that creates floating divs on demand. The following javascript function will create a div tag with another div tag embedded to use as a handlebar, then insert the div tag into the current page, and finally add floating behavior to the div tag:
function createDraggableDiv() {You will then just need to add a button to the page that calls the "createDraggableDiv()" function. The new html body should look something like this:
<
input type="button" value="Add Floating Div" onclick="createDraggableDiv();" />This will allow you to add as many draggable elements to your page as you like, thus demonstrating the power and flexibility available to you once you understand the relationship between using Atlas declaratively and using it programmatically. As a point of reference, here is the complete code for the dynamic drag and drop example:
<%@
Page
Language="C#"
%>
<!DOCTYPE
html
PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
>
<head
runat="server">
<title>Imperative
Drag and Drop II</title>
<script
type="text/javascript">
function
createDraggableDiv() {
var panel=
document.createElement("div");
panel.style.height="100px";
panel.style.width="100px";
panel.style.backgroundColor="Blue";
var panelHandle =
document.createElement("div");
panelHandle.style.height="20px";
panelHandle.style.width="auto";
panelHandle.style.backgroundColor="Green";
panel.appendChild(panelHandle);
var target = $('containerDiv').appendChild(panel);
addFloatingBehavior(panel, panelHandle);
}
function
addFloatingBehavior(ctrl, ctrlHandle){
var
floatingBehavior = new
Sys.UI.FloatingBehavior();
floatingBehavior.set_handle(ctrlHandle);
var dragItem =
new Sys.UI.Control(ctrl);
dragItem.get_behaviors().add(floatingBehavior);
floatingBehavior.initialize();
}
</script>
</head>
<body>
<form
id="form1"
runat="server">
<atlas:ScriptManager
ID="ScriptManager1"
runat="server">
<Scripts>
<atlas:ScriptReference
ScriptName="AtlasUIDragDrop"
/>
</Scripts>
</atlas:ScriptManager>
<h2>Imperative
Drag and Drop Code with javascript: demonstrate dynamic loading of behaviors</h2>
<input
type="button"
value="Add
Floating Div"
onclick="createDraggableDiv();"
/>
<div
id="containerDiv"
style="background-color:Purple;height:800px;width:600px;"/>
</form>
</body>
</html>
IV. Declarative Dropzones
Being able to drag html elements around a page and have them stay where you leave them is visually interesting. To make this behavior truly useful, though, an event should be thrown when the drop occurs. Furthermore, the event that is thrown should depend on where the drop occurs. In other words, there needs to be behavior that can be added to a given html element that will turn it into a "dropzone" or a "drop target", the same way that the floating behavior can be added to an html div tag to turn it into a drag and drop element.
In the following examples, I will show how Atlas supports the concept of
dropzones. In its current state, Atlas does not support an out-of-the-box
behavior for creating dropzone elements in quite the same way it does for
floating elements. It does, however, implement behaviors for a
dragdroplist element and a draggablelistitem element which, when used together,
allow you to create lists that can be reordered by dragging and dropping.
If you would like to explore this functionality some more, there are several good examples of using the dragDropList behavior on the
web, for instance at
The main disadvantage of the dragdropzone behavior is that it only works with items that have been decorated with the dragdroplist behavior. The functionality that this puts at your disposal is fairly specific. To get the sort of open-ended dropzone functionality I describe above, that will also work with the predefined floating behavior, you will need to write your own dropzone behavior class in javascript. Fortunately, this is not all that hard.
Atlas adds several OOP extensions to JavaScript in order to make it more powerful, extensions such as namespaces, abstract classes and interfaces. You will take advantage of these in coding up your own dropzone behavior. If you peer behind the curtain and look at the the source code in the AtlasUIDragDrop.js file (you can do this with the Visual Studio debugger), you will find several interfaces defined there, including one for Sys.UI.DragSource and one for Sys.UI.DropTarget. In fact, both the floatingBehavior class and the draggableListItem class implement the Sys.UI.DragSource interface, while Sys.UI.DropTarget is implemented by the dragDropList class. The code for these two interfaces looks like this:
Sys.UI.IDragSource =
function() {
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;
}
Sys.UI.IDropTarget.registerInterface('Sys.UI.IDropTarget');
Why do you need to implement these interfaces instead of simply writing out brand new classes to support drag, drop and dropzones? The secret is that behind the scenes a third class, called the DragDropManager, is actually coordinating the interactions between draggable elements and dropzone elements, and it only knows how to work with classes that implement IDragSource or the IDropTarget. The DragDropManager class registers which dropzones are legitimate targets for each draggable element, handles the mouseover events to determine when a dropzone has a draggable element over it, and a hundred other things you do not want to do yourself. In fact, it does it so well that the dropzone behavior you are about to write is pretty minimal. First, create a new JavaScript file called DropZoneBehavior.js. I placed my javascript file under a subdirectory called scriptLibrary, but this is not necessary in order to make the dropzone behavior work. Next, copy the following code into your file:
Type.registerNamespace('Custom.UI');
Custom.UI.DropZoneBehavior =
function() {
Custom.UI.DropZoneBehavior.initializeBase(this);
this.initialize
= function() {
Custom.UI.DropZoneBehavior.callBaseMethod(this,
'initialize');
// Register ourselves as a drop
target.
Sys.UI.DragDropManager.registerDropTarget(this);
}
this.dispose
= function() {
Custom.UI.DropZoneBehavior.callBaseMethod(this,
'dispose');
}
this.getDescriptor
= function() {
var td =
Custom.UI.DropZoneBehavior.callBaseMethod(this,
'getDescriptor');
return td;
}
// IDropTarget members.
this.get_dropTargetElement
= function() {
return
this.control.element;
}
this.drop =
function(dragMode,
type, data) {
alert('dropped');
}
this.canDrop
= function(dragMode,
dataType) {
return
true;
}
this.onDragEnterTarget
= function(dragMode,
type, data) {
}
this.onDragLeaveTarget
= function(dragMode,
type, data) {
}
this.onDragInTarget
= function(dragMode,
type, data) {
}
}
Custom.UI.DropZoneBehavior.registerClass('Custom.UI.DropZoneBehavior',
Sys.UI.Behavior, Sys.UI.IDragSource, Sys.UI.IDropTarget, Sys.IDisposable);
Sys.TypeDescriptor.addType('script',
'DropZoneBehavior',
Custom.UI.DropZoneBehavior);
I need to explain this class a bit backwards. The first thing worth noticing is the second to last line that begins "Custom.UI.DropZoneBehavior.registerClass." This is where the dropZoneBehaviorClass defined above gets registered with Atlas. The first parameter of the registerClass method takes the name of the class. The second parameter takes the base class. The remaining parameters take the interfaces that are implemented by the new class. The line following this makes your class available tfor declarative markup scripting. Now back to the top, the "Type.registerNamespace" method allows you to register your custom namespace. The next line declares our new class using an anonymous method syntax. This is a way of writing JavaScript that I am not particularly familiar with, but is very important for making JavaScript object oriented, and is essential for designing Atlas behaviors. Within the anonymous method, the class methods initialize, dispose, and getDescriptor are simply standard methods used for all behavior classes, and in this implementation, all you need to do is call the base method (that is, the method of the base class that you specify in the second to last line of this code sample.) The only thing special you do is to register the drop target with the Sys.UI.DragDropManager in the initialize method. This is the act that makes much of the drag drop magic happen.
Next, you implement the IDropTarget methods. In this example, you are only implementing two methods, "this.canDrop" and "this.drop". For "canDrop", you are just going to return true. More interesting logic can be placed here to determine which floating div tags can actually be dropped on a given target, and even to determine what sorts of floating divs will do what when they are dropped, but in this case you only want a bare-bones implementation of IDropTarget that will allow any floating div to be dropped on it. Your implementation of the "drop" method is similarly bare bones. When a floating element is dropped on one of your drop targets, an alert message will be thrown indicating that something has occurred. And that's about it. You now have a drop behavior that works with the floating behavior we used in the previous examples.
You should now write up a page to show off your new custom dropzone behavior. You can build on the previous samples to accomplish this. In the Atlas Script Manager, besides registering the AtlasUIDragDrop script, you will also want to register your new DropZoneBehavior script:
<atlas:ScriptManager
ID="ScriptManager1"
runat="server">
<Scripts>
<atlas:ScriptReference
ScriptName="AtlasUIDragDrop"
/>
<atlas:ScriptReference
Path="scriptLibrary/DropZoneBehavior.js"
/>
</Scripts>
</atlas:ScriptManager>
Next, you will want to add a new div tag to the html body that can be used as a drop target:
<div
style="background-color:Red;height:200px;width:200px;">
<div
id="draggableDiv"
style="height:100px;width:100px;background-color:Blue;">
<div
id="handleBar"
style="height:20px;width:auto;background-color:Green;">
</div>
</div>
</div>
<div
id="dropZone"
style="background-color:cornflowerblue;height:200px;width:200px;">
Drop Zone
</div>
Finally, you need to add a declarative markup element to add your custom DropZone behavior to the div you plan to use as a dropzone element. The xml markup should look like this:
<script
type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<components>
<control id="dropZone">
<behaviors>
<DropZoneBehavior/>
</behaviors>
</control>
<control id="draggableDiv">
<behaviors>
<floatingBehavior handle="handleBar"/>
</behaviors>
</control>
</components>
</page>
</script>
The code you have just written should basically add a drop zone to the original declarative drag and drop example. When you drop your drag element on the drop zone, an alert message should now appear. You can expand on this code to make the drop method of your custom dropzone behavior do much more interesting things, such as firing off other javascript events in the current page or even calling a webservice, using Atlas, that will in turn process server-side code for you.
V. Imperative Dropzones
To create dropzones using JavaScript instead of declarative script, just add the following JavaScript function to initialize your dropzone element with the custom dropzone behavior:
function
addDropZoneBehavior(ctrl){
var dropZone =
new Sys.UI.Control(ctrl);
var
dropZoneBehavior = new
Custom.UI.DropZoneBehavior();
dropZone.get_behaviors().add(dropZoneBehavior);
dropZoneBehavior.initialize();
}
<%
@ Page Language="C#" %>Besides the dropzone behavior, you may want to also write your own floating behavior. For instance, by default, elements decorated with the floating behavior simply stay where you drop them. You may want to extend this so that your floating div will snap back to its original location when you drop it outside of a drop zone. Additionally, you may want to change the way the dragged element looks while you are dragging it, either by making it transparent, changing its color, or replacing the drag image altogether. All this can be accomplished by creating a behavior that implements the IDragSource interface in the same way you created a custom class that implements the IDropTarget interface. This tutorial should provide you with a starting point for extending the basic drag drop functionality provided with Atlas to create your own behaviors and provide your own functionality. The next step is to build on this to create controls. Using this tutorial as a starting point, you can go on to create Atlas extender controls that implement your behaviors using declarative markup, or even turn around and create server-side controls that automatically create html elements with Atlas behaviors, with the choice being, once again, one between creating server-side controls that are either declarative, and consequently static, or imperative, and consequently slightly more complex but also more flexible. This is a topic that is much too large for the current tutorial; however I hope that in the near future someone will try to do for server-side Atlas programming what this tutorial attempts to do for client-side Atlas scripting.