Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions demo/angular/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
<button (click)="newLayoutNgFor()">new layout</button>
<gridstack [options]="gridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
{{n.content}}
</gridstack-item>
</gridstack>
</div>
Expand All @@ -46,6 +45,9 @@
<button (click)="delete()">remove item</button>
<button (click)="modify()">modify item</button>
<button (click)="newLayout()">new layout</button>
<button (click)="saveGrid()">Save</button>
<button (click)="clearGrid()">Clear</button>
<button (click)="loadGrid()">Load</button>
<gridstack [options]="gridOptionsFull" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
</gridstack>
</div>
Expand All @@ -57,8 +59,8 @@
<button (click)="delete()">remove item</button>
<button (click)="modify()">modify item</button>
<button (click)="newLayout()">new layout</button>
<button (click)="clearGrid()">Clear</button>
<button (click)="saveGrid()">Save</button>
<button (click)="clearGrid()">Clear</button>
<button (click)="loadGrid()">Load</button>
<!-- TODO: addGrid() in code for testing instead ? -->
<gridstack [options]="nestedGridOptions" (changeCB)="onChange($event)" (resizeStopCB)="onResizeStop($event)">
Expand Down
12 changes: 6 additions & 6 deletions demo/angular/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { GridstackItemComponent } from './gridstack-item.component';
import { GridstackComponent } from './gridstack.component';
import { GridstackComponent, gsCreateNgComponents, gsSaveAdditionNgInfo } from './gridstack.component';
import { AngularNgForTestComponent } from './ngFor';
import { AngularNgForCmdTestComponent } from './ngFor_cmd';
import { AngularSimpleComponent } from './simple';
import { AComponent, BComponent, CComponent } from './dummy.component';
import { GridStack } from 'gridstack';

@NgModule({
declarations: [
Expand All @@ -34,10 +35,9 @@ import { AComponent, BComponent, CComponent } from './dummy.component';
export class AppModule {
constructor() {
// register all our dynamic components created in the grid
GridstackComponent.selectorToType = {
'app-a': AComponent,
'app-b': BComponent,
'app-c': CComponent,
};
GridstackComponent.addComponentToSelectorType([AComponent, BComponent, CComponent]);
// set globally our method to create the right widget type
GridStack.addRemoveCB = gsCreateNgComponents; // DONE in case switcher onShow() as well
GridStack.saveCB = gsSaveAdditionNgInfo;
}
}
7 changes: 3 additions & 4 deletions demo/angular/src/app/gridstack-item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright (c) 2022 Alain Dumesny - see GridStack root license
*/

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

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

Expand All @@ -42,8 +42,7 @@ export class GridstackItemComponent implements OnDestroy {
this.el.gridstackNode.grid.update(this.el, val);
} else {
// store our custom element in options so we can update it and not re-create a generic div!
val.el = this.el;
this._options = val;
this._options = {...val, el: this.el};
}
}
/** return the latest grid options (from GS once built, otherwise initial values) */
Expand Down
86 changes: 46 additions & 40 deletions demo/angular/src/app/gridstack.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Copyright (c) 2022 Alain Dumesny - see GridStack root license
*/

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


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

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

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

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

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

constructor(
private readonly zone: NgZone,
// private readonly zone: NgZone,
// private readonly cd: ChangeDetectorRef,
private readonly elementRef: ElementRef<GridCompHTMLElement>,
) {
this.el._gridComp = this;
Expand All @@ -118,19 +125,19 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
this.loaded = !!this.options?.children?.length;
this._grid = GridStack.init(this._options, this.el);
delete this._options; // GS has it now

this.checkEmpty();
}

/** wait until after all DOM is ready to init gridstack children (after angular ngFor and sub-components run first) */
public ngAfterContentInit(): void {
this.zone.runOutsideAngular(() => {
// track whenever the children list changes and update the layout...
this.gridstackItems?.changes
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(() => this.updateAll());
// ...and do this once at least unless we loaded children already
if (!this.loaded) this.updateAll();
this.hookEvents(this.grid);
});
// track whenever the children list changes and update the layout...
this.gridstackItems?.changes
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(() => this.updateAll());
// ...and do this once at least unless we loaded children already
if (!this.loaded) this.updateAll();
this.hookEvents(this.grid);
}

public ngOnDestroy(): void {
Expand Down Expand Up @@ -158,32 +165,35 @@ export class GridstackComponent implements OnInit, AfterContentInit, OnDestroy {
/** check if the grid is empty, if so show alternative content */
public checkEmpty() {
if (!this.grid) return;
this.isEmpty = !this.grid.engine.nodes.length;
const isEmpty = !this.grid.engine.nodes.length;
if (isEmpty === this.isEmpty) return;
this.isEmpty = isEmpty;
// this.cd.detectChanges();
}

/** get all known events as easy to use Outputs for convenience */
private hookEvents(grid?: GridStack) {
if (!grid) return;
grid
.on('added', (event: Event, nodes: GridStackNode[]) => this.zone.run(() => { this.checkEmpty(); this.addedCB.emit({event, nodes}); }))
.on('change', (event: Event, nodes: GridStackNode[]) => this.zone.run(() => this.changeCB.emit({event, nodes})))
.on('disable', (event: Event) => this.zone.run(() => this.disableCB.emit({event})))
.on('drag', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.dragCB.emit({event, el})))
.on('dragstart', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.dragStartCB.emit({event, el})))
.on('dragstop', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.dragStopCB.emit({event, el})))
.on('dropped', (event: Event, previousNode: GridStackNode, newNode: GridStackNode) => this.zone.run(() => this.droppedCB.emit({event, previousNode, newNode})))
.on('enable', (event: Event) => this.zone.run(() => this.enableCB.emit({event})))
.on('removed', (event: Event, nodes: GridStackNode[]) => this.zone.run(() => { this.checkEmpty(); this.removedCB.emit({event, nodes}); }))
.on('resize', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.resizeCB.emit({event, el})))
.on('resizestart', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.resizeStartCB.emit({event, el})))
.on('resizestop', (event: Event, el: GridItemHTMLElement) => this.zone.run(() => this.resizeStopCB.emit({event, el})))
.on('added', (event: Event, nodes: GridStackNode[]) => { this.checkEmpty(); this.addedCB.emit({event, nodes}); })
.on('change', (event: Event, nodes: GridStackNode[]) => this.changeCB.emit({event, nodes}))
.on('disable', (event: Event) => this.disableCB.emit({event}))
.on('drag', (event: Event, el: GridItemHTMLElement) => this.dragCB.emit({event, el}))
.on('dragstart', (event: Event, el: GridItemHTMLElement) => this.dragStartCB.emit({event, el}))
.on('dragstop', (event: Event, el: GridItemHTMLElement) => this.dragStopCB.emit({event, el}))
.on('dropped', (event: Event, previousNode: GridStackNode, newNode: GridStackNode) => this.droppedCB.emit({event, previousNode, newNode}))
.on('enable', (event: Event) => this.enableCB.emit({event}))
.on('removed', (event: Event, nodes: GridStackNode[]) => { this.checkEmpty(); this.removedCB.emit({event, nodes}); })
.on('resize', (event: Event, el: GridItemHTMLElement) => this.resizeCB.emit({event, el}))
.on('resizestart', (event: Event, el: GridItemHTMLElement) => this.resizeStartCB.emit({event, el}))
.on('resizestop', (event: Event, el: GridItemHTMLElement) => this.resizeStopCB.emit({event, el}))
}
}

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

