Skip to content

Commit

Permalink
fix(material): Improve usability of date renderers
Browse files Browse the repository at this point in the history
Previously, data wasn't cleared when the input field was emptied.
This commit will set the value to 'undefined' when the input is empty or
invalid upon blurring. During editing, the data is only updated when the
current input is valid.

Closes eclipsesource#2183
  • Loading branch information
LukasBoll committed Dec 20, 2023
1 parent da3321b commit 67e6bdb
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 26 deletions.
31 changes: 27 additions & 4 deletions packages/material-renderers/src/controls/MaterialDateControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
THE SOFTWARE.
*/
import merge from 'lodash/merge';
import React, { useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import {
ControlProps,
isDateControl,
Expand All @@ -35,7 +35,12 @@ import { withJsonFormsControlProps } from '@jsonforms/react';
import { FormHelperText, Hidden } from '@mui/material';
import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { createOnChangeHandler, getData, useFocus } from '../util';
import {
createOnBlurHandler,
createOnChangeHandler,
getData,
useFocus,
} from '../util';

export const MaterialDateControl = (props: ControlProps) => {
const [focused, onFocus, onBlur] = useFocus();
Expand All @@ -62,6 +67,8 @@ export const MaterialDateControl = (props: ControlProps) => {
appliedUiSchemaOptions.showUnfocusedDescription
);

const [key, setKey] = useState<number>(0);

const format = appliedUiSchemaOptions.dateFormat ?? 'YYYY-MM-DD';
const saveFormat = appliedUiSchemaOptions.dateSaveFormat ?? 'YYYY-MM-DD';

Expand All @@ -73,20 +80,36 @@ export const MaterialDateControl = (props: ControlProps) => {
? errors
: null;
const secondFormHelperText = showDescription && !isValid ? errors : null;

const updateChild = useCallback(() => setKey((key) => key + 1), []);

const onChange = useMemo(
() => createOnChangeHandler(path, handleChange, saveFormat),
[path, handleChange, saveFormat]
);

const onBlurHandler = useMemo(
() =>
createOnBlurHandler(
path,
handleChange,
format,
saveFormat,
updateChild,
onBlur
),
[path, handleChange, format, saveFormat, updateChild]
);
const value = getData(data, saveFormat);

return (
<Hidden xsUp={!visible}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
key={key}
label={label}
value={value}
onChange={onChange}
onAccept={onChange}
format={format}
views={views}
disabled={!enabled}
Expand All @@ -109,7 +132,7 @@ export const MaterialDateControl = (props: ControlProps) => {
},
InputLabelProps: data ? { shrink: true } : undefined,
onFocus: onFocus,
onBlur: onBlur,
onBlur: onBlurHandler,
},
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import React, { useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import merge from 'lodash/merge';
import {
ControlProps,
Expand All @@ -35,7 +35,12 @@ import { withJsonFormsControlProps } from '@jsonforms/react';
import { FormHelperText, Hidden } from '@mui/material';
import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { createOnChangeHandler, getData, useFocus } from '../util';
import {
createOnBlurHandler,
createOnChangeHandler,
getData,
useFocus,
} from '../util';

export const MaterialDateTimeControl = (props: ControlProps) => {
const [focused, onFocus, onBlur] = useFocus();
Expand Down Expand Up @@ -64,7 +69,9 @@ export const MaterialDateTimeControl = (props: ControlProps) => {
);

const format = appliedUiSchemaOptions.dateTimeFormat ?? 'YYYY-MM-DD HH:mm';
const saveFormat = appliedUiSchemaOptions.dateTimeSaveFormat ?? undefined;
const saveFormat = appliedUiSchemaOptions.dateTimeSaveFormat ?? undefined; //'YYYY-MM-DDTHH:mm:Z';

const [key, setKey] = useState<number>(0);

const views = appliedUiSchemaOptions.views ?? [
'year',
Expand All @@ -80,20 +87,35 @@ export const MaterialDateTimeControl = (props: ControlProps) => {
: null;
const secondFormHelperText = showDescription && !isValid ? errors : null;

const updateChild = useCallback(() => setKey((key) => key + 1), []);

const onChange = useMemo(
() => createOnChangeHandler(path, handleChange, saveFormat),
[path, handleChange, saveFormat]
);

const onBlurHandler = useMemo(
() =>
createOnBlurHandler(
path,
handleChange,
format,
saveFormat,
updateChild,
onBlur
),
[path, handleChange, format, saveFormat, updateChild]
);
const value = getData(data, saveFormat);

return (
<Hidden xsUp={!visible}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateTimePicker
key={key}
label={label}
value={value}
onChange={onChange}
onAccept={onChange}
format={format}
ampm={!!appliedUiSchemaOptions.ampm}
views={views}
Expand All @@ -117,7 +139,7 @@ export const MaterialDateTimeControl = (props: ControlProps) => {
},
InputLabelProps: data ? { shrink: true } : undefined,
onFocus: onFocus,
onBlur: onBlur,
onBlur: onBlurHandler,
},
}}
/>
Expand Down
31 changes: 27 additions & 4 deletions packages/material-renderers/src/controls/MaterialTimeControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import React, { useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import merge from 'lodash/merge';
import {
ControlProps,
Expand All @@ -35,7 +35,12 @@ import { withJsonFormsControlProps } from '@jsonforms/react';
import { FormHelperText, Hidden } from '@mui/material';
import { TimePicker, LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { createOnChangeHandler, getData, useFocus } from '../util';
import {
createOnBlurHandler,
createOnChangeHandler,
getData,
useFocus,
} from '../util';

export const MaterialTimeControl = (props: ControlProps) => {
const [focused, onFocus, onBlur] = useFocus();
Expand All @@ -56,6 +61,8 @@ export const MaterialTimeControl = (props: ControlProps) => {
const appliedUiSchemaOptions = merge({}, config, uischema.options);
const isValid = errors.length === 0;

const [key, setKey] = useState<number>(0);

const showDescription = !isDescriptionHidden(
visible,
description,
Expand All @@ -75,19 +82,35 @@ export const MaterialTimeControl = (props: ControlProps) => {
: null;
const secondFormHelperText = showDescription && !isValid ? errors : null;

const updateChild = useCallback(() => setKey((key) => key + 1), []);

const onChange = useMemo(
() => createOnChangeHandler(path, handleChange, saveFormat),
[path, handleChange, saveFormat]
);

const onBlurHandler = useMemo(
() =>
createOnBlurHandler(
path,
handleChange,
format,
saveFormat,
updateChild,
onBlur
),
[path, handleChange, format, saveFormat, updateChild]
);
const value = getData(data, saveFormat);

return (
<Hidden xsUp={!visible}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<TimePicker
key={key}
label={label}
value={value}
onChange={onChange}
onAccept={onChange}
format={format}
ampm={!!appliedUiSchemaOptions.ampm}
views={views}
Expand All @@ -111,7 +134,7 @@ export const MaterialTimeControl = (props: ControlProps) => {
},
InputLabelProps: data ? { shrink: true } : undefined,
onFocus: onFocus,
onBlur: onBlur,
onBlur: onBlurHandler,
},
}}
/>
Expand Down
53 changes: 47 additions & 6 deletions packages/material-renderers/src/util/datejs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,58 @@ export const createOnChangeHandler =
(
path: string,
handleChange: (path: string, value: any) => void,
saveFormat: string | undefined
saveFormat: string
) =>
(time: dayjs.Dayjs) => {
if (!time) {
(value: dayjs.Dayjs) => {
if (!value) {
handleChange(path, undefined);
return;
} else if (value.toString() !== 'Invalid Date') {
const formatedDate = formatDate(value, saveFormat);
handleChange(path, formatedDate);
}
const result = dayjs(time).format(saveFormat);
handleChange(path, result);
};

export const createOnBlurHandler =
(
path: string,
handleChange: (path: string, value: any) => void,
format: string,
saveFormat: string,
rerenderChild: () => void,
onBlur: () => void
) =>
(e: React.FocusEvent<HTMLTextAreaElement | HTMLInputElement, Element>) => {
const date = dayjs(e.target.value, format);
const formatedDate = formatDate(date, saveFormat);
if (formatedDate.toString() === 'Invalid Date') {
handleChange(path, undefined);
rerenderChild();
} else {
handleChange(path, formatedDate);
}
onBlur();
};

const formatDate = (date: dayjs.Dayjs, saveFormat: string) => {
let formatedDate = date.format(saveFormat);
// Workaround to address a bug in Dayjs (https://github.com/iamkun/dayjs/issues/1849)
if (
date.year() < 1000 &&
date.year() > 100 &&
saveFormat.length > formatedDate.length
) {
const indexOfYear = saveFormat.indexOf('YYYY');
if (indexOfYear !== -1) {
formatedDate = [
formatedDate.slice(0, indexOfYear),
0,
formatedDate.slice(indexOfYear),
].join('');
}
}
return formatedDate;
};

export const getData = (
data: any,
saveFormat: string | undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ describe('Material date control', () => {
);
const input = wrapper.find('input').first();
(input.getDOMNode() as HTMLInputElement).value = '1961-04-12';
input.simulate('change', input);
input.simulate('blur', input);
expect(onChangeData.data.foo).toBe('1961-04-12');
});

Expand Down Expand Up @@ -421,7 +421,7 @@ describe('Material date control', () => {
expect(input.props().value).toBe('1980/06');

(input.getDOMNode() as HTMLInputElement).value = '1961/04';
input.simulate('change', input);
input.simulate('blur', input);
expect(onChangeData.data.foo).toBe('04---1961');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ describe('Material date time control', () => {
);
const input = wrapper.find('input').first();
(input.getDOMNode() as HTMLInputElement).value = '1961-12-12 20:15';
input.simulate('change', input);
input.simulate('blur', input);
expect(onChangeData.data.foo).toBe(dayjs('1961-12-12 20:15').format());
});

Expand Down Expand Up @@ -427,7 +427,7 @@ describe('Material date time control', () => {
expect(input.props().value).toBe('23-04-80 01:37:pm');

(input.getDOMNode() as HTMLInputElement).value = '10-12-05 11:22:am';
input.simulate('change', input);
input.simulate('blur', input);
expect(onChangeData.data.foo).toBe('2005/12/10 11:22 am');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ describe('Material time control', () => {
);
const input = wrapper.find('input').first();
(input.getDOMNode() as HTMLInputElement).value = '08:40';
input.simulate('change', input);
expect(onChangeData.data.foo).toBe('08:40:05');
input.simulate('blur', input);
expect(onChangeData.data.foo).toBe('08:40:00');
});

it('should update via action', () => {
Expand Down Expand Up @@ -421,7 +421,7 @@ describe('Material time control', () => {
expect(input.props().value).toBe('02-13');

(input.getDOMNode() as HTMLInputElement).value = '12-01';
input.simulate('change', input);
input.simulate('blur', input);
expect(onChangeData.data.foo).toBe('1//12 am');
});
});

0 comments on commit 67e6bdb

Please sign in to comment.