Skip to content

Commit 5830739

Browse files
authored
Merge pull request #2273 from adumesny/master
more angular wrapper code
2 parents da9a824 + b76efe7 commit 5830739

File tree

7 files changed

+66
-59
lines changed

7 files changed

+66
-59
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
<button (click)="newLayoutNgFor()">new layout</button>
3636
<gridstack [options]="gridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
3737
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
38-
{{n.content}}
3938
</gridstack-item>
4039
</gridstack>
4140
</div>
@@ -46,6 +45,9 @@
4645
<button (click)="delete()">remove item</button>
4746
<button (click)="modify()">modify item</button>
4847
<button (click)="newLayout()">new layout</button>
48+
<button (click)="saveGrid()">Save</button>
49+
<button (click)="clearGrid()">Clear</button>
50+
<button (click)="loadGrid()">Load</button>
4951
<gridstack [options]="gridOptionsFull" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
5052
</gridstack>
5153
</div>
@@ -57,8 +59,8 @@
5759
<button (click)="delete()">remove item</button>
5860
<button (click)="modify()">modify item</button>
5961
<button (click)="newLayout()">new layout</button>
60-
<button (click)="clearGrid()">Clear</button>
6162
<button (click)="saveGrid()">Save</button>
63+
<button (click)="clearGrid()">Clear</button>
6264
<button (click)="loadGrid()">Load</button>
6365
<!-- TODO: addGrid() in code for testing instead ? -->
6466
<gridstack [options]="nestedGridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { BrowserModule } from '@angular/platform-browser';
33

44
import { AppComponent } from './app.component';
55
import { GridstackItemComponent } from './gridstack-item.component';
6-
import { GridstackComponent } from './gridstack.component';
6+
import { GridstackComponent, gsCreateNgComponents, gsSaveAdditionNgInfo } from './gridstack.component';
77
import { AngularNgForTestComponent } from './ngFor';
88
import { AngularNgForCmdTestComponent } from './ngFor_cmd';
99
import { AngularSimpleComponent } from './simple';
1010
import { AComponent, BComponent, CComponent } from './dummy.component';
11+
import { GridStack } from 'gridstack';
1112

