Skip to content

Commit 6932b95

Browse files
authored
Prevent draging outside of the parent element with isBounded prop (#523) (#532)
* add bounded prop * bounded example page * fix * Update config.js
1 parent 1e26de4 commit 6932b95

File tree

9 files changed

+315
-2
lines changed

9 files changed

+315
-2
lines changed

src/App.vue

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<input type="checkbox" v-model="draggable"/> Draggable
3131
<input type="checkbox" v-model="resizable"/> Resizable
3232
<input type="checkbox" v-model="mirrored"/> Mirrored
33+
<input type="checkbox" v-model="bounded"/> Bounded
3334
<input type="checkbox" v-model="responsive"/> Responsive
3435
<input type="checkbox" v-model="preventCollision"/> Prevent Collision
3536
<input type="checkbox" v-model="compact"/> Vertical Compact
@@ -45,6 +46,7 @@
4546
:is-draggable="draggable"
4647
:is-resizable="resizable"
4748
:is-mirrored="mirrored"
49+
:is-bounded="bounded"
4850
:prevent-collision="preventCollision"
4951
:vertical-compact="compact"
5052
:restore-on-drag="restoreOnDrag"
@@ -166,6 +168,7 @@
166168
resizable: true,
167169
mirrored: false,
168170
responsive: true,
171+
bounded: false,
169172
preventCollision: false,
170173
compact: true,
171174
restoreOnDrag: true,

src/components/GridItem.vue

+43-1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@
134134
required: false,
135135
default: null
136136
},
137+
isBounded: {
138+
type: Boolean,
139+
required: false,
140+
default: null
141+
},
137142
/*useCssTransforms: {
138143
type: Boolean,
139144
required: true
@@ -274,7 +279,11 @@
274279
self.resizable = isResizable;
275280
}
276281
};
277-
282+
self.setBoundedHandler = function (isBounded) {
283+
if (self.isBounded === null) {
284+
self.bounded = isBounded;
285+
}
286+
};
278287
self.setRowHeightHandler = function (rowHeight) {
279288
self.rowHeight = rowHeight;
280289
};
@@ -296,6 +305,7 @@
296305
this.eventBus.$on('compact', self.compactHandler);
297306
this.eventBus.$on('setDraggable', self.setDraggableHandler);
298307
this.eventBus.$on('setResizable', self.setResizableHandler);
308+
this.eventBus.$on('setBounded', self.setBoundedHandler);
299309
this.eventBus.$on('setRowHeight', self.setRowHeightHandler);
300310
this.eventBus.$on('setMaxRows', self.setMaxRowsHandler);
301311
this.eventBus.$on('directionchange', self.directionchangeHandler);
@@ -310,6 +320,7 @@
310320
this.eventBus.$off('compact', self.compactHandler);
311321
this.eventBus.$off('setDraggable', self.setDraggableHandler);
312322
this.eventBus.$off('setResizable', self.setResizableHandler);
323+
this.eventBus.$off('setBounded', self.setBoundedHandler);
313324
this.eventBus.$off('setRowHeight', self.setRowHeightHandler);
314325
this.eventBus.$off('setMaxRows', self.setMaxRowsHandler);
315326
this.eventBus.$off('directionchange', self.directionchangeHandler);
@@ -339,6 +350,11 @@
339350
} else {
340351
this.resizable = this.isResizable;
341352
}
353+
if (this.isBounded === null) {
354+
this.bounded = this.layout.isBounded;
355+
} else {
356+
this.bounded = this.isBounded;
357+
}
342358
this.useCssTransforms = this.layout.useCssTransforms;
343359
this.useStyleCursor = this.layout.useStyleCursor;
344360
this.createStyle();
@@ -357,6 +373,9 @@
357373
isResizable: function () {
358374
this.resizable = this.isResizable;
359375
},
376+
isBounded: function () {
377+
this.bounded = this.isBounded;
378+
},
360379
resizable: function () {
361380
this.tryMakeResizable();
362381
},
@@ -645,6 +664,13 @@
645664
newPosition.left = this.dragging.left + coreEvent.deltaX;
646665
}
647666
newPosition.top = this.dragging.top + coreEvent.deltaY;
667+
if(this.bounded){
668+
const bottomBoundary = event.target.offsetParent.clientHeight - this.calcGridItemWHPx(this.h, this.rowHeight, this.margin[1]);
669+
newPosition.top = this.clamp(newPosition.top, 0, bottomBoundary);
670+
const colWidth = this.calcColWidth();
671+
const rightBoundary = this.containerWidth - this.calcGridItemWHPx(this.w, colWidth, this.margin[0]);
672+
newPosition.left = this.clamp(newPosition.left, 0, rightBoundary);
673+
}
648674
// console.log("### drag => " + event.type + ", x=" + x + ", y=" + y);
649675
// console.log("### drag => " + event.type + ", deltaX=" + coreEvent.deltaX + ", deltaY=" + coreEvent.deltaY);
650676
// console.log("### drag end => " + JSON.stringify(newPosition));
@@ -733,6 +759,22 @@
733759
// console.log("### COLS=" + this.cols + " COL WIDTH=" + colWidth + " MARGIN " + this.margin[0]);
734760
return colWidth;
735761
},
762+
// This can either be called:
763+
// calcGridItemWHPx(w, colWidth, margin[0])
764+
// or
765+
// calcGridItemWHPx(h, rowHeight, margin[1])
766+
calcGridItemWHPx(gridUnits,colOrRowSize,marginPx) {
767+
// 0 * Infinity === NaN, which causes problems with resize contraints
768+
if (!Number.isFinite(gridUnits)) return gridUnits;
769+
return Math.round(
770+
colOrRowSize * gridUnits + Math.max(0, gridUnits - 1) * marginPx
771+
);
772+
},
773+
774+
// Similar to _.clamp
775+
clamp(num, lowerBound, upperBound) {
776+
return Math.max(Math.min(num, upperBound), lowerBound);
777+
},
736778
737779
/**
738780
* Given a height and width in pixel values, calculate grid units.

src/components/GridLayout.vue

+7
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@
7474
type: Boolean,
7575
default: false
7676
},
77+
isBounded: {
78+
type: Boolean,
79+
default: false
80+
},
7781
useCssTransforms: {
7882
type: Boolean,
7983
default: true
@@ -249,6 +253,9 @@
249253
isResizable: function() {
250254
this.eventBus.$emit("setResizable", this.isResizable);
251255
},
256+
isBounded: function() {
257+
this.eventBus.$emit("setBounded", this.isBounded);
258+
},
252259
responsive() {
253260
if (!this.responsive) {
254261
this.$emit('update:layout', this.originalLayout);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<template>
2+
<div style="width:100%;height:2000px;">
3+
<div class="layoutJSON">
4+
Displayed as <code>[x, y, w, h]</code>:
5+
<div class="columns">
6+
<div v-for="item in layout">
7+
<b>{{item.i}}</b>: [{{item.x}}, {{item.y}}, {{item.w}}, {{item.h}}]
8+
</div>
9+
</div>
10+
</div>
11+
<hr/>
12+
<input type="checkbox" v-model="draggable"/> Draggable
13+
<input type="checkbox" v-model="resizable"/> Resizable
14+
<input type="checkbox" v-model="bounded"/> Bounded
15+
<br/>
16+
<div style="width:100%;margin-top: 10px;height:100%;">
17+
<grid-layout :layout.sync="layout"
18+
:col-num="12"
19+
:row-height="30"
20+
:is-draggable="draggable"
21+
:is-resizable="resizable"
22+
:is-bounded="bounded"
23+
:vertical-compact="true"
24+
:use-css-transforms="true"
25+
>
26+
<grid-item v-for="item in layout"
27+
:static="item.static"
28+
:x="item.x"
29+
:y="item.y"
30+
:w="item.w"
31+
:h="item.h"
32+
:i="item.i"
33+
>
34+
<span class="text">{{item.i}}</span>
35+
</grid-item>
36+
</grid-layout>
37+
</div>
38+
</div>
39+
</template>
40+
41+
<script>
42+
import { GridLayout, GridItem } from "vue-grid-layout"
43+
44+
export default {
45+
components: {
46+
GridLayout,
47+
GridItem
48+
},
49+
data() {
50+
return {
51+
layout: [
52+
{"x":0,"y":0,"w":2,"h":2,"i":"0"},
53+
{"x":2,"y":0,"w":2,"h":4,"i":"1"},
54+
{"x":4,"y":0,"w":2,"h":5,"i":"2"},
55+
{"x":6,"y":0,"w":2,"h":3,"i":"3"},
56+
{"x":8,"y":0,"w":2,"h":3,"i":"4"},
57+
{"x":10,"y":0,"w":2,"h":3,"i":"5"},
58+
{"x":0,"y":5,"w":2,"h":5,"i":"6"},
59+
{"x":2,"y":5,"w":2,"h":5,"i":"7"},
60+
{"x":4,"y":5,"w":2,"h":5,"i":"8"},
61+
{"x":6,"y":4,"w":2,"h":4,"i":"9"},
62+
{"x":8,"y":4,"w":2,"h":4,"i":"10"},
63+
{"x":10,"y":4,"w":2,"h":4,"i":"11"},
64+
{"x":0,"y":10,"w":2,"h":5,"i":"12"},
65+
{"x":2,"y":10,"w":2,"h":5,"i":"13"},
66+
{"x":4,"y":8,"w":2,"h":4,"i":"14"},
67+
{"x":6,"y":8,"w":2,"h":4,"i":"15"},
68+
{"x":8,"y":10,"w":2,"h":5,"i":"16"},
69+
{"x":10,"y":4,"w":2,"h":2,"i":"17"},
70+
{"x":0,"y":9,"w":2,"h":3,"i":"18"},
71+
{"x":2,"y":6,"w":2,"h":2,"i":"19"}
72+
],
73+
draggable: true,
74+
resizable: true,
75+
bounded: true
76+
}
77+
}
78+
}
79+
</script>
80+
81+
<style scoped>
82+
.vue-grid-layout {
83+
background: #eee;
84+
}
85+
86+
.vue-grid-item:not(.vue-grid-placeholder) {
87+
background: #ccc;
88+
border: 1px solid black;
89+
}
90+
91+
.vue-grid-item .resizing {
92+
opacity: 0.9;
93+
}
94+
95+
.vue-grid-item .static {
96+
background: #cce;
97+
}
98+
99+
.vue-grid-item .text {
100+
font-size: 24px;
101+
text-align: center;
102+
position: absolute;
103+
top: 0;
104+
bottom: 0;
105+
left: 0;
106+
right: 0;
107+
margin: auto;
108+
height: 100%;
109+
width: 100%;
110+
}
111+
112+
.vue-grid-item .no-drag {
113+
height: 100%;
114+
width: 100%;
115+
}
116+
117+
.vue-grid-item .minMax {
118+
font-size: 12px;
119+
}
120+
121+
.vue-grid-item .add {
122+
cursor: pointer;
123+
}
124+
125+
.vue-draggable-handle {
126+
position: absolute;
127+
width: 20px;
128+
height: 20px;
129+
top: 0;
130+
left: 0;
131+
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
132+
background-position: bottom right;
133+
padding: 0 8px 8px 0;
134+
background-repeat: no-repeat;
135+
background-origin: content-box;
136+
box-sizing: border-box;
137+
cursor: pointer;
138+
}
139+
140+
.layoutJSON {
141+
background: #ddd;
142+
border: 1px solid black;
143+
margin-top: 10px;
144+
padding: 10px;
145+
}
146+
147+
.columns {
148+
-moz-columns: 120px;
149+
-webkit-columns: 120px;
150+
columns: 120px;
151+
}
152+
153+
</style>

website/docs/.vuepress/config.js

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ module.exports = {
6464
'08-responsive-predefined-layouts',
6565
'09-dynamic-add-remove',
6666
'10-drag-from-outside',
67+
'11-bounded',
6768
]
6869
}
6970
]
@@ -108,6 +109,7 @@ module.exports = {
108109
'08-responsive-predefined-layouts',
109110
'09-dynamic-add-remove',
110111
'10-drag-from-outside',
112+
'11-bounded',
111113
]
112114
}
113115
]

website/docs/.vuepress/public/examples/10-drag-from-outside.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ <h1>Vue Grid Layout Example 09 - Drag From Outside</h1>
2525
<br/>
2626
<a href="09-dynamic-add-remove.html">Previous example: Dynamic Add/Remove</a>
2727
<br/>
28-
<!--<a href="">Next example: </a>-->
28+
<a href="11-bounded.html">Next example: Bounded</a>
2929

3030
<br/>
3131
This demo shows what happens when an item is added from outside of the grid.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<title>Vue Grid Layout Example 11 - Bounded</title>
7+
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
8+
<link rel="stylesheet" href="app.css">
9+
<!--<link rel="stylesheet" href="../dist/vue-grid-layout.css">-->
10+
</head>
11+
12+
<body>
13+
<h1>Vue Grid Layout Example 11 - Bounded</h1>
14+
15+
<a href="https://github.com/jbaysolutions/vue-grid-layout">View project on Github</a>
16+
<br />
17+
<a href="10-drag-from-outside.html">Previous example: Drag From Outside</a>
18+
<br />
19+
<!-- <a href="09-dynamic-add-remove.html">Next example: Dynamic Add/Remove</a> -->
20+
21+
<div id="app" style="width: 100%;">
22+
<!--<pre>{{ $data | json }}</pre>-->
23+
<div>
24+
<div class="layoutJSON">
25+
Displayed as <code>[x, y, w, h]</code>:
26+
<div class="columns">
27+
<div class="layoutItem" v-for="item in layout">
28+
<b>{{item.i}}</b>: [{{item.x}}, {{item.y}}, {{item.w}}, {{item.h}}]
29+
</div>
30+
</div>
31+
</div>
32+
</div>
33+
<div id="content">
34+
<input type="checkbox" v-model="draggable" /> Draggable
35+
<input type="checkbox" v-model="resizable" /> Resizable
36+
<input type="checkbox" v-model="bounded" /> Bounded
37+
<br />
38+
<grid-layout :layout.sync="layout"
39+
:col-num="12"
40+
:row-height="30"
41+
:is-draggable="draggable"
42+
:is-resizable="resizable"
43+
:is-bounded="bounded"
44+
:vertical-compact="true"
45+
:use-css-transforms="true"
46+
>
47+
<grid-item v-for="item in layout"
48+
:x="item.x"
49+
:y="item.y"
50+
:w="item.w"
51+
:h="item.h"
52+
:i="item.i"
53+
>
54+
<span class="text">{{item.i}}</span>
55+
</grid-item>
56+
</grid-layout>
57+
</div>
58+
</div>
59+
<script src="vue.min.js"></script>
60+
<script src="../dist/vue-grid-layout.umd.min.js"></script>
61+
<script src="11-bounded.js"></script>
62+
</body>
63+
64+
</html>

0 commit comments

Comments
 (0)