2
votes

As You can see in Figure 1 . I have a Main div - which is Droppable

With Four Draggables namely A , B , C , Fit. On drag Of A , B or C div into the droppable Main Div - It occupies the space as shown in Fig 2 (shows when I drag A into the Main Div ).Now if I drag another div (A , B or C) then it should occupy the space with respect to their width size. (height is same as Main Div)

Fig 1 Figure 1

Fig 2 enter image description here

Now if I drag the 4th draggable div i.e with name Fit then a div should be formed inside the Main div with the width equal to whatever space is left in the two adjacent Divs in Main div.

For example : . Suppose 10px space is between A and B (inside Main) and between B and c , space is 40px , So if i Drop FIT in between A and B then a div should be formed with their space difference i.e 10px , similarly 40px div should be formed in between B and C on drag of FIT

It shouldn't squeeze the divs present in the Main Div , neither it should overflow Main DIv

Please Let me know How I can Place the Draggables A, B and C inside the Main div at the user positioned location, without Disturbing the Main div Width.

What I have done is I made div's A , B , C as draggable

$(".classname").draggable({
  helper: 'clone',
  stop: function(event, ui) {
  }
});

and main div Droppable:

$(".maindivclass").droppable();

and on drop of the draggables , I am making those Divs Draggable inside the main div

$("#" + Dropped_div_id).draggable({
  axis: 'x',
  containment: "#Main_divID",
});

But on drop of A, B and C , It always occupies the center location and right now I can drop any number of divs inside the Main div. Please tell me how to make draggables position the exact location where User is dropping and summation of width of all divs dropped should be always equal to the Main div width. Maybe Grid is the solution, but I have dynamic Width divs to drop.

I researched a lot and found something - 10 % relevant to my task is this http://gridster.net/ - But here the container is not fixed height or width. In my case Height of all divs are the same. Only width varies.

More Addtional Info When Asked in comments:

1) When A, B, or C is dragged and dropped to Main, you want the item to be appended into Main.

2) When another is dragged, it should not be dropped / overlap on other divs already present in Main.

3) On drag of Fit, Whatever space left in the Main should become occupied. I mean div with respective width should be created as per the space left in Main div

2
can you create a fiddle with an example of you real structure?Emanuele Parisio
@EmanueleParisio I started a basic fiddle jsfiddle.net/warkentien2/nkm09x7kwarkentien2
@EmanueleParisio yes you can use this jsfiddle jsfiddle.net/warkentien2/nkm09x7k . How to drop the draggables to exact location where user wants and how to avoid overlapping of A, B , C at the time of first drop inside droppableAmar Singh
Thanks for that fiddle @warkentien2Amar Singh
@YoYo to make sure I understand this properly, 1) When A, B, or C is dragged and dropped to Main, you want the item to be appended into Main. Yes? 2) When another is dragged, should it be able to be dragged onto of any others already in Main? 3) When Fit is dragged in before or after an other objects, it should expand to the height and remaining width of space between any objects to it's left/right and to corresponding border of Main? 4) Should object be draggable after they are dropped? or stay locked?Twisty

2 Answers

2
votes

That was Fun and Dirty.

Made you an example that basically does two extra things:

  • Keeps track of the "Dropped" elements positions.
  • Calculates the distance between two elements (or sides) when you drop the FIT element.

JSnippet "Drag and Fit" :)

The code obviously need immprovement but I'm sure it will help you get on track.

JS code:

