If you've ever completed on online survey there's a good chance that you've encountered one of those questions where you choose some personality traits from a column and drag those attributes over to a box. It's usually something along the lines of "Please select up to x number of personality traits that you think best describe what company ABC might be like as a person." Did you then ask yourself "I wonder how they do that?" Well I did, and I found out that it incorporates two related behaviors: Drag & Drop, and element sorting. The first is obvious and is what allows you to drag the label over to the box. The other is more subtle; it allows you to reorder the items within their container. How to put those two features together using the jQuery UI library is the topic of today's article.
Recommended Libraries
As it states in the article title, I'm going to be using jQuery to make the dragging operation easier to implement. But jQuery on its own is not enough for this type of UI behavior. For that, you need the jQuery UI library. It's a set of UI interactions, effects, widgets, and themes built on top of the standard jQuery Library. The two interactions that benefit us are the Droppable and Sortable ones.
Rather than download the libraries, I would encourage you to take advantage of the Google-hosted libraries. Doing so provides your applications with stable, reliable, high-speed, globally available access to all of the most commonly-used JavaScript libraries, thus relieving you from having to host and maintain them. Just link your jQuery scripts directly to the Google site:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script>
The HTML Markup
The most important elements are the "unassigned_attributes", "assigned_attributes" DIVs, but the enclosing table body will eventually play a key role as well. The demo on the jQuery site uses an unordered list but I found that to be unnecessary as long as the labels are vertically stacked.
<form name="post"> <div id="attribute_attachment_section" class="postbox"> <h2 align="center"> <span>Drag & Drop Demo</span> </h2> <div class="inside"> <table id="tblAttachAttributes" class="postbox"> <thead> <tr> <th> Unassigned Attributes </th> <th style="border-left: #ccc 1px double;"> Assigned Attributes </th> </tr> </thead> <tbody> <tr> <td> <div id="unassigned_attributes" class="sortable"> <label name="attributes" id="1234">friendly</label> <label name="attributes" id="666">energetic</label> </div> </td> <td> <div id="assigned_attributes" class="sortable"> <label name="attributes" id="2112">lovable</label> <label name="attributes" id="5150">handsome</label> <label name="attributes" id="1999">intellectual</label> </div> </td> </tr> </tbody> </table> </div> </div> </form>
The CSS Style for all labels
<style> label { display: block; margin-bottom: 2px; color: #F4F4F4; padding: 4px; background: #e6e6e6; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#A3CDEB', endColorstr='#0B74BE'); background: -moz-linear-gradient(100% 100% 90deg, #0B74BE, #A3CDEB) repeat scroll 0 0 transparent; background: -webkit-gradient( linear, left top, left bottom, color-stop(0.05, #A3CDEB), color-stop(1, #0B74BE) ); background-color: #0B74BE; } </style>
The JavaScript Code
Invoking the sortable() function on one or more elements makes all of its/their child elements sortable. Using a class to designate an element as sortable makes it really easy to collect them all using a selector. The sortable() function accepts an object with many attributes, but for now we'll only include connectWith. This allows you to drag between elements. Every element that may be dragged to must be identified.
<script> $(document).ready(function () { $('table#tblAttachAttributes').find('div.sortable').sortable({ connectWith: 'div.sortable' }); }); </script>
Setting the DIV Heights
At this point, the basic functionality is there, but this is not what I would call a usable product. The main issue is that the DIVs collapse when there are no labels within it! At that point, there's no going back. The solution is to set the shorter DIV's height to that of the larger one. Hence, if the DIV with three labels has a height of 80 pixels then we would set the DIV with only two labels to match.
I wrote a couple of functions to accomplish that. The first is called setMenusDivHeight(). It accepts a collection of elements to which it applies the jQuery css() function on the "min-height" attribute. To fetch the greatest height property at the time, I created a second function called getMaxHeight(). It iterates over a collection of elements and returns the largest height value found. By using jQuery's $.fn.extend() mechanism, we can apply our new function directly to the DOM collection! Our functions should be called when the page first loads as well as on the drag start() and stop().
<script> $.fn.extend({ getMaxHeight: function() { var maxHeight = -1; this.each(function() { var height = $(this).height(); maxHeight = maxHeight > height ? maxHeight : height; }); return maxHeight; } }); function setMenusDivHeight($attributeDivs) { return $attributeDivs.css('min-height', $attributeDivs.getMaxHeight()); } setMenusDivHeight($('table#tblAttachAttributes').find('div.sortable')).sortable({ connectWith: 'div.sortable', start: function( event, ui ) { setMenusDivHeight(ui.item.closest('table#tblAttachAttributes').find('div.sortable')) .css('box-shadow', '0 0 10px blue'); }, stop: function( event, ui ) { setMenusDivHeight(ui.item.closest('table#tblAttachAttributes').find('div.sortable')) .css('box-shadow', ''); } }); </script>
A blue outline helps to see the results:
Adding the Finishing Touches
That looks a lot better, but there are a lot more properties that can be set to make the user experience even better. For instance the cursor can be set to the "move" type; that really helps indicate that the element is draggable. The containment property is also a must; it sets the periphery of the dragging area so that an element's travel area is limited.