Skip to content

Commit 35b27f4

Browse files
committed
global GridStack.addRemoveCB
* add: `GridStack.saveCB` global callback for each item during save so app can insert any custom data before serializing it. `save()` can now be passed optional callback * move: `GridStack.addRemoveCB` is now global instead of grid option. `load()` can still be passed different optional callback * fixed addGrid() to handle passing an existing initialized grid already and revamped Angular demo
1 parent b52341f commit 35b27f4

12 files changed

+239
-8477
lines changed

demo/angular/package-lock.json

Lines changed: 0 additions & 8350 deletions
This file was deleted.

demo/angular/src/app/app.component.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ button.active {
55
color: #fff;
66
background-color: #007bff;
77
}
8+
9+
.text-container {
10+
display: flex;
11+
}
Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,76 @@
11

22
<div class="button-container">
33
<p class="pick-info">Pick a demo to load:</p>
4-
<button (click)="show=0" [class.active]="show===0">Simple</button>
5-
<button (click)="show=1" [class.active]="show===1">ngFor case</button>
6-
<button (click)="show=2" [class.active]="show===2">ngFor custom command</button>
7-
<button (click)="show=3" [class.active]="show===3">Component HTML template</button>
8-
<button (click)="show=4" [class.active]="show===4">Component ngFor</button>
9-
<button (click)="show=5" [class.active]="show===5">Component Dynamic</button>
10-
<button (click)="show=6" [class.active]="show===6">Nested Grid</button>
4+
<button (click)="onShow(0)" [class.active]="show===0">Simple</button>
5+
<button (click)="onShow(1)" [class.active]="show===1">ngFor case</button>
6+
<button (click)="onShow(2)" [class.active]="show===2">ngFor custom command</button>
7+
<button (click)="onShow(3)" [class.active]="show===3">Component HTML template</button>
8+
<button (click)="onShow(4)" [class.active]="show===4">Component ngFor</button>
9+
<button (click)="onShow(5)" [class.active]="show===5">Component Dynamic</button>
10+
<button (click)="onShow(6)" [class.active]="show===6">Nested Grid</button>
1111
</div>
1212

1313
<div class="test-container">
1414
<angular-simple-test *ngIf="show===0"></angular-simple-test>
1515
<angular-ng-for-test *ngIf="show===1"></angular-ng-for-test>
1616
<angular-ng-for-cmd-test *ngIf="show===2"></angular-ng-for-cmd-test>
1717

18-
<div *ngIf="show===3" >
18+
<div *ngIf="show===3">
1919
<p><b>COMPONENT template</b>: using DOM template to use components statically</p>
20-
<button (click)="add(gridComp)">add item</button>
21-
<button (click)="delete(gridComp)">remove item</button>
22-
<button (click)="modify(gridComp)">modify item</button>
23-
<button (click)="newLayout(gridComp)">new layout</button>
24-
<gridstack #gridComp [options]="gridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
20+
<button (click)="add()">add item</button>
21+
<button (click)="delete()">remove item</button>
22+
<button (click)="modify()">modify item</button>
23+
<button (click)="newLayout()">new layout</button>
24+
<gridstack [options]="gridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
2525
<gridstack-item gs-x="1" gs-y="0">item 1</gridstack-item>
2626
<gridstack-item gs-x="3" gs-y="0" gs-w="2">item 2 wide</gridstack-item>
2727
</gridstack>
2828
</div>
2929

30-
<div *ngIf="show===4" >
30+
<div *ngIf="show===4">
3131
<p><b>COMPONENT ngFor</b>: Most complete example that uses Component wrapper for grid and gridItem</p>
3232
<button (click)="addNgFor()">add item</button>
3333
<button (click)="deleteNgFor()">remove item</button>
34-
<button (click)="modifyNgFor(gridComp)">modify item</button>
34+
<button (click)="modifyNgFor()">modify item</button>
3535
<button (click)="newLayoutNgFor()">new layout</button>
36-
<gridstack #gridComp [options]="gridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
36+
<gridstack [options]="gridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
3737
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
3838
{{n.content}}
3939
</gridstack-item>
4040
</gridstack>
4141
</div>
4242

