Javascript Drag and Drop

Filed: Sat, Dec 02 2006 under Programming|| Tags: drag javascript drop grab move

Some of the most exciting features of dynamic HTML are the least used. Instead of linking to a video on youTube what if you could overlay that video directly onto your current web page? What if we could even go a step further and let the user move that video anywhere on the page simply by using drag and drop?

What if it's not a question of "what if" but "how"?

A full example page from this tutorial can be found at http://www.hunlock.com/examples/dragdrop.html and you can run this demo by clicking here

It's actually pretty simple to create a drag and drop element. The first step is to set up a division to hold the dragable "window" (henceforth referred to as a layer).

<div id='vidPane' class='vidFrame'></div>

Note that we're setting up the styles in a stylesheet with a class named vidFrame. Don't define the styles with a style tag in the division! Our drag and drop handlers will be looking at the actual class name of the division to decide if it's a movable object or not!

The next step is to assign some basic styles to the layer.

.vidFrame { 
             position: absolute;
             display: none;
             background-color: #ffdead;
             border: 1px solid #800000;
             width: 435px;
             height: 362px;
             cursor: move;
          }

The only absolutely critical line in the style above is "position: absolute". This tells the browser that the layer isn't bound directly to the HTML page it can float above all the other elements. The rest are fairly self explanatory, save maybe for cursor which basically just tells the browser to change the shape of the mouse to a 4 sided "move" arrow when the mouse is over the layer when it's visible.

At the bottom of your web page we need to declare some global javascript variables. We're not doing this in the HEAD area because we need to be able to find our vidPane layer and Javascript can only do that once the browser has placed the division on the page.

<script type="text/javascript">
   var savedTarget=null;                           // The target layer (effectively vidPane)
   var orgCursor=null;                             // The original mouse style so we can restore it
   var dragOK=false;                               // True if we're allowed to move the element under mouse
   var dragXoffset=0;                              // How much we've moved the element on the horozontal
   var dragYoffset=0;                              // How much we've moved the element on the verticle
   vidPaneID = document.getElementById('vidPane'); // Our movable layer
   vidPaneID.style.top='75px';                     // Starting location horozontal
   vidPaneID.style.left='75px';                    // Starting location verticle
</script>

That is indeed a lot of global variables which means every function on the web page can access their data. In general global variables like this are considered a bad thing and future articles will cover drag and drop in a more formal manner. For now though, global variables are easy to use and understand and hopefully will aid you in mastering the basics of drag and drop.

It's very important that you set the top and left starting position in javascript as described above and not rely on the style sheet. By initializing the top end left position of the layer in this manner we make the starting values available to our code later on. If we set the values only in the style sheet then when we ask for the values of style.top we will get unreliable and unpredictable results.

When we styled vidPane we had it start out hidden, so we're going to need a javascript function to make the pane visible. While we're at it, we're going to accept a youTube ID code so when the pane is made visible it will automatically embed the request video and start playing it. You can put the javascript below in the HEAD section of your web page if you wish.

<script type="text/javascript">
   function playVid(vidId) {
      if (vidPaneID.style.display=='block') {
         vidPaneID.style.display='none';
         vidPaneID.innerHTML=''; 
      } else {
         vidPaneID.style.display='block';
         vidPaneID.innerHTML='<A HREF="javascript:playVid()">CLOSE</A>';
         var vidstring ='<center><embed enableJavascript="false" allowScriptAccess="never"';
         vidstring+=' allownetworking="internal" type="application/x-shockwave-flash"';
         vidstring+='  src="http://www.youtube.com/v/'+vidId+'&autoplay=1" ';
         vidstring+=' wmode="transparent" height="350" width=425"></center>';
         vidPaneID.innerHTML+=vidstring;
      }
   }
</script>

This is a fairly straightforward function. It accepts a youTube ID code as a string. Then if the vidPane layer is visible (=='block') it will close the layer by making it invisible and removing all the HTML from inside the layer (instantly stopping any video from playing). If the vidPane layer is NOT visible then the code will set the display to "block" (making the layer visible) and embed the video by setting up the html in a variable called vidstring then injecting that variable into vidPane's innerHTML. I'm using vidstring this way for the readability of this tutorial, if you want to inject the embed directly into the innerHTML you can do so without consequences.

To open and test this, after the BODY tag in your HTML insert the following HTML:

<A HREF="javascript:playVid('s4_4abCWw-w')">Open video</A>

You can replace "s4_4abCWw-w" with any youTube video id you wish of course. When you click on the link, the video window should become visible on the screen and the video should begin playing.

Now we need to make the window dragable! Insert the following three functions into your javascript block.

   function moveHandler(e){
      if (e == null) { e = window.event } 
      if (e.button<=1&&dragOK){
         savedTarget.style.left=e.clientX-dragXoffset+'px';
         savedTarget.style.top=e.clientY-dragYoffset+'px';
         return false;
      }
   }

   function cleanup(e) {
      document.onmousemove=null;
      document.onmouseup=null;
      savedTarget.style.cursor=orgCursor;
      dragOK=false;
   }

   function dragHandler(e){
      var htype='-moz-grabbing';
      if (e == null) { e = window.event; htype='move';} 
      var target = e.target != null ? e.target : e.srcElement;
      orgCursor=target.style.cursor;
      if (target.className=="vidFrame") {
         savedTarget=target;       
         target.style.cursor=htype;
         dragOK=true;
         dragXoffset=e.clientX-parseInt(vidPaneID.style.left);
         dragYoffset=e.clientY-parseInt(vidPaneID.style.top);
         document.onmousemove=moveHandler;
         document.onmouseup=cleanup;
         return false;
      }
   }

"dragHandler" starts the drag event. It sets the default cursor type to a grabbing hand (Mozilla only). If the event data was not passed to the function (IE only), it grabs the event data IE style (and sets the default cursor to a 4 arrow move icon since IE doesn't have the grabber cursor). Then we assign the object data to target. Target now has the information about the element the mouse was over when the event started.

The critical part of dragHandler is the line which reads: if (target.className=="vidFrame") This is the key to making any absolutely positioned object on your web page dragable. If the mouse was over an element with a className of "vidFrame" dragHandler is going to let you move that layer. It's that simple. We use vidFrame here as the class name since the example uses embeded videos, but for simplicity sake you could say: if (target.className=="dragable") and now any element with a classname of "dragable" will be draggable (again assuming it uses position:absolute).

If the className is "vidFrame" then the function sets the mouse to its new shape, marks the starting X and Y offsets, sets dragOK to true and then sets up two event handlers. document.onmousemove=moveHandler tells the browser to call the moveHandler function any time the mouse moves, and document.onmouseup=cleanup tells the browser to call cleanup when the mouse button is released.

Finally drag handler returns false. This is hugely important! By returning false you tell the browser that you are handling the event and not to follow through with its own default behavior. If you don't return false and you try to drag an image it's not going to work because the default behavior of the browser is that if a user drags an image, he wants to drag it to his desktop or a to a desktop application and that will over-ride the behavior YOU are setting up. When you return false the browser will bow to your wishes.

So when draghandler is called it figures out if it's over an element with the classname it's supposed to look for and if it is, it remembers some important information and then tells the browser to call moveHandler when the mouse is moved, and cleanup when the mouse button is release.

"moveHandler" checks to make sure the mouse button is actually down (either zero or one depending on the browser so we just check to see that it's less than or equal to 1) and that the dragOK flag is set to true (so we don't accidentally move something we're not supposed to). If both of those check out out. It calculates how many pixels to move the layer based on the current mouse position. Again we return false to prevent the browser's default behavior from emerging.

"cleanup" will be fired when the user releases the mouse button. It clears out the onmouseup and onmousemove events by making them null. This prevents the browser from calling movehandler and cleanup again. It sets the mouse back to its original shape before we started all of this and it sets the dragOK flag to false just to ensure that even if movehandler is called again somehow nothing is going to happen.

Now there remains only one thing to do to make all of this work. Add the following global to your javascript...

document.onmousedown=dragHandler;

This will tell javascript that whenever there's a mousedown event to call dragHandler. dragHandler will see if the mouse is over an element it's looking for, and if so will set up the movehandler and the cleanup events.

Now, if all the stars have aligned properly and there are no typos, you should be able to click your "open video" link and get a video window that you can drag and drop with abandon anywhere on your page.

A full example page from this tutorial can be found here: http://www.hunlock.com/examples/dragdrop.html

An expanded example showing both a movable picture and video can be found here: http://www.hunlock.com/examples/dragdroppic.html