diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 5225316ec..786ec157d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -40,6 +40,7 @@ import { GridTabsComponent } from './examples/grid-tabs.component'; import { GridTradingComponent } from './examples/grid-trading.component'; import { GridTreeDataHierarchicalComponent } from './examples/grid-tree-data-hierarchical.component'; import { GridTreeDataParentChildComponent } from './examples/grid-tree-data-parent-child.component'; +import { Grid43Component } from './examples/grid43.component'; import { SwtCommonGridTestComponent } from './examples/swt-common-grid-test.component'; import { NgModule } from '@angular/core'; @@ -58,6 +59,7 @@ const routes: Routes = [ { path: 'context', component: GridContextMenuComponent }, { path: 'custom-pagination', component: GridCustomPaginationComponent }, { path: 'custom-tooltip', component: GridCustomTooltipComponent }, + { path: 'csv-grid', component: Grid43Component }, { path: 'drag-recycle', component: GridDragRecycleComponent }, { path: 'editor', component: GridEditorComponent }, { path: 'excel-formula', component: GridExcelFormulaComponent }, diff --git a/src/app/app.component.html b/src/app/app.component.html index c94169b1d..0112e173a 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -201,6 +201,11 @@ 42- Custom Pagination + diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 49dfdbc67..0bcb6c38d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -56,6 +56,7 @@ import { GridTabsComponent } from './examples/grid-tabs.component'; import { GridTradingComponent } from './examples/grid-trading.component'; import { GridTreeDataHierarchicalComponent } from './examples/grid-tree-data-hierarchical.component'; import { GridTreeDataParentChildComponent } from './examples/grid-tree-data-parent-child.component'; +import { Grid43Component } from './examples/grid43.component'; import { HomeComponent } from './examples/home.component'; import { CustomPagerComponent } from './examples/grid-custom-pager.component'; import { RowDetailPreloadComponent } from './examples/rowdetail-preload.component'; @@ -144,6 +145,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj GridTradingComponent, GridTreeDataParentChildComponent, GridTreeDataHierarchicalComponent, + Grid43Component, RowDetailPreloadComponent, RowDetailViewComponent, SwtCommonGridTestComponent, diff --git a/src/app/examples/grid43.component.html b/src/app/examples/grid43.component.html new file mode 100644 index 000000000..51875a6b7 --- /dev/null +++ b/src/app/examples/grid43.component.html @@ -0,0 +1,55 @@ +
+

+ Example 43: Dynamically Create Grid from CSV / Excel import + + + code + + + +

+ +
+ Allow creating a grid dynamically by importing an external CSV or Excel file. This script demo will read the CSV file and will + consider the first row as the column header and create the column definitions accordingly, while the next few rows will be + considered the dataset. Note that this example is demoing a CSV file import but in your application you could easily implemnt + an Excel file uploading. +
+ +
A default CSV file can be download here.
+ +
+
+ + +
+ or +
+ + +
+
+ +
+ + + +
\ No newline at end of file diff --git a/src/app/examples/grid43.component.ts b/src/app/examples/grid43.component.ts new file mode 100644 index 000000000..f76332c59 --- /dev/null +++ b/src/app/examples/grid43.component.ts @@ -0,0 +1,108 @@ +import { ChangeDetectorRef, Component, ViewEncapsulation } from '@angular/core'; +import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { type Column, type GridOption, toCamelCase } from './../modules/angular-slickgrid'; + +const sampleDataRoot = 'assets/data'; + +@Component({ + styles: ['.file-upload { max-width: 300px; }'], + encapsulation: ViewEncapsulation.None, + templateUrl: './grid43.component.html' +}) +export class Grid43Component { + columnDefinitions: Column[] = []; + gridOptions!: GridOption; + dataset: any[] = []; + gridCreated = false; + showSubTitle = true; + uploadFileRef = ''; + templateUrl = `${sampleDataRoot}/users.csv`; + + constructor(private readonly cd: ChangeDetectorRef) { } + + handleFileImport(event: any) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e: any) => { + const content = e.target.result; + this.dynamicallyCreateGrid(content); + }; + reader.readAsText(file); + } + } + + handleDefaultCsv() { + const staticDataCsv = `First Name,Last Name,Age,Type\nBob,Smith,33,Teacher\nJohn,Doe,20,Student\nJane,Doe,21,Student`; + this.dynamicallyCreateGrid(staticDataCsv); + this.uploadFileRef = ''; + } + + destroyGrid() { + this.gridCreated = false; + } + + dynamicallyCreateGrid(csvContent: string) { + // dispose of any previous grid before creating a new one + this.gridCreated = false; + this.cd.detectChanges(); + + const dataRows = csvContent?.split('\n'); + const colDefs: Column[] = []; + const outputData: any[] = []; + + // create column definitions + dataRows.forEach((dataRow, rowIndex) => { + const cellValues = dataRow.split(','); + const dataEntryObj: any = {}; + + if (rowIndex === 0) { + // the 1st row is considered to be the header titles, we can create the column definitions from it + for (const cellVal of cellValues) { + const camelFieldName = toCamelCase(cellVal); + colDefs.push({ + id: camelFieldName, + name: cellVal, + field: camelFieldName, + filterable: true, + sortable: true, + }); + } + } else { + // at this point all column defs were created and we can loop through them and + // we can now start adding data as an object and then simply push it to the dataset array + cellValues.forEach((cellVal, colIndex) => { + dataEntryObj[colDefs[colIndex].id] = cellVal; + }); + + // a unique "id" must be provided, if not found then use the row index and push it to the dataset + if ('id' in dataEntryObj) { + outputData.push(dataEntryObj); + } else { + outputData.push({ ...dataEntryObj, id: rowIndex }); + } + } + }); + + this.gridOptions = { + gridHeight: 300, + gridWidth: 800, + enableFiltering: true, + enableExcelExport: true, + externalResources: [new ExcelExportService()], + headerRowHeight: 35, + rowHeight: 33, + }; + + this.dataset = outputData; + this.columnDefinitions = colDefs; + this.gridCreated = true; + this.cd.detectChanges(); + } + + toggleSubTitle() { + this.showSubTitle = !this.showSubTitle; + const action = this.showSubTitle ? 'remove' : 'add'; + document.querySelector('.subtitle')?.classList[action]('hidden'); + } +} diff --git a/src/assets/data/users.csv b/src/assets/data/users.csv new file mode 100644 index 000000000..0d81e74e9 --- /dev/null +++ b/src/assets/data/users.csv @@ -0,0 +1,5 @@ +First Name,Last Name,Age,User Type +John,Doe,20,Student +Bob,Smith,33,Assistant Teacher +Jane,Doe,21,Student +Robert,Ken,42,Teacher diff --git a/test/cypress/e2e/example43.cy.ts b/test/cypress/e2e/example43.cy.ts new file mode 100644 index 000000000..37c7d01c3 --- /dev/null +++ b/test/cypress/e2e/example43.cy.ts @@ -0,0 +1,88 @@ +describe('Example 43 - Dynamically Create Grid from CSV / Excel import', () => { + const defaultCsvTitles = ['First Name', 'Last Name', 'Age', 'Type']; + const GRID_ROW_HEIGHT = 33; + + it('should display Example title', () => { + cy.visit(`${Cypress.config('baseUrl')}/csv-grid`); + cy.get('h2').should('contain', 'Example 43: Dynamically Create Grid from CSV / Excel import'); + }); + + it('should load default CSV file and expect default column titles', () => { + cy.get('[data-test="static-data-btn"]') + .click(); + + cy.get('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(defaultCsvTitles[index])); + }); + + it('should expect default data in the grid', () => { + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Bob'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Smith'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '33'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', 'Teacher'); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', 'John'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Doe'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '20'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).should('contain', 'Student'); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).should('contain', 'Jane'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Doe'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '21'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(3)`).should('contain', 'Student'); + }); + + it('should sort by "Age" and expect it to be sorted in ascending order', () => { + cy.get('.slick-header-columns .slick-header-column:nth(2)') + .click(); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'John'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Doe'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '20'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', 'Student'); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', 'Jane'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Doe'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '21'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).should('contain', 'Student'); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).should('contain', 'Bob'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Smith'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '33'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(3)`).should('contain', 'Teacher'); + }); + + it('should click again the "Age" column and expect it to be sorted in descending order', () => { + cy.get('.slick-header-columns .slick-header-column:nth(2)') + .click(); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Bob'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Smith'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '33'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', 'Teacher'); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', 'Jane'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Doe'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '21'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).should('contain', 'Student'); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).should('contain', 'John'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Doe'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '20'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(3)`).should('contain', 'Student'); + }); + + it('should filter Smith as "Last Name" and expect only 1 row in the grid', () => { + cy.get('.slick-headerrow .slick-headerrow-column:nth(1) input') + .type('Smith'); + + cy.get('.slick-row') + .should('have.length', 1); + + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Bob'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Smith'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '33'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', 'Teacher'); + }); +});