Search code examples
javascriptjqueryjquery-uijquery-ui-draggablejquery-ui-droppable

Make coordinates of draggable element relative to droppable element after appending to it


I'm trying to make a tool for a website that uses draggable and droppable elements, from jQueryUI.

I have one div that contains the elements (draggables) and a div that the elements will be moved to (droppable). They are completely separate divs and once an element gets dropped into the droppable div it needs to be moved there.

I have code that works for moving it over, getting the droppable to accept the correct stuff, and even code to keep the draggable element draggable only in the droppable div. The issue arises when you initially drag the draggable element to the droppable div.

It retains the coordinates from the original div that contains the draggable elements. So, when I append it to the new element it appears offset a bunch. I can move it into the droppable div after that just fine and it works as expected, but doesn't help much if it can't be there from the start.

Is there no way of just having it convert the coordinates to match that of the droppable div so it appears in the same spot on the page but is inside the droppable div on the DOM?

Example:

HTML

<div id="MoveZone" class="Container">
    <div id="MoveMe"></div>
</div>
<div style="height: 10px;"></div>
<div id="DropZone" class="Container"></div>

CSS

.Container {
    outline-style: solid;
    outline-color: black;
    outline-width: 3px;
    height: 100px;
    width: 100px;
}
#MoveMe {
    outline-style: solid;
    outline-color: blue;
    outline-width: 2px;
    height: 20px;
    width: 20px;
    background-color: ghostwhite;
}

JavaScript

$(document).ready(function () {
    $('#MoveMe').draggable({
        revert: 'invalid'
    });
    $('#DropZone').droppable({
        accept: '#MoveZone div, #DropZone div',
        drop: function (event, ui) {
            ui.draggable.detach().appendTo($(this));
            ui.draggable.draggable('option', 'containment', 'parent');
        }
    });
});

Fiddle

I did find one way to do it, sort of. But it requires hard coding in the droppable div's id...which I don't particularly like. Also, when it gets dropped it slightly offsets to the upper-left, so even then it's not perfect because if it get's put in the top-left corner it will be partially out of the box.

Other Fiddle

EDIT:
The only other way I can think of doing it is some how calculating the offset between the boxes and adjusting the location in the drop method. But I'm not sure how I'd go about that, especially since on my site the location of the droppable spot won't be static. (It's centered in the page, so could move slightly.)


Solution

  • I found the solution that solves the issue. The following JavaScript is what does it.

    $(document).ready(function () {
        $('#MoveMe').draggable({
            revert: 'invalid',
            cursor: 'move'
        });
        $('#DropZone').droppable({
            accept: '#MoveZone div, #DropZone div',
            drop: function (event, ui) {
                var parent = ui.draggable.parent();
                var draggedElement = $(ui.draggable);
                var dropZone = $(this);
    
                var leftOffset = Math.abs(parent.offset().left - dropZone.offset().left);
                var topOffset = dropZone.offset().top - parent.offset().top;
    
                draggedElement.detach().appendTo(dropZone);
    
                draggedElement.css('left', draggedElement.position().left - leftOffset);
                draggedElement.css('top', draggedElement.position().top - topOffset);
    
                draggedElement.draggable('option', 'containment', 'parent');
            }
    
        });
    });
    

    Basically, what does it is that you just calculate the offset between the two div elements and when it get's appended to the droppable div you adjust the top and left CSS to that of the new one.

    For some reason, it doesn't work in JSFiddle, but it does work.