Drag and Drop

This text is obsolete in parts. Please refer to the official DnD documentation.

Drag and Drop (DnD) in web applications is one thing which is gaining popularity these days. Already there is a handful of sites using drag and drop functionality coupled with AJAX to deliver some killer content. Suppose, one day your boss sees one such cool website . You are ordered to implement similar thing in the web application you are working on. You ask yourself if there is a solution that is easy, fast, tested, reusable, customizable and works with all major browsers !

The Dojo DnD API enables you to do all the cool(and complex) drag and drop stuff in a clean way with less pain.

Terminology

This text is obsolete in parts. Please refer to the official DnD documentation.

Source: A source is an HTML node that can be dragged around. It can be a simple node, or a node which contains other nodes - a DIV, a TABLE, etc. In Dojo, a source has type dojo.dnd.Source.

Target: Target is like a placeholder, onto which nodes can be dropped. A good example is a shopping cart application, where an item icon is a source while the shopping cart is a target.

Interestingly, all dojo.dnd.Source objects in Dojo DnD are also targets, so many applications just use dojo.dnd.Source for both. For a pure target, i.e. one that's not draggable, you define the target with type dojo.dnd.Target.

Avatar: Avatar is kind of ghost image of the object being dragged. This ghost image follows the mouse cursor, till the time it is dropped to a proper placeholder. In Dojo this functionality is available to every drag and drop operation by default. The 'Dojo.dnd.Avatar' class does all this for you and displays a nice tooltop with text 'moving' or 'copying', indicating the type of operation being performed.

DnD Manager: The DnD Manager is a singleton, which is responsible for handling all DnD operations on a page. It accomplishes many complex tasks required for successful DnD operations like detecting initialization of valid drag operation by user, creation of Avatar, allowing drop to only eligible targets and such things. Most of the time the default manager works fine, but you can extend or create your own DnD Manager class for more complex scenarios.

Mover: Mover is a mixin class which makes an object (HTML node) movable. Once a node is made movable using this class, user can freely drag and drop the node anywhere on the page, for example a popup window. There is no concept of source and target in this case.

Selector : Selector is the base class for Source in Dojo. This means that every Source is a Selector. A Selector controls how its child nodes can be selected. This also means that every node that participates in DnD operation is part of some selectable list.

Container: Container is the baseclass for Selector. It provides functionality of sensing mouse movement over its children.

To sum up: a container contains nodes, a selector makes it possible to select single or multiple nodes, a mover makes the selected nodes movable, an Avatar provides visual cue during DnD and the DnD Manager makes the overall operation possible.

A Simple Example

This text is obsolete in parts. Please refer to the official DnD documentation.

Let's make things interesting: a simple example of Dojo DnD in action ! In this example we will implement very basic DnD functionality in a web page. We will have a red and a blue rectangle that can be dragged and dropped onto a target.

First, we'll start with the standard header and some CSS:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<html>
<head>
<title>Simple DnD Example</title>
<style type="text/css">
        .target {border: 1px dotted gray; width: 300px; height: 300px;padding: 5px;}
        .source {border: 1px dotted skyblue;height: 200px; width: 300px;}
        .bluesquare {height:50px;width:100%;background-color:skyblue}
        .redsquare {height:50px;width:100%;background-color:red}
</style>
<script type="text/javascript" src="../../dojo.js" djConfig="parseOnLoad: true"></script>
<script type="text/javascript"> 
        dojo.require("dojo.dnd.Source"); // dojo.dnd.source in 0.9
        dojo.require("dojo.parser");   
</script>
</head>

This is an important part. In this example we have only defined four classes. The 'target' and the 'source' class will be used by Dojo. The other two classes are used to create the red and blue rectangle.

Note: in 0.9, to use the class dojo.dnd.Source, you must dojo.require the resource dojo.dnd.source. Note the capitalization here. In Dojo 1.0+, both are capitalized.

Next, to hold the source and targets, we have a table with two cells. The first(left) cell hosts a source and the second(right) cell hosts a target:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<body style="font-size: 12px;">
<h1>A Simple Example</h1>
<table><tbody><tr>
<td>
<!-- Create a source with two nodes -->
<div dojoType="dojo.dnd.Source" jsId="c1" class="source">
        SOURCE
        <div class="dojoDndItem" dndType="blue">
                <div class="bluesquare">BLUE</div>
        </div>
        <div class="dojoDndItem" dndType="red,darkred">
                <div class="redsquare">RED</div>
        </div>
