diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-advanced/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-advanced/main.ts index a0e9712ecb5..b0a6be13837 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-advanced/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-advanced/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, @@ -28,7 +28,7 @@ type SalesRow = { cost: number; }; -const formatter = (params: ValueFormatterParams, formattedString: string): string => { +const formatter = (params: ValueFormatterLiteParams, format: (value: number) => string): string => { const { value } = params; if (value == null) { return ''; @@ -38,42 +38,40 @@ const formatter = (params: ValueFormatterParams, formattedStri return String(value); } - return formattedString; + return format(value); }; -const currencyFormatter = (params: ValueFormatterParams) => - formatter(params, `$${(params.value ?? '').toLocaleString()}`); +const currencyFormatter = (params: ValueFormatterLiteParams) => + formatter(params, (value) => `$${value.toLocaleString()}`); -const percentageFormatter = (params: ValueFormatterParams) => - formatter(params, `${Math.round((params.value ?? 0) * 100)}%`); +const percentageFormatter = (params: ValueFormatterLiteParams) => + formatter(params, (value) => `${Math.round(value * 100)}%`); const columnDefs: ColDef[] = [ { field: 'account', flex: 1.4 }, { field: 'revenue', - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, { field: 'cost', - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, { colId: 'profit', headerName: 'Profit', calculatedExpression: '[revenue] - [cost]', - cellDataType: 'number', + cellDataType: 'currency', sortable: true, filter: 'agNumberColumnFilter', - valueFormatter: currencyFormatter, }, { colId: 'margin', headerName: 'Margin', calculatedExpression: '[profit] / [revenue]', - cellDataType: 'number', + cellDataType: 'percentage', sortable: true, filter: 'agNumberColumnFilter', - valueFormatter: percentageFormatter, }, { colId: 'status', @@ -111,7 +109,21 @@ const rowData: SalesRow[] = [ const gridOptions: GridOptions = { columnDefs, rowData, - calculatedColumns: true, + dataTypeDefinitions: { + currency: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: currencyFormatter, + }, + percentage: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: percentageFormatter, + }, + }, + calculatedColumns: { + dataTypes: ['currency', 'percentage', 'number', 'text', 'boolean'], + }, defaultColDef: { flex: 1, minWidth: 130, diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-api/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-api/main.ts index b28c7bc336c..778e22c0233 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-api/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-api/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, GridApi, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, GridApi, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ColumnApiModule, @@ -27,30 +27,28 @@ type SalesRow = { cost: number; }; -const currencyFormatter = (params: ValueFormatterParams) => +const currencyFormatter = (params: ValueFormatterLiteParams) => params.value == null ? '' : `$${params.value.toLocaleString()}`; -const percentFormatter = (params: ValueFormatterParams) => +const percentFormatter = (params: ValueFormatterLiteParams) => params.value == null ? '' : `${(params.value * 100).toFixed(1)}%`; const marginColumn: ColDef = { colId: 'profitMargin', headerName: 'Profit Margin', calculatedExpression: '([revenue] - [cost]) / [revenue]', - cellDataType: 'number', - valueFormatter: percentFormatter, + cellDataType: 'percentage', }; const columnDefs: ColDef[] = [ { field: 'product', flex: 1 }, - { field: 'revenue', valueFormatter: currencyFormatter }, - { field: 'cost', valueFormatter: currencyFormatter }, + { field: 'revenue', cellDataType: 'currency' }, + { field: 'cost', cellDataType: 'currency' }, { colId: 'profit', headerName: 'Profit', calculatedExpression: '[revenue] - [cost]', - cellDataType: 'number', - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, ]; @@ -82,6 +80,18 @@ let gridApi: GridApi; const gridOptions: GridOptions = { columnDefs, rowData, + dataTypeDefinitions: { + currency: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: currencyFormatter, + }, + percentage: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: percentFormatter, + }, + }, calculatedColumns: true, defaultColDef: { flex: 1, diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/main.ts index d424faf6258..1be63f99279 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, @@ -28,7 +28,7 @@ type SalesRow = { cost: number; }; -const formatter = (params: ValueFormatterParams, formattedString: string): string => { +const currencyFormatter = (params: ValueFormatterLiteParams): string => { const { value } = params; if (value == null) { return ''; @@ -38,32 +38,28 @@ const formatter = (params: ValueFormatterParams, formattedStri return String(value); } - return formattedString; + return `$${value.toLocaleString()}`; }; -const currencyFormatter = (params: ValueFormatterParams) => - formatter(params, `$${(params.value ?? '').toLocaleString()}`); - const columnDefs: ColDef[] = [ { field: 'product', flex: 1 }, { field: 'revenue', editable: true, - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, { field: 'cost', editable: true, - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, { colId: 'profit', headerName: 'Profit', calculatedExpression: '[revenue] - [cost]', - cellDataType: 'number', + cellDataType: 'currency', sortable: true, filter: 'agNumberColumnFilter', - valueFormatter: currencyFormatter, }, ]; @@ -93,8 +89,16 @@ const rowData: SalesRow[] = [ const gridOptions: GridOptions = { columnDefs, rowData, + dataTypeDefinitions: { + currency: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: currencyFormatter, + }, + }, calculatedColumns: { applyMode: 'deferred', + dataTypes: ['currency', 'number', 'text', 'boolean'], }, defaultColDef: { flex: 1, diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-column-groups/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-column-groups/main.ts index a9a9474d850..b189dd38520 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-column-groups/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-column-groups/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, ColGroupDef, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, ColGroupDef, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, @@ -34,7 +34,10 @@ type QuarterlyRevenueRow = { type QuarterField = Exclude; -const formatter = (params: ValueFormatterParams, formattedString: string): string => { +const formatter = ( + params: ValueFormatterLiteParams, + format: (value: number) => string +): string => { const { value } = params; if (value == null) { return ''; @@ -44,22 +47,21 @@ const formatter = (params: ValueFormatterParams, fo return String(value); } - return formattedString; + return format(value); }; -const currencyFormatter = (params: ValueFormatterParams) => - formatter(params, `$${(params.value ?? '').toLocaleString()}`); +const currencyFormatter = (params: ValueFormatterLiteParams) => + formatter(params, (value) => `$${value.toLocaleString()}`); -const percentageFormatter = (params: ValueFormatterParams) => - formatter(params, `${(params.value ?? 0).toLocaleString(undefined, { maximumFractionDigits: 1 })}%`); +const percentageFormatter = (params: ValueFormatterLiteParams) => + formatter(params, (value) => `${(value * 100).toLocaleString(undefined, { maximumFractionDigits: 1 })}%`); const quarterColumn = (field: QuarterField): ColDef => ({ field, colId: field, headerName: field.slice(0, 2).toUpperCase(), columnGroupShow: 'open', - cellDataType: 'number', - valueFormatter: currencyFormatter, + cellDataType: 'currency', }); const columnDefs: (ColDef | ColGroupDef)[] = [ @@ -77,8 +79,7 @@ const columnDefs: (ColDef | ColGroupDef | ColGroupDef | ColGroupDef = { columnDefs, rowData, - calculatedColumns: true, + dataTypeDefinitions: { + currency: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: currencyFormatter, + }, + percentage: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: percentageFormatter, + }, + }, + calculatedColumns: { + dataTypes: ['currency', 'percentage', 'number', 'text', 'boolean'], + }, defaultColDef: { minWidth: 120, flex: 1, diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-data-types/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-data-types/main.ts index b56064baa9f..223044a4877 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-data-types/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-data-types/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, @@ -26,7 +26,7 @@ type SalesRow = { cost: number; }; -const currencyFormatter = (params: ValueFormatterParams): string => { +const currencyFormatter = (params: ValueFormatterLiteParams): string => { const { value } = params; if (value == null) { return ''; diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-helper-lists/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-helper-lists/main.ts index 1476c76f615..48b609efd4c 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-helper-lists/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-helper-lists/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, @@ -26,7 +26,7 @@ type SalesRow = { cost: number; }; -const formatter = (params: ValueFormatterParams, formattedString: string): string => { +const currencyFormatter = (params: ValueFormatterLiteParams): string => { const { value } = params; if (value == null) { return ''; @@ -36,23 +36,19 @@ const formatter = (params: ValueFormatterParams, formattedStri return String(value); } - return formattedString; + return `$${value.toLocaleString()}`; }; -const currencyFormatter = (params: ValueFormatterParams) => - formatter(params, `$${(params.value ?? '').toLocaleString()}`); - const columnDefs: ColDef[] = [ { field: 'product', flex: 1.3 }, - { field: 'revenue', valueFormatter: currencyFormatter }, - { field: 'cost', valueFormatter: currencyFormatter }, + { field: 'revenue', cellDataType: 'currency' }, + { field: 'cost', cellDataType: 'currency' }, { colId: 'profit', headerName: 'Profit', calculatedExpression: '[revenue] - [cost]', - cellDataType: 'number', + cellDataType: 'currency', filter: 'agNumberColumnFilter', - valueFormatter: currencyFormatter, }, ]; @@ -82,8 +78,16 @@ const rowData: SalesRow[] = [ const gridOptions: GridOptions = { columnDefs, rowData, + dataTypeDefinitions: { + currency: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: currencyFormatter, + }, + }, calculatedColumns: { expressionPickers: ['columns'], + dataTypes: ['currency', 'number', 'text', 'boolean'], }, defaultColDef: { flex: 1, diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-highlighting/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-highlighting/main.ts index 68019800bcc..f53e27fbf18 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-highlighting/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-highlighting/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, @@ -26,7 +26,7 @@ type SalesRow = { cost: number; }; -const formatter = (params: ValueFormatterParams, formattedString: string): string => { +const currencyFormatter = (params: ValueFormatterLiteParams): string => { const { value } = params; if (value == null) { return ''; @@ -36,23 +36,19 @@ const formatter = (params: ValueFormatterParams, formattedStri return String(value); } - return formattedString; + return `$${value.toLocaleString()}`; }; -const currencyFormatter = (params: ValueFormatterParams) => - formatter(params, `$${(params.value ?? '').toLocaleString()}`); - const columnDefs: ColDef[] = [ { field: 'product', flex: 1.3 }, - { field: 'revenue', valueFormatter: currencyFormatter }, - { field: 'cost', valueFormatter: currencyFormatter }, + { field: 'revenue', cellDataType: 'currency' }, + { field: 'cost', cellDataType: 'currency' }, { colId: 'profit', headerName: 'Profit', calculatedExpression: '[revenue] - [cost]', - cellDataType: 'number', + cellDataType: 'currency', filter: 'agNumberColumnFilter', - valueFormatter: currencyFormatter, }, ]; @@ -82,8 +78,16 @@ const rowData: SalesRow[] = [ const gridOptions: GridOptions = { columnDefs, rowData, + dataTypeDefinitions: { + currency: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: currencyFormatter, + }, + }, calculatedColumns: { suppressColumnHighlighting: true, + dataTypes: ['currency', 'number', 'text', 'boolean'], }, defaultColDef: { flex: 1, diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-row-groups/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-row-groups/main.ts index 8868eda8225..7b5b7550375 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-row-groups/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-row-groups/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, @@ -28,7 +28,7 @@ type SalesRow = { cost: number; }; -const formatter = (params: ValueFormatterParams, formattedString: string): string => { +const formatter = (params: ValueFormatterLiteParams, format: (value: number) => string): string => { const { value } = params; if (value == null) { return ''; @@ -38,14 +38,14 @@ const formatter = (params: ValueFormatterParams, formattedStri return String(value); } - return formattedString; + return format(value); }; -const currencyFormatter = (params: ValueFormatterParams) => - formatter(params, `$${(params.value ?? '').toLocaleString()}`); +const currencyFormatter = (params: ValueFormatterLiteParams) => + formatter(params, (value) => `$${value.toLocaleString()}`); -const percentageFormatter = (params: ValueFormatterParams) => - formatter(params, `${Math.round((params.value ?? 0) * 100)}%`); +const percentageFormatter = (params: ValueFormatterLiteParams) => + formatter(params, (value) => `${Math.round(value * 100)}%`); const columnDefs: ColDef[] = [ { field: 'productType', rowGroup: true, hide: true }, @@ -53,12 +53,12 @@ const columnDefs: ColDef[] = [ { field: 'revenue', aggFunc: 'sum', - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, { field: 'cost', aggFunc: 'sum', - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, { colId: 'profit', @@ -66,17 +66,15 @@ const columnDefs: ColDef[] = [ calculatedExpression: '[revenue] - [cost]', // aggFunc lets the calculated column aggregate its per-leaf results onto group rows. aggFunc: 'sum', - cellDataType: 'number', + cellDataType: 'currency', filter: 'agNumberColumnFilter', - valueFormatter: currencyFormatter, }, { colId: 'margin', headerName: 'Margin', // No aggFunc: a ratio does not aggregate, so margin evaluates on leaf rows and is blank on groups. calculatedExpression: '[profit] / [revenue]', - cellDataType: 'number', - valueFormatter: percentageFormatter, + cellDataType: 'percentage', }, ]; @@ -106,7 +104,21 @@ const rowData: SalesRow[] = [ const gridOptions: GridOptions = { columnDefs, rowData, - calculatedColumns: true, + dataTypeDefinitions: { + currency: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: currencyFormatter, + }, + percentage: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: percentageFormatter, + }, + }, + calculatedColumns: { + dataTypes: ['currency', 'percentage', 'number', 'text', 'boolean'], + }, defaultColDef: { flex: 1, minWidth: 130, diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns/main.ts index f9764fb1d2a..2a851e8913c 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns/main.ts @@ -1,4 +1,4 @@ -import type { ColDef, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import type { ColDef, GridOptions, ValueFormatterLiteParams } from 'ag-grid-community'; import { ClientSideRowModelModule, ModuleRegistry, @@ -28,7 +28,7 @@ type SalesRow = { cost: number; }; -const formatter = (params: ValueFormatterParams, formattedString: string): string => { +const currencyFormatter = (params: ValueFormatterLiteParams): string => { const { value } = params; if (value == null) { return ''; @@ -38,32 +38,28 @@ const formatter = (params: ValueFormatterParams, formattedStri return String(value); } - return formattedString; + return `$${value.toLocaleString()}`; }; -const currencyFormatter = (params: ValueFormatterParams) => - formatter(params, `$${(params.value ?? '').toLocaleString()}`); - const columnDefs: ColDef[] = [ { field: 'product', flex: 1 }, { field: 'revenue', editable: true, - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, { field: 'cost', editable: true, - valueFormatter: currencyFormatter, + cellDataType: 'currency', }, { colId: 'profit', headerName: 'Profit', calculatedExpression: '[revenue] - [cost]', - cellDataType: 'number', + cellDataType: 'currency', sortable: true, filter: 'agNumberColumnFilter', - valueFormatter: currencyFormatter, }, ]; @@ -93,7 +89,16 @@ const rowData: SalesRow[] = [ const gridOptions: GridOptions = { columnDefs, rowData, - calculatedColumns: true, + dataTypeDefinitions: { + currency: { + baseDataType: 'number', + extendsDataType: 'number', + valueFormatter: currencyFormatter, + }, + }, + calculatedColumns: { + dataTypes: ['currency', 'number', 'text', 'boolean'], + }, defaultColDef: { flex: 1, minWidth: 130, diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc b/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc index 243d53e5507..fa8a9221626 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc @@ -32,6 +32,8 @@ Application-declared calculated columns must define a `colId` explicitly. {% gridExampleRunner title="Calculated Columns" name="calculated-columns" /%} +Register [Cell Data Types](./cell-data-types/) to give users formatting options to pick from when they create a column. The example above provides a `currency` type — see [dataTypes](#datatypes). + Calculated expressions do not need a leading equals sign (`=`). The value inside brackets must match a column `colId`, which defaults to the `field` when no explicit `colId` is provided. Calculated Columns are supported with all row models. Same-row bracket references are reliable everywhere. Cross-row and range references depend on the referenced rows being loaded in the current row model. @@ -91,9 +93,21 @@ const gridOptions = { calculatedColumns: { dataTypes: ['currency', 'number', 'boolean'], }, + columnDefs: [ + { field: 'revenue', cellDataType: 'currency' }, + { field: 'cost', cellDataType: 'currency' }, + { + colId: 'profit', + headerName: 'Profit', + calculatedExpression: '[revenue] - [cost]', + cellDataType: 'currency', + }, + ], }; ``` +Applying the type to your own columns as well means a user who picks **Currency** in the dialog gets the same formatting as the columns you declared. A `valueFormatter` on a column definition cannot be selected in the dialog, so it is not available to users. + Built-in types use the grid's locale text, while custom type names are converted to readable labels in the dialog. If a selected value does not match a registered [Cell Data Type](./cell-data-types/), the grid's normal `cellDataType` validation applies when the calculated column is created. {% gridExampleRunner title="Dialog Data Types" name="calculated-columns-dialog-data-types" /%} @@ -162,13 +176,12 @@ const gridOptions = { { colId: 'profit', calculatedExpression: '[revenue] - [cost]', - cellDataType: 'number', + cellDataType: 'currency', }, { colId: 'margin', calculatedExpression: '[profit] / [revenue]', - cellDataType: 'number', - valueFormatter: params => `${Math.round(params.value * 100)}%`, + cellDataType: 'percentage', }, { colId: 'status', @@ -179,6 +192,8 @@ const gridOptions = { }; ``` +The `currency` and `percentage` types are registered with `dataTypeDefinitions`, as shown in [dataTypes](#datatypes). + {% gridExampleRunner title="Advanced Calculated Columns" name="calculated-columns-advanced" /%} Header cells and grid cells for calculated columns include the `ag-calculated-column` CSS class; use it for custom styling. diff --git a/documentation/ag-grid-docs/src/content/docs/column-headers/index.mdoc b/documentation/ag-grid-docs/src/content/docs/column-headers/index.mdoc index 05bc50ac975..f79327cdefa 100644 --- a/documentation/ag-grid-docs/src/content/docs/column-headers/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/column-headers/index.mdoc @@ -50,6 +50,8 @@ Editable columns can be renamed via: Editable column groups can be renamed via the **Edit Column Name** item in the group header right-click menu or the Columns Tool Panel context menu. +The **Edit Column Name** item is never offered for [calculated columns](./calculated-columns/), even when `headerNameEditable` is set; rename a calculated column from its **Edit Calculated Column** dialog instead. + Committing an empty value sets an empty header name; the header reverts to its Column Definition default only when the edit is cleared programmatically, such as `resetColumnState()`. Edited column names are persisted as part of [Column State](./column-state/) and [Grid State](./grid-state/); edited group names are persisted as part of Grid State. Both survive save and restore. {% note %} diff --git a/documentation/ag-grid-docs/src/content/docs/column-menu/index.mdoc b/documentation/ag-grid-docs/src/content/docs/column-menu/index.mdoc index 707806e0b43..8482ecde72e 100644 --- a/documentation/ag-grid-docs/src/content/docs/column-menu/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/column-menu/index.mdoc @@ -74,6 +74,7 @@ The following is a list of all the default built-in menu items with the rules ab - `sortDescending`: Sort the column in descending order. Not shown when `columnMenu = 'legacy'` or the column is already sorted in descending order. - `sortUnSort`: Clear the sort on the column. Not shown when `columnMenu = 'legacy'` or the column is not sorted. - `calculatedColumn`: Show the Calculated Columns options. If the column selected is a Calculated Column, the menu will show options to edit and remove the column. +- `editColumnName`: Rename the column header. Only shown when `headerNameEditable` is set on the column, and never on a calculated column, which is renamed via its **Edit Calculated Column** dialog instead. - `columnFilter`: Show the column filter. Not shown when `columnMenu = 'legacy'`, a filter is not enabled, or the header filter button or floating filter button are displayed. - `columnChooser`: Show the column chooser. Not shown when `columnMenu = 'legacy'`. - `pinSubMenu`: Sub-menu for pinning. Always shown. diff --git a/documentation/ag-grid-docs/src/content/docs/filter-advanced/index.mdoc b/documentation/ag-grid-docs/src/content/docs/filter-advanced/index.mdoc index 8be328b2673..e04e74647f4 100644 --- a/documentation/ag-grid-docs/src/content/docs/filter-advanced/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/filter-advanced/index.mdoc @@ -275,7 +275,7 @@ All of the [Cell Data Types](./cell-data-types) are supported in the Advanced Fi - **Text** - The value in the input is compared against the cell value before any [Value Formatters](./value-formatters/) are applied (similar to the [Text Filter](./filter-text/)). To change the value being compared against, a [Filter Value Getter](./filter-text/#text-filter-values) can be used. - **Number** - The value in the input is compared against the cell value (like in the [Number Filter](./filter-number/)). -- **BigInt** - The value in the input is parsed as a `bigint` (decimal integer syntax only, optional trailing `n`) and compared against the cell value (like in the [BigInt Filter](./filter-bigint/)). A column's [`bigintParser`](./filter-bigint/#custom-parsing) is used here too, so custom formats such as hexadecimal are also accepted. +- **BigInt** - The value in the input is parsed as a `bigint` (decimal integer syntax only, optional trailing `n`) and compared against the cell value (like in the [BigInt Filter](./filter-bigint/)). A column's [`bigintParser`](./filter-bigint/#custom-parsing) is used here too, so custom formats such as hexadecimal are also accepted, and its `bigintFormatter` is used to display a stored operand in the filter expression and the Filter Builder. - **Boolean** - No values are displayed for booleans as the filter option is used instead. - **Date** and **Date Time** - The value in the input is converted to a `Date` via the [Value Parser](./value-parsers/#value-parser). - **Date String** and **Date Time String** - The value in the input is converted to a `Date` using the [Value Parser](./value-parsers/#value-parser) and the [Date Parser](./cell-data-types/#date-as-string). This is compared against the cell values, which are also converted using the Date Parser. diff --git a/documentation/ag-grid-docs/src/content/docs/filter-bigint/index.mdoc b/documentation/ag-grid-docs/src/content/docs/filter-bigint/index.mdoc index 1a1da04627d..a5bb780c95e 100644 --- a/documentation/ag-grid-docs/src/content/docs/filter-bigint/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/filter-bigint/index.mdoc @@ -46,6 +46,8 @@ The BigInt Filter accepts decimal integer syntax only: To accept other formats, such as hexadecimal, provide a `bigintParser` that converts the entered text to a `bigint` (return `null` for values it cannot parse). Pair it with `allowedCharPattern` so the extra characters can be typed into the filter input. The parsed value is what gets applied to filtering, and the same parser is used by the [Advanced Filter](./filter-advanced/) for `bigint` operands. +The filter model always stores the parsed value as a canonical decimal string, so provide a `bigintFormatter` — the inverse of the parser — to display stored values back in your own format. It is used by the [Floating Filter](./floating-filters/) and by the [Advanced Filter](./filter-advanced/) when displaying an operand, which means an entered value is echoed back in the formatter's format rather than exactly as typed. + ```{% frameworkTransform=true %} const gridOptions = { columnDefs: [ diff --git a/documentation/ag-grid-docs/src/content/docs/formulas/index.mdoc b/documentation/ag-grid-docs/src/content/docs/formulas/index.mdoc index 1e541759b77..fcec58e8ac0 100644 --- a/documentation/ag-grid-docs/src/content/docs/formulas/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/formulas/index.mdoc @@ -134,6 +134,25 @@ If the formula store is mutated outside the grid (for example, syncing from a ba During a [CSV Export](./csv-export), the grid exports the evaluated values of any formulas. During an [Excel Export](./excel-export), the grid exports the formulas themselves so Excel can evaluate them. +## Calculated Columns + +Formulas apply to individual cells: a user types an expression into one cell, and only that cell is affected. + +To derive a value for every row instead, use [Calculated Columns](./calculated-columns/). A calculated column defines one expression that evaluates against each row, referencing other columns by `colId`. Users can add their own calculated columns at runtime through the Column Menu. + +```{% frameworkTransform=true %} +const gridOptions = { + calculatedColumns: true, + columnDefs: [ + { field: 'revenue' }, + { field: 'cost' }, + { colId: 'profit', headerName: 'Profit', calculatedExpression: '[revenue] - [cost]' }, + ], +}; +``` + +Use Formulas for ad-hoc, per-cell expressions; use [Calculated Columns](./calculated-columns/) for a derived value that applies to every row. + ## See also - [Formula Editor Component](./formula-editor-component/) – The rich editing experience for formula cells, including autocomplete and range selection tools. diff --git a/external/ag-website-shared/src/components/consent-fields/ConsentCheckbox.tsx b/external/ag-website-shared/src/components/consent-fields/ConsentCheckbox.tsx index d37868a1144..0a41bf568a7 100644 --- a/external/ag-website-shared/src/components/consent-fields/ConsentCheckbox.tsx +++ b/external/ag-website-shared/src/components/consent-fields/ConsentCheckbox.tsx @@ -14,13 +14,27 @@ interface Props { } export const ConsentCheckbox: FunctionComponent = ({ id, label, error, nested, inputProps }: Props) => { + const errorId = `${id}-error`; + return (
-
{error &&

{error}

}
+
+ {error && ( + + )} +
); }; diff --git a/external/ag-website-shared/src/components/contact-form/ContactForm.tsx b/external/ag-website-shared/src/components/contact-form/ContactForm.tsx index f23f810d5e2..bf23f098b9e 100644 --- a/external/ag-website-shared/src/components/contact-form/ContactForm.tsx +++ b/external/ag-website-shared/src/components/contact-form/ContactForm.tsx @@ -99,7 +99,10 @@ export const ContactForm: FunctionComponent = ({ setValue, formState: { errors }, } = useForm({ - mode: 'onBlur', + // Validate on first blur, then re-validate on every change, so a message + // clears the moment the field is corrected. Plain 'onBlur' only re-validates + // on the next blur, which leaves a stale error next to a ticked checkbox. + mode: 'onTouched', }); // Email tracking consent only applies to France and Italy, so hiding it must also diff --git a/external/ag-website-shared/src/components/product-dropdown/ProductDropdown.module.scss b/external/ag-website-shared/src/components/product-dropdown/ProductDropdown.module.scss index 0dc3f7bf443..d5c3a576d72 100644 --- a/external/ag-website-shared/src/components/product-dropdown/ProductDropdown.module.scss +++ b/external/ag-website-shared/src/components/product-dropdown/ProductDropdown.module.scss @@ -130,7 +130,7 @@ left: 0; // Fixed width gives each product row enough room for its title and // description without truncation. - width: 720px; + width: 800px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } diff --git a/external/ag-website-shared/src/components/trial-licence-form/TrialLicenceForm.tsx b/external/ag-website-shared/src/components/trial-licence-form/TrialLicenceForm.tsx index f2fc8d4eab3..f8288076478 100644 --- a/external/ag-website-shared/src/components/trial-licence-form/TrialLicenceForm.tsx +++ b/external/ag-website-shared/src/components/trial-licence-form/TrialLicenceForm.tsx @@ -254,6 +254,7 @@ function useTrialForm({ submitUrl }: Props) { } }, [ + submitUrl, validatedEmailError, validatedFirstNameError, validatedLastNameError, @@ -292,7 +293,7 @@ function useTrialForm({ submitUrl }: Props) { }; } -export const TrialLicenceForm: FunctionComponent = ({ submitUrl }: Props) => { +export const TrialLicenceForm: FunctionComponent = ({ submitUrl }: Props) => { const { formState, formError, diff --git a/external/ag-website-shared/src/components/trial-licence-form/TrialLicenceFormStudio.tsx b/external/ag-website-shared/src/components/trial-licence-form/TrialLicenceFormStudio.tsx index d76f77d2e1e..81aff8990d0 100644 --- a/external/ag-website-shared/src/components/trial-licence-form/TrialLicenceFormStudio.tsx +++ b/external/ag-website-shared/src/components/trial-licence-form/TrialLicenceFormStudio.tsx @@ -1,3 +1,8 @@ +import { ConsentCheckbox } from '@ag-website-shared/components/consent-fields/ConsentCheckbox'; +import { + CONSENT_LABELS, + DATA_PROCESSING_CONSENT_REQUIRED, +} from '@ag-website-shared/components/consent-fields/consentMessages'; import { Icon } from '@ag-website-shared/components/icon/Icon'; import { TRIAL_LICENCE_FORM_URL, ZI_FORM_ID } from '@constants'; import { trackTrialLicenseFormError, trackTrialLicenseFormSuccess } from '@utils/analytics'; @@ -90,22 +95,55 @@ function useRequiredValidation(initialValue: string = '') { }; } +function useCheckbox(initialValue: boolean = false) { + const [checked, setChecked] = useState(initialValue); + + const handleCheckedChange: ChangeEventHandler = useCallback((e) => { + setChecked(e.target.checked); + }, []); + + return { + checked, + setChecked, + handleCheckedChange, + }; +} + async function submitTrialLicenceFormData({ submitUrl = TRIAL_LICENCE_FORM_URL, firstName, lastName, email, company, + dataProcessingConsent, + marketingEmailConsent, + emailTrackingConsent, + franceOrItaly, }: { submitUrl?: string; firstName: string; lastName: string; email: string; company: string; + dataProcessingConsent: boolean; + marketingEmailConsent: boolean; + emailTrackingConsent: boolean; + franceOrItaly: boolean; }) { const response = await fetch(submitUrl, { method: 'POST', - body: JSON.stringify({ data: { firstName, lastName, email, company } }), + body: JSON.stringify({ + data: { + firstName, + lastName, + email, + company, + dataProcessingConsent, + marketingEmailConsent, + emailTrackingConsent, + franceOrItaly, + }, + }), headers: { 'Content-Type': 'application/json', }, @@ -136,12 +174,35 @@ function useTrialForm({ submitUrl }: Props) { } = useRequiredValidation(); const lastNameError = wasValidated && validatedLastNameError ? validatedLastNameError : ''; + const { checked: dataProcessingConsent, handleCheckedChange: handleDataProcessingConsentChange } = useCheckbox(); + const dataProcessingConsentError = wasValidated && !dataProcessingConsent ? DATA_PROCESSING_CONSENT_REQUIRED : ''; + + const { checked: marketingEmailConsent, handleCheckedChange: handleMarketingEmailConsentChange } = useCheckbox(); + + const { + checked: emailTrackingConsent, + setChecked: setEmailTrackingConsent, + handleCheckedChange: handleEmailTrackingConsentChange, + } = useCheckbox(); + + const { checked: isFranceOrItaly, setChecked: setIsFranceOrItaly } = useCheckbox(); + + // Email tracking consent only applies to France and Italy, so hiding it must also + // withdraw it — never submit a consent the visitor can no longer see + const handleFranceOrItalyChange: ChangeEventHandler = useCallback((e) => { + const { checked } = e.target; + setIsFranceOrItaly(checked); + if (!checked) { + setEmailTrackingConsent(false); + } + }, []); + const handleFormSubmit: FormEventHandler = useCallback( async (e) => { e.preventDefault(); setWasValidated(true); - if (validatedEmailError || validatedFirstNameError || validatedLastNameError) { + if (validatedEmailError || validatedFirstNameError || validatedLastNameError || !dataProcessingConsent) { setFormState('error'); return; } @@ -153,7 +214,17 @@ function useTrialForm({ submitUrl }: Props) { try { const company = (document.getElementById('company') as HTMLInputElement)?.value || ''; - const response = await submitTrialLicenceFormData({ submitUrl, firstName, lastName, email, company }); + const response = await submitTrialLicenceFormData({ + submitUrl, + firstName, + lastName, + email, + company, + dataProcessingConsent, + marketingEmailConsent, + emailTrackingConsent, + franceOrItaly: isFranceOrItaly, + }); if (response.error) { setFormState('error'); @@ -182,7 +253,19 @@ function useTrialForm({ submitUrl }: Props) { setFormState('error'); } }, - [validatedEmailError, validatedFirstNameError, validatedLastNameError, firstName, lastName, email] + [ + submitUrl, + validatedEmailError, + validatedFirstNameError, + validatedLastNameError, + firstName, + lastName, + email, + dataProcessingConsent, + marketingEmailConsent, + emailTrackingConsent, + isFranceOrItaly, + ] ); return { @@ -197,11 +280,20 @@ function useTrialForm({ submitUrl }: Props) { lastName, lastNameError, handleLastNameChange, + dataProcessingConsent, + dataProcessingConsentError, + handleDataProcessingConsentChange, + marketingEmailConsent, + handleMarketingEmailConsentChange, + emailTrackingConsent, + handleEmailTrackingConsentChange, + isFranceOrItaly, + handleFranceOrItalyChange, handleFormSubmit, }; } -export const TrialLicenceFormStudio: FunctionComponent = ({ submitUrl }: Props) => { +export const TrialLicenceFormStudio: FunctionComponent = ({ submitUrl }: Props) => { const { formState, formError, @@ -214,9 +306,18 @@ export const TrialLicenceFormStudio: FunctionComponent = ({ submitUrl }: Props) lastName, lastNameError, handleLastNameChange, + dataProcessingConsent, + dataProcessingConsentError, + handleDataProcessingConsentChange, + marketingEmailConsent, + handleMarketingEmailConsentChange, + emailTrackingConsent, + handleEmailTrackingConsentChange, + isFranceOrItaly, + handleFranceOrItalyChange, handleFormSubmit, } = useTrialForm({ submitUrl }); - const hasFormError = Boolean(emailError || firstNameError || lastNameError); + const hasFormError = Boolean(emailError || firstNameError || lastNameError || dataProcessingConsentError); return (
@@ -271,6 +372,53 @@ export const TrialLicenceFormStudio: FunctionComponent = ({ submitUrl }: Props)

{emailError ? emailError : 'Email required'}

+
+ + + + + + + {isFranceOrItaly && ( + + )} +
+