Drag a "nephew" draggable to an "aunt" droppable while maintaining CSS and dimensions
Asked Answered
I

1

6

I want to drag a draggable #red, nested within some parent, to some droppable #green, which sits a level higher in the div structure.

The dimensions of the divs need to be expressed in percent (to be responsive), and the toplevel divs must be position: relative;, which might make things more complicated.

For comparison, this all works fine for a the simpler case, where draggable #blue and droppable #green are sibling elements, but breaks in the "nephew" scenario (#red on #green).

I understand that I need to:

  • helper: clone the nephew (#red) div, as well as to appendTo: "body", so that I the nephew element can "escape"
  • I also get that I need to append some styles (especially position: relative;) to .ui-draggable-dragging, so that the nephew element's dimensions are calculated with reference to the parent, while in transit.

So far so good.

One wrinkle remains: When the nephew is dragged, it jumps sideways, and can't ever be dropped.

What's going on here?

  $(function() {
    $("#blue").draggable({
      snap: ".hexagon",
      snapMode: "inner",
      snapTolerance: 20,
      opacity: 0.7,
      addClasses: true,
      // stack: ".item",
      revert: "invalid"
    });
    $("#red").draggable({
      snap: ".hexagon",
      snapMode: "inner",
      snapTolerance: 20,
      opacity: 0.7,
      addClasses: true,
      // stack: ".item",
      revert: "invalid",
      appendTo: "body",
      helper: "clone",
    });
    $("#green").droppable({
      accept: ".hexagon",
      tolerance: "fit",
      drop: function(event, ui) {
        $(this)
          .addClass("ui-state-highlight")
          .find("span")
          .html("Dropped!");
      }
    });
  });
.hexagon {
    width: 20%;
    padding-top: 25%;
    overflow: hidden;
    -webkit-clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
    clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
    -webkit-shape-outside: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
    float: left;
    position: relative;
    z-index: 1;
  }

  .outer {
    position: relative;
    z-index: 0;
  }

  .inner {
    background-color: red;
    z-index: 9999;
    position: absolute;
    margin: 0 auto;
    width: 100%;
    top: 0;
    bottom: 0;
  }

  .textstyle {
    color: white;
    font-family: sans-serif;
    top: 25%;
    left: 10%;
    right: 10%;
    bottom: 10%;
    text-align: center;
    position: absolute;
    font-size: 1vw;
  }

  .green {
    background-color: green;
    z-index: 1000;
  }

  .blue {
    background-color: blue;
  }

  .red {
    background-color: red;
  }

  .ui-draggable-dragging {
    position: relative;
    padding-top: 25%;
    width: 20%;
  }
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id="green" class="hexagon outer green">
    <span class="textstyle">
            I am the dropzone.
          </span>
  </div>
  <div id="blue" class="hexagon outer blue">
    <span class="textstyle">
            I drag just fine, because I'm just a sibling element.
    </span>
  </div>
  <div class="hexagon outer">
    <div id="red" class="hexagon inner red">
      <span class="textstyle">
            I am nested, and I make a mess when dragged.
            I am the nephew.
      </span>
    </div>
  </div>
Ianthe answered 1/5, 2017 at 22:21 Comment(2)
Why do you want to set draggable to the child of the red hexagon, instead of directly to the hexagon itself like you did on the blue hex?Algy
My actual use case is a grid of outer hexagons (= cells), into which inner hexagons (= items) are placed. So the business logic seemed to suggest to me to nest the items inside the cells. More to the point, in my use case, I need to be able to easily programmatically place (lots) of inner hexagons (such as #red) "on" (lots of) outer hexagons (such as #green). Does that make sense? Actual use case is here, FYI: maxheld83.shinyapps.io/sort (Here, draggables are not nested, precisely because of this problem, though they'd need to be).Ianthe
E
2

Why does the clone jump?

It appears that the jQuery draggable plugin is adding a left value to the clone on the proviso that it is being positioned absolutely, by forcing .ui-draggable-dragging to position: relative; the plugin is actually positioning the clone left relative to where the element was positioned initially on the page.

To fix this behaviour add an override to the clone to position it absolutely:

.red.ui-draggable-dragging {
    position: absolute;
    bottom: auto;
}

Additionally, add bottom: auto; to override the bottom value set by .inner as this will cause the clone to stretch.

Why can't the clone be dropped?

This is because of tolerance: "fit" which according to the documentation:

"fit": Draggable overlaps the droppable entirely.

Droppable Widget - (https://api.jqueryui.com/droppable/#option-tolerance)

This still causes problems now that the clone is positioned absolutely as it's width is not calculated to be the same as the original .hexagon (the original's width: 20%; is calculated against the body width while the clone's width: 20%; is calculated against the viewport). To mitigate this the default browser margin on the body has been suppressed (although this could also be solved by wrapping the .hexagons in a container).

If the dragging action does not need to be exact tolerance: "fit" can be changed to tolerance: "intersect" which will allow for a bit more leeway.

$(function() {
  $("#blue").draggable({
    snap: ".hexagon",
    snapMode: "inner",
    snapTolerance: 20,
    opacity: 0.7,
    addClasses: true,
    // stack: ".item",
    revert: "invalid"
  });
  $("#red").draggable({
    snap: ".hexagon",
    snapMode: "inner",
    snapTolerance: 20,
    opacity: 0.7,
    addClasses: true,
    // stack: ".item",
    revert: "invalid",
    appendTo: "body",
    helper: "clone"
  });
  $("#green").droppable({
    accept: ".hexagon",
    tolerance: "fit",
    drop: function(event, ui) {
      $(this)
        .addClass("ui-state-highlight")
        .find("span")
        .html("Dropped!");
    }
  });
});
body {
  margin: 0;
}

.hexagon {
  width: 20%;
  padding-top: 25%;
  overflow: hidden;
  -webkit-clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  -webkit-shape-outside: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  float: left;
  position: relative;
  z-index: 1;
}

.outer {
  position: relative;
  z-index: 0;
}

.inner {
  background-color: red;
  z-index: 9999;
  position: absolute;
  margin: 0 auto;
  width: 100%;
  top: 0;
  bottom: 0;
}

.textstyle {
  color: white;
  font-family: sans-serif;
  top: 25%;
  left: 10%;
  right: 10%;
  bottom: 10%;
  text-align: center;
  position: absolute;
  font-size: 1vw;
}

.green {
  background-color: green;
  z-index: 1000;
}

.blue {
  background-color: blue;
}

.red {
  background-color: red;
}

.ui-draggable-dragging {
  position: relative;
  padding-top: 25%;
  width: 20%;
}

.red.ui-draggable-dragging {
  position: absolute;
  bottom: auto;
}
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id="green" class="hexagon outer green">
  <span class="textstyle">
            I am the dropzone.
          </span>
</div>
<div id="blue" class="hexagon outer blue">
  <span class="textstyle">
            I drag just fine, because I'm just a sibling element.
    </span>
</div>
<div class="hexagon outer">
  <div id="red" class="hexagon inner red">
    <span class="textstyle">
            I am nested, and I make a mess when dragged.
            I am the nephew.
      </span>
  </div>
</div>
Esbjerg answered 4/5, 2017 at 13:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.