Skip to content

Commit

Permalink
[DataGrid] Allow to customize label and value for singleSelect
Browse files Browse the repository at this point in the history
  • Loading branch information
m4theushw committed Jan 24, 2023
1 parent 8a711f2 commit 5887a3b
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 167 deletions.
83 changes: 41 additions & 42 deletions docs/pages/x/api/data-grid/grid-col-def.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GridBaseColDef } from '@mui/x-data-grid-pro/internals';
import { GridColDef } from '@mui/x-data-grid-pro';

export interface GridDataGeneratorContext {
/**
Expand All @@ -10,7 +10,7 @@ export interface GridDataGeneratorContext {
values?: Record<string, number>;
}

export interface GridColDefGenerator extends GridBaseColDef {
export type GridColDefGenerator = GridColDef & {
generateData?: (row: any, context: GridDataGeneratorContext) => any;

/**
Expand All @@ -23,4 +23,4 @@ export interface GridColDefGenerator extends GridBaseColDef {
* If `true`, the column will be marked as hidden in the `columnVisibilityModel`.
*/
hide?: boolean;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import {
GRID_DATE_COL_DEF,
GRID_DATETIME_COL_DEF,
} from '@mui/x-data-grid-pro';
import { buildWarning, GridStateColDef, isObject } from '@mui/x-data-grid/internals';
import {
buildWarning,
GridStateColDef,
GridSingleSelectColDef,
isObject,
isSingleSelectColDef,
} from '@mui/x-data-grid/internals';
import { GridExceljsProcessInput, ColumnsStylesInterface } from '../gridExcelExportInterface';
import { GridPrivateApiPremium } from '../../../../models/gridApiPremium';

Expand All @@ -23,7 +29,7 @@ const warnInvalidFormattedValue = buildWarning([
]);

const getFormattedValueOptions = (
colDef: GridColDef,
colDef: GridSingleSelectColDef,
valueOptions: ValueOptions[],
api: GridApi,
) => {
Expand Down Expand Up @@ -84,16 +90,17 @@ const serializeRow = (

switch (cellParams.colDef.type) {
case 'singleSelect': {
if (typeof cellParams.colDef.valueOptions === 'function') {
const castColumn = cellParams.colDef as GridSingleSelectColDef;
if (typeof castColumn.valueOptions === 'function') {
// If value option depends on the row, set specific options to the cell
// This dataValidation is buggy with LibreOffice and does not allow to have coma
const valueOptions = cellParams.colDef.valueOptions({ id, row, field: cellParams.field });
const formattedValueOptions = getFormattedValueOptions(
cellParams.colDef,
valueOptions,
api,
);
dataValidation[column.field] = {
const valueOptions = castColumn.valueOptions({
id,
row,
field: cellParams.field,
});
const formattedValueOptions = getFormattedValueOptions(castColumn, valueOptions, api);
dataValidation[castColumn.field] = {
type: 'list',
allowBlank: true,
formulae: [
Expand All @@ -104,23 +111,23 @@ const serializeRow = (
};
} else {
// If value option is defined for the column, refer to another sheet
dataValidation[column.field] = {
dataValidation[castColumn.field] = {
type: 'list',
allowBlank: true,
formulae: [defaultValueOptionsFormulae[column.field]],
formulae: [defaultValueOptionsFormulae[castColumn.field]],
};
}

const formattedValue = api.getCellParams(id, column.field).formattedValue;
const formattedValue = api.getCellParams(id, castColumn.field).formattedValue;
if (process.env.NODE_ENV !== 'production') {
if (String(cellParams.formattedValue) === '[object Object]') {
warnInvalidFormattedValue();
}
}
if (isObject<{ label: any }>(formattedValue)) {
row[column.field] = formattedValue?.label;
row[castColumn.field] = formattedValue?.label;
} else {
row[column.field] = formattedValue as any;
row[castColumn.field] = formattedValue as any;
}
break;
}
Expand Down Expand Up @@ -296,10 +303,10 @@ export async function buildExcel(

const columnsWithArrayValueOptions = columns.filter(
(column) =>
column.type === 'singleSelect' &&
isSingleSelectColDef(column) &&
column.valueOptions &&
typeof column.valueOptions !== 'function',
);
) as GridSingleSelectColDef[];
const defaultValueOptionsFormulae: { [field: string]: string } = {};

if (columnsWithArrayValueOptions.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
GridGroupingColDefOverride,
GridGroupNode,
} from '@mui/x-data-grid-pro';
import { GridColumnRawLookup } from '@mui/x-data-grid-pro/internals';
import { GridColumnRawLookup, isSingleSelectColDef } from '@mui/x-data-grid-pro/internals';
import { GridApiPremium } from '../../../models/gridApiPremium';
import { GridGroupingColumnFooterCell } from '../../../components/GridGroupingColumnFooterCell';
import { GridGroupingCriteriaCell } from '../../../components/GridGroupingCriteriaCell';
Expand Down Expand Up @@ -66,7 +66,7 @@ const getLeafProperties = (leafColDef: GridColDef): Partial<GridColDef> => ({
headerName: leafColDef.headerName ?? leafColDef.field,
sortable: leafColDef.sortable,
filterable: leafColDef.filterable,
valueOptions: leafColDef.valueOptions,
valueOptions: isSingleSelectColDef(leafColDef) ? leafColDef.valueOptions : undefined,
filterOperators: leafColDef.filterOperators?.map((operator) => ({
...operator,
getApplyFilterFn: (filterItem, column) => {
Expand Down Expand Up @@ -94,7 +94,7 @@ const getGroupingCriteriaProperties = (groupedByColDef: GridColDef, applyHeaderN
const properties: Partial<GridColDef> = {
sortable: groupedByColDef.sortable,
filterable: groupedByColDef.filterable,
valueOptions: groupedByColDef.valueOptions,
valueOptions: isSingleSelectColDef(groupedByColDef) ? groupedByColDef.valueOptions : undefined,
sortComparator: (v1, v2, cellParams1, cellParams2) => {
// We only want to sort the groups of the current grouping criteria
if (
Expand Down
24 changes: 19 additions & 5 deletions packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import { GRID_STRING_COL_DEF } from './gridStringColDef';
import { GridColTypeDef, ValueOptions } from '../models/colDef/gridColDef';
import { GridSingleSelectColDef, ValueOptions } from '../models/colDef/gridColDef';
import { renderEditSingleSelectCell } from '../components/cell/GridEditSingleSelectCell';
import { getGridSingleSelectOperators } from './gridSingleSelectOperators';
import { getLabelFromValueOption } from '../components/panel/filterPanel/filterPanelUtils';
import { isSingleSelectColDef } from '../components/panel/filterPanel/filterPanelUtils';

const isArrayOfObjects = (options: any): options is Array<{ value: any; label: string }> => {
return typeof options[0] === 'object';
};

export const GRID_SINGLE_SELECT_COL_DEF: GridColTypeDef = {
const defaultGetOptionValue = (value: ValueOptions) => {
return typeof value === 'object' ? value.value : value;
};

const defaultGetOptionLabel = (value: ValueOptions) => {
return typeof value === 'object' ? value.label : String(value);
};

export const GRID_SINGLE_SELECT_COL_DEF: Omit<GridSingleSelectColDef, 'field'> = {
...GRID_STRING_COL_DEF,
type: 'singleSelect',
getOptionLabel: defaultGetOptionLabel,
getOptionValue: defaultGetOptionValue,
valueFormatter(params) {
const { id, field, value, api } = params;
const colDef = params.api.getColumn(field);

if (!isSingleSelectColDef(colDef)) {
return '';
}

let valueOptions: Array<ValueOptions>;
if (typeof colDef.valueOptions === 'function') {
valueOptions = colDef.valueOptions!({ id, row: id ? api.getRow(id) : null, field });
Expand All @@ -31,11 +45,11 @@ export const GRID_SINGLE_SELECT_COL_DEF: GridColTypeDef = {
}

if (!isArrayOfObjects(valueOptions)) {
return getLabelFromValueOption(value);
return colDef.getOptionLabel!(value);
}

const valueOption = valueOptions.find((option) => option.value === value);
return valueOption ? getLabelFromValueOption(valueOption) : '';
return valueOption ? colDef.getOptionLabel!(valueOption) : '';
},
renderEditCell: renderEditSingleSelectCell,
filterOperators: getGridSingleSelectOperators(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ import { GridRenderEditCellParams } from '../../models/params/gridCellParams';
import { isEscapeKey } from '../../utils/keyboardUtils';
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import { GridEditModes } from '../../models/gridEditRowModel';
import { ValueOptions } from '../../models/colDef/gridColDef';
import { GridSingleSelectColDef, ValueOptions } from '../../models/colDef/gridColDef';
import {
getLabelFromValueOption,
getValueFromValueOptions,
isSingleSelectColDef,
} from '../panel/filterPanel/filterPanelUtils';
import { useGridApiContext } from '../../hooks/utils/useGridApiContext';

export interface GridEditSingleSelectCellProps
extends GridRenderEditCellParams,
Omit<SelectProps, 'id' | 'tabIndex' | 'value'> {
Omit<SelectProps, 'id' | 'tabIndex' | 'value'>,
Pick<GridSingleSelectColDef, 'getOptionLabel' | 'getOptionValue'> {
/**
* Callback called when the value is changed by the user.
* @param {SelectChangeEvent<any>} event The event source of the callback.
Expand All @@ -28,12 +29,6 @@ export interface GridEditSingleSelectCellProps
* If true, the select opens by default.
*/
initialOpen?: boolean;
/**
* Used to determine the text displayed for a given value option.
* @param {ValueOptions} value The current value option.
* @returns {string} The text to be displayed.
*/
getOptionLabel?: (value: ValueOptions) => string;
}

function isKeyboardEvent(event: any): event is React.KeyboardEvent {
Expand Down Expand Up @@ -61,7 +56,8 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) {
error,
onValueChange,
initialOpen = rootProps.editMode === GridEditModes.Cell,
getOptionLabel = getLabelFromValueOption,
getOptionLabel: getOptionLabelProp,
getOptionValue: getOptionValueProp,
...other
} = props;

Expand All @@ -73,18 +69,34 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) {
const baseSelectProps = rootProps.componentsProps?.baseSelect || {};
const isSelectNative = baseSelectProps.native ?? false;

let valueOptions: Array<ValueOptions>;
if (typeof colDef.valueOptions === 'function') {
valueOptions = colDef.valueOptions!({ id, row, field });
let resolvedColumn: GridSingleSelectColDef | null = null;
if (isSingleSelectColDef(colDef)) {
resolvedColumn = colDef;
}

const getOptionValue = getOptionValueProp || resolvedColumn?.getOptionValue!;
const getOptionLabel = getOptionLabelProp || resolvedColumn?.getOptionLabel!;

let valueOptions: Array<ValueOptions> | null = null;
if (typeof resolvedColumn?.valueOptions === 'function') {
valueOptions = resolvedColumn?.valueOptions!({ id, row, field });
} else {
valueOptions = colDef.valueOptions!;
valueOptions = resolvedColumn?.valueOptions!;
}

const handleChange: SelectProps['onChange'] = async (event) => {
if (!isSingleSelectColDef(colDef) || !valueOptions) {
return;
}

setOpen(false);
const target = event.target as HTMLInputElement;
// NativeSelect casts the value to a string.
const formattedTargetValue = getValueFromValueOptions(target.value, valueOptions);
const formattedTargetValue = getValueFromValueOptions(
target.value,
valueOptions,
getOptionValue,
);

if (onValueChange) {
await onValueChange(event, formattedTargetValue);
Expand Down Expand Up @@ -118,6 +130,10 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) {

const OptionComponent = isSelectNative ? 'option' : MenuItem;

if (!valueOptions || !resolvedColumn) {
return null;
}

return (
<rootProps.components.BaseSelect
ref={ref}
Expand All @@ -136,7 +152,7 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) {
{...rootProps.componentsProps?.baseSelect}
>
{valueOptions.map((valueOption) => {
const value = typeof valueOption === 'object' ? valueOption.value : valueOption;
const value = getOptionValue(valueOption);

return (
<OptionComponent key={value} value={value}>
Expand Down Expand Up @@ -175,11 +191,17 @@ GridEditSingleSelectCell.propTypes = {
*/
formattedValue: PropTypes.any,
/**
* Used to determine the text displayed for a given value option.
* Used to determine the label displayed for a given value option.
* @param {ValueOptions} value The current value option.
* @returns {string} The text to be displayed.
*/
getOptionLabel: PropTypes.func,
/**
* Used to determine the value used for a value option.
* @param {ValueOptions} value The current value option.
* @returns {string} The value to be used.
*/
getOptionValue: PropTypes.func,
/**
* If true, the cell is the active element.
*/
Expand Down
Loading

0 comments on commit 5887a3b

Please sign in to comment.