Dojo Drag and Drop, Part 1: dojo.dnd

By on June 10, 2008 12:02 am
NOTE: This post contains information pertaining to an older version of Dojo.
Read the updated version now!

Most anyone who’s looked at the feature list knows that one of Dojo’s core features is a drag and drop framework. What’s not immediately obvious is that Dojo actually has two drag and drop APIs. The first, dojo.dnd, is designed to manage the process of dragging items between two or more containers, including multiple selection, item acceptance filtering on drop targets, and other behavioral tweaks. The second API, dojo.dnd.move, is a bit lower-level in scope; it’s designed to manage the process of moving single objects around, without the concept of attaching items to containers. Both of these are very useful features to have at your disposal. In this article, we’ll cover dojo.dnd.

Simple Single Source DnD

Meet Dylan. Collecting junk is his passion. A while back, he decided that he needed to get rid of some of it to make room for more interesting junk, so he got a booth at the local farmers’ market and started a small junk outlet business. Like most people passionate about sharing their junk with the world, he decided to open an online storefront.Dylan's Original Junk Outlet His brother, currently working towards a degree in Marketing, convinced him that he needed to brand himself as a form of differentiation; thus was born Dylan’s Original. He decided that his best bet would be to create a user experience so ridiculously awesome that people wouldn’t be able to help buying his junk. That’s where we come in. To demonstrate drag and drop techniques, we’ll help build a mockup of Dylan’s Original Junk Outlet.

Let’s start with the basics. Just about the easiest way to get drag and drop working is to demonstrate a single list that the user can reorder dynamically. First, we’ll create our page skeleton, using Dojo from AOL CDN, spiced up with a bit of CSS. View the starting point.

As you can see, we’re starting with a simple wish list:

Wishlist

  1. Wrist watch
  2. Life jacket
  3. Toy bulldozer
  4. Vintage microphone
  5. TIE fighter

The DnD Workhorse, dojo.dnd.Source

An online store wishlistTo enable drag and drop, dojo.dnd gives us a class called Source, which is basically just what it sounds like: a source for dragged items (as well as a target for dropped items). To instantly turn a DOM node into such a source, create a dojo.dnd.Source out of it:

dojo.require("dojo.dnd.Source");

var wishlist = new dojo.dnd.Source("wishlistNode");
wishlist.insertNodes(false, [
    "Wrist watch",
    "Life jacket",
    "Toy bulldozer",
    "Vintage microphone",
    "TIE fighter"
]);

That’s all there is to it! View the single container DnD example. If you’re the kind of person who likes to do markup-style quick prototyping, instantiate the node in markup with dojoType="dojo.dnd.Source", and use class="dojoDndItem" on draggable child nodes, like so:

  1. Wrist watch
  2. Life jacket
  3. Toy bulldozer
  4. Vintage microphone
  5. TIE fighter

Of course, you have to make sure to dojo.require("dojo.dnd.Source") and to turn on the parser, but there you go. View the demo, markup style.

What can you turn into a DnD source? Well sheesh, what can’t you turn into a DnD source? Take a look at the technical documentation. The dojo.dnd.Source will take into account the node type of your container when creating the child nodes:

  • If the container is <div> or <p>, it will create <div> nodes.
  • If the container is <ul> or <ol>, it will create <li> nodes.
  • If the container is a <table>, it will create a set of <tr><td> and add it to the table’s <tbody>.
  • All other times, it will create <span> nodes.

So basically, turn whatever you want into a dojo.dnd.Source, and Dojo will intelligently set up your DOM. Pretty nifty. Out of the box, dojo.dnd.Source has quite a lot of functionality baked in:

  • Multiple selection. Each container has the notion of a selection; click on an item and it’s “selected.” Ctrl-/Command-click or Shift-click to do multiple selection, just like in a regular desktop application.
  • Child node introspection. In addition to the insertNodes() method demonstrated above, the Source provides a few methods to work with the list of child nodes:
    • getAllNodes() – returns a standard Dojo NodeList of the contained items.
    • forInItems(f, o) – calls f in the context of o for each contained node. Similar to dojo.forEach().
    • selectNone(), selectAll(), getSelectedNodes(), deleteSelectedNodes() – just what they sound like, methods for manipulating the selection state.
    • plus a few other things you can hook into for customizing the way the internal list gets handled. See the technical docs for details.
  • Copy vs. move semantics. By default nodes are moved when you drag them around. However, Ctrl-/Command-dragging does a copy operation instead, similar to the average desktop file manager. This is useful when you don’t want your DnD source to change in response to your drag operations.
  • Drag cancellation. This isn’t technically a property of the Source, but it’s worth noting here that pressing the Esc key cancels the current drag operation. You can do this programmatically, too, if you need to.
  • Automatic avatar creation. The dojo.dnd framework uses “avatars” to represent the nodes you drag around; it creates these for you automatically, based on the data itself. You can customize this, of course. More on that later.

An online store product list, mid-drag

Using Multiple Sources

