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.
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>