forked from gridstack/gridstack.js
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathngFor.ts
131 lines (116 loc) · 4.82 KB
/
ngFor.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* Example using Angular ngFor to loop through items and create DOM items
*/
import { Component, AfterViewInit, Input, ViewChildren, QueryList, ElementRef } from '@angular/core';
import { GridItemHTMLElement, GridStack, GridStackNode, GridStackWidget, Utils } from 'gridstack';
// unique ids sets for each item for correct ngFor updating
let ids = 1;
@Component({
selector: "angular-ng-for-test",
template: `
<p><b>ngFor</b>: Example using Angular ngFor to loop through items and create DOM items. This track changes made to the array of items, waits for DOM rendering, then update GS</p>
<button (click)="add()">add item</button>
<button (click)="delete()">remove item</button>
<button (click)="modify()">modify item</button>
<button (click)="newLayout()">new layout</button>
<div class="grid-stack">
<!-- using angular templating to create DOM, otherwise an easier way is to simply call grid.load(items)
NOTE: this example is NOT complete as there are many more properties than listed (minW, maxW, etc....)
-->
<div *ngFor="let n of items; trackBy: identify"
class="grid-stack-item"
[attr.gs-id]="n.id"
[attr.gs-x]="n.x"
[attr.gs-y]="n.y"
[attr.gs-w]="n.w"
[attr.gs-h]="n.h"
#gridStackItem
>
<div class="grid-stack-item-content">item {{ n.id }}</div>
</div>
</div>
`,
// gridstack.min.css and other custom styles should be included in global styles.scss or here
})
export class AngularNgForTestComponent implements AfterViewInit {
/** list of HTML items that we track to know when the DOM has been updated to make/remove GS widgets */
@ViewChildren("gridStackItem") gridstackItems!: QueryList<ElementRef<GridItemHTMLElement>>;
/** set the items to display. */
@Input() public set items(list: GridStackWidget[]) {
this._items = list || [];
this._items.forEach(w => w.id = w.id || String(ids++)); // make sure a unique id is generated for correct ngFor loop update
}
public get items(): GridStackWidget[] { return this._items}
private grid!: GridStack;
public _items!: GridStackWidget[];
constructor() {
this.items = [
{x: 0, y: 0},
{x: 1, y: 1},
{x: 2, y: 2},
];
}
// wait until after DOM is ready to init gridstack - can't be ngOnInit() as angular ngFor needs to run first!
public ngAfterViewInit() {
this.grid = GridStack.init({
margin: 5,
float: true,
})
.on('change added', (event: Event, nodes: GridStackNode[]) => this.onChange(nodes));
// sync initial actual valued rendered (in case init() had to merge conflicts)
this.onChange();
/**
* this is called when the list of items changes - get a list of nodes and
* update the layout accordingly (which will take care of adding/removing items changed by Angular)
*/
this.gridstackItems.changes.subscribe(() => {
const layout: GridStackWidget[] = [];
this.gridstackItems.forEach(ref => {
const n = ref.nativeElement.gridstackNode || this.grid.makeWidget(ref.nativeElement).gridstackNode;
if (n) layout.push(n);
});
this.grid.load(layout); // efficient that does diffs only
})
}
/** Optional: called when given widgets are changed (moved/resized/added) - update our list to match.
* Note this is not strictly necessary as demo works without this
*/
public onChange(list = this.grid.engine.nodes) {
setTimeout(() => // prevent new 'added' items from ExpressionChangedAfterItHasBeenCheckedError. TODO: find cleaner way to sync outside Angular change detection ?
list.forEach(n => {
const item = this._items.find(i => i.id === n.id);
if (item) Utils.copyPos(item, n);
})
, 0);
}
/**
* CRUD operations
*/
public add() {
// new array isn't required as Angular seem to detect changes to content
// this.items = [...this.items, { x:3, y:0, w:3, id:String(ids++) }];
this.items.push({ x:3, y:0, w:3, id:String(ids++) });
}
public delete() {
this.items.pop();
}
public modify() {
// this will only update the DOM attr (from the ngFor loop in our template above)
// but not trigger gridstackItems.changes for GS to auto-update, so call GS update() instead
// this.items[0].w = 2;
const n = this.grid.engine.nodes[0];
if (n?.el) this.grid.update(n.el, {w:3});
}
public newLayout() {
this.items = [ // test updating existing and creating new one
{x:0, y:1, id:'1'},
{x:1, y:1, id:'2'},
// {x:2, y:1, id:3}, // delete item
{x:3, y:0, w:3}, // new item
];
}
// ngFor unique node id to have correct match between our items used and GS
identify(index: number, w: GridStackWidget) {
return w.id;
}
}