43-
<div *ngIf="show===5" >
43+
<div *ngIf="show===5">
4444
<p><b>COMPONENT dynamic</b>: Best example that uses Component wrapper and dynamic grid creation (drag between grids, from toolbar, etc...)</p>
45-
<button (click)="add(gridComp)">add item</button>
46-
<button (click)="delete(gridComp)">remove item</button>
47-
<button (click)="modify(gridComp)">modify item</button>
48-
<button (click)="newLayout(gridComp)">new layout</button>
49-
<gridstack #gridComp [options]="gridOptionsFull" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
45+
<button (click)="add()">add item</button>
46+
<button (click)="delete()">remove item</button>
47+
<button (click)="modify()">modify item</button>
48+
<button (click)="newLayout()">new layout</button>
49+
<gridstack [options]="gridOptionsFull" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
5050
</gridstack>
5151
</div>
5252

5353

54-
<div *ngIf="show===6" >
54+
<div *ngIf="show===6">
5555
<p><b>Nested Grid</b>: shows nested component grids, like nested.html demo but with Ng Components</p>
56-
<button (click)="add(gridComp)">add item</button>
57-
<button (click)="delete(gridComp)">remove item</button>
58-
<button (click)="modify(gridComp)">modify item</button>
59-
<button (click)="newLayout(gridComp)">new layout</button>
60-
<gridstack #gridComp [options]="nestedGridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
56+
<button (click)="add()">add item</button>
57+
<button (click)="delete()">remove item</button>
58+
<button (click)="modify()">modify item</button>
59+
<button (click)="newLayout()">new layout</button>
60+
<button (click)="clearGrid()">Clear</button>
61+
<button (click)="saveGrid()">Save</button>
62+
<button (click)="loadGrid()">Load</button>
63+
<!-- TODO: addGrid() in code for testing instead ? -->
64+
<gridstack [options]="nestedGridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
65+
<div empty-content>Add items here or reload the grid</div>
6166
</gridstack>
6267
</div>
6368

69+
<div class="grid-container"></div>
70+
71+
<div class="text-container">
72+
<textarea #origTextArea cols="50" rows="50" readonly="readonly"></textarea>
73+
<textarea #textArea cols="50" rows="50" readonly="readonly"></textarea>
74+
</div>
75+
6476
</div>

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

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Component } from '@angular/core';
2-
import { GridStackOptions, GridStackWidget } from 'gridstack';
1+
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
2+
import { GridStack, GridStackOptions, GridStackWidget } from 'gridstack';
33
import { GridstackComponent, NgGridStackWidget, elementCB, nodesCB } from './gridstack.component';
44

