Skip to content

Commit 3b64ec7

Browse files
committed
angular support for nested Grid components
* modified Ng exmaple to show a component that contains a sub-grid (in addition to being a sub-grid example) * fixed GS code to support sub-grids that are not direct children on a grid-item-content but further down this will depend on upcoming V11
1 parent bcdfb1a commit 3b64ec7

File tree

9 files changed

+97
-56
lines changed

9 files changed

+97
-56
lines changed

angular/projects/demo/src/app/app.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
<button (click)="saveGrid()">Save</button>
6565
<button (click)="clearGrid()">Clear</button>
6666
<button (click)="loadGrid()">Load</button>
67+
<!-- add .grid-stack-item for acceptWidgets:true -->
68+
<div class="sidebar-item grid-stack-item">Drag nested</div>
69+
<div class="sidebar-item grid-stack-item">Comp N nested</div>
70+
6771
<!-- TODO: addGrid() in code for testing instead ? -->
6872
<gridstack [options]="nestedGridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
6973
<div empty-content>Add items here or reload the grid</div>

angular/projects/demo/src/app/app.component.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { AngularSimpleComponent } from './simple';
44
import { AngularNgForTestComponent } from './ngFor';
55
import { AngularNgForCmdTestComponent } from './ngFor_cmd';
66

7-
// NOTE: local testing of file
7+
// TEST: local testing of file
88
// import { GridstackComponent, NgGridStackOptions, NgGridStackWidget, elementCB, gsCreateNgComponents, nodesCB } from './gridstack.component';
99
import { GridstackComponent, NgGridStackOptions, NgGridStackWidget, elementCB, gsCreateNgComponents, nodesCB } from 'gridstack/dist/angular';
1010

@@ -46,12 +46,6 @@ export class AppComponent implements OnInit {
4646
children: this.sub0,
4747
}
4848

