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 for
fit` 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.