Of course, if you’re only using a single dojo.dnd.Source in your application, the move/copy distinction is only useful for duplicating nodes in the list. Let’s help Dylan expand. Check out Dylan’s Old Junk Outlet, version 2 (or, the markup version).

What have we changed? Well, for starters, we now have three Sources: the Catalog, the Cart, and the Wishlist. Now you can drag items back and forth between them to see multiple-container dojo.dnd in action. Some items are marked as “out of stock” (more on this in a bit), and—hey, some of these items aren’t junk at all: they’re food! Yes, while we weren’t looking, Dylan merged his junk outlet with Dylan’s Nutritious Dietarium, the company he uses to unload what he doesn’t eat from his garden.

DnD Item Types

The biggest change here is the introduction of item types. Notice how we’re declaring our containers:

var catalog = new dojo.dnd.Source("catalogNode", {
    accept: ["inStock,outOfStock"]
});
catalog.insertNodes(false, [
    { data: "Wrist watch",        type: ["inStock"] },
    { data: "Life jacket",        type: ["inStock"] },
    { data: "Toy bulldozer",      type: ["inStock"] },
    { data: "Vintage microphone", type: ["outOfStock"] },
    { data: "TIE fighter",        type: ["outOfStock"] },
    { data: "Apples",             type: ["inStock"] },
    { data: "Bananas",            type: ["inStock"] },
    { data: "Tomatoes",           type: ["outOfStock"] },
    { data: "Bread",              type: ["inStock"] }
]);
catalog.forInItems(function(item, id, map){
    // set up CSS classes for inStock and outOfStock
    dojo.addClass(id, item.type[0]);
});

var cart = new dojo.dnd.Source("cartNode", {
    accept: ["inStock"]
});
var wishlist = new dojo.dnd.Source("wishlistNode", {
    accept: ["inStock","outOfStock"]
});

In the markup version it looks like so:

Catalog

  • Wrist watch
  • Life jacket
  • Toy bulldozer
  • Vintage microphone
  • TIE fighter
  • Apples
  • Bananas
  • Tomatoes
  • Bread

Cart

Wishlist

Each DnD item can be given a type, specified in JavaScript as the type member of the object(s) you provide to, e.g, insertNodes(), or in markup as dndType. Correspondingly, each DnD container can be given a list of item types to accept. The default type, if you don’t specify it, is “text” for all nodes and containers. Here, we’re using the type to denote whether an item is in stock or not, and we’re using that to determine what can be dropped where: the Cart only accepts items that are in stock, while the Wishlist accepts anything. If you drag around multiple items at once, you’ll notice that you can only drop a set of items on a container that accepts every type of item in the set—no partial drops allowed!

You may have noticed a few issues with this demo:

  • Unless you explicitly invoke copy semantics by pressing the appropriate key, dragging items removes them from the catalog, which doesn’t make much sense for this application.
  • You can do a copy/drag, but then it becomes easy to duplicate items.
  • Using simple lists like this doesn’t really give a great user experience for an establishment as dignified as Dylan’s Original Junk Outlet / Dylan’s Nutritious Dietarium (DOJO/DND, get it? I kill me (groan)).

Let’s start with the appearance.

Customizing Item Creation

As I discussed above, the default drag and drop implementation is intelligent enough to create nodes according to the context in the DOM. However, if you want to display more than a string of text, the default can be lacking, since all it does is put the data into the new child node’s .innerHTML. Fortunately, dojo.dnd gives us a way to customize this: the creator function.

Since Dylan wants the product catalog to be both prettier and more informative, let’s give each item an image, short description, and quantity available. For example, for the wrist watch:

{
    name: "Wrist watch",
    image: "watch.jpg",
    description: "Tell time with Swiss precision",
    quantity: 3
}

We’ll use this structure in the data field in the items we create. To create DOM nodes from this kind of object, we’ll need a function we can pass to the dojo.dnd.Source’s constructor:

// create the DOM representation for the given item
function catalogNodeCreator(item, hint) {
    // create a table/tr/td-based node structure; each item here needs an
    // image, a name, a brief description, and a quantity available
    var tr = document.createElement("tr");
    var imgTd = document.createElement("td");
    var nameTd = document.createElement("td");
    var qtyTd = document.createElement("td");

    var img = document.createElement("img");
    img.src = "images/" + (item.image || "_blank.gif");
    dojo.addClass(imgTd, "itemImg");
    imgTd.appendChild(img);

    nameTd.appendChild(document.createTextNode(item.name || "Product"));
    if (item.description && hint != "avatar"){
        // avatars don't get the description
        var descSpan = document.createElement("span");
        descSpan.innerHTML = item.description;
        nameTd.appendChild(document.createElement("br"));
        nameTd.appendChild(descSpan);
    }
    dojo.addClass(nameTd, "itemText");

    tr.appendChild(imgTd);
    tr.appendChild(nameTd);

    if (hint != "avatar") {
        // avatars don't display the quantity
        qtyTd.innerHTML = item.quantity;
        dojo.addClass(qtyTd, "itemQty");
        tr.appendChild(qtyTd);
    }else{
        // put the avatar into a self-contained table
        var table = document.createElement("table");
        var tbody = document.createElement("tbody");
        tbody.appendChild(tr);
        table.appendChild(tbody);
        node = table;
    }

    // use the quantity when determining the DnD item type
    var type = item.quantity ? ["inStock"] : ["outOfStock"];

    return {node: tr, data: item, type: type};
}

