5

Drawing a box with cursor (lasso) will select multiple .item in this JSFiddle example.

Selected .item's become draggable. Empty .slot without an .item inside is a valid droppable.

When you drop multiple draggables on multiple droppables, only the .item which mouse is on will revert if its corresponding droppable not valid.

How to make every draggable revert if it's dropped on invalid droppable?

Javascript:

$(function () {
  // we are going to store the selected objects in here
  var selected = $([]),
    total = [],
    offset = {
        top: 0,
        left: 0
    };

  $(document).selectable({
    filter: ".item",
    start: function (event, ui) { //remove draggable from selection, otherwise previous selection will still be draggable. 
        $(total).draggable("destroy");
    },
    selected: function (event, ui) { // push selected into total[].
        total.push(ui.selected)
    },
    unselected: function (event, ui) { //console.log('unselect ui: ',ui)
        u = ui.unselected
        //remove unselected from selection[].
        total = jQuery.grep(total, function (n) {
            return n !== u
        });
        //console.log('total array (unselected): ',total)
    },
    stop: function (vent, ui) {
        //remove duplicated element from total[].
        jQuery.unique(total)
        $(total).each(function () {
            $(this).draggable(dragOption)
        })
        //$(total).draggable(dragOption);
        //var widget = $( ".selector" ).draggable( "widget" );
        //console.log('widget: ',widget)
        console.log('break line---------------------------- ')
    }

  });

  //save drag option as an Obj.
  dragOption = {
    opacity: 0.45,
    delay: 300,
    connectToSortable: ".slot"
    //,helper: "clone"
    ,
    distance: 5,
    snap: ".slot",
    snapMode: "inner",
    revert: "invalid",
    start: function (event, ui) {
        console.log('draggable start ui: ', ui)
        selected = $(total).each(function () {
            var el = $(this);
            el.data("offset", el.offset())
        });
        offset = $(this).offset(); //get coordinates relative to document
    },
    drag: function (event, ui) { //console.log(offset.top)
        var dt = ui.position.top - offset.top,
            dl = ui.position.left - offset.left;
        selected.not(this).each(function () {
            // create the variable for we don't need to keep calling $("this")
            // el = current element we are on
            // off = what position was this element at when it was selected, before drag
            var el = $(this),
                off = el.data("offset");
            el.css({
                top: off.top + dt,
                left: off.left + dl
            });
        });
    },
    stop: function (event, ui) {
        console.log('drag stop ui : ', ui)
    }
  };

  //save drop option as an Obj.
  dropOption = {
    accept: '.item',
    drop: function (event, ui) {
        console.log('drop event : ', event);
        console.log('drop ui : ', ui)
    },
    activate: function (event, ui) { //console.log('this : ',this,'\n ui : ',ui)
    },
    out: function (event, ui) { //console.log('out',$(this))
    },
    deactivate: function (event, ui) { //console.log('deactivate')
    },
    tolerance: "intersect",
    instance: function (event, ui) { //console.log('instance ui : ',ui)
    },
    over: function (event, ui) { //console.log('this item : ',ui.draggable[0],'on this slot: ',this)
    },
    activeClass: "green3"

}
  // make empty slot droppable
  $(".slot:not(:has(>div))").droppable(dropOption)
}) <!--doc ready-->

HTML:

<body>
  <div id="container">
    <div id="header"></div>
    <div class="box" id="boxA">
      <h4>box A</h4>
      <div class="slot" id="A1">
        <div class="item" id="a1"></div>
      </div>
      <div class="slot" id="A2">
        <div class="item" id="a2"></div>
      </div>
      <div class="slot" id="A3">
        <div class="item" id="a3"></div>
      </div>
      <div class="slot" id="A4"></div>
      <div class="slot" id="A5"></div>
    </div>
    <div class ="box" id="boxB">
      <h4>box B</h4>
      <div class="slot" id="B1"></div>
      <div class="slot" id="B2">
        <div class="item" id="b2"></div>
      </div>
      <div class="slot" id="B3"></div>
      <div class="slot" id="B4"></div>
      <div class="slot" id="B5">
        <div class="item" id="b5"></div>
      </div>
    </div>
  </div> 
</body>

CSS:

document {
    background-color: #FFF;
}

.box {
    height: 180px;
    float: left;
    border-top-width: 5px;
    border-right-width: 5px;
    border-bottom-width: 5px;
    border-left-width: 5px;
    border-top-style: solid;
    border-right-style: solid;
    border-bottom-style: solid;
    border-left-style: solid;
    border-top-color: #999;
    border-right-color: #999;
    border-bottom-color: #999;
    border-left-color: #999;
    width: 150px;
    text-align: center;
    margin-left: 100px;
}
.item {
    position: absolute;
    font-size: 12px;
    height: 14px;
    background-color: #CCC;
    width: 110px;
    text-decoration: none;
    font-family: Arial, Helvetica, sans-serif;
    color: #999;
    margin-left: 6px;
    text-align: center;
}

