Drag n Drop with Dojo

This article could more be more aptly named “@#$@#$@)* with Dojo.” This is a classic example, IMHO, of where Dojo documentation falls flat on its face. The doc page for drag and drop (aka dojo.dnd) is more of a method and property summary of all the objects that work together to make drag and drop work in Dojo. It doesn’t do a very good job of explaining just how all those objects work together, at least not in an easy to digest manner. There’s also a lot of information there that, frankly, just doesn’t matter to the developer just trying to get some simple drag and drop widgets on his or her web page.

Also missing are practical examples that exist elsewhere in the documentation. In fact, that is one thing that Dojo’s documentation does do well: lots of examples. For someone like me—who has an easier time learning by looking at code examples than wading through textual explanations—that’s a god send. There is one fairly detailed example of a shopping cart I found at sitepen.com. However, it has one major shortcoming: it completely ignores the backend that would drive any real world shopping cart. So, here’s several hours of googling and swearing boiled down to “The quick and dirty guide to using drag and drop with Dojo.”

Getting items on your web page that drag and containers to drop them on is actually quite easy, almost to the point of being ridiculous. Since I think validated HTML is over hyped (a topic for another day), I prefer to use Dojo’s HTML attributes when creating widgets. So how hard is it to get something on the page that drags and drops?

<div dojoType="dojo.dnd.Source" id=source1>
   <div class="dojoDndItem" dndData="A">Item 1</div>
   <div class="dojoDndItem" dndData="B">Item 2</div>
</div>
<div dojoType="dojo.dnd.Source" id=source2>
   <div class="dojoDndItem" dndData="C">Item 3</div>
   <div class="dojoDndItem" dndData="D">Item 4</div>
   <div class="dojoDndItem" dndData="E">Item 5</div>
   <div class="dojoDndItem" dndData="F">Item 6</div>
</div>

That’s all there is to it. You can drag and drop items between your two sources to your heart’s delight. But what happens when you’re ready to record the changes that have been made? What if you’d rather use ids over text strings to record information back to the database? That was where the @#$@#$)&!! came in.

Let’s start with using dojo.connect to catch events. Trying to get a handle on a node with dojo.byId(“source1″) isn’t going to work, presumably because it’s only going to return a DOM node and not a Source object. Drag and drop sources are not dijits, so dijit.byId(“source1″) isn’t going to work either. If that’s the case, then how do you get a handle on your source? Well, O’Reilly to the rescue. In the chapter on drag and drop, Dojo: The Definative Guide has a line like this in one of the examples.

source1 = new dojo.dnd.Source("source1");

Seems a bit redundant to me, but it works. Now you can use ‘source1′ as the object to connect to and it doesn’t seem to cause any problems that you’ve already defined source1 as a source with dojoType=”dojo.dnd.Source”.

Next is the onDrop event. Dojo’s documentation seems to indicate it’s the “preferred” event to catch, except that it doesn’t work. Nothing I could connect to seemed to be firing onDrop. O’Reilly and other sources use onDndDrop which does work, so I ended up with a connect statement that looks something like this:

dojo.connect( source1, 
   "onDndDrop", 
   function(source, nodes, copy, target){
      //your code to work your magic goes here
   }
 );

The parameters are fairly self explanatory. “source” is the source object where the draggable nodes originated. “nodes” is a collection of the items being dragged. “copy” is a boolean indicating whether or not the nodes should be copied to the target or moved from the source. “target” is the source object on which the nodes were dropped.

However, in practice I’ve ended up using the event onDropExternal much more often. Basically this means the item that was dropped on a particular target started out in a different source. Most of the time this probably what you want to catch and it saves you the trouble of adding logic to compare the source and target parameters and respond accordingly.

Whichever event you use, you can iterate over the nodes array passed in and take whatever action is necessary. However, the node array is a collection of DOM elements. There’s no Dojo goodness attached to them. In my earlier code example drag and drop items included a dndData=”n” attribute. This data will not be available in the node array. Each source keeps a hash with information about the nodes it contains. The hash is indexed on the id attribute of the node (which dojo will create if you don’t specify one.) That is where the value you put in dndData ends up. So in order to get at the data you need to do this:

