'use strict';
|
|
var assign = require('object-assign'),
|
PropTypes = require('prop-types'),
|
createClass = require('create-react-class'),
|
moment = require('moment'),
|
React = require('react'),
|
CalendarContainer = require('./src/CalendarContainer');
|
|
var TYPES = PropTypes;
|
var Datetime = createClass({
|
propTypes: {
|
// value: TYPES.object | TYPES.string,
|
// defaultValue: TYPES.object | TYPES.string,
|
onFocus: TYPES.func,
|
onBlur: TYPES.func,
|
onChange: TYPES.func,
|
onViewModeChange: TYPES.func,
|
locale: TYPES.string,
|
utc: TYPES.bool,
|
input: TYPES.bool,
|
// dateFormat: TYPES.string | TYPES.bool,
|
// timeFormat: TYPES.string | TYPES.bool,
|
inputProps: TYPES.object,
|
timeConstraints: TYPES.object,
|
viewMode: TYPES.oneOf(['years', 'months', 'days', 'time']),
|
isValidDate: TYPES.func,
|
open: TYPES.bool,
|
strictParsing: TYPES.bool,
|
closeOnSelect: TYPES.bool,
|
closeOnTab: TYPES.bool
|
},
|
|
getInitialState: function() {
|
var state = this.getStateFromProps(this.props);
|
|
if (state.open === undefined) state.open = !this.props.input;
|
|
state.currentView = this.props.dateFormat
|
? this.props.viewMode || state.updateOn || 'days'
|
: 'time';
|
|
return state;
|
},
|
|
getStateFromProps: function(props) {
|
var formats = this.getFormats(props),
|
date = props.value || props.defaultValue,
|
selectedDate,
|
viewDate,
|
updateOn,
|
inputValue;
|
|
if (date && typeof date === 'string')
|
selectedDate = this.localMoment(date, formats.datetime);
|
else if (date) selectedDate = this.localMoment(date);
|
|
if (selectedDate && !selectedDate.isValid()) selectedDate = null;
|
|
viewDate = selectedDate
|
? selectedDate.clone().startOf('month')
|
: this.localMoment().startOf('month');
|
|
updateOn = this.getUpdateOn(formats);
|
|
if (selectedDate) inputValue = selectedDate.format(formats.datetime);
|
else if (date.isValid && !date.isValid()) inputValue = '';
|
else inputValue = date || '';
|
|
return {
|
updateOn: updateOn,
|
inputFormat: formats.datetime,
|
viewDate: viewDate,
|
selectedDate: selectedDate,
|
inputValue: inputValue,
|
open: props.open
|
};
|
},
|
|
getUpdateOn: function(formats) {
|
if (formats.date.match(/[lLD]/)) {
|
return 'days';
|
} else if (formats.date.indexOf('M') !== -1) {
|
return 'months';
|
} else if (formats.date.indexOf('Y') !== -1) {
|
return 'years';
|
}
|
|
return 'days';
|
},
|
|
getFormats: function(props) {
|
var formats = {
|
date: props.dateFormat || '',
|
time: props.timeFormat || ''
|
},
|
locale = this.localMoment(props.date, null, props).localeData();
|
|
if (formats.date === true) {
|
formats.date = locale.longDateFormat('L');
|
} else if (this.getUpdateOn(formats) !== 'days') {
|
formats.time = '';
|
}
|
|
if (formats.time === true) {
|
formats.time = locale.longDateFormat('LT');
|
}
|
|
formats.datetime =
|
formats.date && formats.time
|
? formats.date + ' ' + formats.time
|
: formats.date || formats.time;
|
|
return formats;
|
},
|
|
componentWillReceiveProps: function(nextProps) {
|
var formats = this.getFormats(nextProps),
|
updatedState = {};
|
|
if (
|
nextProps.value !== this.props.value ||
|
formats.datetime !== this.getFormats(this.props).datetime
|
) {
|
updatedState = this.getStateFromProps(nextProps);
|
}
|
|
if (updatedState.open === undefined) {
|
if (typeof nextProps.open !== 'undefined') {
|
updatedState.open = nextProps.open;
|
} else if (
|
this.props.closeOnSelect &&
|
this.state.currentView !== 'time'
|
) {
|
updatedState.open = false;
|
} else {
|
updatedState.open = this.state.open;
|
}
|
}
|
|
if (nextProps.viewMode !== this.props.viewMode) {
|
updatedState.currentView = nextProps.viewMode;
|
}
|
|
if (nextProps.locale !== this.props.locale) {
|
if (this.state.viewDate) {
|
var updatedViewDate = this.state.viewDate
|
.clone()
|
.locale(nextProps.locale);
|
updatedState.viewDate = updatedViewDate;
|
}
|
if (this.state.selectedDate) {
|
var updatedSelectedDate = this.state.selectedDate
|
.clone()
|
.locale(nextProps.locale);
|
updatedState.selectedDate = updatedSelectedDate;
|
updatedState.inputValue = updatedSelectedDate.format(formats.datetime);
|
}
|
}
|
|
if (nextProps.utc !== this.props.utc) {
|
if (nextProps.utc) {
|
if (this.state.viewDate)
|
updatedState.viewDate = this.state.viewDate.clone().utc();
|
if (this.state.selectedDate) {
|
updatedState.selectedDate = this.state.selectedDate.clone().utc();
|
updatedState.inputValue = updatedState.selectedDate.format(
|
formats.datetime
|
);
|
}
|
} else {
|
if (this.state.viewDate)
|
updatedState.viewDate = this.state.viewDate.clone().local();
|
if (this.state.selectedDate) {
|
updatedState.selectedDate = this.state.selectedDate.clone().local();
|
updatedState.inputValue = updatedState.selectedDate.format(
|
formats.datetime
|
);
|
}
|
}
|
}
|
//we should only show a valid date if we are provided a isValidDate function. Removed in 2.10.3
|
/*if (this.props.isValidDate) {
|
updatedState.viewDate = updatedState.viewDate || this.state.viewDate;
|
while (!this.props.isValidDate(updatedState.viewDate)) {
|
updatedState.viewDate = updatedState.viewDate.add(1, 'day');
|
}
|
}*/
|
this.setState(updatedState);
|
},
|
|
onInputChange: function(e) {
|
var value = e.target === null ? e : e.target.value,
|
localMoment = this.localMoment(value, this.state.inputFormat),
|
update = { inputValue: value };
|
|
if (localMoment.isValid() && !this.props.value) {
|
update.selectedDate = localMoment;
|
update.viewDate = localMoment.clone().startOf('month');
|
} else {
|
update.selectedDate = null;
|
}
|
|
return this.setState(update, function() {
|
return this.props.onChange(
|
localMoment.isValid() ? localMoment : this.state.inputValue
|
);
|
});
|
},
|
|
onInputKey: function(e) {
|
if (e.which === 9 && this.props.closeOnTab) {
|
this.closeCalendar();
|
}
|
},
|
|
showView: function(view) {
|
var me = this;
|
return function() {
|
me.state.currentView !== view && me.props.onViewModeChange(view);
|
me.setState({ currentView: view });
|
};
|
},
|
|
setDate: function(type) {
|
var me = this,
|
nextViews = {
|
month: 'days',
|
year: 'months'
|
};
|
return function(e) {
|
me.setState({
|
viewDate: me.state.viewDate
|
.clone()
|
[type](parseInt(e.target.getAttribute('data-value'), 10))
|
.startOf(type),
|
currentView: nextViews[type]
|
});
|
me.props.onViewModeChange(nextViews[type]);
|
};
|
},
|
|
addTime: function(amount, type, toSelected) {
|
return this.updateTime('add', amount, type, toSelected);
|
},
|
|
subtractTime: function(amount, type, toSelected) {
|
return this.updateTime('subtract', amount, type, toSelected);
|
},
|
|
updateTime: function(op, amount, type, toSelected) {
|
var me = this;
|
|
return function() {
|
var update = {},
|
date = toSelected ? 'selectedDate' : 'viewDate';
|
|
update[date] = me.state[date].clone()[op](amount, type);
|
|
me.setState(update);
|
};
|
},
|
|
allowedSetTime: ['hours', 'minutes', 'seconds', 'milliseconds'],
|
setTime: function(type, value) {
|
var index = this.allowedSetTime.indexOf(type) + 1,
|
state = this.state,
|
date = (state.selectedDate || state.viewDate).clone(),
|
nextType;
|
|
// It is needed to set all the time properties
|
// to not to reset the time
|
date[type](value);
|
for (; index < this.allowedSetTime.length; index++) {
|
nextType = this.allowedSetTime[index];
|
date[nextType](date[nextType]());
|
}
|
|
if (!this.props.value) {
|
this.setState({
|
selectedDate: date,
|
inputValue: date.format(state.inputFormat)
|
});
|
}
|
this.props.onChange(date);
|
},
|
|
updateSelectedDate: function(e, close) {
|
var target = e.target,
|
modifier = 0,
|
viewDate = this.state.viewDate,
|
currentDate = this.state.selectedDate || viewDate,
|
date;
|
|
if (target.className.indexOf('rdtDay') !== -1) {
|
if (target.className.indexOf('rdtNew') !== -1) modifier = 1;
|
else if (target.className.indexOf('rdtOld') !== -1) modifier = -1;
|
|
date = viewDate
|
.clone()
|
.month(viewDate.month() + modifier)
|
.date(parseInt(target.getAttribute('data-value'), 10));
|
} else if (target.className.indexOf('rdtMonth') !== -1) {
|
date = viewDate
|
.clone()
|
.month(parseInt(target.getAttribute('data-value'), 10))
|
.date(currentDate.date());
|
} else if (target.className.indexOf('rdtYear') !== -1) {
|
date = viewDate
|
.clone()
|
.month(currentDate.month())
|
.date(currentDate.date())
|
.year(parseInt(target.getAttribute('data-value'), 10));
|
}
|
|
date
|
.hours(currentDate.hours())
|
.minutes(currentDate.minutes())
|
.seconds(currentDate.seconds())
|
.milliseconds(currentDate.milliseconds());
|
|
if (!this.props.value) {
|
var open = !(this.props.closeOnSelect && close);
|
if (!open) {
|
this.props.onBlur(date);
|
}
|
|
this.setState({
|
selectedDate: date,
|
viewDate: date.clone().startOf('month'),
|
inputValue: date.format(this.state.inputFormat),
|
open: open
|
});
|
} else {
|
if (this.props.closeOnSelect && close) {
|
this.closeCalendar();
|
}
|
}
|
|
this.props.onChange(date);
|
},
|
|
openCalendar: function(e) {
|
if (!this.state.open) {
|
this.setState({ open: true }, function() {
|
this.props.onFocus(e);
|
});
|
}
|
},
|
|
closeCalendar: function() {
|
this.setState({ open: false }, function() {
|
this.props.onBlur(this.state.selectedDate || this.state.inputValue);
|
});
|
},
|
|
handleClickOutside: function() {
|
if (this.props.input && this.state.open && !this.props.open) {
|
this.setState({ open: false }, function() {
|
this.props.onBlur(this.state.selectedDate || this.state.inputValue);
|
});
|
}
|
},
|
|
localMoment: function(date, format, props) {
|
props = props || this.props;
|
var momentFn = props.utc ? moment.utc : moment;
|
var m = momentFn(date, format, props.strictParsing);
|
if (props.locale) m.locale(props.locale);
|
return m;
|
},
|
|
componentProps: {
|
fromProps: [
|
'value',
|
'isValidDate',
|
'renderDay',
|
'renderMonth',
|
'renderYear',
|
'timeConstraints'
|
],
|
fromState: ['viewDate', 'selectedDate', 'updateOn'],
|
fromThis: [
|
'setDate',
|
'setTime',
|
'showView',
|
'addTime',
|
'subtractTime',
|
'updateSelectedDate',
|
'localMoment',
|
'handleClickOutside'
|
]
|
},
|
|
getComponentProps: function() {
|
var me = this,
|
formats = this.getFormats(this.props),
|
props = { dateFormat: formats.date, timeFormat: formats.time };
|
|
this.componentProps.fromProps.forEach(function(name) {
|
props[name] = me.props[name];
|
});
|
this.componentProps.fromState.forEach(function(name) {
|
props[name] = me.state[name];
|
});
|
this.componentProps.fromThis.forEach(function(name) {
|
props[name] = me[name];
|
});
|
|
return props;
|
},
|
|
render: function() {
|
// TODO: Make a function or clean up this code,
|
// logic right now is really hard to follow
|
var className =
|
'rdt' +
|
(this.props.className
|
? Array.isArray(this.props.className)
|
? ' ' + this.props.className.join(' ')
|
: ' ' + this.props.className
|
: ''),
|
children = [];
|
|
if (this.props.input) {
|
var finalInputProps = assign(
|
{
|
type: 'text',
|
className: 'form-control',
|
onClick: this.openCalendar,
|
onFocus: this.openCalendar,
|
onChange: this.onInputChange,
|
onKeyDown: this.onInputKey,
|
value: this.state.inputValue
|
},
|
this.props.inputProps
|
);
|
if (this.props.renderInput) {
|
children = [
|
React.createElement(
|
'div',
|
{ key: 'i' },
|
this.props.renderInput(
|
finalInputProps,
|
this.openCalendar,
|
this.closeCalendar
|
)
|
)
|
];
|
} else {
|
children = [
|
React.createElement('input', assign({ key: 'i' }, finalInputProps))
|
];
|
}
|
} else {
|
className += ' rdtStatic';
|
}
|
|
if (this.state.open) className += ' rdtOpen';
|
|
return React.createElement(
|
'div',
|
{ className: className },
|
children.concat(
|
React.createElement(
|
'div',
|
{ key: 'dt', className: 'rdtPicker' },
|
React.createElement(CalendarContainer, {
|
view: this.state.currentView,
|
viewProps: this.getComponentProps(),
|
onClickOutside: this.handleClickOutside
|
})
|
)
|
)
|
);
|
}
|
});
|
|
Datetime.defaultProps = {
|
className: '',
|
defaultValue: '',
|
inputProps: {},
|
input: true,
|
onFocus: function() {},
|
onBlur: function() {},
|
onChange: function() {},
|
onViewModeChange: function() {},
|
timeFormat: true,
|
timeConstraints: {},
|
dateFormat: true,
|
strictParsing: true,
|
closeOnSelect: false,
|
closeOnTab: true,
|
utc: false
|
};
|
|
// Make moment accessible through the Datetime class
|
Datetime.moment = moment;
|
|
module.exports = Datetime;
|