Skip to content

Commit

Permalink
SIMSBIOHUB 335: Tab Through Observation Edit (#1156)
Browse files Browse the repository at this point in the history
* Modified each custom renderEditCell component of the ObservationTable to manually set hasFocus. This allows the user to Tab forward to the next cell or Shift+Tab back to the previous.
* Wrapped some of the simpler TextField / DatePicker renders in their own functional components so that the same hasFocus pattern could be used.
  • Loading branch information
GrahamS-Quartech authored and KjartanE committed Nov 6, 2023
1 parent aefe64f commit c882b79
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 70 deletions.
33 changes: 33 additions & 0 deletions app/src/components/data-grid/TextFieldDataGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import TextField, { TextFieldProps } from '@mui/material/TextField/TextField';
import useEnhancedEffect from '@mui/material/utils/useEnhancedEffect';
import { GridRenderEditCellParams, GridValidRowModel } from '@mui/x-data-grid';
import { useRef } from 'react';

interface ITextFieldCustomValidation<DataGridType extends GridValidRowModel> {
textFieldProps: TextFieldProps;
dataGridProps: GridRenderEditCellParams<DataGridType>;
}

const TextFieldDataGrid = <DataGridType extends GridValidRowModel>({
textFieldProps,
dataGridProps
}: ITextFieldCustomValidation<DataGridType>) => {
const ref = useRef<HTMLInputElement>();
useEnhancedEffect(() => {
if (dataGridProps.hasFocus) {
ref.current?.focus();
}
}, [dataGridProps.hasFocus]);
return (
<TextField
inputRef={ref}
value={dataGridProps.value ?? ''}
variant="outlined"
type="text"
inputProps={{ inputMode: 'numeric' }}
{...textFieldProps}
/>
);
};

export default TextFieldDataGrid;
48 changes: 48 additions & 0 deletions app/src/components/data-grid/TimePickerDataGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import useEnhancedEffect from '@mui/material/utils/useEnhancedEffect';
import { GridRenderEditCellParams, GridValidRowModel, useGridApiContext } from '@mui/x-data-grid';
import { LocalizationProvider, TimePicker, TimePickerProps } from '@mui/x-date-pickers';
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
import moment from 'moment';
import { useRef } from 'react';

interface ITimePickerDataGridProps<DataGridType extends GridValidRowModel> {
dateFieldProps?: TimePickerProps<any>;
dataGridProps: GridRenderEditCellParams<DataGridType>;
}

const TimePickerDataGrid = <DataGridType extends GridValidRowModel>({
dateFieldProps,
dataGridProps
}: ITimePickerDataGridProps<DataGridType>) => {
const apiRef = useGridApiContext();
const ref = useRef<HTMLInputElement>(null);
useEnhancedEffect(() => {
if (dataGridProps.hasFocus) {
ref.current?.focus();
}
}, [dataGridProps.hasFocus]);
return (
<LocalizationProvider dateAdapter={AdapterMoment}>
<TimePicker
inputRef={ref}
value={(dataGridProps.value && moment(dataGridProps.value, 'HH:mm:ss')) || null}
onChange={(value) => {
apiRef?.current.setEditCellValue({ id: dataGridProps.id, field: dataGridProps.field, value: value });
}}
onAccept={(value) => {
apiRef?.current.setEditCellValue({
id: dataGridProps.id,
field: dataGridProps.field,
value: value?.format('HH:mm:ss')
});
}}
views={['hours', 'minutes', 'seconds']}
timeSteps={{ hours: 1, minutes: 1, seconds: 1 }}
ampm={false}
{...dateFieldProps}
/>
</LocalizationProvider>
);
};

export default TimePickerDataGrid;
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import TextField from '@mui/material/TextField';
import useEnhancedEffect from '@mui/material/utils/useEnhancedEffect';
import { GridRenderCellParams, GridValidRowModel, useGridApiContext } from '@mui/x-data-grid';
import { IAutocompleteDataGridOption } from 'components/data-grid/autocomplete/AutocompleteDataGrid.interface';
import { DebouncedFunc } from 'lodash-es';
import { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';

export interface IAsyncAutocompleteDataGridEditCell<
DataGridType extends GridValidRowModel,
Expand Down Expand Up @@ -52,6 +53,13 @@ const AsyncAutocompleteDataGridEditCell = <DataGridType extends GridValidRowMode

const apiRef = useGridApiContext();

const ref = useRef<HTMLInputElement>();

useEnhancedEffect(() => {
if (dataGridProps.hasFocus) {
ref.current?.focus();
}
}, [dataGridProps.hasFocus]);
// The current data grid value
const dataGridValue = dataGridProps.value;
// The input field value
Expand Down Expand Up @@ -168,6 +176,7 @@ const AsyncAutocompleteDataGridEditCell = <DataGridType extends GridValidRowMode
renderInput={(params) => (
<TextField
{...params}
inputRef={ref}
size="small"
variant="outlined"
fullWidth
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import useEnhancedEffect from '@mui/material/utils/useEnhancedEffect';
import { GridRenderCellParams, GridValidRowModel, useGridApiContext } from '@mui/x-data-grid';
import { IAutocompleteDataGridOption } from 'components/data-grid/autocomplete/AutocompleteDataGrid.interface';
import { useRef } from 'react';

export interface IAutocompleteDataGridEditCellProps<
DataGridType extends GridValidRowModel,
Expand Down Expand Up @@ -45,6 +47,14 @@ const AutocompleteDataGridEditCell = <DataGridType extends GridValidRowModel, Va

const apiRef = useGridApiContext();

const ref = useRef<HTMLInputElement>();

useEnhancedEffect(() => {
if (dataGridProps.hasFocus) {
ref.current?.focus();
}
}, [dataGridProps.hasFocus]);

// The current data grid value
const dataGridValue = dataGridProps.value;

Expand Down Expand Up @@ -98,6 +108,7 @@ const AutocompleteDataGridEditCell = <DataGridType extends GridValidRowModel, Va
renderInput={(params) => (
<TextField
{...params}
inputRef={ref}
size="small"
variant="outlined"
fullWidth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import AsyncAutocompleteDataGridEditCell from 'components/data-grid/autocomplete
import { IAutocompleteDataGridOption } from 'components/data-grid/autocomplete/AutocompleteDataGrid.interface';
import { useBiohubApi } from 'hooks/useBioHubApi';
import debounce from 'lodash-es/debounce';
import { useMemo } from 'react';
import React, { useMemo } from 'react';

export interface ITaxonomyDataGridCellProps<DataGridType extends GridValidRowModel> {
dataGridProps: GridRenderEditCellParams<DataGridType>;
Expand Down
113 changes: 45 additions & 68 deletions app/src/features/surveys/observations/ObservationsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@ import Box from '@mui/material/Box';
import { cyan, grey } from '@mui/material/colors';
import IconButton from '@mui/material/IconButton';
import Skeleton from '@mui/material/Skeleton';
import TextField from '@mui/material/TextField';
import {
DataGrid,
GridColDef,
GridEventListener,
GridInputRowSelectionModel,
GridRowModelUpdate
} from '@mui/x-data-grid';
import { LocalizationProvider, TimePicker } from '@mui/x-date-pickers';
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment';
import AutocompleteDataGridEditCell from 'components/data-grid/autocomplete/AutocompleteDataGridEditCell';
import AutocompleteDataGridViewCell from 'components/data-grid/autocomplete/AutocompleteDataGridViewCell';
import ConditionalAutocompleteDataGridEditCell from 'components/data-grid/conditional-autocomplete/ConditionalAutocompleteDataGridEditCell';
import ConditionalAutocompleteDataGridViewCell from 'components/data-grid/conditional-autocomplete/ConditionalAutocompleteDataGridViewCell';
import TaxonomyDataGridEditCell from 'components/data-grid/taxonomy/TaxonomyDataGridEditCell';
import TaxonomyDataGridViewCell from 'components/data-grid/taxonomy/TaxonomyDataGridViewCell';
import TextFieldDataGrid from 'components/data-grid/TextFieldDataGrid';
import TimePickerDataGrid from 'components/data-grid/TimePickerDataGrid';
import YesNoDialog from 'components/dialog/YesNoDialog';
import { ObservationsTableI18N } from 'constants/i18n';
import { CodesContext } from 'contexts/codesContext';
Expand Down Expand Up @@ -273,23 +272,22 @@ const ObservationsTable = (props: ISpeciesObservationTableProps) => {
align: 'right',
renderEditCell: (params) => {
return (
<TextField
onChange={(event) => {
if (!/^\d{0,7}$/.test(event.target.value)) {
// If the value is not a number, return
return;
}
<TextFieldDataGrid
dataGridProps={params}
textFieldProps={{
onChange: (event) => {
if (!/^\d{0,7}$/.test(event.target.value)) {
// If the value is not a number, return
return;
}

apiRef?.current.setEditCellValue({
id: params.id,
field: params.field,
value: event.target.value
});
apiRef?.current.setEditCellValue({
id: params.id,
field: params.field,
value: event.target.value
});
}
}}
value={params.value ?? ''}
variant="outlined"
type="text"
inputProps={{ inputMode: 'numeric' }}
/>
);
}
Expand Down Expand Up @@ -336,26 +334,7 @@ const ObservationsTable = (props: ISpeciesObservationTableProps) => {
return <>{params.value}</>;
},
renderEditCell: (params) => {
return (
<LocalizationProvider dateAdapter={AdapterMoment}>
<TimePicker
value={(params.value && moment(params.value, 'HH:mm:ss')) || null}
onChange={(value) => {
apiRef?.current.setEditCellValue({ id: params.id, field: params.field, value: value });
}}
onAccept={(value) => {
apiRef?.current.setEditCellValue({
id: params.id,
field: params.field,
value: value?.format('HH:mm:ss')
});
}}
views={['hours', 'minutes', 'seconds']}
timeSteps={{ hours: 1, minutes: 1, seconds: 1 }}
ampm={false}
/>
</LocalizationProvider>
);
return <TimePickerDataGrid dataGridProps={params} />;
}
},
{
Expand All @@ -378,23 +357,22 @@ const ObservationsTable = (props: ISpeciesObservationTableProps) => {
},
renderEditCell: (params) => {
return (
<TextField
onChange={(event) => {
if (!/^-?\d{0,3}(?:\.\d{0,12})?$/.test(event.target.value)) {
// If the value is not a subset of a legal latitude value, prevent the value from being applied
return;
}
<TextFieldDataGrid
dataGridProps={params}
textFieldProps={{
onChange: (event) => {
if (!/^-?\d{0,3}(?:\.\d{0,12})?$/.test(event.target.value)) {
// If the value is not a subset of a legal latitude value, prevent the value from being applied
return;
}

apiRef?.current.setEditCellValue({
id: params.id,
field: params.field,
value: event.target.value
});
apiRef?.current.setEditCellValue({
id: params.id,
field: params.field,
value: event.target.value
});
}
}}
value={params.value ?? ''}
variant="outlined"
type="text"
inputProps={{ inputMode: 'numeric' }}
/>
);
}
Expand All @@ -419,23 +397,22 @@ const ObservationsTable = (props: ISpeciesObservationTableProps) => {
},
renderEditCell: (params) => {
return (
<TextField
onChange={(event) => {
if (!/^-?\d{0,3}(?:\.\d{0,12})?$/.test(event.target.value)) {
// If the value is not a subset of a legal longitude value, prevent the value from being applied
return;
}
<TextFieldDataGrid
dataGridProps={params}
textFieldProps={{
onChange: (event) => {
if (!/^-?\d{0,3}(?:\.\d{0,12})?$/.test(event.target.value)) {
// If the value is not a subset of a legal longitude value, prevent the value from being applied
return;
}

apiRef?.current.setEditCellValue({
id: params.id,
field: params.field,
value: event.target.value
});
apiRef?.current.setEditCellValue({
id: params.id,
field: params.field,
value: event.target.value
});
}
}}
value={params.value ?? ''}
variant="outlined"
type="text"
inputProps={{ inputMode: 'numeric' }}
/>
);
}
Expand Down

0 comments on commit c882b79

Please sign in to comment.