Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drop-able in all possible places

I am currently building a website designer, and one of the core features is to be able to drag & drop to rearrange elements. I have been working on this feature for a few days and have come unstuck a few times. The most important note about this drag and drop system is that the drag-able element can be dropped anywhere within the master container and it will Snap into place so there will be no absolutely positioned elements otherwise the element won't Snap into place.

So first of I started by just building the core draggable bit where you can drag the element, then when you drop the element I am using document.elementFromPoint() to get the element where the cursor position is (note: I have to hide the draggable element otherwise it will return that).

Now I have the element closest to the cursor the main problem is figuring out where the draggable element needs to go relative to that element, because there are 4 options append - prepend - insertBefore - insertAfter. I have managed to get append - prepend & insertBefore working but it's not reliable enough because all I am going is using the height and offset of the target to determine either append or prepend and I am increasing the Y parameter on the getFromPoint to see if I hit a different element in a short distance to determine insertBefore. Here is the code I have so far.

box.addEventListener('mouseup', function (e) {
    if (!dragging) return;
    dragging = false;
    var thisEl = this; // the drag-able element

    // Hide 
    this.style.display = 'none'; // hide the element so we can get 
                                 // document.elementFromPoint

    var el = document.elementFromPoint(e.pageX, e.pageY), // target
        elH = el.offsetHeight, // height of target
        elT = el.offsetTop; // offset of target

    for (var i = 0; i < 15; i++) { // This is a weird part see the reference at the bottom
        var newEl = document.elementFromPoint(e.pageX, e.pageY + i);
        if (newEl !== el) {
            this.style.display = 'block';
            this.style.position = 'static';
            return newEl.parentNode.insertBefore(thisEl, newEl);
        }
    }

    if (e.pageY < elT + (elH / 2)) { // if the pageY is less than the target offset + half of the height, that's how I am calculating prepend
        el.appendChild(this);
        el.insertBefore(this, el.firstChild);
    } else {
        el.appendChild(this); // else append;
    }

    this.style.display = 'block';
    this.style.position = 'static'; // Snap back in with 'static'
});

This is just my mouseup event the one that does all of the work. the other events just make the element draggable, not really important.

Here is a fiddle

http://jsfiddle.net/mLX5A/2/

So if that did not ask the question then here's a short version.

My drag-able element needs to be able to be dropped anywhere and snap into. What is the best way to do this because the way I have done it in the fiddle is definitely not good. How can I detect where the element needs to go relative to the target on mouseup.

Reference to weird section.

Here's how it works (warning, it's not good). When I get the target element with elementFromPoint I then create a loop that will loop 15 times and increment the Y value that goes into the elementFromPoint so basically the elementFromPoint is moving down 15px and if it hits a new element within that short space I am assuming that you want to insert the element before the target.

I am more than happy to receive answers that have nothing to do with this code as that would benefit other users too.

I would like to note that the container that will have drag-able is the main part of the designer. So it is not a choice for me to have all absolutely positioned elements and I wouldn't really be a good idea to put a element in every possible place so that I can detect where the drag-able element has to go because whatever is in that container will be a quality result with no unnecessary content.

I would also like to note that my application does not and will not ever support and old browsers ie IE6, IE7, IE8, IE9

like image 676
iConnor Avatar asked Aug 22 '13 19:08

iConnor


1 Answers

I would consider using jQuery UI's Sortable widget. The portlets example handles the insertBefore and insertAfter requirement. I've created a simple fiddle that builds upon the portlets example and also satisfies the prepend and append requirement.

This is just a start for you that I'm sure you can manipulate as you need. connectWith is important depending on where you want to allow things to be placed.

Fiddle

JS

$(".column").sortable({
    items: ".portlet",
    connectWith: ".column"
});
$(".portlet").sortable({
    items: ".portlet-content",
    connectWith: ".portlet"
});
$(".column").disableSelection();

HTML

<div class="column">
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">One. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">Two. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">Three. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
</div>
<div class="column">
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">Four. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">Five. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
</div>
like image 179
ryan Avatar answered Oct 05 '22 23:10

ryan