55
// unique ids sets for each item for correct ngFor updating
@@ -9,9 +9,14 @@ let ids = 1;
99
templateUrl: './app.component.html',
1010
styleUrls: ['./app.component.css']
1111
})
12-
export class AppComponent {
12+
export class AppComponent implements OnInit {
13+
14+
@ViewChild(GridstackComponent) gridComp?: GridstackComponent;
15+
@ViewChild('origTextArea', {static: true}) origTextEl?: ElementRef<HTMLTextAreaElement>;
16+
@ViewChild('textArea', {static: true}) textEl?: ElementRef<HTMLTextAreaElement>;
17+
1318
// which sample to show
14-
public show = 5;
19+
public show = 6; // nested
1520

1621
/** sample grid options and items to load... */
1722
public items: GridStackWidget[] = [
@@ -30,36 +35,61 @@ export class AppComponent {
3035
}
3136

3237
// nested grid options
33-
public sub1: NgGridStackWidget[] = [ {x:0, y:0, type:'app-a'}, {x:1, y:0, type:'app-b'}, {x:2, y:0, type:'app-c'}, {x:3, y:0}, {x:0, y:1}, {x:1, y:1}];
34-
public sub2: NgGridStackWidget[] = [ {x:0, y:0}, {x:0, y:1, w:2}];
35-
public subOptions: GridStackOptions = {
38+
private subOptions: GridStackOptions = {
3639
cellHeight: 50, // should be 50 - top/bottom
3740
column: 'auto', // size to match container. make sure to include gridstack-extra.min.css
3841
acceptWidgets: true, // will accept .grid-stack-item by default
3942
margin: 5,
4043
};
44+
private sub1: NgGridStackWidget[] = [ {x:0, y:0, type:'app-a'}, {x:1, y:0, type:'app-b'}, {x:2, y:0, type:'app-c'}, {x:3, y:0}, {x:0, y:1}, {x:1, y:1}];
45+
private sub2: NgGridStackWidget[] = [ {x:0, y:0}, {x:0, y:1, w:2}];
46+
private subChildren: NgGridStackWidget[] = [
47+
{x:0, y:0, content: 'regular item'},
48+
{x:1, y:0, w:4, h:4, subGrid: {children: this.sub1, id:'sub1_grid', class: 'sub1', ...this.subOptions}},
49+
{x:5, y:0, w:3, h:4, subGrid: {children: this.sub2, id:'sub2_grid', class: 'sub2', ...this.subOptions}},
50+
]
4151
public nestedGridOptions: GridStackOptions = { // main grid options
4252
cellHeight: 50,
4353
margin: 5,
4454
minRow: 2, // don't collapse when empty
4555
disableOneColumnMode: true,
4656
acceptWidgets: true,
4757
id: 'main',
48-
children: [
49-
{x:0, y:0, content: 'regular item', id: 0},
50-
{x:1, y:0, w:4, h:4, subGrid: {children: this.sub1, id:'sub1_grid', class: 'sub1', ...this.subOptions}},
51-
{x:5, y:0, w:3, h:4, subGrid: {children: this.sub2, id:'sub2_grid', class: 'sub2', ...this.subOptions}},
52-
]
58+
children: this.subChildren
5359
};
60+
private serializedData?: GridStackOptions;
5461

5562
constructor() {
5663
// give them content and unique id to make sure we track them during changes below...
57-
[...this.items, ...this.sub1, ...this.sub2].forEach((w: NgGridStackWidget) => {
64+
[...this.items, ...this.subChildren, ...this.sub1, ...this.sub2].forEach((w: NgGridStackWidget) => {
5865
if (!w.type && !w.subGrid) w.content = `item ${ids}`;
5966
w.id = String(ids++);
6067
});
6168
}
6269

70+
ngOnInit(): void {
71+
this.onShow(this.show);
72+
73+
// TEST
74+
// setTimeout(() => {
75+
// if (!this.gridComp) return;
76+
// this.saveGrid();
77+
// this.clearGrid();
78+
// // this.loadGrid();
79+
// }, 500)
80+
}
81+
82+
public onShow(val: number) {
83+
this.show = val;
84+
const data = val === 6 ? this.nestedGridOptions : this.gridOptionsFull;
85+
if (this.origTextEl) this.origTextEl.nativeElement.value = JSON.stringify(data, null, ' ');
86+
87+
// if (val === 6 && !this.gridComp) {
88+
// const cont: HTMLElement | null = document.querySelector('.grid-container');
89+
// if (cont) GridStack.addGrid(cont, this.serializedData);
90+
// }
91+
}
92+
6393
/** called whenever items change size/position/etc.. */
6494
public onChange(data: nodesCB) {
6595
// TODO: update our TEMPLATE list to match ?
@@ -74,19 +104,26 @@ export class AppComponent {
74104
/**
75105
* TEST dynamic grid operations - uses grid API directly (since we don't track structure that gets out of sync)
76106
*/
77-
public add(gridComp: GridstackComponent) {
107+
public add() {
78108
// TODO: BUG the content doesn't appear until widget is moved around (or another created). Need to force
79109
// angular detection changes...
80-
gridComp.grid?.addWidget({x:3, y:0, w:2, content:`item ${ids}`, id:String(ids++)});
110+
this.gridComp?.grid?.addWidget({x:3, y:0, w:2, content:`item ${ids}`, id:String(ids++)});
81111
}
82-
public delete(gridComp: GridstackComponent) {
83-
gridComp.grid?.removeWidget(gridComp.grid.engine.nodes[0]?.el!);
112+
public delete() {
113+
let grid = this.gridComp?.grid;
114+
if (!grid) return;
115+
let node = grid.engine.nodes[0];
116+
if (node?.subGrid) {
117+
grid = node.subGrid as GridStack;
118+
node = grid?.engine.nodes[0];
119+
}
120+
if (node) grid.removeWidget(node.el!);
84121
}
85-
public modify(gridComp: GridstackComponent) {
86-
gridComp.grid?.update(gridComp.grid.engine.nodes[0]?.el!, {w:3})
122+
public modify() {
123+
this.gridComp?.grid?.update(this.gridComp?.grid.engine.nodes[0]?.el!, {w:3})
87124
}
88-
public newLayout(gridComp: GridstackComponent) {
89-
gridComp.grid?.load([
125+
public newLayout() {
126+
this.gridComp?.grid?.load([
90127
{x:0, y:1, id:'1', minW:1, w:1}, // new size/constrain
91128
{x:1, y:1, id:'2'},
92129
// {x:2, y:1, id:'3'}, // delete item
@@ -105,10 +142,10 @@ export class AppComponent {
105142
public deleteNgFor() {
106143
this.items.pop();
107144
}
108-
public modifyNgFor(gridComp: GridstackComponent) {
145+
public modifyNgFor() {
109146
// this will not update the DOM nor trigger gridstackItems.changes for GS to auto-update, so set new option of the gridItem instead
110147
// this.items[0].w = 3;
111-
const gridItem = gridComp.gridstackItems?.get(0);
148+
const gridItem = this.gridComp?.gridstackItems?.get(0);
112149
if (gridItem) gridItem.options = {w:3};
113150
}
114151
public newLayoutNgFor() {
@@ -119,6 +156,18 @@ export class AppComponent {
119156
{x:3, y:0, w:2, content:'new item'}, // new item
120157
];
121158
}
159+
public clearGrid() {
160+
if (!this.gridComp) return;
161+
this.gridComp.grid?.removeAll(true);
162+
}
163+
public saveGrid() {
164+
this.serializedData = this.gridComp?.grid?.save(false, true) as GridStackOptions || ''; // no content, full options
165+
if (this.textEl) this.textEl.nativeElement.value = JSON.stringify(this.serializedData, null, ' ');
166+
}
167+
public loadGrid() {
168+
if (!this.gridComp) return;
169+
GridStack.addGrid(this.gridComp.el, this.serializedData);
170+
}
122171

123172
// ngFor TEMPLATE unique node id to have correct match between our items used and GS
124173
public identify(index: number, w: GridStackWidget) {

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,13 @@ import { AComponent, BComponent, CComponent } from './dummy.component';
3131
providers: [],
3232
bootstrap: [AppComponent]
3333
})
34-
export class AppModule { }
34+
export class AppModule {
35+
constructor() {
36+
// register all our dynamic components created in the grid
37+
GridstackComponent.selectorToType = {
38+
'app-a': AComponent,
39+
'app-b': BComponent,
40+
'app-c': CComponent,
41+
};
42+
}
43+
}

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

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

8-
import { Component, Type } from '@angular/core';
8+
import { Component } from '@angular/core';
99

1010
@Component({
1111
selector: 'app-a',
@@ -27,13 +27,3 @@ export class BComponent {
2727
})
2828
export class CComponent {
2929
}
30-
31-
/**
32-
* stores the selector -> Type mapping, so we can create items dynamically from a string.
33-
* Unfortunately Ng doesn't provide public access to that mapping.
34-
*/
35-
export const selectorToComponent: {[key: string]: Type<Object>} = {
36-
'app-a': AComponent,
37-
'app-b': BComponent,
38-
'app-c': CComponent,
39-
};

demo/angular/src/app/gridstack-item.component.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ export interface GridItemCompHTMLElement extends GridItemHTMLElement {
1818
selector: 'gridstack-item',
1919
template: `
2020
<div class="grid-stack-item-content">
21-
<!-- TODO: this is where you would create the right component based on some internal type or id IFF !this.options.subGrid
22-
Doing options.content for demo purpose -->
23-
{{options.content}}
21+
<!-- where dynamic items go based on component types, or sub-grids, etc...) -->
22+
<ng-template #container></ng-template>
2423
<!-- any static (defined in dom) content goes here -->
2524
<ng-content></ng-content>
26-
<!-- where dynamic items go (like sub-grids, other dynamic NgComponents, etc...) -->
27-
<ng-template #container></ng-template>
25+
<!-- fallback HTML content from GridStackWidget content field if used instead -->
26+
{{options.content}}
2827
</div>`,
2928
styles: [`
3029
:host { display: block; }

0 commit comments

Comments
 (0)