Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drag and drop multiple selected draggables and revert invalid ones using Jquery UI

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;
}
like image 545
Ray C Lin Avatar asked Nov 08 '14 23:11

Ray C Lin


1 Answers

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:

  • jQuery.unique()
  • is()
  • has()

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

like image 79
T J Avatar answered Oct 29 '22 17:10

T J