for( i=0; i < nodes.length; i++){
   item = this.getItem(nodes[i].id);
   my_data = item.data;
}

Remember, this code is in and event handler so “this” is the source object that fired the event. Do you have to use dndData? No. If you don’t specify dndData, item.data contains the innerHTML of the node. So taking an item from my first example:

<div class="dojoDndItem">Item 1</div>

item.data would contain “Item 1.” Can you specify complex data via HTML and dndData, say with JSON notation? No. You could probably use your own HTML attributes. Something like

<div class=dojoDnDItem myid="123" myname="ABC">DnD Item</div>

would work (theoretically), and then you could then use dojo.attr(node,”myid”) to retrieve the data. That sounds like it should work, but I haven’t tried it. I stuck with using the hash for a couple of reasons. First, if there is a facility already provided, I don’t see any reason to hack something else. Second, adding items to your source containers via Javascript will populate the hash so you may as well get in the habit of using it.

So are you home free? Not just yet. There’s one more gotcha that could trip you up. Remember in my first code example I used dojoType=dojo.dnd.Source? There is also dojo.dnd.AutoSource. What’s the difference? Where you look for your data. If you use dojo.dnd.AutoSource, this.getItem(“node id”) will work. If you use dojo.dnd.Source, the data will still be in the source object and not moved to the target source object when the drop event fires. That means you’ll have to change “this” to “source” to get at your data. I’m sure someone came up with some example implementation where the distinction makes sense. I haven’t found a need for it yet, so I have always used dojo.dnd.AutoSource.

I hope you found this helpful. In my next post I’ll talk about populating DnD sources on the fly. If I’m feeling really ambitious, I might talk about things to watch out for when subclassing dojo.dnd.Source as well.

This entry was posted in Dojo and tagged , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

8 Comments

  1. Rob Enright
    Posted August 11, 2011 at 9:59 pm | Permalink

    Many thanks for a great article. I was also baffled by the documentation and just couldn’t get onDrop to work.

  2. Dan Tosh
    Posted August 17, 2011 at 1:25 am | Permalink

    Thanks for the post. When’s the next one coming?

  3. Posted July 11, 2012 at 3:29 am | Permalink

    Don’t you mean:

    for(var i=0; i < nodes.length; i++){
    var item = source.getItem(nodes[i].id);
    my_data = item.data;
    }

    in the onDropExternal example?

  4. Pierre
    Posted July 13, 2012 at 10:51 am | Permalink

    Really good tutorial! I’ve been trying to add a widget as a dndItem in a dndSource but it just adds a [object] tag in the html .. really looking forward to your next post!

  5. Sean
    Posted July 25, 2012 at 4:12 pm | Permalink

    @Will: You can include var in the loop definition. It’s not strictly necessary if you’re careful about your variable names, but it does make sure that there aren’t any other variables named i in scope that the loop might mess with.

    @Everyone: My apologies that I haven’t kept up with this. Things have been rather crazy the last year or so and keeping this site updated hasn’t made it to the top of my to do list very often. As you can see from the comments, I never even finished tweaking this theme. Cobbler’s children and all.

  6. Philippe
    Posted September 13, 2012 at 2:55 pm | Permalink

    As a side note, if you declare a Source declaratively, rather than doing :
    source1 = new dojo.dnd.Source(“source1″);

    … you can use a jsId attribute on your tag. Don’t forget to wrap your code inside dojo.ready to retrieve it…

  7. Christoph
    Posted December 5, 2012 at 4:46 pm | Permalink

    I think instantiation the same Source another time just to be able to reference it, as suggested in “Dojo: The Definitive Guide”, is not such a good idea. It’s better to reference it by setting a data-dojo-id in the declaration.

  8. Rob
    Posted February 6, 2014 at 3:10 pm | Permalink

    Although simple this is far less confusing than any other dojo documentation I’ve read :) The actual dojo docs give me a real headache!

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

    • Aviation University.com
    • HopsAndGears.com
    • tds1
    • ogio1
    • Aegis