In this post I'd like to make it clear to myself and you, dear reader, what happens behind the scenes of jQuery's draggable objects. jQuery UI is a great tool, but sometimes it's difficult to tame our scripts if we don't know all the details. Draggable objects fall into such category. Setting up a demo page to mimic the basic behavior of the widget section of Wordpress is not that easy because when we start coding we can't know all the nuts and bolts of dragging objects around the screen and from an element to another. Let's see these details.
It's all relative
Draggable objects are made relatively positioned by jQuery UI. This happens when jQuery UI adds draggable
as a CSS class to the target element. But there's more: object coordinates are calculated by taking the page as a reference. This means that when you use the stop()
method of the draggable()
wrapper, the final coordinates of the target element are directly set as inline styles on such element.
Consider the example of two grids: one contains two rows of boxes and the other one is empty. You want to move the boxes from the first grid to the second. Once a box is dragged, you remove it from the first grid and you add it to the second. Basically, this is how Wordpress handles its widgets.
You have the following structure:
<div id="grid"> <div class="row"> <div class="box">1</div> <div class="box">2</div> <div class="box">3</div> </div> <div class="row"> <div class="box">4</div> <div class="box">5</div> <div class="box">6</div> </div> </div> <div id="grid2"></div>
Problems with relative positioning
Boxes are floated. The basic styles are the following:
#grid { padding: 2em 0; margin: 0 auto; width: 615px; } div.row { height: 180px; padding-bottom: 5px; } div.box { width: 200px; height: 180px; line-height: 180px; text-align: center; font-size: 2em; background: #888; color: #fff; border-radius: 5px; float: left; margin-right: 5px; cursor: move; position: relative; }
You try a first attempt with the following jQuery code:
var Grid = new function() { var context = $('#grid'); var boxes = $('div.box', context); var target = $('#grid2'); var runMovements = function() { boxes.each(function() { var box = $(this); box.draggable({ start: function() { target.animate({ opacity: 0.7 }, 'slow'); }, stop: function() { var copy = box.clone(); copy.appendTo(target); box.remove(); target.animate({ opacity: 1 }, 'slow'); } }); }); }; this.init = function() { runMovements(); }; }(); $(function() { Grid.init(); });
But it doesn't work as you expected: the dragged box disappear under the edges of the second grid while you're dragging it. Why? Because the second grid has a greater z-index
value assigned automatically by the browsers. So the first thing you need to do is to set this value to -1:
#grid2 { width: 615px; margin: 1em auto; padding: 0; height: 370px; background: silver; position: relative; z-index: -1; }
It still doesn't work: when you drop the box, the dragged box is positioned outside the target element. As we said earlier, coordinates are calculated taking the whole page as a reference, so we need to override this setting by zeroing the top
and left
properties of each dragged box:
#grid2 div.box { top: 0 !important; left: 0 !important; }
The !important
statement is used here to override the inline styles set by jQuery UI. You can see the test below.