Search code examples
javascriptjqueryjquery-uijquery-ui-droppable

Jquery UI Droppable revert cannot drop again


I have been trying to create a drag and drop system for a word learning application and have run into a problem with the draggable elements not being able to be reused after I revert the and try to drag them again.

To revert the draggable object I used the out method so that when you drag it back out of the droppable area it will revert back to its previous location. I did this by removing the draggable instance and adding it back in, if I try to drag that same element back to the droppable area it wont be able to drop. I have tested with trying to reinitialise the droppable area also but that doesn't seem to change anything.

$(document).ready(function () {
  createDraggable();
  createDroppable();
});

function createDraggable(){
  $(".word").draggable({
    containment: ".stage",
    revert: 'invalid',
    revertDuration: 0
  });
}

function disableOtherDraggable(except){
  $(".word:not(#" + except.attr('id') + ")").draggable('disable');
}

function createDroppable(){
  $('.drop').droppable({
    tolerance: 'touch',
    accept: '.word',
    drop: function(event, ui) {
      ui.draggable.position({
        my: "center",
        at: "center",
        of: $(this),
        using: function(pos) {
          $(this).animate(pos, 200, "linear");
        }
      });
      $(ui.draggable).css('background', "transparent");
      disableOtherDraggable(ui.draggable);
    },
    out: function(event, ui) {
      ui.draggable.mouseup(function () {
        ui.draggable.removeAttr('style');
        $(".word").draggable("destroy");
        createDraggable();
      });
    }
  });
}

I want to be able to let people drop the words and drag them back out if need be. I am going to set up a button to check the dropped word is correct after I get this working.

There are 4 words that can be dragged on this example but it can range from 3 to 5

Update

Here is the updated code that I got working for anyone interested. I created the stage as a droppable area and just toggled that on and off as needed.

$(function() {
  function createDraggable(o) {
    o.draggable({
      containment: ".stage",
      revert: 'invalid',
      revertDuration: 0
    });
  }

  function toggleOtherDraggable() {
    $(".words .word").each(function(i, val){
     if(!$(val).hasClass('ui-dropped')) $(val).draggable('disable');
    });
  }

  function createLineDroppable(){
    $('.drop').droppable({
      tolerance: 'touch',
      accept: '.word',
      drop: function(event, ui) {
        ui.draggable.position({
          my: "center",
          at: "center",
          of: $(this),
          using: function(pos) {
            $(this).animate(pos, 200, "linear");
          }
        });
        $(ui.draggable).css('background', 'transparent');
        $(ui.draggable).addClass('ui-dropped');
        toggleOtherDraggable();
      },
      out: function(){
        $('#stage-drop').droppable('enable');
      }
    });
  }

  function createStageDroppable() {
    $('#stage-drop').droppable({
      tolerance: 'touch',
      accept: '.word',
      disabled: true,
      drop: function(event, ui) {
        $(ui.draggable).css('left', '0');
        $(ui.draggable).css('top', '0');
        $(ui.draggable).css('background', '');
        $(ui.draggable).removeClass('ui-dropped');
        $('#stage-drop').droppable('disable');
        $(".words .word").draggable('enable');
      }
    });
  }

  createDraggable($(".words .word"));
  createLineDroppable();
  createStageDroppable();
});

Solution

  • You need a location to drop a .word, one where you want to disable the others, and one to return to.

    Consider the following example:

    $(function() {
      function createDraggable(o) {
        o.draggable({
          containment: ".stage"
        });
      }
    
      function toggleOtherDraggable() {
        if ($(".words .word").eq(0).hasClass("ui-draggable-disabled")) {
          $(".words .word").draggable('enable');
        } else {
          $(".words .word").draggable('disable');
        }
      }
    
      function createDropWord() {
        $(".words").droppable({
          tolerance: 'touch',
          accept: '.word',
          drop: function(event, ui) {
            var item = ui.draggable;
            item.removeAttr("style");
            $(this).append(item);
          }
        });
      }
    
      function createDropStage() {
        $('.drop').droppable({
          tolerance: 'touch',
          accept: '.word',
          drop: function(event, ui) {
            var item = ui.draggable;
            item.appendTo(this).position({
              my: "center",
              at: "center",
              of: $(this),
              using: function(pos) {
                $(this).animate(pos, 200, "linear");
              }
            }).css('background', "transparent");
            toggleOtherDraggable();
            createDropWord()
          },
          out: function() {
            toggleOtherDraggable();
          }
        });
      }
    
      createDraggable($(".words > .word"));
      createDropStage();
    });
    .word {
      display: inline-block;
      padding: .25em;
    }
    
    .drop {
      width: 340px;
      height: 100px;
      background: #CCC;
      margin: 20px;
    }
    
    .word.ui-draggable-disabled {
      opacity: 0.45;
    }
    <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
    <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 class="stage">
      <div class="ui-widget words">
        <div id="word-1" class="word ui-widget-content">Word 1</div>
        <div id="word-2" class="word ui-widget-content">Word 2</div>
        <div id="word-3" class="word ui-widget-content">Word 3</div>
        <div id="word-4" class="word ui-widget-content">Word 4</div>
      </div>
      <div class="drop"></div>
    </div>

    When you first Drag and Drop to the .drop area, this will center the dropped object and also disable the other draggables, so they cannot be dragged in. When the item is dragged out, they are re-enabled.

    You must use .append() or .appendTo() so that the item that is dropped is then added to the container. This applies to both elements.

    The revert option is only enabled under specific drop circumstances. If the droppable will not accept the item or returns false, then the item is reverted when invalid is the preference.