AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1300&pId=-1
Custom Client Side Drag and Drop Behavior in ASP.NET AJAX
page
by Lifu Chen
Feedback
Average Rating: 
Views (Total / Last 10 Days): 65287/ 153

Introduction

In traditional desktop applications, the Drag & Drop feature is widely used - drag and drop a music file into media player will just add it to the play list or start playing, drag and drop a email into junk mail folder means you want to delete it, drag and drop files between folders will move or copy them. Drag & Drop provides a very friendly way for the end user to bind two applications or two parts of an application together.

But in the browser world, implementing the Drag & Drop feature is much more difficult; JavaScript does not have the built-in support for that common scenario. Fortunately, ASP.NET AJAX introduced an excellent, easy-to-use Drag & Drop framework for client side developers, which allows the developer to simply implement two interfaces and then everything else's done by the framework internally.

This article will introduce the great Drag & Drop framework and show you how to build applications on top of it by a simple but attractive demo.

Pre-Requirements

·         Windows XP, Windows Server 2003 or Windows Vista (with IIS installed and ASP.NET 2.0 properly configured)

·         Visual Studio 2005 (Any version will be ok. The free Express Edition can be downloaded here.)

·         Microsoft ASP.NET AJAX Framework

·         Microsoft ASP.NET Futures CTP

To follow along the demo below, you may also need to download the source code linked at the bottom of this article as a reference.

Understanding ASP.NET AJAX Client Side Drag & Drop Architecture

The Drag & Drop framework of ASP.NET AJAX is defined in PreviewDragDrop.js JavaScript file, which is embedded in the "Futures" assembly - Microsoft.Web.Preview.dll. So if you want to use this feature or create your own behaviors on top of it, you have to send this JavaScript file (as well as the PreviewScript.js file, which works as a support layer for other "Futures" scripts) to the client side via ScriptManager server side control.

Listing 1 - Use ScriptManager Control to Send ASP.NET AJAX Client Side JavaScript Files

<asp:ScriptManager ID="sm" runat="server">
    <Scripts>
        <asp:ScriptReference Assembly="Microsoft.Web.Preview" 
            Name="PreviewScript.js" />
        <asp:ScriptReference Assembly="Microsoft.Web.Preview" 
            Name="PreviewDragDrop.js" />
    </Scripts>
</asp:ScriptManager>

Basically, there are two important concepts in ASP.NET AJAX client side Drag & Drop framework.

·         Draggable items and Drop targets: Draggable items are DOM elements that can be moved around the page, while drop targets are DOM elements that act as "containers" for draggable items. The ASP.NET AJAX Drag & Drop framework allows defining draggable elements by implementing the IDragSource interface. A drop target is, instead, a class (usually inherits from Sys.UI.Behavior) that implements the IDropTarget interface. Figure 1 shows the draggable items and drop targets on the www.start.com web site. It is also possible to create a class that implements both the IDragSource and the IDropTarget interfaces. We will have a control that acts both as a draggable item and a drop target. An example of such control is a "reorder list," which allows user to reorder the items in the list via Drag & Drop operations. Find a live demo at http://ajax.asp.net/ajaxtoolkit/ReorderList/ReorderList.aspx.

·         DragDropManager: DragDropManager is an object with global scope that is instantiated at runtime after ASP.NET AJAX client side framework is initialized. This object can be accessed through the reference Sys.Preview.UI.DragDropManager directly and is generally used to start dragging operations and to register/unregister drop targets. Once started, the DragDropManager handles all the Drag & Drop operations by calling methods defined in IDragSource and IDropTarget interfaces.

Figure 1 - Draggable items and Drop targets

So, the steps we needed to create a drag & drop UI could be:

