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,655 changes: 5,146 additions & 1,509 deletions demo/angular/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~15.0.4",
"@angular-devkit/build-angular": "~12.2.13",
"@angular/cli": "~12.2.13",
"@angular/compiler-cli": "~12.2.0",
"@types/jasmine": "~3.8.0",
Expand Down
53 changes: 44 additions & 9 deletions demo/angular/src/app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,41 @@

The Angular [wrapper component](./gridstack.component.ts) <gridstack> is a better way to use Gridstack, but alternative raw [NgFor](./ngFor.ts) or [Simple](./simple.ts) demos are also given.

## Usage
## Dynamic grid items
this is the recommended way if you are going to have multiple grids (alow drag&drop between) or drag from toolbar to create items, or drag to remove items...

I.E. don't use Angular templating to create children as that is harder to sync.

Code

```typescript
import { GridStackOptions, GridStackWidget } from 'gridstack';
import { GridstackComponent, nodesCB } from './gridstack.component';

/** sample grid options and items to load... */
public gridOptions: GridStackOptions = {
margin: 5,
float: true,
children: [ // or call load() with same data
{x:0, y:0, minW:2, id:'1', content:'Item 1'},
{x:1, y:1, id:'2', content:'Item 2'},
{x:2, y:2, id:'3', content:'Item 3'},
]
}

// called whenever items change size/position/etc..
public onChange(data: nodesCB) {
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
}
```
HTML
```angular2html
<gridstack [options]="gridOptions" (changeGS)="onChange($event)">
</gridstack>
```

