Search code examples
javascriptjquery-uijquery-ui-draggable

jQuery UI: drag multiple objects


My scenario : Container (div) with some objects (div). The objects can be moved inside the container (with the option containment set to parent).

Now i need to move multiple objects at once. To do this i found this useful plugin. Unfortunately this plugin does not handle the property containment, as reported here.

My test on JSFiddle , disable this function

$(".obj").on("drag", function(ev, ui)

To active the multiple drag, click on the objects. I was able to block the drag event.

Problem of my test:

At that point i wouldn't know how to reactivate the drag.

Note

I should probably know the direction of the drag (with start - stop events). But at this point i can't stop the drag.

My solutions

But also the K Scandrett solution is very good. It is very difficult to apply in my particular case, which has been simplified in the example.

Always using this plugin for enable the multiple drag. Each time i select multiple objects and drag them, in the dragstart event i do this (change the property containment of the object, depending on the positions of the selected objects) :

//1024 * 648 is the width of the container
$(obj).unbind("dragstart").bind("dragstart" , function(ev, ui){

    var dimObjFirst = {
        x : parseInt($(this).css("left")),
        y : parseInt($(this).css("top"))
    };
    if($("blablabla > div.ui-selected").length > 1){

        var minLeft = 1024,maxRight = 0,minTop = 648,maxDown = 0;

        $("blablabla > div.ui-selected").each(function(){
            var elem = $(this);
            var dim = {
                w : parseInt(elem.css("width")),
                h : parseInt(elem.css("height")),
                l : parseInt(elem.css("left")),
                t : parseInt(elem.css("top")),
            };
            if(dim.l < minLeft) minLeft = dim.l;
            if(dim.l + dim.w > maxRight) maxRight = dim.l + dim.w;
            if(dim.t < minTop) minTop = dim.t;
            if(dim.t + dim.h > maxDown) maxDown = dim.t + dim.h;
        });

        var offsetContainer = $(".container").offset();
        $(this).draggable( "option" , "containment" , [
          (dimObjFirst.x - minLeft) + parseInt(offsetContainer.left),
          (dimObjFirst.y - minTop) + parseInt(offsetContainer.top),
          (dimObjFirst.x + (1024 - maxRight)) + parseInt(offsetContainer.left),
          (dimObjFirst.y) + (648 - maxDown) + parseInt(offsetContainer.top)
        ]);
    }           
});

$(obj).unbind("dragstop").on("dragstop", function(ev, ui) {

    if($("blablabla > div.ui-selected").length > 1) {
        $("blablabla > div.ui-selected").each(function(){
            $(this).draggable( "option" , "containment" , "parent" );
        });
    }
});

And add this line of code this._setContainment(); at the start of the function _mouseDrag of the jQuery UI plugin.


Solution

  • Looks like a fun project so....

    I implemented it with a bounding box (similar to Twisty's comment).

    I figured the benefit of doing it this way is that it will then constrain all multiple selected objects to the bounds of the container.

    The bounding box I've coloured so you can visualise how it works. Of course you'd likely leave it transparent in practice.

    Code comments are inline, but happy to answer any questions on the code if you have them.

    No plugins were used (just jQuery and jQueryUI).

    var disableclick = false;
    
    var boundingBoxTop, boundingBoxBottom, boundingBoxLeft, boundingBoxRight;
    var $container = $("#container");
    var containerHeight = $container.height();
    var containerWidth = $container.width();
    var containerTop = $container.offset().top;
    var containerLeft = $container.offset().left;
    
    // add the bounding box to the container and make it draggable
    var $boundingBox = $("<div id='boundingBox' style='position:absolute;background-color:#fcf5d4'>").prependTo($container);
    
    $boundingBox.draggable({
      grid: [10, 10],
      containment: "parent",
      stop: function( event, ui ) {
        disableclick = true; // don't want to toggle selection when dragging
        setTimeout(function(e){
            disableclick = false;
        },200);
      },
    });
    
    $(".obj").click(function(e) {
    
      if (!disableclick) {
    
        var $objClicked = $(this);
        $objClicked.toggleClass("ui-selected");
    
        var $selectedItems = $("#container .ui-selected");
    
        // move any items in bounding box back into container before we re-process them
        $boundingBox.find('*').each(function() {
          var $this = $(this);
    
          if ($this.parent().is($boundingBox)) {
            // adjust its positioning to be relative to the container
            $this.css("top", ($this.offset().top - containerTop) + "px");
            $this.css("left", ($this.offset().left - containerLeft) + "px");
            $container.append($this); // return it to the container
          }
        });
    
        // reversing co-ords to what might be expected here so that we can scale them back to what they need to be for a bounding box
        boundingBoxTop = containerHeight;
        boundingBoxBottom = 0;
        boundingBoxLeft = containerWidth;
        boundingBoxRight = 0;
    
        // find the bounds of the smallest rectangle that will cover all the currently selected objects
        $selectedItems.each(function() {
          var $this = $(this);
    
          var top = $this.offset().top - containerTop;
          var bottom = $this.offset().top - containerTop + $this.height();
          var left = $this.offset().left - containerLeft;
          var right = $this.offset().left - containerLeft + $this.width();
    
          boundingBoxTop = (top < boundingBoxTop) ? top : boundingBoxTop;
          boundingBoxBottom = (bottom > boundingBoxBottom) ? bottom : boundingBoxBottom;
          boundingBoxLeft = (left < boundingBoxLeft) ? left : boundingBoxLeft;
          boundingBoxRight = (right > boundingBoxRight) ? right : boundingBoxRight;
        });
    
        // get the height and width of bounding box
        var boundingBoxHeight = boundingBoxBottom -= boundingBoxTop;
        var boundingBoxWidth = boundingBoxRight -= boundingBoxLeft;
    
        if (boundingBoxBottom > 0) // will be negative when nothing is selected
        {
          // update the bounding box with its new position and size
          $boundingBox.css("top", boundingBoxTop + "px");
          $boundingBox.css("left", boundingBoxLeft + "px");
          $boundingBox.css("width", boundingBoxWidth + "px");
          $boundingBox.css("height", boundingBoxHeight + "px");
    
          // add each selected item to the bounding box so we can drag the box with them in it
          $selectedItems.each(function() {
            var $this = $(this);
    
            // correct the item's position to be relative to the bounding box
            $this.css("top", ($this.offset().top - containerTop - boundingBoxTop) + "px");
            $this.css("left", ($this.offset().left - containerLeft - boundingBoxLeft) + "px");
    
            $boundingBox.append($this); // add item to bounding box
          });
        }
      }
    });
    #container {
      position: absolute;
      width: 400px;
      height: 150px;
      background: #eee;
    }
    
    .obj {
      position: absolute;
      background: #ccc;
    }
    
    .ui-selected {
      background: #1C90F3;
    }
    
    #obj1 {
      width: 20px;
      height: 20px;
      left: 20px;
      top: 20px;
    }
    
    #obj2 {
      width: 20px;
      height: 20px;
      left: 100px;
      top: 20px;
    }
    
    #obj3 {
      width: 20px;
      height: 20px;
      left: 20px;
      top: 100px;
    }
    
    #obj4 {
      width: 20px;
      height: 20px;
      left: 100px;
      top: 100px;
    }
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    
    <div style="margin-bottom:10px">
      Click boxes to select/deselect multiple items.<br/>Drag to move selection.
    </div>
    
    <div id="container">
      <div class="obj" id="obj1"></div>
      <div class="obj" id="obj2"></div>
      <div class="obj" id="obj3"></div>
      <div class="obj" id="obj4"></div>
    </div>