</div>
</td>

The outermost <div> tag creates a source object. The inner <div> tags with class 'dojoDnDItem' create nodes, that can participate in DnD. The 'dndType' attribute can be used to specify 'type' of a node. You can specify more than one type for a node. Our nodes contains a red and blue rectangle with text on them. You can replace this content with whatever you like.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<td>
<!-- Create a target that accepts nodes of type red and blue. -->
<div dojoType="dojo.dnd.Target" jsId="c2" class="target" accept="blue,darkred">
        TARGET
</div>
</td>
</tr><tbody/></table>

The above markup creates a pure target that can accept nodes with type 'blue' and 'darkred'. The accept attribute is a comma separated list of 'types' which can be dropped onto this node. This means that only those nodes whose 'dndType' is present in the list can be dropped onto this target. You can restrict DnD operations easily by specifying appropriate 'types' for nodes and targets. With no effort on your part, Dojo creates a default 'avatar' for each element when it is being dragged

As you can see, with pure markup and no code at all we added awesome drag and drop functionality to our page. This was a simple example of DnD, in the next chapters we will dive into more details of the API. We will see how to customize DnD behaviour, use events and restricting DnD operations.

Beautification

This text is obsolete in parts. Please refer to the official DnD documentation.

Our last example looked pretty average. After playing with it for some time you may realize that visual indicators for DnD actions could be a great addition. Wouldn't it be nice if you could customize look of nodes so as to indicate important conditions or events in drag and drop functionality?

This is made very easy in Dojo. You don't have to intercept events and add your own code to modify the look of the nodes. Nodes that take part in DnD make use of certain predefined CSS classes. All you need is to supply a declaration to customize look and feel. We will making some changes to the last example to make it beautiful.

First, we will be using these two images in place of those ugly rectangles (BLUE.png and RED.png). [inline:BLUE.png=test] [inline:RED.png=test]

Then, we change our markup a bit. We will use <img> tags instead of <div>. Finally, we add the magic <style> section. Many classes have been defined in this section. You may want to play around with them a bit. A list of such class can be found in 'dndDefault.css' file located in dojotoolkit/dojo/tests/dnd directory. Given below is the complete source :

