Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add multi format support to DateInput #437

Merged
merged 7 commits into from
Nov 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ lib
es
coverage
yarn.lock
.vscode
.vscode
package-lock.json
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,10 @@ http://react-component.github.io/calendar/examples/index.html
</tr>
<tr>
<td>format</td>
<td>String</td>
<td>String | String[]</td>
<td>depends on whether you set timePicker and your locale</td>
<td>use to format/parse date(without time) value to/from input</td>
<td>use to format/parse date(without time) value to/from input.
When an array is provided, all values are used for parsing and first value for display.</td>
</tr>
<tr>
<td>disabledDate</td>
Expand Down
46 changes: 43 additions & 3 deletions examples/antd-calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class Demo extends React.Component {
locale={cn ? zhCN : enUS}
style={{ zIndex: 1000 }}
dateInputPlaceholder="please input"
formatter={getFormat(state.showTime)}
format={getFormat(state.showTime)}
disabledTime={state.showTime ? disabledTime : null}
timePicker={state.showTime ? timePickerElement : null}
defaultValue={this.props.defaultCalendarValue}
Expand Down Expand Up @@ -158,7 +158,6 @@ class Demo extends React.Component {
>
<DatePicker
animation="slide-up"
disabled={state.disabled}
calendar={calendar}
value={state.value}
onChange={this.onChange}
Expand Down Expand Up @@ -186,6 +185,44 @@ class Demo extends React.Component {
}
}

const multiFormats = ['DD/MM/YYYY', 'DD/MM/YY', 'DDMMYY', 'D/M/YY'];

class DemoMultiFormat extends React.Component {
constructor(props) {
super(props);

this.state = {
value: now,
};
}

onChange = (value) => {
console.log('Calendar change: ', (value && value.format(format)));
this.setState({
value,
});
}

render() {
const state = this.state;
return (<div style={{ width: 400, margin: 20 }}>
<div style={{ marginBottom: 10 }}>
Accepts multiple input formats
<br/>
<small>{multiFormats.join(', ')}</small>
</div>
<Calendar
locale={cn ? zhCN : enUS}
style={{ zIndex: 1000 }}
dateInputPlaceholder="please input"
format={multiFormats}
value={state.value}
onChange={this.onChange}
/>
</div>);
}
}

function onStandaloneSelect(value) {
console.log('onStandaloneSelect');
console.log(value && value.format(format));
Expand Down Expand Up @@ -213,7 +250,7 @@ ReactDOM.render((<div
defaultValue={now}
disabledTime={disabledTime}
showToday
formatter={getFormat(true)}
format={getFormat(true)}
showOk={false}
timePicker={timePickerElement}
onChange={onStandaloneChange}
Expand All @@ -229,5 +266,8 @@ ReactDOM.render((<div
<Demo defaultCalendarValue={defaultCalendarValue} />
</div>
<div style={{ clear: 'both' }}></div>
<div>
<DemoMultiFormat />
</div>
</div>
</div>), document.getElementById('__react-content'));
29 changes: 23 additions & 6 deletions src/date/DateInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import ReactDOM from 'react-dom';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import moment from 'moment';
import { formatDate } from '../util';

const DateInput = createReactClass({
propTypes: {
prefixCls: PropTypes.string,
timePicker: PropTypes.object,
value: PropTypes.object,
disabledTime: PropTypes.any,
format: PropTypes.string,
format: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
locale: PropTypes.object,
disabledDate: PropTypes.func,
onChange: PropTypes.func,
Expand All @@ -24,8 +25,9 @@ const DateInput = createReactClass({
getInitialState() {
const selectedValue = this.props.selectedValue;
return {
str: selectedValue && selectedValue.format(this.props.format) || '',
str: formatDate(selectedValue, this.props.format),
invalid: false,
hasFocus: false,
};
},

Expand All @@ -34,10 +36,12 @@ const DateInput = createReactClass({
this.cachedSelectionEnd = this.dateInputInstance.selectionEnd;
// when popup show, click body will call this, bug!
const selectedValue = nextProps.selectedValue;
this.setState({
str: selectedValue && selectedValue.format(nextProps.format) || '',
invalid: false,
});
if (!this.state.hasFocus) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes str & invalid un-control. It's better to compare selectedValue & format changed to setState instead of focus status.

Copy link
Contributor Author

@onlyann onlyann Nov 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand what you suggest.

This is done to prevent re-formatting the input while a user is editing the date, i.e. to prevent reformatting 1/12/18 to 01/12/2018 until focus is lost.

I could change the condition to be:

const hasMultipleFormats = !!nextProps.format && nextProps.format.length > 1;

if (!hasMultipleFormats || !this.state.hasFocus) {
   this.setState({
      str: formatDate(selectedValue, nextProps.format),
      invalid: false,
   });
}

Would that work for you or am I missing something else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean when props of selectedValue or format changed. Current code will not makes state update. I suggest to modify like this:

if (
  !shallowEqual(this.props.format, format) ||
  (!this.props.selectedValue && selectedValue) ||
  !this.props.selectedValue.isSame(selectedValue, 'date')
) {
  // ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work because on every key stroke, if the input parses to a valid date, the Calendar component will change the selectedValue prop, effectively triggering componentWillReceiveProps on DateInput and that will override the date input to a different format while the user is typing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested. You're correct.

this.setState({
str: formatDate(selectedValue, nextProps.format),
invalid: false,
});
}
},

componentDidUpdate() {
Expand Down Expand Up @@ -115,6 +119,17 @@ const DateInput = createReactClass({
this.dateInputInstance = dateInput;
},

onFocus() {
this.setState({ hasFocus: true });
},

onBlur() {
this.setState((prevState, prevProps) => ({
hasFocus: false,
str: formatDate(prevProps.value, prevProps.format),
}));
},

render() {
const props = this.props;
const { invalid, str } = this.state;
Expand All @@ -130,6 +145,8 @@ const DateInput = createReactClass({
disabled={props.disabled}
placeholder={placeholder}
onChange={this.onInputChange}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</div>
{props.showClear ? (
Expand Down
12 changes: 12 additions & 0 deletions src/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,15 @@ export function isAllowedDate(value, disabledDate, disabledTime) {
}
return true;
}

export function formatDate(value, format) {
if (!value) {
return '';
}

if (Array.isArray(format)) {
format = format[0];
}

return value.format(format);
}
46 changes: 46 additions & 0 deletions tests/Calendar.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,52 @@ describe('Calendar', () => {
expect(onSelect.mock.calls[0][0].format(format)).toBe(expected);
expect(onChange.mock.calls[0][0].format(format)).toBe(expected);
});

it('supports an array of formats when parsing and formats using the first format', () => {
const expected = '21/01/2017';
const value = '21/01/17';

const onSelect = jest.fn();
const onChange = jest.fn();

const calendar = mount(<Calendar
format={['DD/MM/YYYY', 'DD/MM/YY']}
showToday
onSelect={onSelect}
onChange={onChange}
/>);
const input = calendar.find('.rc-calendar-input').hostNodes().at(0);
input.simulate('change', { target: { value } });

expect(onSelect.mock.calls[0][0].format('DD/MM/YYYY')).toBe(expected);
expect(onChange.mock.calls[0][0].format('DD/MM/YYYY')).toBe(expected);
});

it('only reformat the date when the input loses focus', () => {
const value = '21/01/17';

const onSelect = jest.fn();
const onChange = jest.fn();

const calendar = mount(<Calendar
format={['DD/MM/YYYY', 'DD/MM/YY']}
showToday
onSelect={onSelect}
onChange={onChange}
/>);

const input = calendar.find('.rc-calendar-input').hostNodes().at(0);
input.simulate('focus');
input.simulate('change', { target: { value } });

let inputValue = calendar.find('.rc-calendar-input').props().value;
expect(inputValue).toBe('21/01/17');

input.simulate('blur');

inputValue = calendar.find('.rc-calendar-input').props().value;
expect(inputValue).toBe('21/01/2017');
});
});

it('handle clear', () => {
Expand Down