UNPKG

gridstack-profile

Version:

TypeScript/JS lib for dashboard layout and creation, responsive, mobile support, no external dependencies, with many wrappers (React, Angular, Vue, Ember, knockout...)

209 lines (190 loc) 8.04 kB
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; import { GridStack, GridStackOptions, GridStackWidget } from 'gridstack'; import { AngularSimpleComponent } from './simple'; import { AngularNgForTestComponent } from './ngFor'; import { AngularNgForCmdTestComponent } from './ngFor_cmd'; // NOTE: local testing of file // import { GridstackComponent, NgGridStackOptions, NgGridStackWidget, elementCB, gsCreateNgComponents, nodesCB } from './gridstack.component'; import { GridstackComponent, NgGridStackOptions, NgGridStackWidget, elementCB, gsCreateNgComponents, nodesCB } from 'gridstack/dist/angular'; // unique ids sets for each item for correct ngFor updating let ids = 1; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { @ViewChild(AngularSimpleComponent) case0Comp?: AngularSimpleComponent; @ViewChild(AngularNgForTestComponent) case1Comp?: AngularNgForTestComponent; @ViewChild(AngularNgForCmdTestComponent) case2Comp?: AngularNgForCmdTestComponent; @ViewChild(GridstackComponent) gridComp?: GridstackComponent; @ViewChild('origTextArea', {static: true}) origTextEl?: ElementRef<HTMLTextAreaElement>; @ViewChild('textArea', {static: true}) textEl?: ElementRef<HTMLTextAreaElement>; // which sample to show public show = 5; /** sample grid options and items to load... */ 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, } private sub0: NgGridStackWidget[] = [{x:0, y:0, selector:'app-a'}, {x:1, y:0, content:'plain html'}, {x:0, y:1, selector:'app-b'} ]; public gridOptionsFull: NgGridStackOptions = { ...this.gridOptions, children: this.sub0, } // nested grid options private subOptions: GridStackOptions = { cellHeight: 50, // should be 50 - top/bottom column: 'auto', // size to match container. make sure to include gridstack-extra.min.css acceptWidgets: true, // will accept .grid-stack-item by default margin: 5, }; private sub1: NgGridStackWidget[] = [ {x:0, y:0, selector:'app-a'}, {x:1, y:0, selector:'app-b'}, {x:2, y:0, selector:'app-c'}, {x:3, y:0}, {x:0, y:1}, {x:1, y:1}]; private sub2: NgGridStackWidget[] = [ {x:0, y:0}, {x:0, y:1, w:2}]; private subChildren: NgGridStackWidget[] = [ {x:0, y:0, content: 'regular item'}, {x:1, y:0, w:4, h:4, subGridOpts: {children: this.sub1, class: 'sub1', ...this.subOptions}}, {x:5, y:0, w:3, h:4, subGridOpts: {children: this.sub2, class: 'sub2', ...this.subOptions}}, ] public nestedGridOptions: NgGridStackOptions = { // main grid options cellHeight: 50, margin: 5, minRow: 2, // don't collapse when empty disableOneColumnMode: true, acceptWidgets: true, children: this.subChildren }; private serializedData?: NgGridStackOptions; constructor() { // give them content and unique id to make sure we track them during changes below... [...this.items, ...this.subChildren, ...this.sub1, ...this.sub2, ...this.sub0].forEach((w: NgGridStackWidget) => { if (!w.selector && !w.content && !w.subGridOpts) w.content = `item ${ids}`; w.id = String(ids++); }); } ngOnInit(): void { this.onShow(this.show); // TEST // setTimeout(() => { // if (!this.gridComp) return; // this.saveGrid(); // // this.clearGrid(); // this.delete(); // this.delete(); // this.loadGrid(); // this.delete(); // this.delete(); // }, 500) } public onShow(val: number) { this.show = val; // set globally our method to create the right widget type if (val < 3) GridStack.addRemoveCB = undefined; else GridStack.addRemoveCB = gsCreateNgComponents; // let the switch take affect then load the starting values (since we sometimes save()) setTimeout(() => { let data; switch(val) { case 0: data = this.case0Comp?.items; break; case 1: data = this.case1Comp?.items; break; case 2: data = this.case2Comp?.items; break; case 3: data = this.gridComp?.grid?.save(true, true); break; case 4: data = this.items; break; case 5: data = this.gridOptionsFull; break; case 6: data = this.nestedGridOptions; break; } if (this.origTextEl) this.origTextEl.nativeElement.value = JSON.stringify(data, null, ' '); }); if (this.textEl) this.textEl.nativeElement.value = ''; // if (val === 6 && !this.gridComp) { // const cont: HTMLElement | null = document.querySelector('.grid-container'); // if (cont) GridStack.addGrid(cont, this.serializedData); // } } /** 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]); } public onResizeStop(data: elementCB) { console.log('resizestop ', data.el.gridstackNode); } /** * TEST dynamic grid operations - uses grid API directly (since we don't track structure that gets out of sync) */ public add() { // TODO: BUG the content doesn't appear until widget is moved around (or another created). Need to force // angular detection changes... this.gridComp?.grid?.addWidget({x:3, y:0, w:2, content:`item ${ids}`, id:String(ids++)}); } public delete() { let grid = this.gridComp?.grid; if (!grid) return; let node = grid.engine.nodes[0]; // delete any children first before subGrid itself... if (node?.subGrid && node.subGrid.engine.nodes.length) { grid = node.subGrid; node = grid.engine.nodes[0]; } if (node) grid.removeWidget(node.el!); } public modify() { this.gridComp?.grid?.update(this.gridComp?.grid.engine.nodes[0]?.el!, {w:3}) } public newLayout() { this.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 ]); } /** * ngFor case: TEST TEMPLATE operations - 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:2, content:`item ${ids}`, id:String(ids++)}); } public deleteNgFor() { this.items.pop(); } public modifyNgFor() { // 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 = this.gridComp?.gridstackItems?.get(0); if (gridItem) gridItem.options = {w:3}; } 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:2, content:'new item'}, // new item ]; } public clearGrid() { if (!this.gridComp) return; this.gridComp.grid?.removeAll(true); } public saveGrid() { this.serializedData = this.gridComp?.grid?.save(false, true) as GridStackOptions || ''; // no content, full options if (this.textEl) this.textEl.nativeElement.value = JSON.stringify(this.serializedData, null, ' '); } public loadGrid() { if (!this.gridComp) return; GridStack.addGrid(this.gridComp.el, this.serializedData); } // 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... } }