1213
@NgModule({
1314
declarations: [
@@ -34,10 +35,9 @@ import { AComponent, BComponent, CComponent } from './dummy.component';
3435
export class AppModule {
3536
constructor() {
3637
// 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-
};
38+
GridstackComponent.addComponentToSelectorType([AComponent, BComponent, CComponent]);
39+
// set globally our method to create the right widget type
40+
GridStack.addRemoveCB = gsCreateNgComponents; // DONE in case switcher onShow() as well
41+
GridStack.saveCB = gsSaveAdditionNgInfo;
4242
}
4343
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (c) 2022 Alain Dumesny - see GridStack root license
44
*/
55

6-
import { ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild, ViewContainerRef, OnDestroy } from '@angular/core';
6+
import { Component, ElementRef, Input, ViewChild, ViewContainerRef, OnDestroy } from '@angular/core';
77
import { GridItemHTMLElement, GridStackNode } from 'gridstack';
88

99
/** store element to Ng Class pointer back */
@@ -28,7 +28,7 @@ export interface GridItemCompHTMLElement extends GridItemHTMLElement {
2828
styles: [`
2929
:host { display: block; }
3030
`],
31-
changeDetection: ChangeDetectionStrategy.OnPush,
31+
// changeDetection: ChangeDetectionStrategy.OnPush, // IFF you want to optimize and control when ChangeDetection needs to happen...
3232
})
3333
export class GridstackItemComponent implements OnDestroy {
3434

@@ -42,8 +42,7 @@ export class GridstackItemComponent implements OnDestroy {
4242
this.el.gridstackNode.grid.update(this.el, val);
4343
} else {
4444
// store our custom element in options so we can update it and not re-create a generic div!
45-
val.el = this.el;
46-
this._options = val;
45+
this._options = {...val, el: this.el};
4746
}
4847
}
4948
/** return the latest grid options (from GS once built, otherwise initial values) */

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

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* Copyright (c) 2022 Alain Dumesny - see GridStack root license
44
*/
55

6-
import { AfterContentInit, ChangeDetectionStrategy, Component, ContentChildren, ElementRef, EventEmitter, Input,
7-
NgZone, OnDestroy, OnInit, Output, QueryList, Type, ViewChild, ViewContainerRef, createComponent, EnvironmentInjector } from '@angular/core';
6+
import { AfterContentInit, Component, ContentChildren, ElementRef, EventEmitter, Input,
7+
OnDestroy, OnInit, Output, QueryList, Type, ViewChild, ViewContainerRef, reflectComponentType } from '@angular/core';
88
import { Subject } from 'rxjs';
99
import { takeUntil } from 'rxjs/operators';
1010
import { GridHTMLElement, GridItemHTMLElement, GridStack, GridStackNode, GridStackOptions, GridStackWidget } from 'gridstack';
@@ -17,7 +17,6 @@ export type elementCB = {event: Event, el: GridItemHTMLElement};
1717
export type nodesCB = {event: Event, nodes: GridStackNode[]};
1818
export type droppedCB = {event: Event, previousNode: GridStackNode, newNode: GridStackNode};
1919

20-
2120
/** extends to store Ng Component selector, instead/inAddition to content */
2221
export interface NgGridStackWidget extends GridStackWidget {
2322
type?: string; // component type to create as content
@@ -31,6 +30,7 @@ export interface GridCompHTMLElement extends GridHTMLElement {
3130
_gridComp?: GridstackComponent;
3231
}
3332

33+
/** selector string to runtime Type mapping */
3434
export type SelectorToType = {[key: string]: Type<Object>};
3535

3636
/**
@@ -49,7 +49,7 @@ export type SelectorToType = {[key: string]: Type<Object>};
4949
styles: [`
5050
:host { display: block; }
5151
`],
52-
changeDetection: ChangeDetectionStrategy.OnPush,
52+
// changeDetection: ChangeDetectionStrategy.OnPush, // IFF you want to optimize and control when ChangeDetection needs to happen...
5353
})
5454
export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
5555

@@ -97,8 +97,14 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
9797
* Unfortunately Ng doesn't provide public access to that mapping.
9898
*/
9999
public static selectorToType: SelectorToType = {};
100-
public static addSelector(key: string, type: Type<Object>) {
101-
GridstackComponent.selectorToType[key] = type;
100+
/** add a list of ng Component to be mapped to selector */
101+
public static addComponentToSelectorType(typeList: Array<Type<Object>>) {
102+
typeList.forEach(type => GridstackComponent.selectorToType[ GridstackComponent.getSelector(type) ] = type);
103+
}
104+
/** return the ng Component selector */
105+
public static getSelector(type: Type<Object>): string {
106+
const mirror = reflectComponentType(type)!;
107+
return mirror.selector;
102108
}
103109

104110
private _options?: GridStackOptions;
@@ -107,7 +113,8 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
107113
private ngUnsubscribe: Subject<void> = new Subject();
108114

109115
constructor(
110-
private readonly zone: NgZone,
116+
// private readonly zone: NgZone,
117+
// private readonly cd: ChangeDetectorRef,
111118
private readonly elementRef: ElementRef<GridCompHTMLElement>,
112119
) {
113120
this.el._gridComp = this;
@@ -118,19 +125,19 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
118125
this.loaded = !!this.options?.children?.length;
119126
this._grid = GridStack.init(this._options, this.el);
120127
delete this._options; // GS has it now
128+
129+
this.checkEmpty();
121130
}
122131

123132
/** wait until after all DOM is ready to init gridstack children (after angular ngFor and sub-components run first) */
124133
public ngAfterContentInit(): void {
125-
this.zone.runOutsideAngular(() => {
126-
// track whenever the children list changes and update the layout...
127-
this.gridstackItems?.changes
128-
.pipe(takeUntil(this.ngUnsubscribe))
129-
.subscribe(() => this.updateAll());
130-
// ...and do this once at least unless we loaded children already
131-
if (!this.loaded) this.updateAll();
132-
this.hookEvents(this.grid);
133-
});
134+
// track whenever the children list changes and update the layout...
135+
this.gridstackItems?.changes
136+
.pipe(takeUntil(this.ngUnsubscribe))
137+
.subscribe(() => this.updateAll());
138+
// ...and do this once at least unless we loaded children already
139+
if (!this.loaded) this.updateAll();
140+
this.hookEvents(this.grid);
134141
}
135142

136143
public ngOnDestroy(): void {
@@ -158,32 +165,35 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
158165
/** check if the grid is empty, if so show alternative content */
159166
public checkEmpty() {
160167
if (!this.grid) return;
161-
this.isEmpty = !this.grid.engine.nodes.length;
168+
const isEmpty = !this.grid.engine.nodes.length;
169+
if (isEmpty === this.isEmpty) return;
170+
this.isEmpty = isEmpty;
171+
// this.cd.detectChanges();
162172
}
163173

164174
/** get all known events as easy to use Outputs for convenience */
165175
private hookEvents(grid?: GridStack) {
166176
if (!grid) return;
167177
grid
168-
.on('added', (event: Event, nodes: GridStackNode[]) => this.zone.run(() => { this.checkEmpty(); this.addedCB.emit({event, nodes}); }))
169-
.on('change', (event: Event, nodes: GridStackNode[]) => this.zone.run(() => this.changeCB.emit({event, nodes})))
170-
.on('disable', (event: Event) => this.zone.run(() => this.disableCB.emit({event})))
171-
.on('drag', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.dragCB.emit({event, el})))
172-
.on('dragstart', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.dragStartCB.emit({event, el})))
173-
.on('dragstop', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.dragStopCB.emit({event, el})))
174-
.on('dropped', (event: Event, previousNode: GridStackNode, newNode: GridStackNode) => this.zone.run(() => this.droppedCB.emit({event, previousNode, newNode})))
175-
.on('enable', (event: Event) => this.zone.run(() => this.enableCB.emit({event})))
176-
.on('removed', (event: Event, nodes: GridStackNode[]) => this.zone.run(() => { this.checkEmpty(); this.removedCB.emit({event, nodes}); }))
177-
.on('resize', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.resizeCB.emit({event, el})))
178-
.on('resizestart', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.resizeStartCB.emit({event, el})))
179-
.on('resizestop', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.resizeStopCB.emit({event, el})))
178+
.on('added', (event: Event, nodes: GridStackNode[]) => { this.checkEmpty(); this.addedCB.emit({event, nodes}); })
179+
.on('change', (event: Event, nodes: GridStackNode[]) => this.changeCB.emit({event, nodes}))
180+
.on('disable', (event: Event) => this.disableCB.emit({event}))
181+
.on('drag', (event: Event, el: GridItemHTMLElement) => this.dragCB.emit({event, el}))
182+
.on('dragstart', (event: Event, el: GridItemHTMLElement) => this.dragStartCB.emit({event, el}))
183+
.on('dragstop', (event: Event, el: GridItemHTMLElement) => this.dragStopCB.emit({event, el}))
184+
.on('dropped', (event: Event, previousNode: GridStackNode, newNode: GridStackNode) => this.droppedCB.emit({event, previousNode, newNode}))
185+
.on('enable', (event: Event) => this.enableCB.emit({event}))
186+
.on('removed', (event: Event, nodes: GridStackNode[]) => { this.checkEmpty(); this.removedCB.emit({event, nodes}); })
187+
.on('resize', (event: Event, el: GridItemHTMLElement) => this.resizeCB.emit({event, el}))
188+
.on('resizestart', (event: Event, el: GridItemHTMLElement) => this.resizeStartCB.emit({event, el}))
189+
.on('resizestop', (event: Event, el: GridItemHTMLElement) => this.resizeStopCB.emit({event, el}))
180190
}
181191
}
182192