Things to note here:

  • Firstly, we’re going to be using tables for our DnD sources now. That’ll help improve the presentation (and no, not in the heretical tables-for-layout way).
  • In the bit at the end, we dynamically choose the DnD item type based on the quantity provided. Notice that the type specifier is actually an array; we can compile the types as combinations of property strings if we want, by adding items to the array.
  • The creator function takes a hint in the second parameter. When hint=="avatar" we’re being asked to create a DOM representation of the avatar, so our function takes that into account. Here we skip displaying the description and quantity when we make an avatar, and we put the entire avatar into its own table, since the default node that contains the avatar(s) is itself a table, and we don’t want to ruin our DOM.

An online store product catalog

At this point, we can introduce version 3 of the demo (we’re doing it all programmatically from here on out, so there’s no markup-style version). There’s a substantial overhaul in the appearance now, thanks to our table-based DnD sources. We’ve now put the wishlist and shopping cart into a couple of dijit.TitlePanes so we can easily toggle their visibility. This demonstrates a couple of concepts:

Creating Pure Targets

var cart = new dojo.dnd.Target("cartPaneNode", {accept: ["inStock"]});

We have a new class here: dojo.dnd.Target. This is just a thin wrapper around dojo.dnd.Source, but it sets an internal variable isSource = false, rendering it a pure target. You can drop items on it, but can’t drag them back out again! Incidentally, you can freely manipulate this field at runtime on a regular dojo.dnd.Source; we’ll do that later on.

Changing the “Drop Parent”

cart.parent = dojo.query("#cartNode tbody")[0];

Here we use the dojo.dnd.Source’s internal parent field to change the drop behavior. You see, this object separates the concept of its 1) own node and the 2) node that’ll contain its children (mostly so tables work, since the children actually live underneath the <tbody>). We can take advantage of that by actually dropping items into a node further underneath the one that we create the TitlePane on. See, given the markup we’re using:

We don’t want to drop items on a closed TitlePane (the <div>s) and create items as their immediate children; we want them to get put inside a table that lives inside the TitlePane, so the DOM stays legal (we’re moving <tr>s around, and we don’t want them to get attached directly to a <div>)

Things are starting to look better, but there are still a couple of changes we can make to demonstrate a few more concepts.

Handling Events

The drag and drop framework uses Dojo’s topic system to handle event communication. We can polish up a few things if we take advantage of this. For example, it would be nice if we could show the number of items in the wishlist and cart when they’re closed so we needn’t open them to check. Plus, it would clean things up visually if we cleared the selection states of our containers when we drop an item. We can do that.

Building a Drop Handler

Let’s hook into the drop notification. We can either listen for the DnD topics directly, or we can simply connect to the existing method handlers. Either one is fine; there is a subtle semantic difference, which we’ll discuss in a minute.

// calculate simple totals in the wishlist and cart titles
var setupCartTitle = function(){
    var title = "Shopping Cart";
    var cartLength = cart.getAllNodes().length;
    if(cartLength){
        var items = cartLength > 1 ? " items" : " item";
        title += " (" + cartLength + items + ")";
    }
    cartPane.setTitle(title);
};
var setupWishlistTitle = function(){
    var title = "Wishlist";
    var wishlistLength = wishlist.getAllNodes().length;
    if(wishlistLength){
        var items = wishlistLength > 1 ? " items" : " item";
        title += " (" + wishlistLength + items + ")";
    }
    wishlistPane.setTitle(title);
};
dojo.connect(cart, "onDndDrop", setupCartTitle);
dojo.connect(wishlist, "onDndDrop", setupWishlistTitle);

So here we connect to the cart’s and wishlist’s onDndDrop() respective methods and do some simple logic. However, be careful: these are fired by the topic system, so they both execute every time an item is dropped, no matter which container it’s dropped on. In other words, DnD notifications are broadcasted everywhere rather than sent to specific objects. In this particular case here, that’s no problem, since we’re just setting up the TitlePane titles; however, if you want to make sure your handler only executes for some particular object, you need something like this at the beginning of your function:

if(dojo.dnd.manager().target !== this){
    return;
}

That’ll do it. Later on when we decide to add code specific to each object into these handler functions, we’ll be glad we’re using this method, but as-is, we’re not doing anything that can’t be handled by a single function that doesn’t care about the items being tracked (the drag source, target, etc.). If you’re thinking we wouldn’t need this target snippet if we had just dojo.subscribe()‘d to the topic itself in the first place, you’d be absolutely correct. That’s the subtle semantic difference I mentioned above; for a global operation, listening for the drag topics makes sense; if you want to connect to a single object’s drag notifications, use the dojo.connect() style above, with the .target check snippet.