$(function() {
    var set = {
        appendClass: 'added',
        area: { s:"#droppable", w:0, left:0 },
        added : [],
    };
    $( ".exact-pos, #drag_fit" ).draggable({
        revert: "invalid",
        appendTo: ".wrap",
        helper: "clone"
    });
    $( "#droppable" ).droppable({
      accept:".exact-pos, .fill",
      drop: function( event, ui ) {
        var $area = $(this);
        var $origin_ele = $(ui.draggable).clone();
        var $ele_clone = $area.closest('.wrap').find('.ui-draggable-dragging').eq(0);
        var mes = {};
        set.area.w = $area.outerWidth();
        set.area.l = $area.offset().left;
        if (!$origin_ele.hasClass("fill")) { // Handle non fit
             mes = { // measure
               s:$origin_ele.attr('class') + " " + set.appendClass,
               l:$ele_clone.offset().left - set.area.l,
               w:$(ui.draggable).outerWidth(),
               r:0
             }
             //Check borders:
             if (mes.l < 0) { mes.l = 0; }
             if (mes.l + mes.w > set.area.w) { mes.l = set.area.w - mes.w; }
             //Set right side:
             mes.r = mes.l + mes.w;
             var revert = false;
             //Check overlaping left:
             $.each(set.added, function(index, ele) {
                if (mes.l >= ele.l && mes.l <= ele.r) {
                   mes.l = ele.r;
                   mes.r = mes.l + mes.w;
                }
             });
             //Check overlapping right: will revert
             $.each(set.added, function(index, ele) {
                if (mes.r >= ele.l && mes.r <= ele.r) {
                    revert = true;
                } else if (mes.r >= ele.r && mes.l <= ele.l) {
                    revert = true;
                } else if (mes.r > set.area.w) {
                    revert = true;
                }
             });
             //append or revert
             if (!revert) {
                ui.draggable.draggable('option','revert',false);  
                $area.append($origin_ele.addClass("added").css({"left":mes.l}));
                set.added.push($.extend(true, {}, mes));
             } else {
                ui.draggable.draggable('option','revert',true);  
             }
        } else {
            var f = { l:0,w:0,r:0 };
            var c = $ele_clone.offset().left - set.area.l + ($(ui.draggable).outerWidth() / 2);
            var betw = -1;
            var fleft = 0;
            var fright = 0;
            var revert = false;
            //sort by position:
            set.added.sort(function(a, b) {
                return a.l - b.l;
            });
            //after which elemnt:
            $.each(set.added, function(index, ele) {
                if (ele.r < c) { betw = index; }
             });
            //calc dim:
            if (betw == -1 && set.added.length == 0) {
                fright = set.area.w;
            } else if (betw == -1 && set.added.length > 0) {
                fright = set.added[0].l;
            } else if (betw == set.added.length - 1) {
                fleft = set.added[betw].r;
                fright = set.area.w;
            } else if (typeof set.added[betw + 1] != 'undefined') {
                fleft = set.added[betw].r;
                fright = set.added[betw + 1].l;
            }
            if (fright - fleft > 1) {
                f.l = fleft;
                f.w = fright - fleft;
                f.r = f.l + f.w;
            } else {
              revert = true;
            }
            //append or revert:
            if (!revert) {
                ui.draggable.draggable('option','revert',false);  
                $area.append($origin_ele.addClass("added").css({"left":f.l, width:f.w}).text(""));
                set.added.push($.extend(true, {}, f));
             } else {
                ui.draggable.draggable('option','revert',true);  
             }
        }
      }
    });
    $('#resetAll').click(function resetAll() {
        set.added = [];
        $(set.area.s).find('div').fadeOut(function(){ $(this).remove(); });
    });
});
2
votes

This answer is not fully complete, yet it is very functional and I plan on updating it. This currently will perform the following functions:

  • Drag an A, B, C, or Fit object into the Main area
  • Drag/Move an object that is already in the Main area
  • Drag other A, B, C, or Fit object into Main area but not on top of existing objects
  • Reset for more testing
  • If Fit object is dragged into Main area, and no other objects exist, it will fill the entire area
  • If Fit object is dragged into Main area that contains objects, will determine the remaining space between objects and boundry of Main area and will fill the area left or right of existing objects

Current working example: https://jsfiddle.net/Twisty/35nd6y3g/10/

HTML

<div class="wrapper">
  <div class="box droppable" id="main">Main Div - droppable</div>
  <div class="box draggable new" id="a">drag A</div>
  <div class="box draggable new" id="b">drag B</div>
  <div class="box draggable new" id="c">drag C</div>
  <div class="box draggable new" id="fit" data-width="150">fit</div>
</div>
<br />
<button id="resetBtn">Reset</button>
<input type="text" id="leftDetails" style="width: 75%" />

CSS

body {
  overflow: hidden;
}

.wrapper {
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  align-items: center;
  height: 450px;
}

.box {
  border: 1px solid black;
  display: flex;
  //justify-content: center;
  align-items: center;
}

#main {
  position: relative;
  width: 150px;
  height: 450px;
}

#main .box {
  background: #9d9d9d;
}

#a {
  width: 30px;
  height: 450px;
}

#b {
  width: 45px;
  height: 450px;
}

#c {
  width: 75px;
  height: 450px;
}

#fit {
  width: 30px;
  height: 40px;
}

jQuery

