Experimenting with jQuery Draggables and ASP.NET

One of the keystones of modern AJAX-enabled web sites is Drag and Drop. This article looks at the current state of Draggables that form part of the jQuery UI Library.

Back in the day, I needed to create a web-based Flatplan application. For those that don't know, a Flatplan is used in magazine publishing and it consists of a grid representing the pages of a magazine. Someone in the production department used to write on this using a pencil (a hollow tube of wood filled with a stick of graphite that left marks on paper) to indicate where specific advertisements of various sizes and editorial items (articles and news stories) would appear in the final printed item. An eraser was used as items were constantly moved about from page to page to accommodate the demands of advertisers booking space right up to the final minute.

The final solution was (I thought) elegant and featured various sized divs stacked on top of eachother using incrementing z-indexes, which could be dragged around the web page and left wherever the user wanted. They used AJAX (xmlhttprequest) to report their position, which was stored in a database. When the user opened the application, each of the divs "remembered" their position and took it up at start-up. The Javascript file to manage all this was several hundreds of lines long, and was the heavily customised result of a snippet I found through Googling. jQuery's UI Library would probably have made this so much easier, but it wasn't available then. So I have recently been fiddling about with the Draggables to see what they are capable of.

To make use of Draggables, you need to download the UI library and then use the ui.core and ui.draggable files along with jquery:

 

<script src="script/jquery-1.3.1.min.js" type="text/javascript"></script> 

<script src="script/ui.core.min.js" type="text/javascript"></script> 

<script src="script/ui.draggable.min.js" type="text/javascript"></script> 

 

Now I need something to drag around the page, so I add a div:

 

<body> 

  <form id="form1" runat="server">

    <div id="d1"> 

      <p>Move This Div</p>

    </div>

  </form>

</body> 

 

To make the div draggable, very few lines of javascript are needed (along with a couple of lines of CSS to add a border etc). You just need to apply draggable() to the id of the element using dot notation:

 

<script type="text/javascript"> 

  $(function() {

    $("#d1").draggable();

  });

</script> 

 

Clicking and dragging will allow you to move this around the browser wherever you like. When you release the mouse, the div stays where it is positioned after dragging. Actually, clicking on any part of the div will allow you to drag in FireFox, Opera and Safari. IE7 is a little different. The first drag can be made by holding down the left mouse button anywhere on the div. Once released, second and subsequent drags require that the text is clicked on. IE7 seems to need a "drag handle" of some sort after the first move. Explictly setting the bacground-color property of the div corrects this.

As the div is moved around the browser, it's position is available to the drag callback, so I've added a <table> to record this:

 

<table id="console"> 

  <tr>

    <td>drag x: </td>

    <td><input type="text" id="x" class="console" /></td>

    <td>drag y: </td>

    <td><input type="text" id="y" class="console" /></td>

  </tr>

</table> 

 

A little Javascript is needed to handle the callback, so the <script> block is altered and an option applied to draggable():

 

<script type="text/javascript"> 

  $(function() {

    $("#d1").draggable({

      drag: function(event, ui) {

         $("#x").val(ui.position.left);

         $("#y").val(ui.position.top);

     }

   });

  });

</script> 

 

Now, as the div is dragged around, the text boxes are updated to display the current position of the div:

The absolutePosition is also available, so the table is amended to add a couple of rows and the Javascript is changed to record these values as well:

 

<script type="text/javascript"> 

  $(function() {

    $("#d1").draggable({

      drag: function(event, ui) {

         $("#x").val(ui.position.left);

         $("#y").val(ui.position.top);

         $("#x2").val(ui.absolutePosition.left);

         $("#y2").val(ui.absolutePosition.top);

     }

   });

  });

</script> 

 

There's a discrepancy between the two measurements. The reason for this is that position gives the position of the div relative to its starting point. absolutePosition gives the positon of the div relative to the browser window. At the moment, in IE7, the default starting position of the div is 15px down and 10px into the page. This is because, by default, IE7 applies a margin to the <body> element of the document. If this is removed - body{margin:0;} - both position and absolutePosition will be the same.

You will also notice that the div occupies a starting position tucked right up into the top and left of the browser window. As an interesting diversion, if you remove the margin:0 from the body, and try running the page in FireFox, Safari and Opera, you will see that each browser applies a different default margin to the body. This is one of the sources of problems trying to get pages to look the same in all browsers, so for that reason, one of the first things I do with any new site is to remove the margin from the body element in the site's CSS file.

Now some more changes to the Javascript:

 

<script type="text/javascript"> 

  $(function() {

    $("input").val(''); // Reset console values

    $("#d1").draggable({

      drag: function(event, ui) {

         $("d1").css("opacity", "0.6"); // Semi-transparent while being dragged

      },

      stop: function(event, ui) {

         $("#x").val(ui.absolutePosition.left);

         $("#y").val(ui.absolutePosition.top);

         $("d1").css("opacity", "1.0"); // Revert to fully opaque when dragging stopped

     },

     cursor: "move"

   });

  });

</script> 

 

These changes result in the text box values being reset when the page is refreshed. That stops FireFox from holding on to the values as it does by default. Secondly, I have set the opacity of the item being dragged to 0.6 while it is being dragged, and then added a reference to the stop callback. In this, I register the position of the item once the mouse has been released from the item and reset its opacity. I also change the cursor for the item that has draggable() applied to it to the cross arrows that are familiar to users when they move stuff around Windows. The result looks like this during a drag:

And this after the drag has stopped:

Any HTML element can have draggable() applied to it. For example, here it has been applied to an img tag:

We've seen three of the options that draggable() can have applied to it: start, stop and cursor. there are many others and the full documentation is available here. If I had used jQuery for my old Flatplan application, stop is the place where I would have used AJAX to send the absolutePosition to the database so that draggable elements' positions could be persisted.