diff --git a/src/assets/data/users.csv b/src/assets/data/users.csv new file mode 100644 index 00000000..0d81e74e --- /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/src/examples/slickgrid/App.tsx b/src/examples/slickgrid/App.tsx index b34fe06e..dc2de436 100644 --- a/src/examples/slickgrid/App.tsx +++ b/src/examples/slickgrid/App.tsx @@ -43,6 +43,7 @@ import Example39 from './Example39'; import Example40 from './Example40'; import Example41 from './Example41'; import Example42 from './Example42'; +import Example43 from './Example43'; const routes: Array<{ path: string; route: string; component: any; title: string; }> = [ { path: 'example1', route: '/example1', component: , title: '1- Basic Grid / 2 Grids' }, @@ -86,6 +87,7 @@ const routes: Array<{ path: string; route: string; component: any; title: string { path: 'example40', route: '/example40', component: , title: '40- Infinite Scroll from JSON data' }, { path: 'example41', route: '/example41', component: , title: '41- Drag & Drop' }, { path: 'example42', route: '/example42', component: , title: '42- Custom Pagination' }, + { path: 'example43', route: '/example43', component: , title: '43- Create Grid from CSV' }, ]; export default function Routes() { diff --git a/src/examples/slickgrid/Example43.tsx b/src/examples/slickgrid/Example43.tsx new file mode 100644 index 00000000..bff09ea5 --- /dev/null +++ b/src/examples/slickgrid/Example43.tsx @@ -0,0 +1,150 @@ +import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { type Column, type GridOption, SlickgridReact, toCamelCase } from '../../slickgrid-react'; +import { useState } from 'react'; + +export default function Example43() { + const [gridCreated, setGridCreated] = useState(false); + const [gridOptions, setGridOptions] = useState(); + const [columnDefinitions, setColumnDefinitions] = useState([]); + const [dataset, setDataset] = useState([]); + const templateUrl = new URL('./data/users.csv', import.meta.url).href; + const [uploadFileRef, setUploadFileRef] = useState(''); + const [showSubTitle, setShowSubTitle] = useState(true); + + function handleFileImport(event: any) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e: any) => { + const content = e.target.result; + dynamicallyCreateGrid(content); + }; + reader.readAsText(file); + } + } + + function handleDefaultCsv() { + const staticDataCsv = `First Name,Last Name,Age,Type\nBob,Smith,33,Teacher\nJohn,Doe,20,Student\nJane,Doe,21,Student`; + dynamicallyCreateGrid(staticDataCsv); + setUploadFileRef(''); + } + + function dynamicallyCreateGrid(csvContent: string) { + // dispose of any previous grid before creating a new one + setGridCreated(false); + + 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 }); + } + } + }); + + setGridOptions({ + gridHeight: 300, + gridWidth: 800, + enableFiltering: true, + enableExcelExport: true, + externalResources: [new ExcelExportService()], + headerRowHeight: 35, + rowHeight: 33, + }); + + setDataset(outputData); + setColumnDefinitions(colDefs); + setGridCreated(true); + } + + function toggleSubTitle() { + setShowSubTitle(!showSubTitle); + const action = !showSubTitle ? 'remove' : 'add'; + document.querySelector('.subtitle')?.classList[action]('hidden'); + } + + return ( +
+

+ Example 43: Dynamically Create Grid from CSV / Excel import + + see  + + 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.
+ +
+
+ + handleFileImport($event)} /> +
+ or +
+ +
+
+ +
+ +
+ {gridCreated && + } +
+
+ ) +} diff --git a/src/examples/slickgrid/data/users.csv b/src/examples/slickgrid/data/users.csv new file mode 100644 index 00000000..0d81e74e --- /dev/null +++ b/src/examples/slickgrid/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 00000000..2234f73b --- /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')}/example43`); + 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'); + }); +});