·         Create draggable items by implementing the IDragSource interface. The class that implements this interface is also responsible to call the Sys.Preview.UI.DragDropManager.startDragDrop() method to start the dragging operation. (Typically, this is done in an event handler for the mousedown event of the control's DOM element.) Each draggable item has its own dataType, which is an identifier that allows to group draggable items.

·         Create drop targets by implementing the IDropTarget interface. A class that implements this interface is responsible for registering the drop target by invoking the Sys.Preview.UI.DragDropManager.registerDropTarget() method. Each drop target has a list of acceptable data types, which specifies which "types" of draggable items can be dropped on that target.

In summary, a Drag & Drop operation is typically invoked by an IDragSource object with the call to the startDragDrop() method of the DragDropManager. Then, the operation is handled by the DragDropManager, through calls to the IDragSource methods of the item being dragged and IDropTarget methods of the registered drop target(s).

Show off the Demo - Drag Products and Drop into Shopping Cart

Let us create a demo to see how to implement IDragSource and IDropTarget interfaces to customize the Drag & Drop behavior.

The demo application is an online store. Products are listed in the left part of the page while there is also a shopping cart floating on the top right corner.

Figure 2 - The Initial UI of the Online Store

The customer can just simply drag products from the left list and drop into the shopping cart to "buy" it, which makes the user feel as if he were in the supermarket! Cool, isn't it?

Figure 3 - Drag Products and Drop into the Shopping Cart

If the user releases the mouse button in Figure 3, the current product will be added to the shopping cart. Each time the user drags and drops the same product, the quantity of that product will increase by 1. Figure 4 shows the shopping cart after adding a few copies of the books.

Figure 4 - The Shopping Cart with a Few Books

When the user clicks the "Order" button in the shopping cart, client side application will send the products' ID and quantity back to the server to process the order. After processing, client side application will show user the response from the server.

Figure 5 - Processing the Order

Creating the Demo Web Site

After installing ASP.NET AJAX 1.0 and ASP.NET AJAX "Futures," you launch Visual Studio and create a new "ASP.NET AJAX CTP-Enabled Web Site," as shown in Figure 6.

Figure 6 - Creating New ASP.NET AJAX CTP-Enabled Web Site

Then Visual Studio will create a new Web Site using the selected template and you can find that Microsoft.Web.Preview.dll is located in Bin folder already, while the web.config configuration file is also configured properly to enable ASP.NET AJAX (including "Futures" part).

Manipulating Data - Web Services

A lot of server jobs can be moved to the client side in an Ajax application, but the server still has its own responsibility; in this demo application it is manipulating data, which means sending products information to the client side and processing customer's order.

Web Service is the ideal way to expose data to client side and ASP.NET AJAX asynchronies communication layer and it also has perfect built-in support for invoking Web Service from client side JavaScript.

Let us create a Web Service file called ShoppingService.asmx. Remember the [ScriptService] attribute which is required if you want ASP.NET AJAX asynchronies communication layer to generate client side JavaScript proxy for this Web Service. Then you could easily invoke methods defined in the Web Service using JavaScript from client side.

Listing 2 - ShoppingService Skelton

[System.Web.Script.Services.ScriptService]
public class ShoppingService : System.Web.Services.WebService
{
}

Add a private property Data to this Web Service class, which is used to represent the products in our online store. Here we just hard coded 5 ASP.NET 2.0 books as the products, but in the real world application, we usually get these items from Database.

Listing 3 - Adding Data to ShoppingService

private List < Product > _products;
private List < Product > Products
{
  get
  {
    if (_products == null)
    {
      _products = new List < Product > ();
 
      _products.Add(new Product(1, "Professional ASP.NET 2.0", 49.99));
      _products.Add(new Product(2, "Programming ASP.NET, 3rd Edition", 49.95));
      _products.Add(new Product(3, "ASP.NET 2.0 Unleashed", 55.99));
      _products.Add(new Product(4, "Beginning ASP.NET 2.0", 39.99));
      _products.Add(new Product(5, "Professional ASP.NET 2.0 Special Edition",
        59.99));
    }
    return _products;
  }
}

The Product class mentioned in Listing 3 encapsulated the information of a single product.

Listing 4 - The Product Class

public class Product
{
  private int _id;
  public int Id
  {
    get
    {
      return _id;
    }
    set
    {
      _id = value;
    }
  }
 
  private string _name;
  public string Name
  {
    get
    {
      return _name;
    }
    set
    {
      _name = value;
    }
  }
 
  private double _price;
  public double Price
  {
    get
    {
      return _price;
    }
    set
    {
      _price = value;
    }
  }
 
  public Product(){}
 
  public Product(int id, string name, double price)
  {
    this._id = id;
    this._name = name;
    this._price = price;
  }
}

Back to the ShoppingService Web Service class and add a method named GetProducts() to return all the available products to the client side.

Listing 5 - GetProducts() Method

[WebMethod]
public Product[] GetProducts()
{
  return Products.ToArray();
}

Still in the ShoppingService Web Service class, we need another method to process the order. This Order() method accepts a Dictionary<string, int> parameter. The key is the ID of a product and the value is the quantity of this product. In this method we calculate the total quantity and total price of the products in the customer's shopping cart and make the order.

Listing 6 - Order() Method

[WebMethod]
public string Order(Dictionary < stringint > productsToBeOrdered)
{
  // total quantity of the products
  int totalQuantity = 0;
 
  // total price
  double totalPrice = 0;
 
  // calculate total quantity and total price
  foreach (KeyValuePair < stringint > productQuantity in productsToBeOrdered)
  {
    foreach (Product product in Products)
    {
      if (product.Id.ToString() == productQuantity.Key)
      {
        totalQuantity += productQuantity.Value;
        totalPrice += (product.Price * productQuantity.Value);
        break;
      }
    }
  }
 
// save to database or other work can be done here
// ......
 
// return a formated string
  return string.Format(
    "You've ordered {0} product(s) and the total price is ${1}. Thank you!",
    totalQuantity, totalPrice);
}
Draggable Product - Implement IDragSource by DraggableProductBehavior

Move to the client side JavaScript part. Let us create a DraggableProductBehavior Behavior class which implements Sys.Preview.UI.IDragSource interface and will be attached to the product DOM elements to provide dragging functionality.

Create a JavaScript file with the name ShoppingCart.js in the ASP.NET AJAX CTP-Enabled Web Site. Then register a client side namespace.

Listing 7 - Register Namespace in ASP.NET AJAX Client Side Framework

Type.registerNamespace("Dflying");

Add some instance fields to the DraggableProductBehavior class.

Listing 8 - Instance Fields of DraggableProductBehavior

Dflying.DraggableProductBehavior = function(element) {
  // initialize base class
  Dflying.DraggableProductBehavior.initializeBase(this, [element]);
    
  // mousedown event handler
  this._mouseDownHandler = Function.createDelegate(this, this._handleMouseDown);
    
  // the product object attached to this draggable item
  this._product = null;
    
  // the transparent element when dragging the item
  this._visual = null;
}

Now let udefine the prototype part of the DraggableProductBehavior class.

Listing 9 - Prototype Part of DraggableProductBehavior

Dflying.DraggableProductBehavior.prototype = {
}

In the prototype part of the DraggableProductBehavior class, we should implement the IDragSource interface. List 10 shows the methods defined in IDragSource interface and the customized implementations in our case.

Listing 10 - Implement IDragSource Interface

// returns the data type of this draggable item - "Product"
get_dragDataType: function() {
    return "Product";
},
 
// get the data object attached to this draggable item
getDragData: function(context) {
    return this._product;
},
 
// drag mode is set to copy
get_dragMode: function() {
    return Sys.Preview.UI.DragMode.Copy;
},
 
// this method will be called when user starts dragging
onDragStart: function() {
},
 
// this method will be called when user is dragging
onDrag: function() {
},
 
// this method will be called when user finishes dragging
onDragEnd: function(canceled) {
    if (this._visual)
        // remove the transparent element
        this.get_element().parentNode.removeChild(this._visual);
},

For the product property, we also need to add getter and setter in this prototype part to follow ASP.NET AJAX client side naming convention ("get_" and "set_" prefixes).

Listing 11 - Getter and Setter for "product" Property

// 'product' property
get_product: function(product) {
    return this._product;
},
 
set_product: function(product) {
    this._product = product;
},

The mousedown event handler should be defined in the prototype part. In the event handler, we first build the transparent element to reflect the visual effect of a product during dragging and then inform the DropManager that the dragging operation has started by calling its startDragDrop() method.

Listing 12 - mousedown Event Handler

// mousedown event handler
_handleMouseDown: function(ev) {
    // DragDropManager needs this setting
    window._event = ev; 
 
    // set the style of the transparent element which follows the cusor
    this._visual = this.get_element().cloneNode(true);
    this._visual.style.opacity = "0.7";
    this._visual.style.filter = 
        "progid:DXImageTransform.Microsoft.BasicImage(opacity=0.7)";
    this._visual.style.zIndex = 99999;
    this.get_element().parentNode.appendChild(this._visual);
    var location = Sys.UI.DomElement.getLocation(this.get_element());
    Sys.UI.DomElement.setLocation(this._visual, location.x, location.y);
 
    // start dragging 
    Sys.Preview.UI.DragDropManager.startDragDrop(this, this._visual, null);
},

Lastly, do not forget the initialize() and dispose() method, which are required for all the ASP.NET AJAX client side components.

Listing 13 - initialize() and dispose() Method

initialize: function() {
    $addHandler(this.get_element(), "mousedown", this._mouseDownHandler);
},
 
dispose: function() {
    if (this._mouseDownHandler)
        $removeHandler(this.get_element(), "mousedown", this._mouseDownHandler);
    this._mouseDownHandler = null;
    
    // dispose base class
    Dflying.DraggableProductBehavior.callBaseMethod(this, 'dispose');
}

Now we have finished the prototype part of DraggableProductBehavior class. Let us just register it with the client side ASP.NET AJAX framework.

Listing 14 - Register DraggableProductBehavior Class

Dflying.DraggableProductBehavior.registerClass(
    "Dflying.DraggableProductBehavior",
    Sys.UI.Behavior, 
    Sys.Preview.UI.IDragSource
);

Once the DraggableProductBehavior is attached to a DOM element, the element could be dragged around the page.

Droppable Shopping Cart - Implement IDropTarget by ShoppingCartBehavior

If customer can only drag products around the page, it makes no sense. So the next step is creating a ShoppingCartBehavior Behavior class which implements Sys.Preview.UI.IDropTarget interface and will be attached to the shopping cart DOM element to make it act as the drop target.

Still in ShoppingCart.js file, add the instance fields to the ShoppingCartBehavior class.

Listing 15 - Instance Fields of ShoppingCartBehavior

Dflying.ShoppingCartBehavior = function(element) {
  // initialize base class
  Dflying.ShoppingCartBehavior.initializeBase(this, [element]);
    
  // product list of this shopping cart
  this._products = new Object();
}

Similar to DraggableProductBehavior, then we have to define the prototype part of the ShoppingCartBehavior class.

Listing 16 - Prototype Part of ShoppingCartBehavior

Dflying.ShoppingCartBehavior.prototype = {
}

In the prototype part of the ShoppingCartBehavior class, we first implement the IDropTarget interface. List 17 shows the methods defined in IDropTarget interface and the customized implementations in our case.

Listing 17 - Implement IDragSource Interface

// return a DOM element represents the shopping cart
get_dropTargetElement: function() {
    return this.get_element();
},
 
// if this draggable item can be dropped into this drop target
canDrop: function(dragMode, dataType, data) {
    return (dataType == "Product" && data);
},
 
// drop this draggable item into this drop target
drop : function(dragMode, dataType, data) {
    if (dataType == "Product" && data) {
        // the first product of this type, set quantity to 1
        if (this._products[data.Id] == null) {
            this._products[data.Id] = {Product: data, Quantity: 1};
        }
        // already contains products of this type, increase the quantity by 1
        else {
            this._products[data.Id].Quantity++;
        }
        
        // refresh shopping cart UI
        this._refreshShoppingCart();
        
        // set background color of shopping cart element to white
        this.get_element().style.backgroundColor = "#fff";
    }
},
 
// this method will be called when a draggable item is entering this drop target
onDragEnterTarget : function(dragMode, dataType, data) {
    if (dataType == "Product" && data) {
        // set background color of shopping cart element to gray
        this.get_element().style.backgroundColor = "#E0E0E0";
    }
},
 
// this method will be called when a draggable item is leaving this drop target
onDragLeaveTarget : function(dragMode, dataType, data) {
    if (dataType == "Product" && data) {
        // set background color of shopping cart element to white
        this.get_element().style.backgroundColor = "#fff";
    }
},
 
// this method will be called when a draggable item is hovering on this drop target
onDragInTarget : function(dragMode, dataType, data) {
},

The helper function _refreshShoppingCart() mentioned above is used to refresh the UI of the shopping cart according to the products in it.

Listing 18 - Helper Function _refreshShoppingCart()

// refresh shopping cart UI according to current products
_refreshShoppingCart: function() {
    var cartBuilder = new Sys.StringBuilder();
    for (var id in this._products) {
        cartBuilder.append("<div>");
        cartBuilder.append(this._products[id].Product.Name);
        cartBuilder.append(" * ");
        cartBuilder.append(this._products[id].Quantity);
        cartBuilder.append("</div>");
    }
    
    this.get_element().innerHTML = cartBuilder.toString();
},

And another function defined in this prototype part of the ShoppingCartBehavior class is getProductsToBeOrdered(). The other part of the application can just call this function to get a dictionary object that represents products currently in customer's shopping cart; the key is the product ID and the value is product quantity.

Listing 19 - getProductsToBeOrdered() Function

getProductsToBeOrdered: function() {
  var productsToBeOrdered = new Object();
  for (var id in this._products) {
    productsToBeOrdered[id] = this._products[id].Quantity;
  } 
    return productsToBeOrdered;
},

The next will be the initialize() and dispose() method, which is essential to ASP.NET AJAX client side components. You may find the calls to DragDropManager's registerDropTarget() and unregisterDropTarget() method to register/unregister the drop target.

Listing 20 - initialize() and dispose() Method

initialize: function() {
  // initialize base class
  Dflying.ShoppingCartBehavior.callBaseMethod(this, "initialize");
    
  // register this drop target in DragDropManager
  Sys.Preview.UI.DragDropManager.registerDropTarget(this);
},
 
dispose: function() {
  // unregister this drop target in DragDropManager
  Sys.Preview.UI.DragDropManager.unregisterDropTarget(this);
    
  // disponse base class
  Dflying.ShoppingCartBehavior.callBaseMethod(this, "dispose");
}

After finishing the prototype part, let us register it with the client side ASP.NET AJAX framework.

Listing 21 - Register ShoppingCartBehavior Class

Dflying.ShoppingCartBehavior.registerClass("Dflying.ShoppingCartBehavior",
    Sys.UI.Behavior, Sys.Preview.UI.IDropTarget);

Once the ShoppingCartBehavior is attached to a DOM element, the element could accept products dropping into it.

Before closing ShoppingCart.js, we should also include this line of code at the bottom of the file to notify ASP.NET AJAX client side framework that this JavaScript file has been downloaded successfully.

Listing 22 - Notify ASP.NET AJAX Client Side Framework

if(typeof(Sys)!=='undefined') Sys.Application.notifyScriptLoaded();
The ASPX Page and ScriptManager Control

Create an ASP.NET page and add an ASP.NET AJAX server side ScriptManager control, sending the PreviewScript.js and PreviewDragDrop.js script files as well as the client side proxy of ShoppingService.asmx Web Service to the browser.

Listing 23 - The ScriptManager Control

<asp:ScriptManager ID="sm" runat="server">
    <Scripts>
        <asp:ScriptReference Assembly="Microsoft.Web.Preview" 
            Name="PreviewScript.js" />
        <asp:ScriptReference Assembly="Microsoft.Web.Preview" 
            Name="PreviewDragDrop.js" />
        <asp:ScriptReference Path="ShoppingCart.js" />
    </Scripts>
    <Services>
        <asp:ServiceReference Path="ShoppingService.asmx" />
    </Services>
</asp:ScriptManager>

The HTML for the shopping cart is really simple. The <div /> with the ID equals shoppingCart will act as the shopping cart while the <input /> with ID equals btnOrder is used to make the order.

Listing 24 - Shopping Cart HTML

<div id="shoppingCartContainer">
    <div><strong>Shopping Cart</strong></div>
    <div id="shoppingCart">Drop Products Here...</div>
    <input id="btnOrder" type="button" value="Order" 
        onclick="return btnOrder_onclick()" />
</div>

The HTML for the products container is even simpler - just a <div />. All the products shown on the page will be generated in the runtime.

Listing 25 - Products Container HTML

<h1>Dflying's Products</h1>
<div id="productContainer"></div>

Shopping cart and products container related CSS classes are defined below and make the page look prettier.

Listing 26 - Shopping Cart and Products Container Related CSS Classes

#shoppingCartContainer
{
    float: right; 
    width: 260px; 
    border: 1px solid black;
    margin: 3px;
}
#productContainer div
{
    width: 300px;
    padding: 3px;
    margin: 3px;
    border: 1px solid #666;
    background-color: #fff;
    cursor: move;
}
Get Products via Web Service

When the client side application initialized, we had to invoke the Web Service to get the products in the online store and display them on to the page.

Add the following JavaScript code to a <script /> block of the ASP.NET page, invoking the Web Service and attaching ShoppingCartBehavior to the shopping cart element.

Listing 27 - Get Products and Attach ShoppingCartBehavior

function pageLoad(sender, args) {
  // get all the products by invoking Web Service method
  ShoppingService.GetProducts(onProductsGot);
    
  // attach ShoppingCartBehavior to the shopping cart element
  $create(
    Dflying.ShoppingCartBehavior, 
    {"name": "myShoppingCartBehavior"},
    null, 
    null, 
    $get("shoppingCart")
    );
}

In the callback function onProductsGot(), we simply iterate the collection and create DOM elements represent the product retrieved from the server and attach DraggableProductBehavior to them.

Listing 28 - Create Product Elements and Attach DraggableProductBehavior

function onProductsGot(result)
{
  // get product container element
  var productContainer = $get("productContainer");
 
  // iterate product container
  for (var index = 0; index < result.length; ++index)
  {
    // current product
    var thisProduct = result[index];
 
    // create DOM element and insert into product container element
    var productElem = document.createElement("div");
    productElem.innerHTML = thisProduct.Name + " - $" + thisProduct.Price;
    productContainer.appendChild(productElem);
 
    // attach DraggableProductBehavior to this product element
    $create(Dflying.DraggableProductBehavior,
    {
      "product": thisProduct
    }
    ,  // set 'product' property
    nullnull, productElem);
  }
}
Order Products via Web Service

If the customer clicks the "Order" button in the shopping cart, our application will send the ID/quantity pairs in the shopping cart back to the server side Web Service to process the order.

Listing 29 - Process Order by Sending Shopping Cart Data back to Server

function btnOrder_onclick()
{
  // get ShoppingCartBehavior attached to the shopping cart element
  var shoppingCartBehavior = Sys.UI.Behavior.getBehaviorByName($get(
    "shoppingCart"), "myShoppingCartBehavior");
 
  // get product Id and quantity in the shopping cart
  var productsToBeOrdered = shoppingCartBehavior.getProductsToBeOrdered();
 
  // invoke Web Service method to process the order
  ShoppingService.Order(productsToBeOrdered, onOrdered);
}

I just want to make the demo as simple as possible, so the callback function onOrdered() will simply use JavaScript alert() function to display the message returned from server.

Listing 30 - onOrdered() Callback Function

function onOrdered(result) {
    alert(result);
}

Now we have finished the demo online store application. If everything goes fine, you run the Web Site and will be able to play with the amazing Drag & Drop functionality, as shown in Figure 2 through Figure 5.

Downloads
Conclusion

ASP.NET AJAX provides a powerful client side Drag & Drop framework which allows developers easily create a rich, Drag & Drop enabled web user interface. It does not matter how the requirements vary, you can still easily create your own customized Drag & Drop behaviors.



©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-05-18 11:13:37 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search