$(document).ready(function() {
  console.log("Main: " + $("#main").position().left + " Drag A: " + $("#a").position().left + "Drag B: " + $("#b").position().left + " Drag C: " + $("#c").position().left);
  var mainBoxes = {};
  $("#resetBtn").click(function() {
    $("#main").find("div.box").remove();
    mainBoxes = {};
  });
  var dragOrig;
  $(".draggable").draggable({
    containment: "#main",
    axis: "x",
    revert: true,
    start: function(e, ui) {
      dragOrig = ui.position;
    },
    drag: function(e, ui) {
      $("#leftDetails").val(ui.helper.text() + " pos left: " + ui.position.left + " off left: " + ui.offset.left);
      if (Object.keys(mainBoxes).length === 0) {
        return;
      }
      var offLeft = ui.offset.left - $("#main").offset().left;
      var offRight = offLeft + ui.helper.width();
      //var offMouse = e.clientX - $("#main").offset().left;
      $.each(mainBoxes, function(k, v) {
        if (offLeft >= v.left && offLeft <= v.right) {
          ui.position.left = ui.position.left + v.right;
          console.log("Info: Left Edge collided with ", k, ui.position);
        }
        if (offRight > v.left && offRight < v.right) {
          //ui.position.left = ui.position.left + (v.left - ui.helper.width());
          ui.position.left = ui.position.left + v.right;
          console.log("Info: Right Edge collided with ", k, ui.position);
        }
      });
    },
  });
  $(".droppable").droppable({
    hoverClass: "hover",
    accept: ".box",
    drop: function(event, ui) {
      // add functionality here
      //var pos = ui.position;
      var off = ui.offset;
      var c = $("#main .box").length + 1;
      var $newBox = ui.draggable.clone();
      if (ui.draggable.attr("id") === "fit") {
        console.log("Info: Adding 'fit' class");
        $newBox.addClass("fit");
        if (Object.keys(mainBoxes).length === 0) {
          console.log("Info: No other boxes found. Set Max Width.");
          $newBox.css({
            width: "150px",
            height: "450px",
            left: "-1px"
          });
        } else {
          var lefts = Object.keys(mainBoxes).map(function(k) {
            return mainBoxes[k].left;
          });
          var rights = Object.keys(mainBoxes).map(function(k) {
            return mainBoxes[k].right;
          });
          var minLeft = Math.min.apply(null, lefts);
          var maxRight = Math.max.apply(null, rights);
          if ((ui.offset.left - $("#main").offset().left) + ui.helper.width() < minLeft) {
            console.log("Info: 'fit' dropped left of boxes.");
            $newBox.css({
              width: minLeft + "px",
              height: "450px",
              left: "-1px"
            });
            $newBox.data("width", minLeft);
            console.log("Info: ", minLeft, maxRight, $newBox[0]);
          }
          if ((ui.offset.left - $("#main").offset().left) > maxRight) {
            console.log("Info: 'fit' dropped right of boxes.");
            $newBox.css({
              width: (150 - maxRight) + "px",
              height: "450px",
              left: maxRight + "px"
            });
            $newBox.data("width", 150 - maxRight);
            console.log("Info: ", minLeft, maxRight, (450 - maxRight), $newBox[0]);
          }
        }
      } else {
        $newBox = ui.draggable.clone();
        if ($newBox.hasClass("new")) {
          $newBox.removeClass("new");
        } else {
          return;
        }
      }
      console.log("Info: Removing .draggable");
      $newBox.removeClass("draggable");
      console.log("Info: Setting ID = box" + c);
      $newBox.attr("id", "box" + c);
      $newBox.draggable({
        containment: "#main",
        axis: "x",
        drag: function(e, ui) {
          if (Object.keys(mainBoxes).length === 0) {
            return;
          }
          var offLeft = ui.offset.left - $("#main").offset().left;
          var offRight = offLeft + ui.helper.width();
          $.each(mainBoxes, function(k, v) {
            if (k == ui.helper.attr("id")) {
              return false;
            }
            if (offLeft > v.left && offLeft < v.right) {
              ui.position.left = v.right;
              console.log("Left Edge Collison with ", k);
            }
            if (offRight > v.left && offRight < v.right) {
              ui.position.left = v.left - ui.helper.width();
              console.log("Info: Right Edge Collision with ", k);
            }
          });
        },
        stop: function(e, ui) {
          mainBoxes[ui.helper.attr("id")] = {
            left: ui.offset.left - $("#main").offset().left,
            right: (ui.offset.left - $("#main").offset().left) + ui.helper.width()
          };
          console.log("Info: Updating ", ui.helper.attr("id"), mainBoxes);
        }
      });
      if ($newBox.draggable("instance") !== "undefined") {
        console.log("box" + c + ": is draggable.");
      }
      $newBox.css({
        left: $newBox.hasClass("fit") ? $newBox.css("left") : (off.left - $("#main").offset().left) + "px",
        top: "-1px",
        position: 'absolute',
        height: "450px",
        width: $newBox.hasClass("fit") ? $newBox.css("width") : ui.draggable.width() + "px"
      });
      console.log("box" + c + ": ", $newBox[0], " now being appended.");
      $("#main").append($newBox);
      mainBoxes['box' + c] = {
        'width': $newBox.width(),
        'left': off.left - $("#main").offset().left,
        'right': (off.left - $("#main").offset().left) + $newBox.width()
      };
      console.log("Added: ", $newBox.attr("id"), mainBoxes);
    }
  });
});

As mentioned, this is nearly complete. Due to the use of Viewport Units, positioning is being thrown off. This is not a good use case, but it's what I started with from the fiddle that was offered up.

To track object, I created an Object called mainBoxes. This gets updated when an object is dropped into the droppable. Each index contains a left, right, and width that is populated from the offset left position value. mainBoxes is used for collision detection when dragging further objects and to calculate the width forfit` objects.

We have 2 drag scenarios, dragging a new item into the Main are or moving an item that is already there. When we drag and drop a new item, it is updated, draggable is destroyed, draggable is reset with new properties, and we append it into #main. Draggable by default does not append elements, it simply changes it's position on the page. Thus, the div would be moved, but would never be a child of #main.

The Viewport units created challenges to collision detection and the prevention of dragging an object onto another existing object. I have added lots of details to console for a clear understanding of all the activity. I plan to address this by converting all the lengths and positions to Pixel units.

This should get you going. I also found the following plugin: https://sourceforge.net/projects/jquidragcollide/ I may investigate if this is a better tool to make use of.