diff --git a/src/examples/slickgrid/App.tsx b/src/examples/slickgrid/App.tsx index c25abec..b34fe06 100644 --- a/src/examples/slickgrid/App.tsx +++ b/src/examples/slickgrid/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { Link, Navigate, Route, Routes as BaseRoutes, useLocation } from 'react-router-dom'; import { NavBar } from '../../NavBar'; diff --git a/src/examples/slickgrid/Example1.tsx b/src/examples/slickgrid/Example1.tsx index 0d0e57d..cace2cb 100644 --- a/src/examples/slickgrid/Example1.tsx +++ b/src/examples/slickgrid/Example1.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import { type Column, Formatters, @@ -7,106 +5,61 @@ import { SlickgridReact, type SlickgridReactInstance } from '../../slickgrid-react'; +import { useState } from 'react'; const NB_ITEMS = 995; -interface Props { } - -interface State { - title: string; - subTitle: string; - gridOptions1?: GridOption; - gridOptions2?: GridOption; - columnDefinitions1: Column[]; - columnDefinitions2: Column[]; - dataset1: any[]; - dataset2: any[]; -} - -export default class Example1 extends React.Component { - private _darkModeGrid1 = false; - reactGrid1!: SlickgridReactInstance; - - constructor(public readonly props: Props) { - super(props); - - this.state = { - title: 'Example 1: Basic Grids', - subTitle: `Simple Grids with Fixed Sizes (800 x 225)`, - gridOptions1: undefined, - gridOptions2: undefined, - columnDefinitions1: [], - columnDefinitions2: [], - dataset1: [], - dataset2: [] - }; - } - - componentDidMount() { - document.title = this.state.title; +export default function Example1() { + const title = 'Example 1: Basic Grids'; + document.title = title; + const defaultBrowserDarkMode = isBrowserDarkModeEnabled(); - // define the grid options & columns and then create the grid itself - this.defineGrids(); + const [darkModeGrid1, setDarkModeGrid1] = useState(defaultBrowserDarkMode); + const [reactGrid1, setReactGrid1] = useState(); - // mock some data (different in each dataset) - this.setState(() => ({ - dataset1: this.mockData(NB_ITEMS), - dataset2: this.mockData(NB_ITEMS) - })); - } - - reactGrid1Ready(reactGrid: SlickgridReactInstance) { - this.reactGrid1 = reactGrid; - } - - isBrowserDarkModeEnabled() { - return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false; - } + // mock some data (different in each dataset) + const [dataset1] = useState(mockData(NB_ITEMS)); + const [dataset2] = useState(mockData(NB_ITEMS)); /* Define grid Options and Columns */ - defineGrids() { - const columns: Column[] = [ - { id: 'title', name: 'Title', field: 'title', sortable: true, minWidth: 100 }, - { id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, minWidth: 100 }, - { id: '%', name: '% Complete', field: 'percentComplete', sortable: true, minWidth: 100 }, - { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso }, - { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso }, - { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', sortable: true, minWidth: 100 } - ]; - this._darkModeGrid1 = this.isBrowserDarkModeEnabled(); - const gridOptions1: GridOption = { - darkMode: this._darkModeGrid1, - gridHeight: 225, - gridWidth: 800, - enableAutoResize: false, - enableSorting: true - }; - - // copy the same Grid Options and Column Definitions to 2nd grid - // but also add Pagination in this grid - const gridOptions2: GridOption = { - darkMode: false, - gridHeight: 225, - gridWidth: 800, - enableAutoResize: false, - enableSorting: true, - enablePagination: true, - pagination: { - pageSizes: [5, 10, 20, 25, 50], - pageSize: 5 - }, - }; - - this.setState((state: State) => ({ - ...state, - columnDefinitions1: columns, - columnDefinitions2: columns, - gridOptions1, - gridOptions2 - })); + const columnDefinitions1: Column[] = [ + { id: 'title', name: 'Title', field: 'title', sortable: true, minWidth: 100 }, + { id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, minWidth: 100 }, + { id: '%', name: '% Complete', field: 'percentComplete', sortable: true, minWidth: 100 }, + { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso }, + { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso }, + { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', sortable: true, minWidth: 100 } + ]; + const columnDefinitions2: Column[] = [...columnDefinitions1]; + + const gridOptions1: GridOption = { + darkMode: defaultBrowserDarkMode, + gridHeight: 225, + gridWidth: 800, + enableAutoResize: false, + enableSorting: true + }; + + // copy the same Grid Options and Column Definitions to 2nd grid + // but also add Pagination in this grid + const gridOptions2: GridOption = { + darkMode: false, + gridHeight: 225, + gridWidth: 800, + enableAutoResize: false, + enableSorting: true, + enablePagination: true, + pagination: { + pageSizes: [5, 10, 20, 25, 50], + pageSize: 5 + }, + }; + + function isBrowserDarkModeEnabled() { + return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false; } - mockData(count: number) { + function mockData(count: number) { // mock a dataset const mockDataset: any[] = []; for (let i = 0; i < count; i++) { @@ -129,57 +82,62 @@ export default class Example1 extends React.Component { return mockDataset; } - toggleDarkModeGrid1() { - this._darkModeGrid1 = !this._darkModeGrid1; - if (this._darkModeGrid1) { + function reactGrid1Ready(reactGrid: SlickgridReactInstance) { + setReactGrid1(reactGrid); + } + + function toggleDarkModeGrid1() { + const isDarkMode = !darkModeGrid1; + setDarkModeGrid1(isDarkMode); + if (isDarkMode) { document.querySelector('.grid-container1')?.classList.add('dark-mode'); } else { document.querySelector('.grid-container1')?.classList.remove('dark-mode'); } - this.reactGrid1.slickGrid?.setOptions({ darkMode: this._darkModeGrid1 }); + reactGrid1?.slickGrid?.setOptions({ darkMode: isDarkMode }); } - render() { - return !this.state.gridOptions1 ? '' : ( -
-

- {this.state.title} - - see  - - code - - -

-
{this.state.subTitle}
- -

-
- Grid 1 - -
-

- -
- this.reactGrid1Ready($event.detail)} /> + return ( +
+

+ {title} + + see  + + code + + +

+
+ Simple Grids with Fixed Sizes (800 x 225) +
+ +

+
+ Grid 1 +
+

+ +
+ reactGrid1Ready($event.detail)} /> +
-
+
-

Grid 2 (with local Pagination)

- -
- ); - } +

Grid 2 (with local Pagination)

+ +
+ ); } diff --git a/src/examples/slickgrid/Example2.tsx b/src/examples/slickgrid/Example2.tsx index 490c41e..e48fb73 100644 --- a/src/examples/slickgrid/Example2.tsx +++ b/src/examples/slickgrid/Example2.tsx @@ -8,8 +8,7 @@ import { type SlickgridReactInstance, } from '../../slickgrid-react'; import DOMPurify from 'dompurify'; -import React from 'react'; -import type BaseSlickGridState from './state-slick-grid-base'; +import { useState } from 'react'; interface DataItem { id: number; @@ -23,9 +22,6 @@ interface DataItem { phone: string; completed: number; } -interface State extends BaseSlickGridState { - resizerPaused: boolean; -} // create my custom Formatter with the Formatter type const myCustomCheckmarkFormatter: Formatter = (_row, _cell, value) => { @@ -41,116 +37,73 @@ const customEnableButtonFormatter: Formatter = (_row: number, _cell: n `; }; -interface Props { } - -export default class Example2 extends React.Component { - title = 'Example 2: Grid with Formatters'; - subTitle = ` - Grid with Custom and/or included Slickgrid Formatters (Docs). -
    -
  • The 2 last columns are using Custom Formatters
  • -
    • The "Completed" column uses a the "onCellClick" event and a formatter to simulate a toggle action
    -
  • - Support Excel Copy Buffer (SlickGrid Copy Manager Plugin), you can use it by simply enabling "enableExcelCopyBuffer" flag. - Note that it will only evaluate Formatter when the "exportWithFormatter" flag is enabled (through "ExcelExportOptions" or "TextExportOptions" or the column definition) -
  • -
  • This example also has auto-resize enabled, and we also demo how you can pause the resizer if you wish to
  • -
- `; - - reactGrid!: SlickgridReactInstance; - - constructor(public readonly props: Props) { - super(props); - - this.state = { - gridOptions: undefined, - columnDefinitions: [], - dataset: [], - resizerPaused: false, - }; - } - - componentDidMount() { - document.title = this.title; - - // define the grid options & columns and then create the grid itself - this.defineGrid(); - } - - reactGridReady(reactGrid: SlickgridReactInstance) { - this.reactGrid = reactGrid; - } - - /* Define grid Options and Columns */ - defineGrid() { - // the columns field property is type-safe, try to add a different string not representing one of DataItems properties - const columns: Column[] = [ - { id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, width: 70 }, - { id: 'phone', name: 'Phone Number using mask', field: 'phone', sortable: true, type: FieldType.number, minWidth: 100, formatter: Formatters.mask, params: { mask: '(000) 000-0000' } }, - { id: 'duration', name: 'Duration (days)', field: 'duration', formatter: Formatters.decimal, params: { minDecimal: 1, maxDecimal: 2 }, sortable: true, type: FieldType.number, minWidth: 90, exportWithFormatter: true }, - { id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, type: FieldType.number, sortable: true, minWidth: 100 }, - { id: 'percent2', name: '% Complete', field: 'percentComplete2', formatter: Formatters.progressBar, type: FieldType.number, sortable: true, minWidth: 100 }, - { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, type: FieldType.date, minWidth: 90, exportWithFormatter: true }, - { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, sortable: true, type: FieldType.date, minWidth: 90, exportWithFormatter: true }, - { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: myCustomCheckmarkFormatter, type: FieldType.number, sortable: true, minWidth: 100 }, - { - id: 'completed', name: 'Completed', field: 'completed', type: FieldType.number, sortable: true, minWidth: 100, - formatter: customEnableButtonFormatter, - onCellClick: (_e, args) => { - this.toggleCompletedProperty(args && args.dataContext); - } +export default function Example2() { + const title = 'Example 2: Grid with Formatters'; + document.title = title; + + const [reactGrid, setReactGrid] = useState(); + const [resizerPaused, setResizerPaused] = useState(false); + const [dataset] = useState(getData()); + + // the columns field property is type-safe, try to add a different string not representing one of DataItems properties + const columnDefinitions: Column[] = [ + { id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, width: 70 }, + { id: 'phone', name: 'Phone Number using mask', field: 'phone', sortable: true, type: FieldType.number, minWidth: 100, formatter: Formatters.mask, params: { mask: '(000) 000-0000' } }, + { id: 'duration', name: 'Duration (days)', field: 'duration', formatter: Formatters.decimal, params: { minDecimal: 1, maxDecimal: 2 }, sortable: true, type: FieldType.number, minWidth: 90, exportWithFormatter: true }, + { id: 'complete', name: '% Complete', field: 'percentComplete', formatter: Formatters.percentCompleteBar, type: FieldType.number, sortable: true, minWidth: 100 }, + { id: 'percent2', name: '% Complete', field: 'percentComplete2', formatter: Formatters.progressBar, type: FieldType.number, sortable: true, minWidth: 100 }, + { id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, sortable: true, type: FieldType.date, minWidth: 90, exportWithFormatter: true }, + { id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, sortable: true, type: FieldType.date, minWidth: 90, exportWithFormatter: true }, + { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: myCustomCheckmarkFormatter, type: FieldType.number, sortable: true, minWidth: 100 }, + { + id: 'completed', name: 'Completed', field: 'completed', type: FieldType.number, sortable: true, minWidth: 100, + formatter: customEnableButtonFormatter, + onCellClick: (_e, args) => { + toggleCompletedProperty(args?.dataContext); } - ]; - - const gridOptions: GridOption = { - autoResize: { - container: '#demo-container', - rightPadding: 10 - }, - enableCellNavigation: true, - showCustomFooter: true, // display some metrics in the bottom custom footer - customFooterOptions: { - // optionally display some text on the left footer container - leftFooterText: 'custom footer text', - hideTotalItemCount: true, - hideLastUpdateTimestamp: true - }, - // sanitize is returning a different TrustedHTML type, not sure why but we can cast as string to avoid build issue, the result will still be TrustedHTML - sanitizer: (dirtyHtml) => DOMPurify.sanitize(dirtyHtml, { ADD_ATTR: ['level'], RETURN_TRUSTED_TYPE: true }), - - // you customize all formatter at once certain options through "formatterOptions" in the Grid Options - // or independently through the column definition "params", the option names are the same - /* - formatterOptions: { - dateSeparator: '.', - decimalSeparator: ',', - displayNegativeNumberWithParentheses: true, - minDecimal: 0, - maxDecimal: 2, - thousandSeparator: '_' - }, - */ - - // when using the ExcelCopyBuffer, you can see what the selection range is - enableExcelCopyBuffer: true, - // excelCopyBufferOptions: { - // onCopyCells: (e, args: { ranges: SelectedRange[] }) => console.log('onCopyCells', args.ranges), - // onPasteCells: (e, args: { ranges: SelectedRange[] }) => console.log('onPasteCells', args.ranges), - // onCopyCancelled: (e, args: { ranges: SelectedRange[] }) => console.log('onCopyCancelled', args.ranges), - // } - }; - - this.setState((state) => ({ - ...state, - columnDefinitions: columns, - gridOptions, - dataset: this.getData(), - })); - } + } + ]; + + const gridOptions: GridOption = { + autoResize: { + container: '#demo-container', + rightPadding: 10 + }, + enableCellNavigation: true, + showCustomFooter: true, // display some metrics in the bottom custom footer + customFooterOptions: { + // optionally display some text on the left footer container + leftFooterText: 'custom footer text', + hideTotalItemCount: true, + hideLastUpdateTimestamp: true + }, + // sanitize is returning a different TrustedHTML type, not sure why but we can cast as string to avoid build issue, the result will still be TrustedHTML + sanitizer: (dirtyHtml) => DOMPurify.sanitize(dirtyHtml, { ADD_ATTR: ['level'], RETURN_TRUSTED_TYPE: true }), + + // you customize all formatter at once certain options through "formatterOptions" in the Grid Options + // or independently through the column definition "params", the option names are the same + /* + formatterOptions: { + dateSeparator: '.', + decimalSeparator: ',', + displayNegativeNumberWithParentheses: true, + minDecimal: 0, + maxDecimal: 2, + thousandSeparator: '_' + }, + */ + + // when using the ExcelCopyBuffer, you can see what the selection range is + enableExcelCopyBuffer: true, + // excelCopyBufferOptions: { + // onCopyCells: (e, args: { ranges: SelectedRange[] }) => console.log('onCopyCells', args.ranges), + // onPasteCells: (e, args: { ranges: SelectedRange[] }) => console.log('onPasteCells', args.ranges), + // onCopyCancelled: (e, args: { ranges: SelectedRange[] }) => console.log('onCopyCancelled', args.ranges), + // } + }; // mock a dataset - getData() { + function getData() { const mockDataset: any[] = []; for (let i = 0; i < 500; i++) { @@ -162,7 +115,7 @@ export default class Example2 extends React.Component { mockDataset[i] = { id: i, title: 'Task ' + i, - phone: this.generatePhoneNumber(), + phone: generatePhoneNumber(), duration: (i % 33 === 0) ? null : Math.random() * 100 + '', percentComplete: randomPercent, percentComplete2: randomPercent, @@ -175,7 +128,7 @@ export default class Example2 extends React.Component { return mockDataset; } - generatePhoneNumber(): string { + function generatePhoneNumber(): string { let phone = ''; for (let i = 0; i < 10; i++) { phone += Math.round(Math.random() * 9) + ''; @@ -183,52 +136,64 @@ export default class Example2 extends React.Component { return phone; } - togglePauseResizer() { - this.setState((state) => ({ - ...state, - resizerPaused: !state.resizerPaused - })); - this.reactGrid?.resizerService.pauseResizer(this.state.resizerPaused); + function reactGridReady(instance: SlickgridReactInstance) { + setReactGrid(instance); + } + + function togglePauseResizer() { + const isPaused = !resizerPaused; + setResizerPaused(isPaused); + reactGrid?.resizerService.pauseResizer(isPaused); } - toggleCompletedProperty(item: any) { + function toggleCompletedProperty(item: any) { // toggle property if (typeof item === 'object') { item.completed = !item.completed; // simulate a backend http call and refresh the grid row after delay window.setTimeout(() => { - this.reactGrid?.gridService.updateItemById(item.id, item, { highlightRow: false }); + reactGrid?.gridService.updateItemById(item.id, item, { highlightRow: false }); }, 250); } } - render() { - return !this.state.gridOptions ? '' : ( -
-

- {this.title} - - see  - - code - - -

-
- - - this.reactGridReady($event.detail)} - /> + return ( +
+

+ {title} + + see  + + code + + +

+
+ Grid with Custom and/or included Slickgrid Formatters (Docs). +
    +
  • The 2 last columns are using Custom Formatters
  • +
    • The "Completed" column uses a the "onCellClick" event and a formatter to simulate a toggle action
    +
  • + Support Excel Copy Buffer (SlickGrid Copy Manager Plugin), you can use it by simply enabling "enableExcelCopyBuffer" flag. + Note that it will only evaluate Formatter when the "exportWithFormatter" flag is enabled (through "ExcelExportOptions" or "TextExportOptions" or the column definition) +
  • +
  • This example also has auto-resize enabled, and we also demo how you can pause the resizer if you wish to
  • +
- ); - } + + + + reactGridReady($event.detail)} + /> +
+ ); } diff --git a/src/examples/slickgrid/Example3.tsx b/src/examples/slickgrid/Example3.tsx index b08c605..6c06d5f 100644 --- a/src/examples/slickgrid/Example3.tsx +++ b/src/examples/slickgrid/Example3.tsx @@ -1,6 +1,5 @@ import fetchJsonp from 'fetch-jsonp'; import i18next from 'i18next'; -import React from 'react'; import { type AutocompleterOption, @@ -20,17 +19,16 @@ import { type SlickgridReactInstance, type VanillaCalendarOption, } from '../../slickgrid-react'; +import { useState } from 'react'; + import { CustomInputEditor } from './custom-inputEditor'; import { CustomInputFilter } from './custom-inputFilter'; -import type BaseSlickGridState from './state-slick-grid-base'; import SAMPLE_COLLECTION_DATA from './data/collection_100_numbers.json'; import SAMPLE_COLLECTION_DATA_URL from './data/collection_100_numbers.json?url'; import COUNTRIES_COLLECTION from './data/countries.json'; import COUNTRY_NAMES from './data/country_names.json'; -interface Props { } - const NB_ITEMS = 100; // you can create custom validator to pass to an inline editor @@ -63,460 +61,404 @@ const taskFormatter = (_row: number, _cell: number, value: any) => { return ''; }; -interface State extends BaseSlickGridState { - isAutoEdit: boolean; - updatedObject: any; - alertWarning: any; -} - -export default class Example3 extends React.Component { - title = 'Example 3: Editors / Delete'; - subTitle = ` - Grid with Inline Editors and onCellClick actions (Docs). -
    -
  • Multiple Editors & Filters are available: AutoComplete, Checkbox, Date, Slider, SingleSelect, MultipleSelect, Float, Text, LongText... even Custom Editor
  • -
  • When using 'enableCellNavigation: true', clicking on a cell will automatically make it active & selected.
  • -
    • If you don't want this behavior, then you should disable 'enableCellNavigation'
    -
  • Inline Editors requires 'enableCellNavigation: true' (not sure why though)
  • -
  • - Support Excel Copy Buffer (SlickGrid Copy Manager Plugin), you can use it by simply enabling 'enableExcelCopyBuffer' flag. - Note that it will only evaluate Formatter when the 'exportWithFormatter' flag is enabled (through 'ExportOptions' or the column definition) -
  • -
  • MultipleSelect & SingeSelect Editors & Filters can use a regular 'collection' or 'collectionAsync' to load it asynchronously
  • -
      -
    • Click on 'Add Item' and see the Editor/Filter or the 'Prerequesites' column change
    • -
    • Any Editor/Filter with a 'collection' can be changed dynamically later in the future
    • -
    -
- `; - private _commandQueue: EditCommand[] = []; - reactGrid!: SlickgridReactInstance; - duplicateTitleHeaderCount = 1; - - constructor(public readonly props: Props) { - super(props); - this.state = { - gridOptions: undefined, - columnDefinitions: [], - dataset: [], - isAutoEdit: true, - updatedObject: null, - alertWarning: '', - }; - } - - componentDidMount() { - document.title = this.title; - // populate the dataset once the grid is ready - const options = this.getGridOptions(); - const columns = this.getColumns(); - - this.setState((state: State) => ({ - ...state, - dataset: this.mockData(NB_ITEMS), - gridOptions: options, - columnDefinitions: columns, - })); - } +export default function Example3() { + const title = 'Example 3: Editors / Delete'; + document.title = title; + + const [duplicateTitleHeaderCount, setDuplicateTitleHeaderCount] = useState(1); + const [commandQueue] = useState([]); + const [isAutoEdit, setIsAutoEdit] = useState(true); + const [updatedObject, setUpdatedObject] = useState(); + const [alertWarning, setAlertWarning] = useState(''); + const [dataset] = useState(getData(NB_ITEMS)); + const [reactGrid, setReactGrid] = useState(); + + const [columnDefinitions, setColumnDefinitions] = useState([ + { + id: 'edit', + field: 'id', + excludeFromColumnPicker: true, + excludeFromGridMenu: true, + excludeFromHeaderMenu: true, + formatter: Formatters.icon, + params: { iconCssClass: 'mdi mdi-pencil pointer' }, + minWidth: 30, + maxWidth: 30, + // use onCellClick OR grid.onClick.subscribe which you can see down below + onCellClick: (_e: any, args: OnEventArgs) => { + console.log(args); + setAlertWarning(`Editing: ${args.dataContext.title}`); + reactGrid?.gridService.highlightRow(args.row, 1500); + reactGrid?.gridService.setSelectedRow(args.row); + }, + }, + { + id: 'delete', + field: 'id', + excludeFromColumnPicker: true, + excludeFromGridMenu: true, + excludeFromHeaderMenu: true, + formatter: Formatters.icon, + params: { iconCssClass: 'mdi mdi-trash-can pointer' }, + minWidth: 30, + maxWidth: 30, + // use onCellClick OR grid.onClick.subscribe which you can see down below + /* + onCellClick: (e: Event, args: OnEventArgs) => { + console.log(args); + alertWarning = `Deleting: ${args.dataContext.title}`; + } + */ + }, + { + id: 'title', + name: 'Title', + field: 'title', + filterable: true, + sortable: true, + type: FieldType.string, + editor: { + model: Editors.longText, + placeholder: 'something', + title: 'some title', + validator: myCustomTitleValidator, // use a custom validator + }, + minWidth: 100, + onCellChange: (_e: Event, args: OnEventArgs) => { - getColumns(): Column[] { - return [ - { - id: 'edit', - field: 'id', - excludeFromColumnPicker: true, - excludeFromGridMenu: true, - excludeFromHeaderMenu: true, - formatter: Formatters.icon, - params: { iconCssClass: 'mdi mdi-pencil pointer' }, - minWidth: 30, - maxWidth: 30, - // use onCellClick OR grid.onClick.subscribe which you can see down below - onCellClick: (_e: any, args: OnEventArgs) => { - console.log(args); - - this.setState((state: State) => ({ - ...state, - alertWarning: `Editing: ${args.dataContext.title}` - })); - this.reactGrid.gridService.highlightRow(args.row, 1500); - this.reactGrid.gridService.setSelectedRow(args.row); - }, + setAlertWarning(`Updated Title: ${args.dataContext.title}`); }, - { - id: 'delete', - field: 'id', - excludeFromColumnPicker: true, - excludeFromGridMenu: true, - excludeFromHeaderMenu: true, - formatter: Formatters.icon, - params: { iconCssClass: 'mdi mdi-trash-can pointer' }, - minWidth: 30, - maxWidth: 30, - // use onCellClick OR grid.onClick.subscribe which you can see down below - /* - onCellClick: (e: Event, args: OnEventArgs) => { - console.log(args); - this.alertWarning = `Deleting: ${args.dataContext.title}`; - } - */ + }, + { + id: 'title2', + name: 'Title, Custom Editor', + field: 'title', + filterable: true, + sortable: true, + type: FieldType.string, + editor: { + model: CustomInputEditor, + placeholder: 'custom', + validator: myCustomTitleValidator, // use a custom validator }, - { - id: 'title', - name: 'Title', - field: 'title', - filterable: true, - sortable: true, - type: FieldType.string, - editor: { - model: Editors.longText, - placeholder: 'something', - title: 'some title', - validator: myCustomTitleValidator, // use a custom validator - }, - minWidth: 100, - onCellChange: (_e: Event, args: OnEventArgs) => { - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - this.setState((state: State, props: Props) => ({ - ...state, - alertWarning: `Updated Title: ${args.dataContext.title}` - })); - }, + filter: { + model: CustomInputFilter, + placeholder: '🔎︎ custom', }, - { - id: 'title2', - name: 'Title, Custom Editor', - field: 'title', - filterable: true, - sortable: true, - type: FieldType.string, - editor: { - model: CustomInputEditor, - placeholder: 'custom', - validator: myCustomTitleValidator, // use a custom validator - }, - filter: { - model: CustomInputFilter, - placeholder: '🔎︎ custom', - }, - minWidth: 70, + minWidth: 70, + }, + { + id: 'duration', + name: 'Duration (days)', + field: 'duration', + filterable: true, + minWidth: 100, + sortable: true, + type: FieldType.number, + filter: { model: Filters.slider, filterOptions: { hideSliderNumber: false } }, + editor: { + model: Editors.slider, + minValue: 0, + maxValue: 100, + // editorOptions: { hideSliderNumber: true }, + }, + /* + editor: { + // default is 0 decimals, if no decimals is passed it will accept 0 or more decimals + // however if you pass the 'decimalPlaces', it will validate with that maximum + model: Editors.float, + minValue: 0, + maxValue: 365, + // the default validation error message is in English but you can override it by using 'errorMessage' + // errorMessage: i18next.t('INVALID_FLOAT', { maxDecimal: 2 }), + params: { decimalPlaces: 2 }, }, - { - id: 'duration', - name: 'Duration (days)', - field: 'duration', - filterable: true, - minWidth: 100, - sortable: true, - type: FieldType.number, - filter: { model: Filters.slider, filterOptions: { hideSliderNumber: false } }, - editor: { - model: Editors.slider, - minValue: 0, - maxValue: 100, - // editorOptions: { hideSliderNumber: true }, + */ + }, + { + id: 'complete', + name: '% Complete', + field: 'percentComplete', + filterable: true, + formatter: Formatters.multiple, + type: FieldType.number, + editor: { + // We can also add HTML text to be rendered (any bad script will be sanitized) but we have to opt-in, else it will be sanitized + enableRenderHtml: true, + collection: Array.from(Array(101).keys()).map((k) => ({ + value: k, + label: k, + symbol: '', + })), + customStructure: { + value: 'value', + label: 'label', + labelSuffix: 'symbol', + }, + collectionSortBy: { + property: 'label', + sortDesc: true, }, - /* - editor: { - // default is 0 decimals, if no decimals is passed it will accept 0 or more decimals - // however if you pass the 'decimalPlaces', it will validate with that maximum - model: Editors.float, - minValue: 0, - maxValue: 365, - // the default validation error message is in English but you can override it by using 'errorMessage' - // errorMessage: i18next.t('INVALID_FLOAT', { maxDecimal: 2 }), - params: { decimalPlaces: 2 }, + collectionFilterBy: { + property: 'value', + value: 0, + operator: OperatorType.notEqual, }, - */ + model: Editors.singleSelect, + // validator: (value, args) => { + // if (value < 50) { + // return { valid: false, msg: 'Please use at least 50%' }; + // } + // return { valid: true, msg: '' }; + // } }, - { - id: 'complete', - name: '% Complete', - field: 'percentComplete', - filterable: true, - formatter: Formatters.multiple, - type: FieldType.number, - editor: { - // We can also add HTML text to be rendered (any bad script will be sanitized) but we have to opt-in, else it will be sanitized - enableRenderHtml: true, - collection: Array.from(Array(101).keys()).map((k) => ({ - value: k, - label: k, - symbol: '', - })), - customStructure: { - value: 'value', - label: 'label', - labelSuffix: 'symbol', - }, - collectionSortBy: { - property: 'label', - sortDesc: true, + minWidth: 100, + params: { + formatters: [ + Formatters.collectionEditor, + Formatters.percentCompleteBar, + ], + }, + }, + { + id: 'start', + name: 'Start', + field: 'start', + filterable: true, + filter: { model: Filters.compoundDate }, + formatter: Formatters.dateIso, + sortable: true, + minWidth: 100, + type: FieldType.date, + editor: { + model: Editors.date, + }, + }, + { + id: 'finish', + name: 'Finish', + field: 'finish', + filterable: true, + filter: { model: Filters.compoundDate }, + formatter: Formatters.dateIso, + sortable: true, + minWidth: 100, + type: FieldType.date, // dataset cell input format + // outputType: FieldType.dateUs, // date picker format + saveOutputType: FieldType.dateUtc, // save output date format + editor: { + model: Editors.date, + // override any of the calendar options through 'filterOptions' + editorOptions: { range: { min: 'today' } } as VanillaCalendarOption, + }, + }, + { + id: 'cityOfOrigin', + name: 'City of Origin', + field: 'cityOfOrigin', + filterable: true, + sortable: true, + minWidth: 100, + editor: { + model: Editors.autocompleter, + placeholder: '🔎︎ search city', + + // We can use the autocomplete through 3 ways 'collection', 'collectionAsync' or with your own autocomplete options + // use your own autocomplete options, instead of fetch-jsonp, use React HttpClient or FetchClient + // here we use fetch-jsonp just because I'm not sure how to configure React HttpClient with JSONP and CORS + editorOptions: { + minLength: 3, + forceUserInput: true, + fetch: (searchText: string, updateCallback: (items: false | any[]) => void) => { + /** with React Http, note this demo won't work because of CORS */ + // http.get(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`).subscribe(data => updateCallback(data)); + + /** with JSONP AJAX will work locally but not on the GitHub demo because of CORS */ + fetchJsonp(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`) + .then((response) => response.json()) + .then((json) => updateCallback(json)) + .catch((ex) => console.log('invalid JSONP response', ex)); }, - collectionFilterBy: { - property: 'value', - value: 0, - operator: OperatorType.notEqual, + } as AutocompleterOption, + }, + filter: { + model: Filters.autocompleter, + // placeholder: '🔎︎ search city', + + // We can use the autocomplete through 3 ways 'collection', 'collectionAsync' or with your own autocomplete options + // collectionAsync: httpFetch.fetch(URL_COUNTRIES_COLLECTION), + + // OR use your own autocomplete options, instead of fetch-jsonp, use React HttpClient or FetchClient + // here we use fetch-jsonp just because I'm not sure how to configure React HttpClient with JSONP and CORS + filterOptions: { + minLength: 3, + fetch: (searchText: string, updateCallback: (items: false | any[]) => void) => { + /** with React Http, note this demo won't work because of CORS */ + // http.get(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`).subscribe(data => updateCallback(data)); + + /** with JSONP AJAX will work locally but not on the GitHub demo because of CORS */ + fetchJsonp(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`) + .then((response) => response.json()) + .then((json) => updateCallback(json)) + .catch((ex) => console.log('invalid JSONP response', ex)); }, - model: Editors.singleSelect, - // validator: (value, args) => { - // if (value < 50) { - // return { valid: false, msg: 'Please use at least 50%' }; - // } - // return { valid: true, msg: '' }; - // } - }, - minWidth: 100, - params: { - formatters: [ - Formatters.collectionEditor, - Formatters.percentCompleteBar, - ], - }, + } as AutocompleterOption, }, - { - id: 'start', - name: 'Start', - field: 'start', - filterable: true, - filter: { model: Filters.compoundDate }, - formatter: Formatters.dateIso, - sortable: true, - minWidth: 100, - type: FieldType.date, - editor: { - model: Editors.date, - }, + }, + { + id: 'countryOfOrigin', + name: 'Country of Origin', + field: 'countryOfOrigin', + formatter: Formatters.complexObject, + dataKey: 'code', + labelKey: 'name', + type: FieldType.object, + sortComparer: SortComparers.objectString, + filterable: true, + sortable: true, + minWidth: 100, + editor: { + model: Editors.autocompleter, + customStructure: { label: 'name', value: 'code' }, + collectionAsync: Promise.resolve(COUNTRIES_COLLECTION), }, - { - id: 'finish', - name: 'Finish', - field: 'finish', - filterable: true, - filter: { model: Filters.compoundDate }, - formatter: Formatters.dateIso, - sortable: true, - minWidth: 100, - type: FieldType.date, // dataset cell input format - // outputType: FieldType.dateUs, // date picker format - saveOutputType: FieldType.dateUtc, // save output date format - editor: { - model: Editors.date, - // override any of the calendar options through 'filterOptions' - editorOptions: { range: { min: 'today' } } as VanillaCalendarOption, - }, + filter: { + model: Filters.autocompleter, + customStructure: { label: 'name', value: 'code' }, + collectionAsync: Promise.resolve(COUNTRIES_COLLECTION), }, - { - id: 'cityOfOrigin', - name: 'City of Origin', - field: 'cityOfOrigin', - filterable: true, - sortable: true, - minWidth: 100, - editor: { - model: Editors.autocompleter, - placeholder: '🔎︎ search city', - - // We can use the autocomplete through 3 ways 'collection', 'collectionAsync' or with your own autocomplete options - // use your own autocomplete options, instead of fetch-jsonp, use React HttpClient or FetchClient - // here we use fetch-jsonp just because I'm not sure how to configure React HttpClient with JSONP and CORS - editorOptions: { - minLength: 3, - forceUserInput: true, - fetch: (searchText: string, updateCallback: (items: false | any[]) => void) => { - /** with React Http, note this demo won't work because of CORS */ - // this.http.get(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`).subscribe(data => updateCallback(data)); - - /** with JSONP AJAX will work locally but not on the GitHub demo because of CORS */ - fetchJsonp(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`) - .then((response) => response.json()) - .then((json) => updateCallback(json)) - .catch((ex) => console.log('invalid JSONP response', ex)); - }, - } as AutocompleterOption, - }, - filter: { - model: Filters.autocompleter, - // placeholder: '🔎︎ search city', - - // We can use the autocomplete through 3 ways 'collection', 'collectionAsync' or with your own autocomplete options - // collectionAsync: this.httpFetch.fetch(URL_COUNTRIES_COLLECTION), - - // OR use your own autocomplete options, instead of fetch-jsonp, use React HttpClient or FetchClient - // here we use fetch-jsonp just because I'm not sure how to configure React HttpClient with JSONP and CORS - filterOptions: { - minLength: 3, - fetch: (searchText: string, updateCallback: (items: false | any[]) => void) => { - /** with React Http, note this demo won't work because of CORS */ - // this.http.get(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`).subscribe(data => updateCallback(data)); - - /** with JSONP AJAX will work locally but not on the GitHub demo because of CORS */ - fetchJsonp(`http://gd.geobytes.com/AutoCompleteCity?q=${searchText}`) - .then((response) => response.json()) - .then((json) => updateCallback(json)) - .catch((ex) => console.log('invalid JSONP response', ex)); - }, - } as AutocompleterOption, - }, + }, + { + id: 'countryOfOriginName', + name: 'Country of Origin Name', + field: 'countryOfOriginName', + filterable: true, + sortable: true, + minWidth: 100, + editor: { + model: Editors.autocompleter, + collectionAsync: Promise.resolve(COUNTRY_NAMES), }, - { - id: 'countryOfOrigin', - name: 'Country of Origin', - field: 'countryOfOrigin', - formatter: Formatters.complexObject, - dataKey: 'code', - labelKey: 'name', - type: FieldType.object, - sortComparer: SortComparers.objectString, - filterable: true, - sortable: true, - minWidth: 100, - editor: { - model: Editors.autocompleter, - customStructure: { label: 'name', value: 'code' }, - collectionAsync: Promise.resolve(COUNTRIES_COLLECTION), - }, - filter: { - model: Filters.autocompleter, - customStructure: { label: 'name', value: 'code' }, - collectionAsync: Promise.resolve(COUNTRIES_COLLECTION), - }, + filter: { + model: Filters.autocompleter, + collectionAsync: Promise.resolve(COUNTRY_NAMES), }, - { - id: 'countryOfOriginName', - name: 'Country of Origin Name', - field: 'countryOfOriginName', - filterable: true, - sortable: true, - minWidth: 100, - editor: { - model: Editors.autocompleter, - collectionAsync: Promise.resolve(COUNTRY_NAMES), - }, - filter: { - model: Filters.autocompleter, - collectionAsync: Promise.resolve(COUNTRY_NAMES), - }, + }, + { + id: 'effort-driven', + name: 'Effort Driven', + field: 'effortDriven', + filterable: true, + type: FieldType.boolean, + filter: { + model: Filters.singleSelect, + collection: [ + { value: '', label: '' }, + { value: true, label: 'True' }, + { value: false, label: 'False' }, + ], }, - { - id: 'effort-driven', - name: 'Effort Driven', - field: 'effortDriven', - filterable: true, - type: FieldType.boolean, - filter: { - model: Filters.singleSelect, - collection: [ - { value: '', label: '' }, - { value: true, label: 'True' }, - { value: false, label: 'False' }, - ], + formatter: Formatters.checkmarkMaterial, + editor: { + model: Editors.checkbox, + }, + minWidth: 70, + }, + { + id: 'prerequisites', + name: 'Prerequisites', + field: 'prerequisites', + filterable: true, + formatter: taskFormatter, + exportWithFormatter: true, + sanitizeDataExport: true, + minWidth: 100, + sortable: true, + type: FieldType.string, + editor: { + // We can load the 'collection' asynchronously (on first load only, after that we will simply use 'collection') + // 3 ways are supported (fetch, Promise or RxJS when available) + + // 1- use `fetch` + // collectionAsync: fetch(URL_SAMPLE_COLLECTION_DATA), + + // OR 2- use a Promise + collectionAsync: Promise.resolve(SAMPLE_COLLECTION_DATA), + + // OR a regular 'collection' load + // collection: Array.from(Array(NB_ITEMS).keys()).map(k => ({ value: k, label: k, prefix: 'Task', suffix: 'days' })), + collectionSortBy: { + property: 'value', + sortDesc: true, + fieldType: FieldType.number, + }, + customStructure: { + label: 'label', + value: 'value', + labelPrefix: 'prefix', }, - formatter: Formatters.checkmarkMaterial, - editor: { - model: Editors.checkbox, + collectionOptions: { + separatorBetweenTextLabels: ' ', }, - minWidth: 70, + model: Editors.multipleSelect, }, - { - id: 'prerequisites', - name: 'Prerequisites', - field: 'prerequisites', - filterable: true, - formatter: taskFormatter, - exportWithFormatter: true, - sanitizeDataExport: true, - minWidth: 100, - sortable: true, - type: FieldType.string, - editor: { - // We can load the 'collection' asynchronously (on first load only, after that we will simply use 'collection') - // 3 ways are supported (fetch, Promise or RxJS when available) - - // 1- use `fetch` - // collectionAsync: fetch(URL_SAMPLE_COLLECTION_DATA), - - // OR 2- use a Promise - collectionAsync: Promise.resolve(SAMPLE_COLLECTION_DATA), - - // OR a regular 'collection' load - // collection: Array.from(Array(NB_ITEMS).keys()).map(k => ({ value: k, label: k, prefix: 'Task', suffix: 'days' })), - collectionSortBy: { - property: 'value', - sortDesc: true, - fieldType: FieldType.number, - }, - customStructure: { - label: 'label', - value: 'value', - labelPrefix: 'prefix', - }, - collectionOptions: { - separatorBetweenTextLabels: ' ', - }, - model: Editors.multipleSelect, + filter: { + collectionAsync: fetch(SAMPLE_COLLECTION_DATA_URL), + // collectionAsync: Promise.resolve(SAMPLE_COLLECTION_DATA), + // collectionAsync: new Promise((resolve) => { + // window.setTimeout(() => { + // resolve(Array.from(Array(dataset.length).keys()).map(k => ({ value: k, label: `Task ${k}` }))); + // }); + // }), + + // OR a regular collection load + // collection: Array.from(Array(NB_ITEMS).keys()).map(k => ({ value: k, label: k, prefix: 'Task', suffix: 'days' })), + collectionSortBy: { + property: 'value', + sortDesc: true, + fieldType: FieldType.number, }, - filter: { - collectionAsync: fetch(SAMPLE_COLLECTION_DATA_URL), - // collectionAsync: Promise.resolve(SAMPLE_COLLECTION_DATA), - // collectionAsync: new Promise((resolve) => { - // window.setTimeout(() => { - // resolve(Array.from(Array(this.dataset.length).keys()).map(k => ({ value: k, label: `Task ${k}` }))); - // }); - // }), - - // OR a regular collection load - // collection: Array.from(Array(NB_ITEMS).keys()).map(k => ({ value: k, label: k, prefix: 'Task', suffix: 'days' })), - collectionSortBy: { - property: 'value', - sortDesc: true, - fieldType: FieldType.number, - }, - customStructure: { - label: 'label', - value: 'value', - labelPrefix: 'prefix', - }, - collectionOptions: { - separatorBetweenTextLabels: ' ', - }, - model: Filters.multipleSelect, - operator: OperatorType.inContains, + customStructure: { + label: 'label', + value: 'value', + labelPrefix: 'prefix', + }, + collectionOptions: { + separatorBetweenTextLabels: ' ', }, + model: Filters.multipleSelect, + operator: OperatorType.inContains, }, - ]; - } - - getGridOptions(): GridOption { - return { - autoEdit: this.state.isAutoEdit, - autoCommitEdit: false, - autoResize: { - container: '#demo-container', - rightPadding: 10, - }, - editable: true, - enableCellNavigation: true, - enableExcelCopyBuffer: true, - enableFiltering: true, - editCommandHandler: (_item, _column, editCommand) => { - this._commandQueue.push(editCommand); - editCommand.execute(); - }, - i18n: i18next, - }; - } + }, + ]); + + const gridOptions: GridOption = { + autoEdit: isAutoEdit, + autoCommitEdit: false, + autoResize: { + container: '#demo-container', + rightPadding: 10, + }, + editable: true, + enableCellNavigation: true, + enableExcelCopyBuffer: true, + enableFiltering: true, + editCommandHandler: (_item, _column, editCommand) => { + commandQueue.push(editCommand); + editCommand.execute(); + }, + i18n: i18next, + }; /** Add a new row to the grid and refresh the Filter collection */ - addItem() { - const lastRowIndex = this.state.dataset?.length; - const newRows = this.mockData(1, lastRowIndex); + function addItem() { + const lastRowIndex = dataset?.length; + const newRows = getData(1, lastRowIndex); // wrap into a timer to simulate a backend async call window.setTimeout(() => { // at any time, we can poke the 'collection' property and modify it - const requisiteColumnDef = this.state.columnDefinitions?.find( + const requisiteColumnDef = columnDefinitions?.find( (column: Column) => column.id === 'prerequisites' ); if (requisiteColumnDef) { @@ -525,7 +467,7 @@ export default class Example3 extends React.Component { if (Array.isArray(collectionEditor) && Array.isArray(collectionFilter)) { // add the new row to the grid - this.reactGrid.gridService.addItem(newRows[0], { + reactGrid?.gridService.addItem(newRows[0], { highlightRow: false, }); @@ -554,8 +496,8 @@ export default class Example3 extends React.Component { } /** Delete last inserted row */ - deleteItem() { - const requisiteColumnDef = this.state.columnDefinitions?.find( + function deleteItem() { + const requisiteColumnDef = columnDefinitions?.find( (column: Column) => column.id === 'prerequisites' ); if (requisiteColumnDef) { @@ -565,22 +507,22 @@ export default class Example3 extends React.Component { if (Array.isArray(collectionEditor) && Array.isArray(collectionFilter)) { // sort collection in descending order and take out last option from the collection const selectCollectionObj = - this.sortCollectionDescending(collectionEditor).pop(); - this.sortCollectionDescending(collectionFilter).pop(); - this.reactGrid.gridService.deleteItemById(selectCollectionObj.value); + sortCollectionDescending(collectionEditor).pop(); + sortCollectionDescending(collectionFilter).pop(); + reactGrid?.gridService.deleteItemById(selectCollectionObj.value); } } } - sortCollectionDescending(collection: any[]) { + function sortCollectionDescending(collection: any[]) { return collection.sort((item1, item2) => item1.value - item2.value); } - mockData(itemCount: number, startingIndex = 0) { + function getData(itemCount: number, startingIndex = 0) { // mock a dataset const tempDataset: any[] = []; for (let i = startingIndex; i < startingIndex + itemCount; i++) { - const randomYear = 2000 + this.randomBetween(4, 15); + const randomYear = 2000 + randomBetween(4, 15); const randomFinishYear = new Date().getFullYear() - 3 + Math.floor(Math.random() * 10); // use only years not lower than 3 years ago const randomMonth = Math.floor(Math.random() * 11); @@ -614,60 +556,52 @@ export default class Example3 extends React.Component { return tempDataset; } - randomBetween(min: number, max: number): number { + function randomBetween(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1) + min); } - onCellChanged(_e: Event, args: any) { + function onCellChanged(_e: Event, args: any) { console.log('onCellChange', args); - this.setState((state: State) => ({ - ...state, - updatedObject: { ...args.item }, - })); + setUpdatedObject({ ...args.item }); } - onCellClicked(_e: Event, args: any) { - const metadata = this.reactGrid.gridService.getColumnFromEventArguments(args); + function onCellClicked(_e: Event, args: any) { + const metadata = reactGrid?.gridService.getColumnFromEventArguments(args); - if (metadata.columnDef.id === 'edit') { - this.setState((state: State) => ({ - ...state, - alertWarning: `Open a modal window to edit: ${metadata.dataContext.title}`, - })); + if (metadata?.columnDef.id === 'edit') { + setAlertWarning(`Open a modal window to edit: ${metadata.dataContext.title}`); // highlight the row, to customize the color, you can change the SASS variable $row-highlight-background-color - this.reactGrid.gridService.highlightRow(args.row, 1500); + reactGrid?.gridService.highlightRow(args.row, 1500); // you could also select the row, when using 'enableCellNavigation: true', it automatically selects the row - // this.reactGrid.gridService.setSelectedRow(args.row); - } else if (metadata.columnDef.id === 'delete') { + // reactGrid.gridService.setSelectedRow(args.row); + } else if (metadata?.columnDef.id === 'delete') { if (confirm('Are you sure?')) { - this.reactGrid.gridService.deleteItemById(metadata.dataContext.id); - this.setState((state: State) => ({ - ...state, - alertWarning: `Deleted: ${metadata.dataContext.title}`, - })); + reactGrid?.gridService.deleteItemById(metadata.dataContext.id); + setAlertWarning(`Deleted: ${metadata.dataContext.title}`); } } } - onCellValidationError(_e: Event, args: any) { + function onCellValidationError(_e: Event, args: any) { if (args.validationResults) { alert(args.validationResults.msg); } } - changeAutoCommit() { - this.state.gridOptions!.autoCommitEdit = !this.state.gridOptions!.autoCommitEdit; - this.reactGrid?.slickGrid.setOptions({ - autoCommitEdit: this.state.gridOptions!.autoCommitEdit, + function changeAutoCommit() { + gridOptions.autoCommitEdit = !gridOptions.autoCommitEdit; + reactGrid?.slickGrid.setOptions({ + autoCommitEdit: gridOptions.autoCommitEdit, }); return true; } - dynamicallyAddTitleHeader() { + function dynamicallyAddTitleHeader() { + setDuplicateTitleHeaderCount(duplicateTitleHeaderCount + 1); const newCol = { - id: `title${this.duplicateTitleHeaderCount++}`, + id: `title${duplicateTitleHeaderCount}`, name: 'Title', field: 'title', editor: { @@ -682,201 +616,196 @@ export default class Example3 extends React.Component { // you can dynamically add your column to your column definitions // and then use the spread operator [...cols] OR slice to force React to review the changes - this.setState((state: State) => ({ - ...state, - columnDefinitions: [...this.state.columnDefinitions!, newCol], - })); + setColumnDefinitions([...columnDefinitions!, newCol]); // NOTE if you use an Extensions (Checkbox Selector, Row Detail, ...) that modifies the column definitions in any way // you MUST use 'getAllColumnDefinitions()' from the GridService, using this will be ALL columns including the 1st column that is created internally // for example if you use the Checkbox Selector (row selection), you MUST use the code below /* - const allColumns = this.reactGrid.gridService.getAllColumnDefinitions(); + const allColumns = reactGrid.gridService.getAllColumnDefinitions(); allColumns.push(newCol); - this.columnDefinitions = [...allColumns]; // (or use slice) reassign to column definitions for React to do dirty checking + columnDefinitions = [...allColumns]; // (or use slice) reassign to column definitions for React to do dirty checking */ } - dynamicallyRemoveLastColumn() { - this.state.columnDefinitions.pop(); - - this.setState((state: State) => ({ - ...state, - columnDefinitions: this.state.columnDefinitions.slice(), - })); + function dynamicallyRemoveLastColumn() { + columnDefinitions.pop(); + setColumnDefinitions(columnDefinitions.slice()); /* // remove your column the full set of columns // and use slice or spread [...] to trigger an React dirty change allOriginalColumns.pop(); - this.columnDefinitions = allOriginalColumns.slice(); + columnDefinitions = allOriginalColumns.slice(); */ } - setAutoEdit(isAutoEdit: boolean) { - this.setState((state: State) => ({ ...state, isAutoEdit })); - - this.reactGrid.slickGrid.setOptions({ + function setAutoEdit(isAutoEdit: boolean) { + setIsAutoEdit(isAutoEdit); + reactGrid?.slickGrid.setOptions({ autoEdit: isAutoEdit, }); return true; } - reactGridReady(reactGrid: SlickgridReactInstance) { - this.reactGrid = reactGrid; + function reactGridReady(reactGrid: SlickgridReactInstance) { + setReactGrid(reactGrid); } - undo() { - const command = this._commandQueue.pop(); + function undo() { + const command = commandQueue.pop(); if (command && SlickGlobalEditorLock.cancelCurrentEdit()) { command.undo(); - this.reactGrid.slickGrid.gotoCell(command.row, command.cell, false); + reactGrid?.slickGrid.gotoCell(command.row, command.cell, false); } } - render() { - let objectAlert: any = null; - if (this.state.updatedObject) { - objectAlert = ( -
- Updated Item: {' '} - {JSON.stringify(this.state.updatedObject, null, 2)} -
- ); - } - let alertWarning: any = null; - if (this.state.alertWarning) { - alertWarning = ( -
- Updated Item: {this.state.alertWarning} -
- ); - } + return ( +
+

+ {title} + + see  + + code + + +

+
+ Grid with Inline Editors and onCellClick actions (Docs). +
    +
  • Multiple Editors & Filters are available: AutoComplete, Checkbox, Date, Slider, SingleSelect, MultipleSelect, Float, Text, LongText... even Custom Editor
  • +
  • When using 'enableCellNavigation: true', clicking on a cell will automatically make it active & selected.
  • +
    • If you don't want this behavior, then you should disable 'enableCellNavigation'
    +
  • Inline Editors requires 'enableCellNavigation: true' (not sure why though)
  • +
  • + Support Excel Copy Buffer (SlickGrid Copy Manager Plugin), you can use it by simply enabling 'enableExcelCopyBuffer' flag. + Note that it will only evaluate Formatter when the 'exportWithFormatter' flag is enabled (through 'ExportOptions' or the column definition) +
  • +
  • MultipleSelect & SingeSelect Editors & Filters can use a regular 'collection' or 'collectionAsync' to load it asynchronously
  • +
      +
    • Click on 'Add Item' and see the Editor/Filter or the 'Prerequesites' column change
    • +
    • Any Editor/Filter with a 'collection' can be changed dynamically later in the future
    • +
    +
+
- const marginTop5px = { marginTop: '5px' }; - - return !this.state.gridOptions ? '' : ( -
-

- {this.title} - - see  - - code - +
+
+ + + + -

-
- -
-
- - - -