#header, #footer {
    float: left;
    height: 200px;
    width: 100%;
}
.slot{
    border-top-width: 1px;
    border-right-width: 1px;
    border-bottom-width: 1px;
    border-left-width: 1px;
    border-top-style: dotted;
    border-right-style: dotted;
    border-bottom-style: dotted;
    border-left-style: dotted;
    height: 15px;
    width: 120px;
    margin-top: 2px;
    text-align: center;
    vertical-align: middle;
    line-height: 90px;
    margin-right: auto;
    margin-left: auto;
    }
.ui-selecting {
    background: #FECA40;
  }
.ui-selected {
    background-color: #F90;
  }
.green3 {
    background-color: #D9FFE2;
}
Ray C Lin
  • 703
  • 3
  • 8
  • 24

2 Answers2

5

You can make use of document.elementFromPoint.

Updated Fiddle

For dropping/reverting the original draggable, we do the following once it is dropped in drop event:

  1. Check whether the droppable contains an .item or not in the dropevent. If the droppable doesn't already contain an .item, Go to step 2, else go to step3
  2. append() the element to corresponding .slot after resetting the positioning using css()
  3. Revert the element using animate() method to previous position which we cached using .data() on start event.

For dropping/reverting the extra items being dragged:

  • We find the elements on the left and right visible area of each item being dragged (1px far from the element being dragged, Which is supposed to be the drop targets) usingdocument.elementFromPoint.

  • Since no actual drop event is triggered for these elements, we make use of the stop event of draggable to check whether either of the target's is a .slot

  • If either of them is a .slot, which means the item was dropped over a droppable, we proceed to step1 mentioned above, else we go to step3 (manually revert the item to it's original position)


$(function() {

  var dragOption = {
      delay: 10,
      distance: 5,
      opacity: 0.45,
      revert: "invalid",
      revertDuration: 100,
      start: function(event, ui) {
        $(".ui-selected").each(function() {
          $(this).data("original", $(this).position());
        });
      },
      drag: function(event, ui) {
        var offset = ui.position;
        $(".ui-selected").not(this).each(function() {
          var current = $(this).offset(),
            targetLeft = document.elementFromPoint(current.left - 1, current.top),
            targetRight = document.elementFromPoint(current.left + $(this).width() + 1, current.top);
          $(this).css({
            position: "relative",
            left: offset.left,
            top: offset.top
          }).data("target", $.unique([targetLeft, targetRight]));
        });
      },
      stop: function(event, ui) {
        $(".ui-selected").not(this).each(function() {
          var $target = $($(this).data("target")).filter(function(i, elm) {
            return $(this).is(".slot") && !$(this).has(".item").length;
          });
          if ($target.length) {
            $target.append($(this).css({
              top: 0,
              left: 0
            }))
          } else {
            $(this).animate({
              top: 0,
              left: 0
            }, "slow");
          }

        });
        $(".ui-selected").data("original", null)
          .data("target", null)
          .removeClass("ui-selected");
      }
    },
    dropOption = {
      accept: '.item',
      activeClass: "green3",
      drop: function(event, ui) {
        if ($(this).is(".slot") && !$(this).has(".item").length) {
          $(this).append(ui.draggable.css({
            top: 0,
            left: 0
          }));
        } else {
          ui.draggable.animate({
            top: 0,
            left: 0
          }, 50);
        }
      }
    }

  $(".box").selectable({
    filter: ".item",
    start: function(event, ui) {
      $(".ui-draggable").draggable("destroy");
    },
    stop: function(event, ui) {
      $(".ui-selected").draggable(dragOption)
    }
  });
  $(".slot").droppable(dropOption);
});
.box {
  float: left;
  width: 150px;
  height: 180px;
  text-align: center;
  margin-left: 20px;
  border: 5px solid #999;
}
.slot {
  position: relative;
  width: 120px;
  height: 15px;
  margin-top: 2px;
  margin: 0 auto;
  border: 1px dotted;
}
.item {
  width: 110px;
  height: 14px;
  margin: 0 auto;
  z-index: 1;
  background-color: #CCC;
}
.ui-selecting {
  background: #FECA40;
}
.ui-selected {
  background-color: #F90;
}
.green3 {
  background-color: #D9FFE2;
}
<link href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<div id="container">
  <div id="boxA" class="box">
    <h4>box A</h4>

    <div id="A1" class="slot">
      <div id="a1" class="item"></div>
    </div>
    <div id="A2" class="slot">
      <div id="a2" class="item"></div>
    </div>
    <div id="A3" class="slot">
      <div id="a3" class="item"></div>
    </div>
    <div id="A4" class="slot"></div>
    <div id="A5" class="slot"></div>
  </div>
  <div id="boxB" class="box">
    <h4>box B</h4>

    <div id="B1" class="slot"></div>
    <div id="B2" class="slot">
      <div id="b2" class="item"></div>
    </div>
    <div id="B3" class="slot"></div>
    <div id="B4" class="slot"></div>
    <div id="B5" class="slot">
      <div id="b5" class="item"></div>
    </div>
  </div>
</div>

Other references:


P.S: This will only work if the draggable is smaller than droppable (Which is true in this case)

T J
  • 42,762
  • 13
  • 83
  • 138
0

If you mean, you want to drag and drop something and you want to revert if the data is dropped in the wrong area, then you can you use following code as a sample to achive your target

$(function() {
  $(".connectedSortable").sortable({
    connectWith: ".connectedSortable",
    placeholder: "ui-state-highlight",
    //containment: "parent",
    revert: true
  });
  $("#sortable2").sortable({
    connectWith: ".connectedSortable",
    receive: function(event, ui) {
      if (ui.item.attr('cust-attr') != 'm') {
        console.log('wrong element');
        $("#" + ui.sender.attr('id')).sortable('cancel');
      }
    }
  });
  $("#sortable2x").sortable({
    connectWith: ".connectedSortable",
    receive: function(event, ui) {
      if (ui.item.attr('cust-attr') == 'm') {
        console.log('wrong element');
        $("#" + ui.sender.attr('id')).sortable('cancel');
      }
    }
  });
});
#sortable1,
#sortable1x,
#sortable2,
#sortable2x {
  list-style-type: none;
  margin: 0;
  padding: 0 0 2.5em;
  float: left;
  margin-right: 10px;
}
#sortable1 li,
#sortable1x li,
#sortable2 li,
#sortable2x li {
  box-shadow: 2px 2px 0 #6D6D6D;
  cursor: pointer;
  margin: 0 5px 5px 5px;
  padding: 5px;
  font-size: 1.2em;
  width: 150px;
}
#sortable2 .ui-selecting,
#sortable2x .ui-selecting {
  background: #FECA40;
}
#sortable2 .ui-selected,
#sortable2x .ui-selected {
  background: #F39814;
  color: white;
}
#sortable1x,
#sortable2x {
  list-style-type: none;
  margin: 0;
  width: 60%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.11.2/jquery-ui.js"></script>
