Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6cb7213

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 * also hange to protected for easier subclassing Note: this will depend on upcoming V11
1 parent bcdfb1a commit 6cb7213

File tree

11 files changed

+118
-70
lines changed

11 files changed

+118
-70
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: 8 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
@@ -23,6 +27,10 @@ import { NgCompInputs, NgGridStackWidget } from './gridstack.component';
2327
* things that are not mapped directly into @Input() fields for example.
2428
*/
2529
public deserialize(w: NgGridStackWidget) {
30+
// save full description for meta data
31+
this.widgetItem = w;
32+
if (!w) return;
33+
2634
if (w.input) Object.assign(this, w.input);
2735
}
2836
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class GridstackItemComponent implements OnDestroy {
5757
return this.el.gridstackNode || this._options || {el: this.el};
5858
}
5959

60-
private _options?: GridStackNode;
60+
protected _options?: GridStackNode;
6161

6262
/** return the native element that contains grid specific fields as well */
6363
public get el(): GridItemCompHTMLElement { return this.elementRef.nativeElement; }
@@ -67,7 +67,7 @@ export class GridstackItemComponent implements OnDestroy {
6767
delete this._options;
6868
}
6969

70-
constructor(private readonly elementRef: ElementRef<GridItemHTMLElement>) {
70+
constructor(protected readonly elementRef: ElementRef<GridItemCompHTMLElement>) {
7171
this.el._gridItemComp = this;
7272
}
7373

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

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ export type NgCompInputs = {[key: string]: any};
2121

2222
/** extends to store Ng Component selector, instead/inAddition to content */
2323
export interface NgGridStackWidget extends GridStackWidget {
24-
selector?: string; // component type to create as content
25-
input?: NgCompInputs; // serialized data for the component input fields
24+
/** Angular tag selector for this component to create at runtime */
25+
selector?: string;
26+
/** serialized data for the component input fields */
27+
input?: NgCompInputs;
28+
/** nested grid options */
29+
subGridOpts?: NgGridStackOptions;
2630
}
2731
export interface NgGridStackNode extends GridStackNode {
2832
selector?: string; // component type to create as content
@@ -116,15 +120,15 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
116120
return reflectComponentType(type)!.selector;
117121
}
118122

119-
private _options?: GridStackOptions;
120-
private _grid?: GridStack;
121-
private _sub: Subscription | undefined;
122-
private loaded?: boolean;
123+
protected _options?: GridStackOptions;
124+
protected _grid?: GridStack;
125+
protected _sub: Subscription | undefined;
126+
protected loaded?: boolean;
123127

124128
constructor(
125-
// private readonly zone: NgZone,
126-
// private readonly cd: ChangeDetectorRef,
127-
private readonly elementRef: ElementRef<GridCompHTMLElement>,
129+
// protected readonly zone: NgZone,
130+
// protected readonly cd: ChangeDetectorRef,
131+
protected readonly elementRef: ElementRef<GridCompHTMLElement>,
128132
) {
129133
this.el._gridComp = this;
130134
}
@@ -181,7 +185,7 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
181185
}
182186

183187
/** get all known events as easy to use Outputs for convenience */
184-
private hookEvents(grid?: GridStack) {
188+
protected hookEvents(grid?: GridStack) {
185189
if (!grid) return;
186190
grid
187191
.on('added', (event: Event, nodes: GridStackNode[]) => { this.checkEmpty(); this.addedCB.emit({event, nodes}); })
@@ -198,7 +202,7 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
198202
.on('resizestop', (event: Event, el: GridItemHTMLElement) => this.resizeStopCB.emit({event, el}))
199203
}
200204

201-
private unhookEvents(grid?: GridStack) {
205+
protected unhookEvents(grid?: GridStack) {
202206
if (!grid) return;
203207
grid.off('added change disable drag dragstart dragstop dropped enable removed resize resizestart resizestop');
204208
}
@@ -214,13 +218,17 @@ export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w:
214218
//
215219
if (!host) return;
216220
if (isGrid) {
217-
const container = (host.parentElement as GridItemCompHTMLElement)?._gridItemComp?.container;
218221
// TODO: figure out how to create ng component inside regular Div. need to access app injectors...
219222
// if (!container) {
220223
// const hostElement: Element = host;
221224
// const environmentInjector: EnvironmentInjector;
222225
// grid = createComponent(GridstackComponent, {environmentInjector, hostElement})?.instance;
223226
// }
227+
228+
const gridItemCom = (host.parentElement as GridItemCompHTMLElement)?._gridItemComp;
229+
if (!gridItemCom) return;
230+
// check if gridItem has a child component with 'container' exposed to create under..
231+
const container = (gridItemCom.childWidget as any)?.container || gridItemCom.container;
224232
const gridRef = container?.createComponent(GridstackComponent);
225233
const grid = gridRef?.instance;
226234
if (!grid) return;
@@ -234,17 +242,15 @@ export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w:
234242
if (!gridItem) return;
235243
gridItem.ref = gridItemRef
236244

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-
}
245+
// define what type of component to create as child, OR you can do it GridstackItemComponent template, but this is more generic
246+
const selector = (w as NgGridStackWidget).selector;
247+
const type = selector ? GridstackComponent.selectorToType[selector] : undefined;
248+
if (type) {
249+
const childWidget = gridItem.container?.createComponent(type)?.instance as BaseWidget;
250+
// if proper BaseWidget subclass, save it and load additional data
251+
if (childWidget && typeof childWidget.serialize === 'function' && typeof childWidget.deserialize === 'function') {
252+
gridItem.childWidget = childWidget;
253+
childWidget.deserialize(w);
248254
}
249255
}
250256

@@ -271,7 +277,7 @@ export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w:
271277

272278
/**
273279
* called for each item in the grid - check if additional information needs to be saved.
274-
* Note: since this is options minus gridstack private members using Utils.removeInternalForSave(),
280+
* Note: since this is options minus gridstack protected members using Utils.removeInternalForSave(),
275281
* this typically doesn't need to do anything. However your custom Component @Input() are now supported
276282
* using BaseWidget.serialize()
277283
*/

doc/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ Change log
118118
* fix: [#2736](https://github.com/gridstack/gridstack.js/bug/2736) safe practices around GridStackWidget.content no longer setting innerHTML
119119
* fix: [#2231](https://github.com/gridstack/gridstack.js/bug/2231),[#1840](https://github.com/gridstack/gridstack.js/bug/1840),[#2354](https://github.com/gridstack/gridstack.js/bug/2354)
120120
big overall to how we do sidepanel drag&drop helper. see release notes.
121+
* feat: [#2818](https://github.com/gridstack/gridstack.js/pull/2818) support for Angular Component hosting true sub-grids (that size according to parent) without requring them to be only child of grid-item-content.
121122

122123
## 10.3.1 (2024-07-21)
123124
* fix: [#2734](https://github.com/gridstack/gridstack.js/bug/2734) rotate() JS error

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

0 commit comments

Comments
 (0)