Skip to content

Commit

Permalink
using drag and drop to create nested Grids - part1
Browse files Browse the repository at this point in the history
* partial fix for gridstack#1009
*  Create sub-grids on the fly, by dragging items completely over others (nest) vs partially (push) using new flag `GridStackOptions.subGrid.createDynamic=true`.
* ability to pause drag&drop collision until the user stops moving - see `DDDragOpt.pause`

TODO: need to make it work on already nested grids, remove nested on drag out, more testing...
  • Loading branch information
adumesny committed Sep 24, 2022
1 parent f09f796 commit ea9207d
Show file tree
Hide file tree
Showing 14 changed files with 421 additions and 140 deletions.
13 changes: 13 additions & 0 deletions demo/demo.css
Expand Up @@ -60,3 +60,16 @@ h1 {
.sidebar .grid-stack-item .grid-stack-item-content {
background: none;
}

/* make nested grid have slightly darker bg take almost all space (need some to tell them apart) so items inside can have similar to external size+margin */
.grid-stack > .grid-stack-item.grid-stack-sub-grid > .grid-stack-item-content {
background: rgba(0,0,0,0.1);
inset: 0 2px;
}
.grid-stack.grid-stack-nested {
background: none;
/* background-color: red; */
/* take entire space */
position: absolute;
inset: 0; /* TODO change top: if you have content in nested grid */
}
22 changes: 11 additions & 11 deletions demo/float.html
Expand Up @@ -23,20 +23,21 @@ <h1>Float grid demo</h1>
<script src="events.js"></script>
<script type="text/javascript">
let grid = GridStack.init({
float: true,
// float: false,
// disableResize: true, // TEST no resizing, but dragging
resizable: { handles: 'all'} // do all sides for testing
// resizable: { handles: 'all'} // do all sides for testing
// draggable: { pause: true },
subGrid: { createDynamic: true, column: 'auto' },
});
addEvents(grid);

let count = 0;
let items = [
{x: 1, y: 1}, //, locked:true, content:"locked"},
{x: 2, y: 2, w: 3},
{x: 4, y: 2},
{x: 3, y: 1, h: 2},
{x: 0, y: 6, w: 2, h: 2}
{x: 0, y: 0},
{x: 1, y: 0},
{x: 2, y: 0, w: 2},
];
let count = 0;
items.forEach(e => e.content = String(count++));

addNewWidget = function() {
let n = items[count] || {
Expand All @@ -45,16 +46,15 @@ <h1>Float grid demo</h1>
w: Math.round(1 + 3 * Math.random()),
h: Math.round(1 + 3 * Math.random())
};
n.content = n.content || String(count);
n.content = n.content || String(count++);
grid.addWidget(n);
count++;
};

toggleFloat = function() {
grid.float(! grid.getFloat());
document.querySelector('#float').innerHTML = 'float: ' + grid.getFloat();
};
addNewWidget();
grid.load(items);
</script>
</body>
</html>
1 change: 1 addition & 0 deletions demo/index.html
Expand Up @@ -16,6 +16,7 @@ <h1>Demos</h1>
<li><a href="knockout.html">Knockout.js</a></li>
<li><a href="mobile.html">Mobile touch (JQ)</a></li>
<li><a href="nested.html">Nested grids</a></li>
<li><a href="nested_constraint.html">Nested Constraint grids</a></li>
<li><a href="nested_advanced.html">Nested Advanced grids</a></li>
<li><a href="react-hooks.html">ReactJS (Hooks)</a></li>
<li><a href="react.html">ReactJS</a></li>
Expand Down
21 changes: 1 addition & 20 deletions demo/nested.html
Expand Up @@ -4,29 +4,10 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nested grids demo (ES6)</title>
<title>Nested grids demo</title>
<link rel="stylesheet" href="demo.css"/>
<link rel="stylesheet" href="../dist/gridstack-extra.min.css"/>
<script src="../dist/gridstack-all.js"></script>
<style type="text/css">
/* make nested grids have slightly darker bg */
.grid-stack.grid-stack-nested {
background: #e4e4c1;
}
/* make nested grid take almost all space (need some to tell them apart) so items inside can have similar to external size+margin */
.grid-stack > .grid-stack-item.grid-stack-nested > .grid-stack-item-content {
/* inset: 0 2px; not IE */
top: 0;
bottom: 0;
left: 2px;
right: 2px;
}
/* make nested grid take entire item content */
.grid-stack-item-content .grid-stack {
min-height: 100%;
min-width: 100%;
}
</style>
</head>
<body>
<div class="container-fluid">
Expand Down
38 changes: 15 additions & 23 deletions demo/nested_advanced.html
Expand Up @@ -4,26 +4,19 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nested grids demo (ES6)</title>
<title>Advance Nested grids demo</title>
<link rel="stylesheet" href="demo.css"/>
<link rel="stylesheet" href="../dist/gridstack-extra.min.css"/>
<script src="../dist/gridstack-all.js"></script>
<style type="text/css">
.grid-stack.grid-stack-nested {
background: rgba(255, 255, 255, 0.3);
}
.grid-stack-item.sub .grid-stack-item-content {
background: lightpink;
}
</style>
</head>
<body>
<div class="container-fluid">
<h1>Advanced Nested grids demo</h1>
<p>This example shows sub-grids only accepting pink items, while parent accept all.</p>
<p>Create sub-grids on the fly, by dragging items completely over others (nest) vs partially (push) using
the new v7 API <code>GridStackOptions.subGrid.createDynamic=true</code></p>
<p>This will use the new delay drag&drop option <code>DDDragOpt.pause</code> to tell the gesture difference</p>
<a class="btn btn-primary" onClick="addNested()" href="#">Add Widget</a>
<a class="btn btn-primary" onClick="addNewWidget('.sub1')" href="#">Add Widget Grid1</a>
<a class="btn btn-primary" onClick="addNewWidget('.sub2')" href="#">Add Widget Grid2</a>
<a class="btn btn-primary" onClick="addNewWidget('sub1_grid')" href="#">Add Widget Grid1</a>
<span>entire save/re-create:</span>
<a class="btn btn-primary" onClick="save()" href="#">Save</a>
<a class="btn btn-primary" onClick="destroy()" href="#">Destroy</a>
Expand All @@ -38,28 +31,27 @@ <h1>Advanced Nested grids demo</h1>
</div>

<script type="text/javascript">
let sub1 = [ {x:0, y:0}, {x:1, y:0}, {x:2, y:0}, {x:3, y:0}, {x:0, y:1}, {x:1, y:1}];
let sub2 = [ {x:0, y:0}, {x:0, y:1, w:2}];
let main = [{x:0, y:0}, {x:1, y:0}, {x:0, y:1}]
let sub1 = [{x:0, y:0}, {x:1, y:0}];
let count = 0;
[...sub1, ...sub2].forEach(d => d.content = String(count++));
[...main, ...sub1].forEach(d => d.content = String(count++));
let subOptions = {
cellHeight: 50,
cellHeight: 50, // should be 50 - top/bottom
column: 'auto', // size to match container. make sure to include gridstack-extra.min.css
itemClass: 'sub', // style sub items differently and use to prevent dragging in/out
acceptWidgets: '.grid-stack-item.sub', // only pink sub items can be inserted
margin: 2,
minRow: 1, // don't collapse when empty
acceptWidgets: true, // will accept .grid-stack-item by default
createDynamic: true, // NEW v7 api to create sub-grids on the fly
margin: 5,
};
let options = { // main grid options
cellHeight: 50,
margin: 5,
minRow: 2, // don't collapse when empty
acceptWidgets: true,
id: 'main',
subGrid: subOptions,
children: [
{y:0, content: 'regular item'},
{x:1, w:4, h:4, content: 'nested 1 - can drag items out', subGrid: {children: sub1, dragOut: true, class: 'sub1', ...subOptions}},
{x:5, w:4, h:4, content: 'nested 2 - constrained to parent (JQ only)', subGrid: {children: sub2, class: 'sub2', ...subOptions}},
...main,
{x:2, y:0, w:2, h:3, subGrid: {children: sub1, id:'sub1_grid', ...subOptions}/*,content: "<div>nested grid here</div>"*/},
]
};

Expand Down
106 changes: 106 additions & 0 deletions demo/nested_constraint.html
@@ -0,0 +1,106 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Constraint nested grids demo</title>
<link rel="stylesheet" href="demo.css"/>
<link rel="stylesheet" href="../dist/gridstack-extra.min.css"/>
<script src="../dist/gridstack-all.js"></script>
<style type="text/css">
.grid-stack-item.sub .grid-stack-item-content {
background: lightpink;
}
</style>
</head>
<body>
<div class="container-fluid">
<h1>Constraint Nested grids demo</h1>
<p>This example shows sub-grids only accepting pink items, while parent accept all.</p>
<a class="btn btn-primary" onClick="addNested()" href="#">Add Widget</a>
<a class="btn btn-primary" onClick="addNewWidget('.sub1')" href="#">Add Widget Grid1</a>
<a class="btn btn-primary" onClick="addNewWidget('.sub2')" href="#">Add Widget Grid2</a>
<span>entire save/re-create:</span>
<a class="btn btn-primary" onClick="save()" href="#">Save</a>
<a class="btn btn-primary" onClick="destroy()" href="#">Destroy</a>
<a class="btn btn-primary" onClick="load()" href="#">Create</a>
<span>partial save/load:</span>
<a class="btn btn-primary" onClick="save(true, false)" href="#">Save list</a>
<a class="btn btn-primary" onClick="save(false, false)" href="#">Save no content</a>
<a class="btn btn-primary" onClick="destroy(false)" href="#">Clear</a>
<a class="btn btn-primary" onClick="load(false)" href="#">Load</a>
<br><br>
<!-- grid will be added here -->
</div>

<script type="text/javascript">
let sub1 = [ {x:0, y:0}, {x:1, y:0}, {x:2, y:0}, {x:3, y:0}, {x:0, y:1}, {x:1, y:1}];
let sub2 = [ {x:0, y:0}, {x:0, y:1, w:2}];
let count = 0;
[...sub1, ...sub2].forEach(d => d.content = String(count++));
let subOptions = {
cellHeight: 50,
column: 'auto', // size to match container. make sure to include gridstack-extra.min.css
itemClass: 'sub', // style sub items differently and use to prevent dragging in/out
acceptWidgets: '.grid-stack-item.sub', // only pink sub items can be inserted
margin: 2,
minRow: 1, // don't collapse when empty
};
let options = { // main grid options
cellHeight: 50,
margin: 5,
minRow: 2, // don't collapse when empty
acceptWidgets: true,
id: 'main',
children: [
{y:0, content: 'regular item'},
{x:1, w:4, h:4, subGrid: {children: sub1, class: 'sub1', ...subOptions}},
{x:5, w:4, h:4, subGrid: {children: sub2, class: 'sub2', ...subOptions}},
]
};

// create and load it all from JSON above
let grid = GridStack.addGrid(document.querySelector('.container-fluid'), options);

addNested = function() {
grid.addWidget({x:0, y:100, content:"new item"});
}

addNewWidget = function(selector) {
let subGrid = document.querySelector(selector).gridstack;
let node = {
x: Math.round(6 * Math.random()),
y: Math.round(5 * Math.random()),
w: Math.round(1 + 1 * Math.random()),
h: Math.round(1 + 1 * Math.random()),
content: String(count++)
};
subGrid.addWidget(node);
return false;
};

save = function(content = true, full = true) {
options = grid.save(content, full);
console.log(options);
// console.log(JSON.stringify(options));
}
destroy = function(full = true) {
if (full) {
grid.destroy();
grid = undefined;
} else {
grid.removeAll();
}
}
load = function(full = true) {
if (full) {
grid = GridStack.addGrid(document.querySelector('.container-fluid'), options);
} else {
grid.load(options);
}
}

</script>
</body>
</html>
8 changes: 8 additions & 0 deletions doc/CHANGES.md
Expand Up @@ -5,6 +5,7 @@ Change log
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*

- [7-dev (TBD)](#7-dev-tbd)
- [6.0.2 (2022-09-23)](#602-2022-09-23)
- [6.0.1 (2022-08-27)](#601-2022-08-27)
- [6.0.0 (2022-08-21)](#600-2022-08-21)
Expand Down Expand Up @@ -71,6 +72,13 @@ Change log

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 7-dev (TBD)
* add [#1009](https://github.com/gridstack/gridstack.js/issues/1009) Create sub-grids on the fly,
by dragging items completely over others (nest) vs partially (push) using new flag `GridStackOptions.subGrid.createDynamic=true`.
Thank you [StephanP] for sponsoring it.<br>
See [advance Nested](https://github.com/gridstack/gridstack.js/blob/master/demo/nested_advanced.html)
* add - ability to pause drag&drop collision until the user stops moving - see `DDDragOpt.pause` (used for creating nested grids on the fly based on gesture).

## 6.0.2 (2022-09-23)
* fixed [#2034](https://github.com/gridstack/gridstack.js/issues/2034) `removeWidget()` breaking resize handle feedback
* fixed [#2043](https://github.com/gridstack/gridstack.js/issues/2043) when swapping shapes in maxRow grid, make sure we still check for 50% coverage
Expand Down
25 changes: 21 additions & 4 deletions src/dd-draggable.ts
Expand Up @@ -57,6 +57,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
protected helperContainment: HTMLElement;
/** @internal properties we change during dragging, and restore back */
protected static originStyleProp = ['transition', 'pointerEvents', 'position', 'left', 'top'];
/** @internal pause before we call the actual drag hit collision code */
protected dragTimeout: number;

constructor(el: HTMLElement, option: DDDraggableOpt = {}) {
super();
Expand Down Expand Up @@ -106,6 +108,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
}

public destroy(): void {
if (this.dragTimeout) window.clearTimeout(this.dragTimeout);
delete this.dragTimeout;
if (this.dragging) this._mouseUp(this.mouseDownEvent);
this.disable(true);
delete this.el;
Expand Down Expand Up @@ -148,18 +152,31 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
return true;
}

/** @internal method to call actual drag event */
protected _callDrag(e: DragEvent) {
if (!this.dragging) return;
const ev = Utils.initEvent<DragEvent>(e, { target: this.el, type: 'drag' });
if (this.option.drag) {
this.option.drag(ev, this.ui());
}
this.triggerEvent('drag', ev);
}

/** @internal called when the main page (after successful mousedown) receives a move event to drag the item around the screen */
protected _mouseMove(e: DragEvent): boolean {
// console.log(`${count++} move ${e.x},${e.y}`)
let s = this.mouseDownEvent;

if (this.dragging) {
this._dragFollow(e);
const ev = Utils.initEvent<DragEvent>(e, { target: this.el, type: 'drag' });
if (this.option.drag) {
this.option.drag(ev, this.ui());
// delay actual grid handling drag until we pause for a while if set
if (DDManager.pauseDrag) {
const pause = Number.isInteger(DDManager.pauseDrag) ? DDManager.pauseDrag as number : 100;
if (this.dragTimeout) window.clearTimeout(this.dragTimeout);
this.dragTimeout = window.setTimeout(() => this._callDrag(e), pause);
} else {
this._callDrag(e);
}
this.triggerEvent('drag', ev);
} else if (Math.abs(e.x - s.x) + Math.abs(e.y - s.y) > 3) {
/**
* don't start unless we've moved at least 3 pixels
Expand Down

0 comments on commit ea9207d

Please sign in to comment.