Speaking of that snippet, this is the first time we’ve seen dojo.dnd.manager(); this call gives us access to the singleton Manager object the DnD system uses to coordinate everything. This object handles all of the business logic of dragging and dropping, publishing the topics, initiates avatar creation, and so forth. It’s a handy source of information on the overall DnD system state. As above, see the technical docs for the details.

Listening Directly to the Topics

Since I mentioned that you can subscribe to the topics yourself when it makes sense, let’s cook up an example. When you start dragging items around, it’s not completely intuitive where you’re allowed to drop them; you have to keep dragging until the avatar turns green. On top of that, there’s no immediate feedback that your drop was successful. We can create a better experience than that.

function highlightTargets(){
    var props = {
            margin: { start: '0', end: '-5', unit: 'px' },
            borderWidth: { start: '0', end: '5', unit: 'px' }
    };
    var m = dojo.dnd.manager();
    var hasZero = false;
    dojo.forEach(m.nodes, function(node){
        // check the selected item(s) to look for a zero quantity
        // so we know whether we can highlight the cart
        if(m.source.getItem(node.id).data.quantity == 0){
            hasZero = true;
        }
    });
    dojo.style("wishlistPaneNode", "borderColor", "#97e68d");
    dojo.style("cartPaneNode", "borderColor", "#97e68d");
    dojo.anim("wishlistPaneNode", props, 250);
    if(!hasZero){
        dojo.anim("cartPaneNode", props, 250);
        dojo.byId("cartPaneNode").isHighlighted = true;
    }
}

function unhighlightTargets(dropTarget){
    var props = {
            margin: { start: '-5', end: '0', unit: 'px' },
            borderWidth: { start: '5', end: '0', unit: 'px' }
    };
    cpn = dojo.byId("cartPaneNode");
    var cartIsHighlighted = cpn.isHighlighted;
    cpn.isHighlighted = false;
    if(dropTarget && dropTarget.node && dropTarget.node.id){
        // dropTarget lets us know which node to highlight yellow
        switch(dropTarget.node.id){
            case "wishlistPaneNode":
                if(cartIsHighlighted){
                    dojo.anim("cartPaneNode", props, 250);
                }
                dojo.style("wishlistPaneNode", "borderColor", "#ffff33");
                dojo.anim("wishlistPaneNode", props, 500, null, null, 750);
                break;
            case "cartPaneNode":
                dojo.anim("wishlistPaneNode", props, 250);
                dojo.style("cartPaneNode", "borderColor", "#ffff33");
                dojo.anim("cartPaneNode", props, 500, null, null, 750);
                break;
            default:
                dojo.anim("wishlistPaneNode", props, 250);
                if(cartIsHighlighted){
                    dojo.anim("cartPaneNode", props, 250);
                }
        }
    }else{
        dojo.anim("wishlistPaneNode", props, 250);
        if(cartIsHighlighted){
            dojo.anim("cartPaneNode", props, 250);
        }
    }
}

Then, in our initialization function:

var resetSelections = function(){
    cart.selectNone();
    wishlist.selectNone();
    junkCatalog.selectNone();
    foodCatalog.selectNone();
};

// highlight valid drop targets when a drag operation starts;
dojo.subscribe("/dnd/start", null, highlightTargets);
dojo.subscribe("/dnd/cancel", null, unhighlightTargets);

dojo.subscribe("/dnd/drop", function(){
    resetSelections();
    unhighlightTargets(dojo.dnd.manager().target);
});

Since we’re listening to the topic broadcast itself, we know these will only run once per event. Manipulating a bit of CSS with dojo.anim() helps make the drag and drop system a bit friendlier here, and that’s always a good thing.

Armed with knowledge of how to run code at various parts of the drag and drop timeline, we can see that it wouldn’t be difficult to extend this further. For example, our item quantities basically only determine whether you can drop something on the shopping cart, but it would be great if those updated when you did so (but not when you drop on the wishlist!). Since we have two separate DnD sources here, they each track their own selection state. Maybe we could set it up so that when you click on an item in one of the sources, it clears the selection on the other one. And of course, this little storefront doesn’t have any prices! In a real store you’d want to add that, and probably upgrade setupCartTitle() to calculate a subtotal. The sky’s the limit.

Avoiding Duplicate Items

One thing I hadn’t pointed out before was that in version 3 of our demo, in addition to specifying the node creator function when instantiating our dojo.dnd.Source objects, we also passed a parameter copyOnly: true. This overrides the default move semantics to do a copy operation by default, without requiring a special key press. This is nice because now we can avoid removing items from the catalog(s) when we drag them around, but the downside is that if you drop an item on the container where it already lives, it duplicates the item.

Huh. That’s interesting, because we’re not specifying the accept type for the catalogs, so they should default to ["text"], which should keep us from dropping the products on them (we’re explicitly giving them different types, remember). However, it obviously doesn’t work the way we want. If you dig into the Dojo source, you’ll see that the reason is because the function that checks for matches between item types and container accept values automatically accepts “self drops,” short circuiting the item type check. Often, that’s the correct behavior, but for our copyOnly-style DnD here, this is backwards. Fortunately again, overriding this is easy: just replace the object’s checkAcceptance() function:

