Reputation: 3350
I am trying to allow an element to be dragable/resizable inside of another element but not in the area of another child of that element.
Given the following table row:
I want to be able to drag and resize the blue area anywhere within the table row, but not where the grey table cell is (i.e. it would move/resize freely within the first four cells but be stopped when it reaches the last one).
I can constrain it to the table row using the containment: '.middle tr'
or containment: $el.closest('tr'),
(where $el
is a selector for the blue element) options in JQuery.draggable
and JQuery.resizable
, but I haven't been able to find a way that works for excluding the last column from the containment.
How do you exclude an element from the containment area?
Example with code:
$(function() {
$("td").droppable({
drop: function(event, ui) {
var draggable = ui.draggable;
var newLeft = draggable.offset().left - draggable.closest(".middle table").offset().left;
var childNum = Math.round((newLeft + 100) / 100);
var newContainer = $(this).closest('tr').find('td:nth-of-type(' + childNum + ')');
draggable.css({
top: '',
left: ''
});
newContainer.append(draggable);
},
});
$(".planning-spot").resizable({
grid: [100, 10000000],
handles: "e",
containment: ".middle tr"
}).each(function() {
var $el = $(this);
$el.draggable({
containment: $el.closest('tr'),
axis: 'x',
});
});
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
overflow: hidden;
table-layout: fixed;
width: 500px;
margin: 20px;
}
tr {
position: relative;
background: #FFF;
}
td, th {
position: relative;
padding: 0;
width: 100px;
height: 30px;
border: 1px solid #000;
}
.planning-spot {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 90px;
border-radius: 10px;
margin: 5px;
text-align: center;
z-index: 1;
cursor: pointer;
color: #FFF;
width: 290px;
background: #3CF;
}
.no-planning {
background: #CCC;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/jquery-ui.min.css" rel="stylesheet"/>
<div class="wrapper">
<div class="middle">
<table>
<tbody>
<tr>
<td>
<div></div>
<div class="planning-spot">18 / 20</div>
</td>
<td>
<div></div>
</td>
<td>
<div></div>
</td>
<td>
<div></div>
</td>
<td class="no-planning">
<div></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
http://jsfiddle.net/xpvt214o/668937/
I'm aware that I can check the location during the drop
and stop
functions of droppable
and resizable
respectively and reverse the action if needed (which is my current solution), but I'm looking for a solution to prevent it being moved into an "unwanted" area in the first place.
Upvotes: 3
Views: 490
Reputation: 2886
You can determine the draggable area with getBoundingClientRect()
and assign containment to an array wich take the width of each resizeable elements
$(function() {
$("td").droppable({
drop: function(event, ui) {
var draggable = ui.draggable;
var newLeft = draggable.offset().left - draggable.closest(".middle table").offset().left;
var childNum = Math.round((newLeft + 100) / 100);
var newContainer = $(this).closest('tr').find('td:nth-of-type(' + childNum + ')');
draggable.css({
top: '',
left: ''
});
newContainer.append(draggable);
},
});
$(".planning-spot").resizable({
grid: [100, 10000000],
handles: "e",
containment: ".middle tr"
}).each(function() {
var $el = $(this);
var bBoxTr = $el.closest('tr')[0].getBoundingClientRect();
var bBoxTd = $el.closest('tr').children(".no-planning")[0].getBoundingClientRect();
var x1 = bBoxTr.x, y1 = bBoxTr.y;
var x2 = bBoxTr.right-bBoxTd.width, y2 = bBoxTr.bottom;
$el.draggable({
// containment: $el.closest('tr'),
containment: [x1,y1,x2-$el.width()-10,y2],
axis: 'x',
});
});
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
table {
border-collapse: no-collapse;
overflow: hidden;
table-layout: fixed;
width: 500px;
margin: 20px;
}
tr {
position: relative;
background: #FFF;
}
td, th {
position: relative;
padding: 0;
width: 100px;
height: 30px;
border: 1px solid #000;
}
.planning-spot {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 90px;
border-radius: 10px;
margin: 5px;
text-align: center;
z-index: 1;
cursor: pointer;
color: #FFF;
width: 290px;
background: #3CF;
}
.no-planning {
background: #CCC;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/jquery-ui.min.css" rel="stylesheet"/>
<div class="wrapper">
<div class="middle">
<table>
<tbody>
<tr id="test">
<td>
<div></div>
<div class="planning-spot">18 / 20</div>
</td>
<td>
<div></div>
</td>
<td>
<div></div>
</td>
<td>
<div></div>
</td>
<td class="no-planning">
<div></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
Edit:
Since getBoundingClientRect();
is not supported by all browsers, better go with jquery features, position()
, height()
and width();
.
See the revised snippet below, the function getDragArea
returns an array with the coordinates of the drag area. I add a listener (mouseover) which permit to reset drag area when needed.
The table is wrapped in a scrolling div to test behavior.
$(function() {
$("td").droppable({
drop: function(event, ui) {
var draggable = ui.draggable;
var newLeft = draggable.offset().left - draggable.closest(".middle table").offset().left;
var childNum = Math.round((newLeft + 100) / 100);
var newContainer = $(this).closest('tr').find('td:nth-of-type(' + childNum + ')');
draggable.css({
top: '',
left: ''
});
newContainer.append(draggable);
},
});
$(".planning-spot").resizable({
grid: [100, 10000000],
handles: "e",
containment: ".middle tr"
}).each(function() {
$(this).on('mouseover',function(){
$(this).draggable({
containment: getDragArea($(this)),
axis: 'x',
});
})
});
function getDragArea($el){
var $tr = $el.closest('tr');
var $tds = $el.closest('tr').children(".no-planning");
var width = $el.closest('tr').width()-$tds.width()*$tds.length;
var height = $el.closest('tr').height();
var position = $el.closest('tr').position();
var x1 = position.left, y1 = position.top;
var x2 = x1+width, y2 = y1+height;
return [x1,y1,x2-$el.width()-10,y2]
}
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
table {
border-collapse: no-collapse;
overflow: hidden;
table-layout: fixed;
width: 500px;
margin: 20px;
}
tr {
position: relative;
background: #FFF;
}
td, th {
position: relative;
padding: 0;
width: 100px;
height: 30px;
border: 1px solid #000;
}
.planning-spot {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 90px;
border-radius: 10px;
margin: 5px;
text-align: center;
z-index: 1;
cursor: pointer;
color: #FFF;
width: 290px;
background: #3CF;
}
.no-planning {
background: #CCC;
}
.wrapper{
overflow:auto;
width:500px;
height:120px;
border: solid 1px red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/jquery-ui.min.css" rel="stylesheet"/>
<div class="wrapper">
<div class="middle">
<table>
<tbody>
<tr id="test">
<td>
<div></div>
<div class="planning-spot">18 / 20</div>
</td>
<td>
<div></div>
</td>
<td>
<div></div>
</td>
<td>
<div></div>
</td>
<td class="no-planning">
<div></div>
</td>
<td class="no-planning">
<div></div>
</td>
</tr>
</tbody>
</table>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
</div>
</div>
Upvotes: 2
Reputation: 26085
I did the trick by checking if drag event will cause collision with non dragable item.
Checkout code below:
$(function() {
var nonDragableItems = $(".no-planning");
$("td").droppable({
drop: function(event, ui) {
var draggable = ui.draggable;
var newLeft = draggable.offset().left - draggable.closest(".middle table").offset().left;
var childNum = Math.round((newLeft + 100) / 100);
var newContainer = $(this).closest('tr').find('td:nth-of-type(' + childNum + ')');
draggable.css({
top: '',
left: ''
});
newContainer.append(draggable);
},
});
function onDrag(e, ui){
var dragElemRect = ui.helper[0].getBoundingClientRect();
for(var i=0;i<nonDragableItems.length;i++){
var elemRect = nonDragableItems[i].getBoundingClientRect();
if(areColliding(dragElemRect, elemRect))
return false;
}
}
function areColliding(rect1, rect2){
if(rect1.bottom <= rect2.top ||
rect1.top >= rect2.bottom ||
rect1.left >= rect2.right ||
rect1.right <= rect2.left)
return false;
return true;
}
$(".planning-spot").resizable({
grid: [100, 10000000],
handles: "e",
containment: ".middle tr"
}).each(function() {
var $el = $(this);
$el.draggable({
containment: $el.closest('tr'),
axis: 'x',
drag: onDrag
});
});
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
overflow: hidden;
table-layout: fixed;
width: 500px;
margin: 20px;
}
tr {
position: relative;
background: #FFF;
}
td, th {
position: relative;
padding: 0;
width: 100px;
height: 30px;
border: 1px solid #000;
}
.planning-spot {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 90px;
border-radius: 10px;
margin: 5px;
text-align: center;
z-index: 1;
cursor: pointer;
color: #FFF;
width: 290px;
background: #3CF;
}
.no-planning {
background: #CCC;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div class="wrapper">
<div class="middle">
<table>
<tbody>
<tr>
<td>
<div></div>
<div class="planning-spot">18 / 20</div>
</td>
<td>
<div></div>
</td>
<td>
<div></div>
</td>
<td>
<div></div>
</td>
<td class="no-planning">
<div></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
Upvotes: 1