49-
// sidebar content to create storing the Widget description to be used on drop
50-
public sidebarContent: NgGridStackWidget[] = [
51-
{selector: 'app-a'},
52-
{selector: 'app-b', w:2, maxW: 3},
53-
];
54-
5549
// nested grid options
5650
private subOptions: GridStackOptions = {
5751
cellHeight: 50, // should be 50 - top/bottom
@@ -61,17 +55,20 @@ export class AppComponent implements OnInit {
6155
};
6256
public sub1: NgGridStackWidget[] = [ {x:0, y:0, selector:'app-a'}, {x:1, y:0, selector:'app-b'}, {x:2, y:0, selector:'app-c'}, {x:3, y:0}, {x:0, y:1}, {x:1, y:1}];
6357
public sub2: NgGridStackWidget[] = [ {x:0, y:0}, {x:0, y:1, w:2}];
58+
public sub3: NgGridStackWidget = { selector: 'app-n', w:2, h:2, subGridOpts: { children: [{selector: 'app-a'}, {selector: 'app-b', y:0, x:1}]}};
6459
private subChildren: NgGridStackWidget[] = [
6560
{x:0, y:0, content: 'regular item'},
66-
{x:1, y:0, w:4, h:4, subGridOpts: {children: this.sub1, class: 'sub1', ...this.subOptions}},
67-
{x:5, y:0, w:3, h:4, subGridOpts: {children: this.sub2, class: 'sub2', ...this.subOptions}},
61+
{x:1, y:0, w:4, h:4, subGridOpts: {children: this.sub1}},
62+
// {x:5, y:0, w:3, h:4, subGridOpts: {children: this.sub2}},
63+
this.sub3,
6864
]
6965
public nestedGridOptions: NgGridStackOptions = { // main grid options
7066
cellHeight: 50,
7167
margin: 5,
7268
minRow: 2, // don't collapse when empty
7369
acceptWidgets: true,
74-
children: this.subChildren
70+
subGridOpts: this.subOptions, // all sub grids will default to those
71+
children: this.subChildren,
7572
};
7673
public twoGridOpt1: NgGridStackOptions = {
7774
column: 6,
@@ -91,11 +88,20 @@ export class AppComponent implements OnInit {
9188
public twoGridOpt2: NgGridStackOptions = { ...this.twoGridOpt1, float: false }
9289
private serializedData?: NgGridStackOptions;
9390

91+
// sidebar content to create storing the Widget description to be used on drop
92+
public sidebarContent6: NgGridStackWidget[] = [
93+
{ w:2, h:2, subGridOpts: { children: [{content: 'nest 1'}, {content: 'nest 2'}]}},
94+
this.sub3,
95+
];
96+
public sidebarContent7: NgGridStackWidget[] = [
97+
{selector: 'app-a'},
98+
{selector: 'app-b', w:2, maxW: 3},
99+
];
100+
94101
constructor() {
95102
// give them content and unique id to make sure we track them during changes below...
96103
[...this.items, ...this.subChildren, ...this.sub1, ...this.sub2, ...this.sub0].forEach((w: NgGridStackWidget) => {
97-
if (!w.selector && !w.content && !w.subGridOpts) w.content = `item ${ids}`;
98-
w.id = String(ids++);
104+
if (!w.selector && !w.content && !w.subGridOpts) w.content = `item ${ids++}`;
99105
});
100106
}
101107

@@ -132,9 +138,11 @@ export class AppComponent implements OnInit {
132138
case 3: data = this.gridComp?.grid?.save(true, true); break;
133139
case 4: data = this.items; break;
134140
case 5: data = this.gridOptionsFull; break;
135-
case 6: data = this.nestedGridOptions; break;
141+
case 6: data = this.nestedGridOptions;
142+
GridStack.setupDragIn('.sidebar-item', undefined, this.sidebarContent6);
143+
break;
136144
case 7: data = this.twoGridOpt1;
137-
GridStack.setupDragIn('.sidebar>.grid-stack-item', undefined, this.sidebarContent);
145+
GridStack.setupDragIn('.sidebar-item', undefined, this.sidebarContent7);
138146
break;
139147
}
140148
if (this.origTextEl) this.origTextEl.nativeElement.value = JSON.stringify(data, null, ' ');

angular/projects/demo/src/app/app.module.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { AppComponent } from './app.component';
55
import { AngularNgForTestComponent } from './ngFor';
66
import { AngularNgForCmdTestComponent } from './ngFor_cmd';
77
import { AngularSimpleComponent } from './simple';
8-
import { AComponent, BComponent, CComponent } from './dummy.component';
8+
import { AComponent, BComponent, CComponent, NComponent } from './dummy.component';
99

10-
// local testing
10+
// TEST local testing
1111
// import { GridstackModule } from './gridstack.module';
1212
// import { GridstackComponent } from './gridstack.component';
1313
import { GridstackModule, GridstackComponent } from 'gridstack/dist/angular';
@@ -25,6 +25,7 @@ import { GridstackModule, GridstackComponent } from 'gridstack/dist/angular';
2525
AComponent,
2626
BComponent,
2727
CComponent,
28+
NComponent,
2829
],
2930
exports: [
3031
],
@@ -34,6 +35,6 @@ import { GridstackModule, GridstackComponent } from 'gridstack/dist/angular';
3435
export class AppModule {
3536
constructor() {
3637
// register all our dynamic components created in the grid
37-
GridstackComponent.addComponentToSelectorType([AComponent, BComponent, CComponent]);
38+
GridstackComponent.addComponentToSelectorType([AComponent, BComponent, CComponent, NComponent]);
3839
}
3940
}

angular/projects/demo/src/app/dummy.component.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
// dummy testing component that will be grid items content
77

8-
import { Component, OnDestroy, Input } from '@angular/core';
8+
import { Component, OnDestroy, Input, ViewChild, ViewContainerRef } from '@angular/core';
99

10-
// local testing
10+
// TEST local testing
1111
// import { BaseWidget } from './base-widget';
1212
// import { NgCompInputs } from './gridstack.component';
1313
import { BaseWidget, NgCompInputs } from 'gridstack/dist/angular';
@@ -37,3 +37,22 @@ export class BComponent extends BaseWidget implements OnDestroy {
3737
export class CComponent extends BaseWidget implements OnDestroy {
3838
ngOnDestroy() { console.log('Comp C destroyed'); }
3939
}
40+
41+
/** Component that host a sub-grid as a child with controls above/below it. */
42+
@Component({
43+
selector: 'app-n',
44+
template: `
45+
<div>Comp N</div>
46+
<ng-template #container></ng-template>
47+
`,
48+
/** make the subgrid take entire remaining space even when empty (so you can drag back inside without forcing 1 row) */
49+
styles: [`
50+
:host { height: 100%; display: flex; flex-direction: column; }
51+
::ng-deep .grid-stack.grid-stack-nested { flex: 1; }
52+
`],
53+
})
54+
export class NComponent extends BaseWidget implements OnDestroy {
55+
/** this is where the dynamic nested grid will be hosted. gsCreateNgComponents() looks for 'container' like GridstackItemComponent */
56+
@ViewChild('container', { read: ViewContainerRef, static: true}) public container?: ViewContainerRef;
57+
ngOnDestroy() { console.log('Comp N destroyed'); }
58+
}

angular/projects/demo/src/styles.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ h1 {
6363
}
6464
.grid-stack.grid-stack-nested {
6565
background: none;
66-
/* background-color: red; */
66+
}
67+
.grid-stack-item-content>.grid-stack.grid-stack-nested {
6768
/* take entire space */
6869
position: absolute;
6970
inset: 0; /* TODO change top: if you have content in nested grid */

angular/projects/lib/src/lib/base-widget.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { NgCompInputs, NgGridStackWidget } from './gridstack.component';
1212

1313
@Injectable()
1414
export abstract class BaseWidget {
15+
16+
/** variable that holds the complete definition of this widgets (with selector,x,y,w,h) */
17+
public widgetItem?: NgGridStackWidget;
18+
1519
/**
1620
* REDEFINE to return an object representing the data needed to re-create yourself, other than `selector` already handled.
1721
* This should map directly to the @Input() fields of this objects on create, so a simple apply can be used on read

angular/projects/lib/src/lib/gridstack.component.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type NgCompInputs = {[key: string]: any};
2323
export interface NgGridStackWidget extends GridStackWidget {
2424
selector?: string; // component type to create as content
2525
input?: NgCompInputs; // serialized data for the component input fields
26+
subGridOpts?: NgGridStackOptions; // nested grid options
2627
}
2728
export interface NgGridStackNode extends GridStackNode {
2829
selector?: string; // component type to create as content
@@ -214,13 +215,17 @@ export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w:
214215
//
215216
if (!host) return;
216217
if (isGrid) {
217-
const container = (host.parentElement as GridItemCompHTMLElement)?._gridItemComp?.container;
218218
// TODO: figure out how to create ng component inside regular Div. need to access app injectors...
219219
// if (!container) {
220220
// const hostElement: Element = host;
221221
// const environmentInjector: EnvironmentInjector;
222222
// grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
223223
// }
224+
225+
const gridItemCom = (host.parentElement as GridItemCompHTMLElement)?._gridItemComp;
226+
if (!gridItemCom) return;
227+
// check if gridItem has a child component with 'container' exposed to create under..
228+
const container = (gridItemCom.childWidget as any)?.container || gridItemCom.container;
224229
const gridRef = container?.createComponent(GridstackComponent);
225230
const grid = gridRef?.instance;
226231
if (!grid) return;
@@ -234,17 +239,16 @@ export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w:
234239
if (!gridItem) return;
235240
gridItem.ref = gridItemRef
236241

237-
// IFF we're not a subGrid, define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
238-
if (!w.subGridOpts) {
239-
const selector = (w as NgGridStackWidget).selector;
240-
const type = selector ? GridstackComponent.selectorToType[selector] : undefined;
241-
if (type) {
242-
const childWidget = gridItem.container?.createComponent(type)?.instance as BaseWidget;
243-
// if proper BaseWidget subclass, save it and load additional data
244-
if (childWidget && typeof childWidget.serialize === 'function' && typeof childWidget.deserialize === 'function') {
245-
gridItem.childWidget = childWidget;
246-
childWidget.deserialize(w);
247-
}
242+
// define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
243+
const selector = (w as NgGridStackWidget).selector;
244+
const type = selector ? GridstackComponent.selectorToType[selector] : undefined;
245+
if (type) {
246+
const childWidget = gridItem.container?.createComponent(type)?.instance as BaseWidget;
247+
// if proper BaseWidget subclass, save it and load additional data
248+
if (childWidget && typeof childWidget.serialize === 'function' && typeof childWidget.deserialize === 'function') {
249+
gridItem.childWidget = childWidget;
250+
childWidget.widgetItem = w;
251+
childWidget.deserialize(w);
248252
}
249253
}
250254

src/dd-gridstack.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export class DDGridStack {
7979
dEl.setupDraggable({
8080
...grid.opts.draggable,
8181
...{
82-
// containment: (grid.parentGridItem && grid.opts.dragOut === false) ? grid.el.parentElement : (grid.opts.draggable.containment || null),
82+
// containment: (grid.parentGridNode && grid.opts.dragOut === false) ? grid.el.parentElement : (grid.opts.draggable.containment || null),
8383
start: opts.start,
8484
stop: opts.stop,
8585
drag: opts.drag

src/gridstack.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export class GridStack {
217217
public engine: GridStackEngine;
218218

219219
/** point to a parent grid item if we're nested (inside a grid-item in between 2 Grids) */
220-
public parentGridItem?: GridStackNode;
220+
public parentGridNode?: GridStackNode;
221221

222222
protected static engineClass: typeof GridStackEngine;
223223
protected resizeObserver: ResizeObserver;
@@ -357,13 +357,13 @@ export class GridStack {
357357
}
358358

359359
// check if we're been nested, and if so update our style and keep pointer around (used during save)
360-
const grandParent: GridItemHTMLElement = this.el.parentElement?.parentElement;
361-
let parentGridItem = grandParent?.classList.contains(gridDefaults.itemClass) ? grandParent.gridstackNode : undefined;
362-
if (parentGridItem) {
363-
parentGridItem.subGrid = this;
364-
this.parentGridItem = parentGridItem;
360+
const parentGridItem: GridItemHTMLElement = this.el.closest('.' + gridDefaults.itemClass);
361+
let parentNode = parentGridItem?.gridstackNode;
362+
if (parentNode) {
363+
parentNode.subGrid = this;
364+
this.parentGridNode = parentNode;
365365
this.el.classList.add('grid-stack-nested');
366-
parentGridItem.el.classList.add('grid-stack-sub-grid');
366+
parentNode.el.classList.add('grid-stack-sub-grid');
367367
}
368368

369369
this._isAutoCellHeight = (opts.cellHeight === 'auto');
@@ -511,7 +511,7 @@ export class GridStack {
511511
let grid: GridStack = this;
512512
while (grid && !subGridTemplate) {
513513
subGridTemplate = grid.opts?.subGridOpts;
514-
grid = grid.parentGridItem?.grid;
514+
grid = grid.parentGridNode?.grid;
515515
}
516516
//... and set the create options
517517
ops = Utils.cloneDeep({ ...(subGridTemplate || {}), children: undefined, ...(ops || node.subGridOpts || {}) });
@@ -584,20 +584,20 @@ export class GridStack {
584584
* to the original grid-item. Also called to remove empty sub-grids when last item is dragged out (since re-creating is simple)
585585
*/
586586
public removeAsSubGrid(nodeThatRemoved?: GridStackNode): void {
587-
let pGrid = this.parentGridItem?.grid;
587+
let pGrid = this.parentGridNode?.grid;
588588
if (!pGrid) return;
589589

590590
pGrid.batchUpdate();
591-
pGrid.removeWidget(this.parentGridItem.el, true, true);
591+
pGrid.removeWidget(this.parentGridNode.el, true, true);
592592
this.engine.nodes.forEach(n => {
593593
// migrate any children over and offsetting by our location
594-
n.x += this.parentGridItem.x;
595-
n.y += this.parentGridItem.y;
594+
n.x += this.parentGridNode.x;
595+
n.y += this.parentGridNode.y;
596596
pGrid.makeWidget(n.el, n);
597597
});
598598
pGrid.batchUpdate(false);
599-
if (this.parentGridItem) delete this.parentGridItem.subGrid;
600-
delete this.parentGridItem;
599+
if (this.parentGridNode) delete this.parentGridNode.subGrid;
600+
delete this.parentGridNode;
601601

602602
// create an artificial event for the original grid now that this one is gone (got a leave, but won't get enter)
603603
if (nodeThatRemoved) {
@@ -979,8 +979,8 @@ export class GridStack {
979979
this.el.parentNode.removeChild(this.el);
980980
}
981981
this._removeStylesheet();
982-
if (this.parentGridItem) delete this.parentGridItem.subGrid;
983-
delete this.parentGridItem;
982+
if (this.parentGridNode) delete this.parentGridNode.subGrid;
983+
delete this.parentGridNode;
984984
delete this.opts;
985985
delete this._placeholder;
986986
delete this.engine;
@@ -1623,7 +1623,7 @@ export class GridStack {
16231623
/** @internal */
16241624
protected _updateContainerHeight(): GridStack {
16251625
if (!this.engine || this.engine.batchMode) return this;
1626-
const parent = this.parentGridItem;
1626+
const parent = this.parentGridNode;
16271627
let row = this.getRow() + this._extraDragRow; // this checks for minRow already
16281628
const cellHeight = this.opts.cellHeight as number;
16291629
const unit = this.opts.cellHeightUnit;
@@ -1776,9 +1776,9 @@ export class GridStack {
17761776

17771777
// see if we're nested and take our column count from our parent....
17781778
let columnChanged = false;
1779-
if (this._autoColumn && this.parentGridItem) {
1780-
if (this.opts.column !== this.parentGridItem.w) {
1781-
this.column(this.parentGridItem.w, 'none');
1779+
if (this._autoColumn && this.parentGridNode) {
1780+
if (this.opts.column !== this.parentGridNode.w) {
1781+
this.column(this.parentGridNode.w, 'none');
17821782
columnChanged = true;
17831783
}
17841784
} else {
@@ -1828,7 +1828,7 @@ export class GridStack {
18281828
protected _updateResizeEvent(forceRemove = false): GridStack {
18291829
// only add event if we're not nested (parent will call us) and we're auto sizing cells or supporting dynamic column (i.e. doing work)
18301830
// or supporting new sizeToContent option.
1831-
const trackSize = !this.parentGridItem && (this._isAutoCellHeight || this.opts.sizeToContent || this.opts.columnOpts
1831+
const trackSize = !this.parentGridNode && (this._isAutoCellHeight || this.opts.sizeToContent || this.opts.columnOpts
18321832
|| this.engine.nodes.find(n => n.sizeToContent));
18331833

18341834
if (!forceRemove && trackSize && !this.resizeObserver) {
@@ -2266,7 +2266,7 @@ export class GridStack {
22662266
oGrid.engine.removedNodes.push(origNode);
22672267
oGrid._triggerRemoveEvent()._triggerChangeEvent();
22682268
// if it's an empty sub-grid that got auto-created, nuke it
2269-
if (oGrid.parentGridItem && !oGrid.engine.nodes.length && oGrid.opts.subGridDynamic) {
2269+
if (oGrid.parentGridNode && !oGrid.engine.nodes.length && oGrid.opts.subGridDynamic) {
22702270
oGrid.removeAsSubGrid();
22712271
}
22722272
}
@@ -2303,7 +2303,7 @@ export class GridStack {
23032303
// resizeToContent is skipped in _prepareElement() until node is visible (clientHeight=0) so call it now
23042304
this.resizeToContentCheck(false, node);
23052305
if (subGrid) {
2306-
subGrid.parentGridItem = node;
2306+
subGrid.parentGridNode = node;
23072307
if (!subGrid.opts.styleInHead) subGrid._updateStyles(true); // re-create sub-grid styles now that we've moved
23082308
}
23092309
this._updateContainerHeight();

0 commit comments

Comments
 (0)