183193
/**
184-
* called by GS when a new item needs to be created, which we do as a Angular component, or deleted (skip)
194+
* can be used when a new item needs to be created, which we do as a Angular component, or deleted (skip)
185195
**/
186-
function createNgComponents(host: GridCompHTMLElement | HTMLElement, w: NgGridStackWidget | GridStackOptions, add: boolean, isGrid: boolean): HTMLElement | undefined {
196+
export function gsCreateNgComponents(host: GridCompHTMLElement | HTMLElement, w: NgGridStackWidget | GridStackOptions, add: boolean, isGrid: boolean): HTMLElement | undefined {
187197
if (add) {
188198
if (!host) return;
189199
// create the grid item dynamically - see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
@@ -217,14 +227,10 @@ function createNgComponents(host: GridCompHTMLElement | HTMLElement, w: NgGridSt
217227
}
218228

219229
/**
220-
* called when saving the grid - put the extra info of type, otherwise content
230+
* can be used when saving the grid - make sure we save the content from the field (not HTML as we get ng markups)
231+
* and can put the extra info of type, otherwise content
221232
*/
222-
function saveAdditionNgInfo(n: NgGridStackNode, w: NgGridStackWidget) {
223-
// NOT needed as we get that by default in this case
224-
// if (n.type) w.type = n.type;
225-
// else if (n.content) w.content = n.content;
233+
export function gsSaveAdditionNgInfo(n: NgGridStackNode, w: NgGridStackWidget) {
234+
if (n.type) w.type = n.type;
235+
else if (n.content) w.content = n.content;
226236
}
227-
228-
// set globally our method to create the right widget type
229-
GridStack.addRemoveCB = createNgComponents;
230-
GridStack.saveCB = saveAdditionNgInfo;

demo/angular/src/app/simple.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// gridstack.min.css and other custom styles should be included in global styles.scss
1818
})
1919
export class AngularSimpleComponent implements OnInit {
20-
private items: GridStackWidget[] = [
20+
public items: GridStackWidget[] = [
2121
{ x: 0, y: 0, w: 9, h: 6, content: '0' },
2222
{ x: 9, y: 0, w: 3, h: 3, content: '1' },
2323
{ x: 9, y: 3, w: 3, h: 3, content: '2' },

src/gridstack-engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ export class GridStackEngine {
538538

539539
public removeAll(removeDOM = true): GridStackEngine {
540540
delete this._layouts;
541-
if (this.nodes.length === 0) return this;
541+
if (!this.nodes.length) return this;
542542
removeDOM && this.nodes.forEach(n => n._removeDOM = true); // let CB remove actual HTML (used to set _id to null, but then we loose layout info)
543543
this.removedNodes = this.nodes;
544544
this.nodes = [];

src/gridstack.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ export class GridStack {
178178
* add = false: the item will be removed from DOM (if not already done)
179179
* grid = true|false for grid vs grid-items
180180
*/
181-
public static addRemoveCB: AddRemoveFcn;
181+
public static addRemoveCB?: AddRemoveFcn;
182182

183183
/**
184184
* callback during saving to application can inject extra data for each widget, on top of the grid layout properties
185185
*/
186-
public static saveCB: SaveFcn;
186+
public static saveCB?: SaveFcn;
187187

188188
/** scoping so users can call GridStack.Utils.sort() for example */
189189
public static Utils = Utils;
@@ -899,11 +899,11 @@ export class GridStack {
899899
if (!removeDOM) {
900900
this.removeAll(removeDOM);
901901
this.el.classList.remove(this._styleSheetClass);
902+
this.el.removeAttribute('gs-current-row');
902903
} else {
903904
this.el.parentNode.removeChild(this.el);
904905
}
905906
this._removeStylesheet();
906-
this.el.removeAttribute('gs-current-row');
907907
if (this.parentGridItem) delete this.parentGridItem.subGrid;
908908
delete this.parentGridItem;
909909
delete this.opts;
@@ -1286,7 +1286,7 @@ export class GridStack {
12861286
/** @internal */
12871287
protected _triggerAddEvent(): GridStack {
12881288
if (this.engine.batchMode) return this;
1289-
if (this.engine.addedNodes && this.engine.addedNodes.length > 0) {
1289+
if (this.engine.addedNodes?.length) {
12901290
if (!this._ignoreLayoutsNodeChange) {
12911291
this.engine.layoutsNodesChange(this.engine.addedNodes);
12921292
}
@@ -1301,7 +1301,7 @@ export class GridStack {
13011301
/** @internal */
13021302
public _triggerRemoveEvent(): GridStack {
13031303
if (this.engine.batchMode) return this;
1304-
if (this.engine.removedNodes && this.engine.removedNodes.length > 0) {
1304+
if (this.engine.removedNodes?.length) {
13051305
this._triggerEvent('removed', this.engine.removedNodes);
13061306
this.engine.removedNodes = [];
13071307
}

0 commit comments

Comments
 (0)