// based on dojo.dnd.Source.checkAcceptance()
function checkAcceptanceWithoutSelfDrop(source, nodes) {
    if(this == source){ return false; }
    for(var i = 0; i < nodes.length; ++i){
        var type = source.getItem(nodes[i].id).type;
        // type instanceof Array
        var flag = false;
        for(var j = 0; j < type.length; ++j){
            if(type[j] in this.accept){
                flag = true;
                break;
            }
        }
        if(!flag){
            return false;
        }
    }
    return true;
}

Then in our initialization code,

junkCatalog.checkAcceptance = checkAcceptanceWithoutSelfDrop;
foodCatalog.checkAcceptance = checkAcceptanceWithoutSelfDrop;

But wait! That's not all. There's still a small bug here: self drops are properly blocked at first, but after you drop an item successfully on the wishlist or shopping cart, self drops are accepted again. The reason is basically due to the timing and placement of the internal calls that cache acceptance criteria inside dojo.dnd.Manager. I won't go into the details, but suffice it to say that we need to add this to our drop topic handler from before:

// reset the manager's drop flag to false, since in our
// case DnD operations always start on a container that
// will not allow "self drops"
dojo.dnd.manager().canDrop(false);

This way whenever a drag operation is initiated, we always start in a "cannot drop" state, and now all is right with the world. Take a look at version 4 of our demo to see it all working together. Some of the code has been reorganized and/or moved to an external file to reduce clutter, but the new stuff is all there if you view the page source.

An online storefront

Tweaking DnD Behavior

The final thing left to talk about for this demo is the set of buttons we introduce in version 4. We have buttons to clear the wishlist and shopping cart now, but notice the buttons at the bottom of the page. These demonstrate different ways to change the way DnD behaves. You can read the code to see how they work, but here's what they do:

  • isSource ("enable DnD"): If you've ever wondered how to turn DnD on and off completely, here's one way. This toggles the isSource member of each of our sources; I mentioned this earlier, but recall that when this is false, the manager won't initiate any drag operations. Objects will still accept drops, but if there's nothing acting as a source, we've effectively disabled the DnD system.
  • withHandles ("drag via handles only"): If you give a DOM node the dojoDndHandle class, dojo.dnd will consider it a handle. Each Source has a member variable withHandles that determines whether you can drag any part of an item, or just the handle. This demo sets up the product images as the handles, so if you toggle the button, you'll see the drag behavior change accordingly.
  • skipForm ("no button in the demo"): If you have form elements in your drag items, you can use the skipForm field to toggle whether or not clicks inside them will initiate drags. Setting it to true will allow you to, say, select text in a <textarea> without dragging everything around.

Finishing Up

For reference, here are the steps we've taken so far:

By putting our Javascript code into an external .js file and adding a bit of markup (that we remove via JS in the initialization sequence), we can even create a new version that validates as XHTML 1.0 Strict!

There's quite a lot to discuss in dojo.dnd. For example, we haven't really touched the CSS it uses to let your app know what's happening in DnD land (you can see what this demo uses in dnd.css). There's also a whole discussion we could have on how to cleanly set up your own custom DnD sources by extending dojo.dnd.Source with dojo.declare(). A lot of dojo.dnd's internals are specifically set up to be easy to override with your own code so you can customize just about any part necessary. And finally, there's the dojo.dnd.move API, but as I said at the beginning, that's for next time.

Until then, happy dragging and dropping!

