javascriptjqueryjquery-ui-sortablejquery-ui-draggablejquery-ui-droppable

JQuery droppable div inside a sortable list


I'm trying to create a simple sortable and draggable list composed of :

I managed to do this so far : JSFiddle

HTML Code :

<h3>DRAGGABLE</h3>
<ul id="draggables">
  <li class="draggable">Item 1</li>
  <li class="draggable">Item 2</li>
  <li class="draggable">Item 3</li>
  <li class="draggable">Item 4</li>
  <li class="draggable">Item 5</li>
</ul>

<h3>SORTABLE</h3>
<ul id="itemsContainer">
  <li class="item">Item 1</li>
  <li class="item">
    <p>Item with drop zone</p>
    <div class="droppable">DROP HERE</div>
  </li>
  <li class="item">Item 3</li>
  <li class="item">Item 4</li>
  <li class="item">Item 5</li>
</ul>

The JS Code is here :

$(".draggable").draggable({
  connectToSortable: '#itemsContainer',
  helper: 'clone',
  revert: 'invalid'
});

$("#itemsContainer").sortable({
  revert: true
});

$(".droppable").droppable({
  drop: function(event, ui) {
    $(this).html("Dropped!");
    $(this).css("background-color", "red");
    $(ui.helper[0]).css("background-color", "green");
  }
});

The problem is, when an item is dropped inside the droppable div, it detects correctly that a drop is happening, but the item is still added to the sortable list. I thought the "greedy" option would prevent this from happening. Any ideas ?

EDIT : If I apply a styling to the ui.helper[0] object (aka the cloned draggable object), it stays on until the item is actually sorted in the sortable list.


Solution

  • Might be a bit of overkill, but this is what I could come up with.

    When we stop dragging a draggable item, we need to see if the item is over a droppable area, if so append it to said area.

    Edit: Now we can drop a sortable item from the same list into the drop zone by connecting the sortable list to the drop zone and accepting the sortable items.

    jsFiddle

    var over = false; // is the item over a droppable area?
    var el_over = null; // if over a drop area, what drop area?
    var de = null; // the item to detach
    
    $(".draggable-item").draggable({
      connectToSortable: ".list-2",
      stop: function(event, ui) {
        if (over) {
          de = $(this).detach();
          el_over.append(de);
        }
        over = false;
        el_over = null;
      }
    });
    
    $(".list-2").sortable({
      connectWith: '.drop-zone, .list-2',
      cursor: "move",
      stop: function(event, ui) {
        if (over) {
          de = ui.item.detach();
          el_over.append(de);
        }
        over = false;
        el_over = null;
      }
    });
    
    $(".drop-zone").droppable({
      accept: ".draggable-item, .sortable-item",
      over: function(event, ui) {
        //console.log("over");
        over = true;
        el_over = $(this);
      },
      out: function(event, ui) {
        //console.log("out");
        over = false;
        el_over = null;
      }
    });
    ul {
      padding: 10px;
      list-style-type: none;
      width: 200px;
    }
    
    li {
      text-align: center;
      padding: 5px 10px;
      margin: 5px;
    }
    
    .draggable-item {
      background: #9bcdf0;
    }
    
    .sortable-item {
      background: #6c2020;
    }
    
    .drop-zone {
      min-height: 30px;
      background: #fff;
      padding: 1px 0;
    }
    
    .drop-zone .draggable-item {
      width: auto !important;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    <ul class="list-1">
      <li class="draggable-item">draggable 1</li>
      <li class="draggable-item">draggable 2</li>
      <li class="draggable-item">draggable 3</li>
      <li class="draggable-item">draggable 4</li>
      <li class="draggable-item">draggable 5</li>
    </ul>
    
    <ul class="list-2">
      <li class="sortable-item">sortable 1</li>
      <li class="sortable-item">sortable 2</li>
      <li class="sortable-item">sortable 3
        <div class="drop-zone"></div>
      </li>
      <li class="sortable-item">sortable 4</li>
      <li class="sortable-item">sortable 5</li>
    </ul>

    Updated answer: Turns out we don't need all that overkill XD. If we just initialize the .drop-zone as a sortable and connect it to itself and list-2 we get the same outcome.

    Here is the updated fiddle.

    $(".draggable-item").draggable({
        connectToSortable: ".list-2",
    });
    
    $(".list-2").sortable({
        connectWith: '.drop-zone, .list-2',
        cursor: "move",
    });
    
    $(".drop-zone").droppable({
        accept: ".draggable-item, .sortable-item",
    });
    
    $(".drop-zone").sortable({
        connectWith: '.drop-zone, .list-2',
      items: '.draggable-item, .sortable-item',
    });