1

I have a 2x2 grid of droppable areas [[A,B][C,D]] and under the grid is a 1x4 grid of draggables. I only want certain draggables next to each other. So for example, if there is a draggable in B, and I drag a different draggable into A, is there a way to make the draggable in B revert? The draggables have data-row and data-col so that I can grab the draggable in the prev/next column if I need to.

$(".draggable").draggable({
            scroll: false,
            snap: ".snaptarget",
            snapMode: "inner",
            stack: ".draggable",
            revert: function (event, ui) {
                var $draggable = $(this);
                $draggable.data("uiDraggable").originalPosition = {
                    top: 0,
                    left: 0
                };

                return !event;
            }
});

$(".snaptarget").droppable({
            accept: ".draggable",
            drop: function (event, ui) {
                var $draggable = $(ui.draggable);
                var $droppable = $(this);

                // This droppable is taken, so don't allow other draggables
                $droppable.droppable('option', 'accept', ui.draggable);

                ui.draggable.position({
                    my: "center",
                    at: "center",
                    of: $droppable,
                    using: function (pos) {
                        $draggable.animate(pos, "fast", "linear");
                    }
                });

                // Disable prev or next droppable if the pagewidth == 1
                if ($droppable.data("col") == 1) {
                    $droppable.next().droppable("option", "disabled", true);
                    var nextDrag = $(".draggable[data-row='" + $droppable.data("row") + "'][data-col='2']");
                    if (nextDrag.length) {
                        // I need to revert nextDrag if there is one.
                        // I've tried this but it doesn't seem to work
                        nextDrag.data("uiDraggable").originalPosition = {
                           top: 0,
                           left: 0
                        }
                    }
                }
            },
            tolerance: "pointer"
        });
homes
  • 265
  • 1
  • 3
  • 12
  • There is no way to execute a `revert` of another draggable the way that you may be thinking about it. You can execute a check in `stop` or `drop` to check the next element. If you need, you can then animate the movement of the bad div back to it's start or origin. – Twisty Jul 28 '16 at 17:59
  • @Twisty I was looking into that next, but was just checking if there was way. Thanks. – homes Jul 28 '16 at 18:41
  • Not via `revert`. You're on the right track. I created this fiddle for testing: https://jsfiddle.net/Twisty/a4ucb6y3/ – Twisty Jul 28 '16 at 18:49
  • This is starting to work: https://jsfiddle.net/Twisty/a4ucb6y3/4/ – Twisty Jul 28 '16 at 19:15

1 Answers1

1

Took a little bit of work, I am never good with offsets and positioning. Here's the key:

  function returnItem(item, target) {
    // Get Origin
    var oPos = item.data("uiDraggable").originalPosition;
    // Adjust Postion using animation
    item.position({
      my: "top left",
      at: "top left+" + oPos.left,
      of: target,
      using: function(pos) {
        item.animate(pos, "fast", "linear");
      }
    });
  }

Here is a working example based on the Draggable Snap to element grid example:

https://jsfiddle.net/Twisty/a4ucb6y3/6/

HTML

<div id="target">
  <div class="snaptarget ui-widget-header" data-col="1" data-row="1" style="top: 0; left: 0;">
  </div>
  <div class="snaptarget ui-widget-header" data-col="2" data-row="1" style="top: 0; left: 80px;">
  </div>
  <div class="snaptarget ui-widget-header" data-col="1" data-row="2" style="top: 80px; left: 0;">
  </div>
  <div class="snaptarget ui-widget-header" data-col="2" data-row="2" style="top: 80px; left: 80px;">
  </div>
</div>

<br style="clear:both">

<div id="source">
  <div id="drag-A" class="draggable ui-widget-content" style="left: 0;">
    <p>Drag A</p>
  </div>
  <div id="draggable2" class="draggable ui-widget-content" style="left: 80px;">
    <p>Drag B</p>
  </div>
  <div id="draggable3" class="draggable ui-widget-content" style="left: 160px;">
    <p>Drag C</p>
  </div>
  <div id="draggable4" class="draggable ui-widget-content" style="left: 240px;">
    <p>Drag D</p>
  </div>
</div>

CSS

.draggable {
  width: 80px;
  height: 80px;
  font-size: .9em;
  position: absolute;
  top: 0;
}

.draggable p {
  text-align: center;
  height: 1em;
  margin-top: 30px;
}

#source {
  width: 320px;
  height: 80px;
  position: relative;
}

#target {
  width: 160px;
  height: 160px;
  position: relative
}

.snaptarget {
  width: 80px;
  height: 80px;
  position: absolute;
}

jQuery

$(function() {
  function returnItem(item, target) {
    // Get Origin
    var oPos = item.data("uiDraggable").originalPosition;
    // Adjust Postion using animation
    di.position({
      my: "top left",
      at: "top left+" + oPos.left,
      of: target,
      using: function(pos) {
        item.animate(pos, "fast", "linear");
      }
    });
  }

  $(".draggable").draggable({
    scroll: false,
    snap: ".snaptarget",
    snapMode: "inner",
    stack: ".draggable",
    revert: "invalid",
    start: function(e, ui) {
      var off = $("#source").position();
      ui.helper.data("uiDraggable").originalPosition = {
        top: ui.position.top,
        left: ui.position.left
      };
    }
  });

  $(".snaptarget").droppable({
    accept: ".draggable",
    drop: function(event, ui) {
      var $draggable = $(ui.draggable);
      var $droppable = $(this);

      // This droppable is taken, so don't allow other draggables
      $droppable.droppable('option', 'accept', ui.draggable);

      // Disable prev or next droppable if the pagewidth == 1
      if ($droppable.data("col") == 1) {
        $droppable.next().droppable("option", "disabled", true);
        var nextDrag = $(".draggable[data-row='" + $droppable.data("row") + "'][data-col='2']");
        if (nextDrag.length) {
          // I need to revert nextDrag if there is one.
          returnItem(nextDrag, $("#source"));
        }
      }
    },
    tolerance: "pointer"
  });
});

In draggable, when we start to drag, we want to record the original position (in case we need to later revert). The revert option is set to invalid in case the user drags it off some other place weird.

We add the position to data of the dragged item so that it can be read later.

When that item is dropped is when the magic happens. You had done all the checking, just needed to return the item if it didn't fit. if nextDrag exists, we return it to it's source.

Going forward, you may want to consider appending, cloning, and removing the elements in the start/stop events. As it is now, we're really only adjust the positioning of the elements, not their hierarchy in the DOM. Depending on what your needs are, this may not matter.

Twisty
  • 30,304
  • 2
  • 26
  • 45