## ngFor with wrapper
For simple case where you control the children creation (gridstack doesn't do create.re-parenting)

Code

Expand Down Expand Up @@ -35,7 +69,7 @@ HTML
```angular2html
<gridstack [options]="gridOptions" (changeCB)="onChange($event)">
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
Hello
Item {{n.id}}
</gridstack-item>
</gridstack>
```
Expand All @@ -45,14 +79,15 @@ You can see a fuller example at [app.component.ts](https://github.com/gridstack/

to build the demo, go to demo/angular and run `yarn` + `yarn start` and Navigate to `http://localhost:4200/`

### Caveats
## Caveats

- This wrapper needs v7.2+ to run as it needs the latest changes
- Code isn't compiled into a lib YET. You'll need to copy those files. Let me know (slack) if you are using it...

- This wrapper needs v7.1.2+ to run as it needs the latest changes
## ngFor Caveats
- This wrapper handles well ngFor loops, but if you're using a trackBy function (as I would recommend) and no element id change after an update,
you must manually update the `GridstackItemComponent.option` directly - see [modify()](./app.component.ts#L58) example.
- The original client list of items is not updated to match **content** changes made by gridstack (TBD later), but adding new item or removing (as shown in demo) will update those new items. Client could use change/added/removed events to sync that list if they wish to do so now.
- Code isn't compiled into a side lib to use right now - you need to copy those files for now. Let me know (slack) if you are using it...
you must manually update the `GridstackItemComponent.option` directly - see [modifyNgFor()](./app.component.ts#L83) example.
- The original client list of items is not updated to match **content** changes made by gridstack (TBD later), but adding new item or removing (as shown in demo) will update those new items. Client could use change/added/removed events to sync that list if they wish to do so.


thank you!
Would appreciate getting help doing the same for React and Vue (2 other popular frameworks)
- Alain
25 changes: 18 additions & 7 deletions demo/angular/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<button (click)="show=0">Simple</button>
<button (click)="show=1">ngFor case</button>
<button (click)="show=2">ngFor custom command</button>
<button (click)="show=3">Component</button>
<button (click)="show=3">Component ngFor</button>
<button (click)="show=4">Component Dynamic</button>
</div>

<div class="test-container">
Expand All @@ -13,16 +14,26 @@
<angular-ng-for-cmd-test *ngIf="show===2"></angular-ng-for-cmd-test>

<div *ngIf="show===3" >
<p><b>COMPONENT</b>: Most complete example that uses Component wrapper for grid and gridItem</p>
<button (click)="add(grid)">add item</button>
<button (click)="delete(grid)">remove item</button>
<button (click)="modify(grid)">modify item</button>
<button (click)="newLayout(grid)">new layout</button>
<gridstack #grid [options]="gridOptions" (changeCB)="onChange($event)" (resizestopCB)="onResizeStop($event)">
<p><b>COMPONENT ngFor</b>: Most complete example that uses Component wrapper for grid and gridItem</p>
<button (click)="addNgFor()">add item</button>
<button (click)="deleteNgFor()">remove item</button>
<button (click)="modifyNgFor(gridComp)">modify item</button>
<button (click)="newLayoutNgFor()">new layout</button>
<gridstack #gridComp [options]="gridOptions" (changeGS)="onChange($event)" (resizestop)="onResizeStop($event)">
<gridstack-item *ngFor="let n of items; trackBy: identify" [options]="n">
{{n.content}}
</gridstack-item>
</gridstack>
</div>

<div *ngIf="show===4" >
<p><b>COMPONENT dynamic</b>: Best example that uses Component wrapper and dynamic grid creation (drag between grids, from toolbar, etc...)</p>
<button (click)="add(gridComp)">add item</button>
<button (click)="delete(gridComp)">remove item</button>
<button (click)="modify(gridComp)">modify item</button>
<button (click)="newLayout(gridComp)">new layout</button>
<gridstack #gridComp [options]="gridOptionsFull" (changeGS)="onChange($event)" (resizestop)="onResizeStop($event)">
</gridstack>
</div>

</div>
57 changes: 40 additions & 17 deletions demo/angular/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,23 @@ let ids = 1;
})
export class AppComponent {
// which sample to show
show = 3;
public show = 4;

/** sample grid options and items to load... */
public gridOptions: GridStackOptions = {
margin: 5,
float: true,
}
public items: GridStackWidget[] = [
{x: 0, y: 0, minW: 2},
{x: 1, y: 1},
{x: 2, y: 2},
];
public gridOptions: GridStackOptions = {
margin: 5,
float: true,
minRow: 1,
}
public gridOptionsFull: GridStackOptions = {
...this.gridOptions,
children: this.items,
}

constructor() {
// give them content and unique id to make sure we track them during changes below...
Expand All @@ -34,45 +39,63 @@ export class AppComponent {

/** called whenever items change size/position/etc.. */
public onChange(data: nodesCB) {
// TODO: update our TEMPLATE list to match ?
// NOTE: no need for dynamic as we can always use grid.save() to get latest layout, or grid.engine.nodes
console.log('change ', data.nodes.length > 1 ? data.nodes : data.nodes[0]);
// TODO: update our list to match ?
}

public onResizeStop(data: elementCB) {
console.log('resizestop ', data.el.gridstackNode);
}

/**
* CRUD TEST operations
* TEST dynamic grid operations - uses grid API directly (since we don't track structure that gets out of sync)
*/
public add(gridComp: GridstackComponent) {
gridComp.grid?.addWidget({x:3, y:0, w:2, content:`item ${ids}`, id:String(ids++)});
}
public delete(gridComp: GridstackComponent) {
gridComp.grid?.removeWidget(gridComp.grid.engine.nodes[0]?.el!);
}
public modify(gridComp: GridstackComponent) {
gridComp.grid?.update(gridComp.grid.engine.nodes[0]?.el!, {w:3})
}
public newLayout(gridComp: GridstackComponent) {
gridComp.grid?.load([
{x:0, y:1, id:'1', minW:1, w:1}, // new size/constrain
{x:1, y:1, id:'2'},
// {x:2, y:1, id:'3'}, // delete item
{x:3, y:0, w:2, content:'new item'}, // new item
]);
}

/**
* TEST TEMPLATE operations for ngFor case - NOT recommended unless you have no GS creating/re-parenting
*/
public addNgFor() {
// new array isn't required as Angular detects changes to content with trackBy:identify()
// this.items = [...this.items, { x:3, y:0, w:3, content:`item ${ids}`, id:String(ids++) }];
this.items.push({x:3, y:0, w:3, content:`item ${ids}`, id:String(ids++)});
this.items.push({x:3, y:0, w:2, content:`item ${ids}`, id:String(ids++)});
}

public delete(gridComp: GridstackComponent) {
public deleteNgFor() {
this.items.pop();
}

public modify(gridComp: GridstackComponent) {
public modifyNgFor(gridComp: GridstackComponent) {
// this will not update the DOM nor trigger gridstackItems.changes for GS to auto-update, so set new option of the gridItem instead
// this.items[0].w = 3;
const gridItem = gridComp.gridstackItems?.get(0);
if (gridItem) gridItem.options = {w:3};
}

/** test updating existing and creating new one */
public newLayout(gridComp: GridstackComponent) {
public newLayoutNgFor() {
this.items = [
{x:0, y:1, id:'1', minW:1, w:1}, // new size/constrain
{x:1, y:1, id:'2'},
// {x:2, y:1, id:'3'}, // delete item
{x:3, y:0, w:3, content:'new item'}, // new item
{x:3, y:0, w:2, content:'new item'}, // new item
];
}

// ngFor unique node id to have correct match between our items used and GS
// ngFor TEMPLATE unique node id to have correct match between our items used and GS
public identify(index: number, w: GridStackWidget) {
return w.id; // or use index if no id is set and you only modify at the end...
}
Expand Down
8 changes: 8 additions & 0 deletions demo/angular/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ import { AngularSimpleComponent } from './simple';
imports: [
BrowserModule
],
exports: [
GridstackComponent,
GridstackItemComponent,
],
entryComponents: [ // entry list needed to dynamically create those
GridstackComponent,
GridstackItemComponent,
],
providers: [],
bootstrap: [AppComponent]
})
Expand Down
19 changes: 6 additions & 13 deletions demo/angular/src/app/gridstack-item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { GridItemHTMLElement, GridStackNode } from 'gridstack';
selector: 'gridstack-item',
template: `
<div class="grid-stack-item-content">
{{options.content}}
<ng-content></ng-content>
</div>`,
styles: [`
Expand All @@ -24,24 +25,24 @@ export class GridstackItemComponent {

/** list of options for creating/updating this item */
@Input() public set options(val: GridStackNode) {
if (this.element.gridstackNode?.grid) {
if (this.el.gridstackNode?.grid) {
// already built, do an update...
this.element.gridstackNode.grid.update(this.element, val);
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.element;
val.el = this.el;
this._options = val;
}
}
/** return the latest grid options (from GS once built, otherwise initial values) */
public get options(): GridStackNode {
return this.element.gridstackNode || this._options || {};
return this.el.gridstackNode || this._options || {};
}

private _options?: GridStackNode;

/** return the native element that contains grid specific fields as well */
public get element(): GridItemHTMLElement { return this.elementRef.nativeElement; }
public get el(): GridItemHTMLElement { return this.elementRef.nativeElement; }

/** clears the initial options now that we've built */
public clearOptions() {
Expand All @@ -50,12 +51,4 @@ export class GridstackItemComponent {

constructor(private readonly elementRef: ElementRef<GridItemHTMLElement>) {
}

// none of those have parentElement set from which we could get the grid to auto-init ourself!
// so we will let the parent track us instead...
// ngOnInit() {
// this.element.parentElement
// }
// ngAfterContentInit() {
// }
}
Loading