/**
* called when saving the grid - put the extra info of type, otherwise content
* can be used when saving the grid - make sure we save the content from the field (not HTML as we get ng markups)
* and can put the extra info of type, otherwise content
*/
function saveAdditionNgInfo(n: NgGridStackNode, w: NgGridStackWidget) {
// NOT needed as we get that by default in this case
// if (n.type) w.type = n.type;
// else if (n.content) w.content = n.content;
export function gsSaveAdditionNgInfo(n: NgGridStackNode, w: NgGridStackWidget) {
if (n.type) w.type = n.type;
else if (n.content) w.content = n.content;
}

// set globally our method to create the right widget type
GridStack.addRemoveCB = createNgComponents;
GridStack.saveCB = saveAdditionNgInfo;
2 changes: 1 addition & 1 deletion demo/angular/src/app/simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// gridstack.min.css and other custom styles should be included in global styles.scss
})
export class AngularSimpleComponent implements OnInit {
private items: GridStackWidget[] = [
public items: GridStackWidget[] = [
{ x: 0, y: 0, w: 9, h: 6, content: '0' },
{ x: 9, y: 0, w: 3, h: 3, content: '1' },
{ x: 9, y: 3, w: 3, h: 3, content: '2' },
Expand Down
2 changes: 1 addition & 1 deletion src/gridstack-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ export class GridStackEngine {

public removeAll(removeDOM = true): GridStackEngine {
delete this._layouts;
if (this.nodes.length === 0) return this;
if (!this.nodes.length) return this;
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)
this.removedNodes = this.nodes;
this.nodes = [];
Expand Down
10 changes: 5 additions & 5 deletions src/gridstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,12 @@ export class GridStack {
* add = false: the item will be removed from DOM (if not already done)
* grid = true|false for grid vs grid-items
*/
public static addRemoveCB: AddRemoveFcn;
public static addRemoveCB?: AddRemoveFcn;

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

/** scoping so users can call GridStack.Utils.sort() for example */
public static Utils = Utils;
Expand Down Expand Up @@ -899,11 +899,11 @@ export class GridStack {
if (!removeDOM) {
this.removeAll(removeDOM);
this.el.classList.remove(this._styleSheetClass);
this.el.removeAttribute('gs-current-row');
} else {
this.el.parentNode.removeChild(this.el);
}
this._removeStylesheet();
this.el.removeAttribute('gs-current-row');
if (this.parentGridItem) delete this.parentGridItem.subGrid;
delete this.parentGridItem;
delete this.opts;
Expand Down Expand Up @@ -1286,7 +1286,7 @@ export class GridStack {
/** @internal */
protected _triggerAddEvent(): GridStack {
if (this.engine.batchMode) return this;
if (this.engine.addedNodes && this.engine.addedNodes.length > 0) {
if (this.engine.addedNodes?.length) {
if (!this._ignoreLayoutsNodeChange) {
this.engine.layoutsNodesChange(this.engine.addedNodes);
}
Expand All @@ -1301,7 +1301,7 @@ export class GridStack {
/** @internal */
public _triggerRemoveEvent(): GridStack {
if (this.engine.batchMode) return this;
if (this.engine.removedNodes && this.engine.removedNodes.length > 0) {
if (this.engine.removedNodes?.length) {
this._triggerEvent('removed', this.engine.removedNodes);
this.engine.removedNodes = [];
}
Expand Down