<link href="http://code.jquery.com/ui/1.11.2/themes/smoothness/jquery-ui.css" rel="stylesheet">
<div style="width:600px;height:360px;">
  <div style="float:left;">
    Set 1 : &nbsp;
  </div>
  <div>
    <ul id="sortable1" class="connectedSortable" style="border: 1px solid #000000;width:220px;height:300px;padding:4px;">
      <li class="ui-state-default" cust-attr="m">Item 1 Set 1 X</li>
      <li class="ui-state-default" cust-attr="m">Item 2 Set 1 X</li>
      <li class="ui-state-default">Item 3 Set 1 X</li>
      <li class="ui-state-default">Item 4 Set 1 X</li>
      <li class="ui-state-default">Item 5 Set 1 X</li>
    </ul>
    <ul id="sortable1x" class="connectedSortable" style="border: 1px solid #000000;width:220px;height:300px;padding:4px;">
      <li class="ui-state-default" cust-attr="m">Item 1 Set 1 Y</li>
      <li class="ui-state-default" cust-attr="m">Item 2 Set 1 Y</li>
      <li class="ui-state-default">Item 3 Set 1 Y</li>
      <li class="ui-state-default">Item 4 Set 1 Y</li>
      <li class="ui-state-default">Item 5 Set 1 Y</li>
    </ul>
  </div>
</div>
<div style="width:600px;height:360px;">
  <div style="float:left;">
    Set 2 : &nbsp;
  </div>
  <div>
    <ul id="sortable2" class="connectedSortable connectedSortablex" style="border: 1px solid #000000;width:220px;height:300px;padding:4px;">

    </ul>
    <ul id="sortable2x" class="connectedSortable connectedSortablex" style="border: 1px solid #000000;width:220px;height:300px;padding:4px;">

    </ul>
  </div>
</div>

Click the below link to see demo http://jsfiddle.net/g90rau5p/3/

If you want multiple items drop, use multisort jquery library which can be used along with jqueryUI. For more details about it go to https://github.com/shvetsgroup/jquery.multisortable

Manu Mathew
  • 487
  • 4
  • 17
  • 1
    no, I meant multiple draggables on multiple droppables, a draggable reverts if it's placed on an invalid droppable, a draggable stays in new position if it's place on a valid droppable. – Ray C Lin Nov 15 '14 at 21:28
  • Here I had tried to do what you asked except multiple to one at a time. Hope you can achieve that from this by giving some modifications to this code. – Manu Mathew Nov 17 '14 at 06:28
  • You can use multisort library along with JqueryUI to achive that functionality. For more details please go through the following link: https://github.com/shvetsgroup/jquery.multisortable – Manu Mathew Nov 17 '14 at 09:35