Comments

  • David

    Nice tutorial. Although it’d be much nicer if all your JavaScript was inline instead of a separate file. It makes tracking your changes *much* harder.

  • sant

    Good stuff, it would be very gratefull, if you could provide js file code all together at one place ofter finishing up.

  • Hello David and sant; thanks for the feedback. I’ve uploaded an all-inline version of the final demo to http://www.sitepen.com/labs/code/dnd/intro/dnd/4a-dnd.html if you want to see everything (well, except the CSS) in one place.

  • David

    Now *that’s* the kind of JS demo page I like.

  • Dilla

    Great Tutorial..
    nice css
    this is absolutely not a ‘junk’
    n thanks for the all-inline version

  • sant

    Thanks David, for uploading js file. Can you provide a sample application of resizable panels using dojo, i have been searching for 2 days, i couldn’t find any good one’s.Thank you very much in advance.

  • Thats the best Dojo tutorial on DnD ever! The link to the API doc is also very handy (I think it make DnD the most documented part of the Dojo API!)

    There does seem to be a lot of code in the header of that file, with most of it being procedural and not easy to reuse. I hope you make good on the promise to show us how to “extending dojo.dnd.Source with dojo.declare()”. I guess that would help with writing JS in the ‘Dojo style’ generally (which could make a nice article all of its own).

    Love your work. Thats the most funky shopping cart interface I’ve seen online to date!

  • this dnd tutorial realy helped me to develop my new applicatio, especially the event handling part! good job. By the way: does anybody know how to make work dnd on iPhons’e Safari? Fabian

  • slickt10

    This was great, although, I really wish someone would figure out how to do dnd with Grid. Im trying to figure out how to drag from a grid to a tree. If anyone has input please let me know or paste a link here

  • @fabian: check out our latest blog posts on iPhone and dnd: http://www.sitepen.com/blog/2008/07/10/touching-and-gesturing-on-the-iphone/ . The short answer… it should be possible with iPhone 2.0.

  • So, I am using a custom creator, and it creates a couple of widgets per li, namely, a checkbox and a slider. The scenario is just to resort the list of these items (so, checkbox label slider make up one item)

    When I attempt to drag, I get this in Firebug:

    [Exception… “‘Error: Tried to register widget with id==Floor1_container but that id is already registered’ when calling method: [nsIDOMEventListener::handleEvent]” nsresult: “0x8057001c (NS_ERROR_XPC_JS_THREW_JS_OBJECT)” location: “” data: no]

    Line 0

    Any ideas?
    Thanks,
    Ruprict

  • sohil

    Hi,

    I’m new to the Dojo toolkit so please bear with me if any of my queries seem elementary. I’m building a sample application based on your DnD tutorial. So I have a simple list whose elements the user can reorder using drag and drop. This is the code I’m using.

    @import url(“http://o.aolcdn.com/dojo/1.1.1/dojo/resources/dojo.css”);
    @import url(“http://o.aolcdn.com/dojo/1.1.1/dojo/resources/dnd.css”);

    dojo.require(“dojo.dnd.Source”);

    function test(){
    alert(“hello”);
    dojo.require(“dojo.dnd.Source”);
    var wishlist = new dojo.dnd.Source(“wishlistNode”);
    wishlist.insertNodes(false, [
    “Wrist watch”,
    “Life jacket”,
    “Toy bulldozer”,
    “Vintage microphone”,
    “TIE fighter”
    ]);
    }

    Wishlist

    This differs from your sample in the tutorial in the following respect – Instead of setting up the DOM at the time of the page load using dojo.addOnLoad(init()); I’m providing a button which when clicked creates the list. If I take off the submit button and call test using the onLoad event of the body tag even then it works. My query is does Drag and Drop work only when the DnD elements are set up at the time the page loads. What if once the page has been loaded, can I use DOM and add DnD capabilities to other elements on the page dynamically through javascript functions without having to reload the whole page.

    Kindly let me know if I’m missing something really obvious. Thanks in anticipation.

  • @sohil:

    There’s nothing about the DnD API that requires it to be run at startup; it’s perfectly able to build things on demand at runtime. There’s probably something in another part of the code that’s causing the issue. I’ll contact you out of band.

  • tom

    Hi Revin Guillen,

    Its a very nice and interesting tutorial regarding RnD using DOJO. I want to build a functionality like weebly.com. how dojo will exactly fix in this context. i am waiting for your replay.

  • @Tom: Hi there, have you seen http://roxer.com , which seems comparable to the site you list, but uses Dojo throughout to deliver that user experience? You might also get inspiration from http://projectzero.org .

    We would of course be happy to help out through our development and support services ( http://sitepen.com/services/ ) if you are looking for more direct, one-on-one assistance with your application!

  • cameraman_ben

    Hi,

    This article has proven very useful getting me started with Dojo. I’m writing a site featuring pairs of ordered lists on a number of tabs. I’m able to populate the lists using xhrGet and the programmatic approach from your tutorial and the accept facility I am able to shift items from one the left-hand list to the right on each tab.

    Here comes the problem that I have been pondering for hours now. I can’t drag back from the right-hand lists to the left. Tinkering around I can flip the problem but there always seems to be one list that won’t take items.

    Finally, I had another go with your examples and with ‘Dylan’s Old Junk Outlet, Version 2’ – the programmatic version – exactly the same thing happens. I can go from catalogue to cart to wishlist to cart but not back to catalogue. Yet on the markup version I can.

    I’m currently using Firefox 3.0.3 on Windows XP to test.

    Otherwise thanks for a very clear tutorial.

  • cameraman_ben

    Now working without changing anything – must be finger trouble or something odd with my setup.

  • Mona Everett

    This is a wonderful tutorial. However, I am struggling with a problem and I cannot find a working example anywhere.
    I can make two unordered lists or dictionary lists and drag between them. What I cannot make work is dragging options between two HTML Select lists ( using size = 10 so that they are scrollable ). I can drag within the lists but not from one to the other.

    Please, if you have a DECLARATIVE working example of this, I would certainly appreciate it and, since I cannot find an example, I suspect that other people might appreciate it too.

  • Marv

    Mona,

    I don’t know the code you’re using for the drop(), but if it’s innerHTML, I’ve run into the problem of innerHTML update on a object before.

    Solution is to put a around the select and add your own “” and “” to the innerHTML.

  • Marv

    My reply above left out an important word of a ‘div’ in the solution: wrap a div around the target select.

  • Ben

    Hi. I’m trying to modify this code to refresh the list of nodes with a new datasource. But the code breaks when insertNodes is run again. See sample code at:
    http://www.mobilevoter.org/coco/dndTest.html

    FF ignores the error.
    IE breaks completely.

    Any ideas?

  • Eduardo

    Hi,
    dojo.DnD always create the dragged item at the drop container.
    Is there a way, using the standard features of dojo.DnD to have a replace situation?
    I have two containers, container1 can only have 1 item, container2 can have multiple items.
    When I drag from container2 to container1 I need to replace the container1 item for the dragged one and send the old container1 item back to container2.
    Can you give me some light how to implement it?

    Thanks

  • In your tutorial you once used:

    var catalog = new dojo.dnd.Source(“catalogNode”, {
    accept: [“inStock,outOfStock”]
    });
    ….
    and later:

    var wishlist = new dojo.dnd.Source(“wishlistNode”, {
    accept: [“inStock”,”outOfStock”]
    });

    So are both ways possible to add the accept-List? [“inStock,outOfStock”] / [“inStock”,”outOfStock”]?

  • @Christian Gaeking:

    Good catch! The [“inStock,outOfStock”] version is wrong, actually. It’s supposed to be an array of strings. So [“inStock”,”outOfStock”] is the version to use. The reason it still ended up working is because the checkAcceptance function on the DnD Source object short circuits the item type check and allows you to drop any item that originated in the Source, regardless of the item’s specified type.

    Thanks for finding this; I’ll fix the code.

  • Subba

    Great Tutorial on Drag and Drop.
    But i
    when we continuously drag food/junk items to the right side the browser is extending horizontally and horizontal scroll bar is coming.
    Is there a way to remove that behavior?

  • dsr

    Hi

    Great tutorial for dojo drag and drop.
    I am also implementing similar kind of drag and drop.
    But i am getting one error. When i continuously drag any item to right side the browser gets extended and gets the horizontal scroll bar.
    How can i avoid the behavior?

  • phil bowles

    <>

    I couldn’t agree more. Can you get a job doing the rest of the Dojo docs? If they were all like this,
    a) Dojo’s “support” load would halve
    b) Its user base would rocket
    c) There would be more good karma and less stress in the world

    Excellent Job
    (one cyber beer coming riiiiight up!)

  • yash

    This doesn’t work in IE 8

  • Pingback: Chris Weldon’s Blog » Dojo Drag-n-Drop and Form Submission()

  • J-R

    Hi,
    I have been trying to replicate “Dylan’s Old Junk Outlet, version 2” programmatically and I am stuck at the “forinitems”.
    I get a node is null error messsage and I’ve been trying to debug it for 3 days without success. That’s the part where I’m trying to add the class to the unordered list items that were just created.
    my small code snippet is below:
    Can you tell me what’s wrong with the forInItems? It’s the only part not working. All help is appreciated. Thanks,
    JR

    var accPaneStructure = new dijit.layout.AccordionPane({title:”Structure”, id:”accPaneStructure”});

    //Add structure list items
    var structureNode = dojo.create(“ul”);
    dojo.place(structureNode, accPaneStructure.containerNode);
    var catalog = new dojo.dnd.Source(structureNode, {accept: [“Scenario”,”multiPortfolio”,”Portfolio”]});
    catalog.insertNodes(false, [
    { data: “Scenarios”, type: [“Scenario”] },
    { data: “Multi-portfolio”, type: [“multiPortfolio”] },
    { data: “Portfolio”, type: [“Portfolio”] }]);

    catalog.forInItems(function(item, id, map){
    dojo.addClass(dojo.byId(id), item.type[0]);
    });

    accPaneStructure.startup();

  • emq

    Hi,

    In Dojo 0.4, there was a way to create a customized drop indicator, did it see the same feature mentioned in the new Dojo DND. Is there a way to do the same? And can you share how to generate a drop indicator?

    Thanks,

  • i want to ask how i can display a div target while operation drag n drop..

    can you give me simple example like a list item without images so only text…

    Thank you

  • Meera

    Hi,

    I need a smilar functionality. I tried going through your article, I am not able to understand as where to start. I am not able to find any example or the source code. Please help.
    Thanks.

  • Atif

    Hi guys,

    Is it possible to use Dojo for dragging and dropping elements from a popup page to the parent page? Any ideas would be appreciated…

    Cheers

  • daboy

    Any idea if you can constrain a dragable (moveable) item within a dojo.dnd.Source that was created programmatically with a creator? It looks like parentConstrainedMoveable is what I need, but how to apply to a Source’s item(s)…?

  • Indrajit Kanjilal

    Excellent tutorial. I could make my application run with dnd within an hour with this help. Thanks!

  • arnaud

    Hi, what a wonderfull work !

    This is a perfect learning by practice…

    But when I try to use your solution (Using Multiple Sources, markup version) in my struts2 application, I’ve got three simple lists with no Drag Drop ability and this error in the Firefox console :
    Could not load ‘dojo.dnd.Source’; last tried ‘__package__.js’
    http://localhost:9000/my_app/struts/dojo/struts_dojo.js

    Any ideas ? Thanks for helping…

  • Alan

    You say in your example, in the paragraph just above “Avoiding Duplicate Items” that you could update the quantities in the sources (catalog) after the items have been dragged to the cart – I have not been able to achieve this. Here’s the code I used:

    dojo.subscribe(“/dnd/drop”, function(source,nodes,iscopy)
    {
    resetSelections();
    unhighlightTargets(dojo.dnd.manager().target);
    if (dojo.dnd.manager().target.node.id == “cartPaneNode”) {
    console.log(“qty = ” + source.getItem(nodes[0].id).data.quantity);
    source.getItem(nodes[0].id).data.quantity–;
    console.log(“qty = ” + source.getItem(nodes[0].id).data.quantity);
    }
    // reset the manager’s drop flag to false, since in our
    // case DnD operations always start on a container that
    // will not allow “self drops”
    dojo.dnd.manager().canDrop(false);
    });

    The console logs show that the quantity has been decreased, but the quantity shown in the catalog doesn’t change. Does anyone know why?

  • Great tutorial, helped me heaps to get a great dnd solution up and going from an existing custom dojo widget

  • This is an excellent overview. Is it possible to have sources with sources in them? I want to be able to drag between buckets and also to move the buckets around.

  • Chris Thompson

    None of the five demo versions work in Internet Explorer 8 unless Compatibility View is turned on for the website. (I’m running IE 8.0.6001.18882.) Firefox works beautifully out-of-the-box. (I’m running Firefox 3.5.7.)

  • Hi everyone,

    Yes good tutorial here about dojo capabilities but I want to resolve IE bugs:
    They happens due to script tags and specially the way IE is loading the webpage.

    Solution:
    Insert the dojo libraries at the bottom of the documents and it will work great.

    Have a good day!

  • Jake

    Awesome tutorial. This tutorial is how one should be written.

  • Great Article! Semantically one order of magnitude higher than any dojonDnD reference. I wish you could say more about inheriting elements from source. Do you know any reference to that?

    Best,

  • Pingback: DojoCampus » Blog Archive » Skip the mark-up: dojo.body, create and place()

  • James Hunny

    As others have said, I don’t want the draggable item to ‘push’ (scroll) the body of the page, this should be preventable by parenting the draggable item within another element but that technique is broken if you popup your draggable item using dijit.popup.open because that always parents your item to the body……..ARGH!

  • Mitul

    Can anyone pls give me the classes use in dnd…also can anyone help me out with dnd dat cotains a list box and it is bound with sql data source and i want to drag and drop its individual item..

  • Helen

    Hi,
    I have a list of items which are of type dojoDndItem. The user can drag and drop those items to change its ordering inside a table.
    My problem is: these items are not totally independent with each other. When the user drags a parent item, I want the dependent item is automatically dragged along with its parent item as well.
    Could you guys help me please.
    Thanks
    Helen

  • This is an awesome tutorial – thanks!

    Does anyone know if it’s possible to achieve nested targets (targets within targets) without using DND tree? There were some patches listed in the Dojo forums, but the links seem to be all broken now.

    Thanks,

    Rob

  • Mark

    What happens if you want to remove only one item from the Cart? Why do you have to clear the whole cart to remove only one item? Thanks

  • Mike

    Is this up to date ? Would be great to know if this tutorial survived the api changes in 1.5 before expending too much time on it.

  • anomynous

    The demo is still working so most likely yes!

  • Chris

    The demo is loading 1.1! Mike is right to ask about 1.5.

  • John R Ramsden

    Preparing a 1.5 version of one’s own based on this excellent article would be a great exercise in getting to grips with Dojo 1.5 drag-n-drop (while gaining some familiarity with the old API, in case you encounter code that uses it and perhaps have to convert that!).

  • Topo

    Hi. Where is part2? Where is dnd.move tutorial?
    Thanks

  • David Gilmore

    Very spiff!! Dylan’s a lucky guy. (grin) Thanks Revin!…

  • Pingback: Drag n Drop with Dojo — Dezynworks()

  • Pingback: Dojo Drag’n’Drop Redux()

  • Hubert Kouame

    Thanks for your tutorial !i’m a beginner in dojo. i have a question . How can i access to the items in cart ?

    var cart = new dojo.dnd.Source(“cartNode”, {
    accept: [“inStock”]
    });

  • Tom

    The links to the demos are broken.

  • james

    the links to the demos are _still_ broken.

    I would reeeeeeeely like to see how this stuff works.
    maybe get something updated for 1.7?

  • james

    oh look….
    my bad, I should have looked around a bit before complaining….

    the 1.7 stuff can be found here…
    http://www.sitepen.com/blog/2011/12/05/dojo-drag-n-drop-redux/

    the links work and everything!

  • marcelo

    thanks for your tutorial, here a question..
    How can I capture the value of one selected item???

    thanks again.

  • Prince Pegram

    Timely piece – Incidentally , if your business have been needing to merge some PDF files , my colleague merged a tool here http://goo.gl/4ducvc.