<html>
<head>
<title>Beautification</title>
<style type="text/css">
        .target {border: 1px dotted gray; width: 300px; height: 300px;padding: 5px; 
              -moz-border-radius:8pt 8pt;radius:8pt;}
	.source {border: 1px dotted skyblue;height: 200px; width: 300px;
               -moz-border-radius:8pt 8pt;radius:8pt;}
	.dojoDndItemOver {background: #feb;border: 1px dotted gray; }
	.dojoDndItemBefore {border-left: 2px dotted gray; }
	.dojoDndItemAfter {border-right: 2px dotted gray; }
	.target .dojoDndItemAnchor {border:1px solid gray;}
	.dojoDndAvatar {font-size: 75%; color: black;}
	.dojoDndAvatar td {padding-left: 20px; padding-right: 4px;height:20px}
	.dojoDndAvatarHeader {background: #ccc; background-repeat: no-repeat;}
	.dojoDndAvatarItem {background: #eee;}
	.dojoDndMove .dojoDndAvatarHeader {background-image: url(images/dndNoMove.png);}
	.dojoDndMove .dojoDndAvatarCanDrop .dojoDndAvatarHeader {background-image: 
                url(images/dndMove.png);}
</style>
<script type="text/javascript" src="../../dojo.js" djConfig="parseOnLoad: true">
</script>
<script type="text/javascript"> 
        dojo.require("dojo.dnd.source"); // capital-S Source in 1.0
        dojo.require("dojo.parser");	
</script>
</head>
<body style="font-size: 12px;">
<h1>A Beautification Example</h1>

<table><tbody><tr>
<td>
SOURCE
<div dojoType="dojo.dnd.Source" jsId="c1" class="source">
        <img src="BLUE.png" class="dojoDndItem" dndType="blue"/>
	<img src="RED.png" class="dojoDndItem" dndType="red"/>
    	<img src="BLUE.png" class="dojoDndItem" dndType="blue"/>
	<img src="RED.png" class="dojoDndItem" dndType="red"/>   
    	<img src="BLUE.png" class="dojoDndItem" dndType="blue"/>
	<img src="RED.png" class="dojoDndItem" dndType="red"/>   
    	<img src="BLUE.png" class="dojoDndItem" dndType="blue"/>
	<img src="RED.png" class="dojoDndItem" dndType="red"/>   
    	<img src="BLUE.png" class="dojoDndItem" dndType="blue"/>     
</div>
</td>
<td>
TARGET
<div dojoType="dojo.dnd.Target" jsId="c2" class="target" accept="red,blue">
</div>
</td>
</tr><tbody/></table>

</body>
</html>

Note that we haven't added any code yet. But now if you run this example you will agree that it certainly looks more beautiful. Some points worth noticing are:

  • The target and source and clearly marked.
  • Node gets highlighted when mouse hovers over it.
  • The node that was last to be dropped shows a black gray border making it distinguishable from others.
  • When you move the avatar onto the target, the left or right border of the node beneath get highlighted , indicating whether your node will be inserted before or after it.
  • The avatar looks very pretty. Observe the nice little image on top-left of the avatar. It changes to a green arrow when you move the avatar over target. This indicates that it is possible to drop the node there. But if you move the avatar out of the target, it shows a red sign.

All this without writing any special code. With some imagination and creativity, you would be able to generate awesome looking pages, in no time.

Drag and Drop Actions

This text is obsolete in parts. Please refer to the official DnD documentation. Instead of complicated topic processors you should use onDrop(), onDropInternal(), onDropExternal() local events.

For a DnD application to be really useful, you would require mechanism to add user defined actions for different DnD events. This could be anything from updating a text box value to sending data back to server using AJAX. In this chapter we will see how to hook up actions to DnD events. This can be achieved with the publish/subscribe mechanism.

The following messages can be subscribed to :

  • /dnd/source/over
  • /dnd/start
  • /dnd/drop
  • /dnd/cancel
Given below is code snippet that attaches a callback function to a message:
dojo.subscribe("/dnd/drop", function(source,nodes,iscopy){
  //code to perform some action			
});

dojo.subscribe("/dnd/start", function(source,nodes,iscopy){
  //code to perform some action		
});

dojo.subscribe("/dnd/source/over", function(source){
  //code to perform some action	
});

dojo.subscribe("/dnd/cancel", function(){
  //code to perform some action	
});
The parameters to the callback function are:
  • source : Source of the nodes which are being dragged/dropped.
  • nodes: Array of HTML node objects. To get corresponding DnDItem object use the following method:
    var jsnode = source.getItem(nodes[0].id);
    var d = jsnode.data;
    var t  = jsnode.type;
  • iscopy: This parameter is a boolean , which is true for a copy operation and false for drag operation.

If you are interested in getting the target object, then you can use following method:

var t = dojo.dnd.manager().target;
When we create a target or source via markup, we can specify a name for global level javascript variable for it, using the jsId attribute.
<div dojoType="dojo.dnd.Source" jsId="cart" class="target" accept="red,blue" id="target1">
</div>
This automatically creates a global variable 'cart' of the type "dojo.dnd.Source".

DnD Events Example
Now let us have a look at simple example of DnD events. In this example, I have created a simple shopping cart. Each blue and red sphere inside the source(shelf) has some price associated with it. This is assigned using the 'dndData' item. You can select multiple spheres from the source(shelf) and drop them onto the target (shopping cart). You will see the updated total price of your cart items. If you drop certain items back to the shelf, the corresponding amount is deducted from the total. I have attached a text file with this article which contains complete source. You will require BLUE.png and RED.png images to run the example.

Below is a excerpt from the file followed by explanation :

<script type="text/javascript"> 
dojo.require("dojo.dnd.source"); // capital-S Source in Dojo 1.0

var item_price;
var total = 0;
function AddItems(target,nodes)
{
	for (var i=0;i<nodes.length;i++)
	total += parseFloat((target.getItem(nodes[i].id)).data);
	dojo.byId("cost").innerHTML = total;
}

function SubstractItems(target,nodes)
{
	for (var i=0;i<nodes.length;i++)
	total -= parseInt((target.getItem(nodes[i].id)).data);
	dojo.byId("cost").innerHTML = total;
}
	
function ShowPrice(target,nodes)
{	var sum =0;
	for (var i=0;i<nodes.length;i++)
	sum += parseInt((target.getItem(nodes[i].id)).data);
	dojo.byId("msg").innerHTML = "Selected Item Price is $"+ sum ;
}

function ClearMsg()
{   dojo.byId("msg").innerHTML = "";}

function init()
{
	
	dojo.subscribe("/dnd/drop", function(source,nodes,iscopy){
  		var t = dojo.dnd.manager().target;
		ClearMsg();	
		if(t == source) return;
		if(t == cart)AddItems(t,nodes);
		if(t == shelf)SubstractItems(t,nodes);});

	dojo.subscribe("/dnd/start", function(source,nodes,iscopy){
  		var t = dojo.dnd.manager().target;
		ShowPrice(source,nodes);});

	dojo.subscribe("/dnd/cancel", function(){
  		ClearMsg();});

}

dojo.addOnLoad( init );
</script>

<div dojoType="dojo.dnd.Source" jsId="shelf" class="source" id="source1" accept="red,blue" singular=false>
    <img src="BLUE.png" class="dojoDndItem" dndType="blue" dndData="10" title="$10"/>
    <img src="BLUE.png" class="dojoDndItem" dndType="blue" dndData="3" title="$3"/> 
	.....    
</div>

<div dojoType="dojo.dnd.Source" jsId="cart" class="target" accept="red,blue" id="target1"></div>

Total Price (USD): <span id="cost">0.00</span><br/>

<b>Message: <span id="msg" style="color:blue"></span></b>

  • A global level javascript variable 'total' is used to store the updated price.
  • When the user starts dragging items, the 'ShowPrice' function gets called, which shows the total price of only the selected spheres.
  • The callback function for "dnd\drop" message does the following :
    • If the target and source are the same, exit and don't modify the total. This is because item has been dropped on same source from where it was dragged.
    • If target is cart, then call 'AddItems()' to add the prices of dropped items to the total.
    • If target is shelf, it means that items are being moved from cart to shelf, so call the 'SubstractItems()' function to deduct prices of those items from total.

This was a simple example of handling the DnD events to execute custom defined actions. So far we have covered basics, looked at using CSS for rich user interface and seen how to handle events. In next chapters we will learn about the 'node creator' function and build a very simple web based application to demonstrate use of DnD.

New Events in Dojo 1.0

Moveable implements three new events:

  • onMove, which implements the move itself
  • onMoving, which is called before onMove, so you have a chance to change something, for example, the new position of the move to implement some restrictions.
  • onMoved, which is called after the move, so you can update other objects after the move.

Events have better locality than topics: instead of getting called on every move and check if it is "the right" move, you can connect directly to events on the Moveable. Nevertheless topics are still supported.

Advanced Topics

This text is obsolete in parts. Please refer to the official DnD documentation.

So far we have seen how to use markup to accomplish drag and drop. In this chapter we will have a look at some features, we have not discussed. This chapter features a sample 'Shopping Cart Application'. You can download the related files and refer to the 'readme.txt' to try it out. Everything we will discuss in this chapter has gone into the making of this application.

Creating Source/Target with javascript
To do this we need to place two container tags on the page. The following javascript code will create a Source and Target object for us.

var node_creator = function(data, hint){
var types = [];
var node = dojo.doc().createElement("div");
if(data == 'RED'){ types.push("red"); dojo.addClass(node, "redsquare");}
if(data == 'BLUE'){ types.push("blue");dojo.addClass(node, "bluesquare"); } 
node.innerHTML = data;
node.id = dojo.dnd.getUniqueId();
return {node: node, data: data, type: types};};

c1 = new dojo.dnd.Source("c1", {creator: node_creator, accept: ["item"],
  horizontal: false, copyOnly: false});
c2 = new dojo.dnd.Target("c2", { creator: targetnode_creator,
  accept: ["item"]});
c2.insertNodes(false, ["RED","GREEN");
The following points should be noted:
  • 'c1' and 'c2' are ids of <div> tags used to create source and target.
  • 'node_creator' function needs to be specified. This is called for creation of each node. We will discuss this part in detail in the very next topic.
  • 'copyOnly' set to true indicates that always a copy operation is performed (instead of moving nodes). If set to false 'CTRL' key needs to be used for copying.
  • 'horizontal' set to true indicates horizontal alignment, else use vertical alignment.
  • Nodes can only be inserted using call 'insertNodes'. First parameter if set to true will result in selection of the inserted nodes. Second parameter is an array. We need to understand the working of 'insertNodes' and 'node_creator' in more detail.
The Node Creator Function.
Typically, items that particpate in DnD would originate from server in one form or another. In that case they can't be hard-coded at design time, they have to be created dynamically at runtime. Also it makes sense to assume that the server will only give data (XML,JSON,CSV..). HTML elements (UI) for each of this data item will have to be created at client side using javascript. The node creator function is meant precisely to do this !
{"id": "S001",
 "name": "PENCIL", 
 "price": "3.20", 
"rating": "3",
"description": "A really cool product.",
"img_url": "images/RED.png"}
Sample JSON from server HTML node created by the 'node_creator' function. Customized avatar created using 'node_creator'
Let us first have a look at the call to 'insertNodes()' function. The first parameter is boolean, if set to 'true' will result in marking the inserted nodes as selected. The second parameter is very important. It actually expects an array of javascript objects. Any object can be passed, but most probably you will want to pass data coming from server here. Important thing to know here is that the 'creator' function will be called for each object in the array. Every object in the array is passed to 'creator' function as the 'data' parameter. The creator function creates a HTML node based on our data object. Also, the creator function decides the 'type' and 'id' of node .You can pass any object in the array provided your creator function is capable of handling it.

What data would come from the server and how we would like to interpret is for us to decide. We need to write our 'node_creator' function accordingly. In this case, let us assume that using dojo.xhrGet(AJAX) call, we received the string 'RED,BLUE' from the server. So I have passed two string objects "RED" and "BLUE" in the call to 'insertNodes()'.

Our node creator function is a simple one. It creates a new <div> tag with unique id for each node, based on the value of 'data'. It also assigns 'type' to the corresponding DnD item. The return statement is very important. The creator function returns a javascript object containing the node to be inserted, data related to the node and type assigned to the node. It is possible to assign multiple types to a single node.

The 'hint' parameter is used to customize creation of avatar. Dojo does create a default avatar for you (which is a carbon-copy of the node being dragged). But at times you will want your avatar to show different information or to show the same information in different a way.

Clearing Items from Source/Target
To clear all items you can use the following code:
c2.selectAll();
c2.deleteSelectedNodes();
c2.clearItems();

Iterating through all nodes in Source/Target
To iterate through all nodes in a source or target you can use the following code:
var x = c2.getAllNodes();
for(i =0;i<x.length;i++)
{//jsNode is javascript object for node
 //x[i] represents HTML element for the node
var jsnode = c2.getItem(x[i].id);}

Simple Shopping Cart Application
This is a simple application which contains a PHP script that acts like a service URL and HTML page, which is the main UI. The HTML page talks to service URL using AJAX for getting list of shop items and saving of cart items. You may want to spend some time looking at the source for this example, to see how web applications involving DnD could be designed.

With the 'Test.php' script following actions are supported:
MethodURLResult
GETTest.php?action=get_all_itemsReturns JSON for all items in the shop. This is stored in a plain text file (data.txt). In real world scenario it would come from database.
POSTTest.php?action=save_cart_itemsSends JSON for all items in the shopping cart back to server, where they are stored in Session.
GET Test.php?action=get_cart_itemsReturns JSON for all shopping cart items, saved by user (with the intent of buying later) .These were saved in the Session.
GET Test.php?action=resetAll saved items in shopping cart are deleted. Session is cleared.

If you open the 'shopping_cart.html' page, you will see three buttons 'Save', 'Logout' and 'Reset'. Try dragging and dropping some items from 'Today's Special' on 'Shopping Cart'. Click 'Save' and add some more items on Shopping Cart. Now click 'Logout'. On the logout page, click 'Login Again'. You should see only the saved items in your shopping cart. If you click 'Reset' your shopping cart will become empty.


Important: Since this is just a sample, in the file 'shopping_cart.html' , I have set
djConfig="usePlainJson: true"
In real life scenario, this could make your page vulnerable to 'JavaScript Hijacking', I haven't bothered about this. It is recommended you use 'text/json-comment-filtered' as mime type while making XHR calls. This document contains good information on the security risks and ways to prevent it.


To try out the shopping cart application you need to download all the files and follow instructions in 'readme.txt'. These files are attached at the end. This is a very simple and rudimentary example, but still illustrates key concepts in making use of DnD in real world scenarios. This concludes our adventures with the DnD API in Dojo.

Shopping Cart Example Files
Please Note:
I am still working on this example. This is my first attempt with PHP programing :-). Any suggestion/optimizations are welcomed. This message will be removed once the work is reviewed officially.