react-form-calendar
Version:
Datepicker widget react component
336 lines (278 loc) • 8.77 kB
JavaScript
var React = require('react');
var cs = require('classnames');
var moment = require('moment');
require('moment-range');
var DaysView = require('./DaysView');
var MonthsView = require('./MonthsView');
var YearsView = require('./YearsView');
var Utils = require('./Utils');
var _keyDownActions = Utils.keyDownActions;
function toDate(date) {
if (date instanceof Date) {
return date;
}
return new Date(date);
}
var Calendar = React.createClass({
propTypes: {
closeOnSelect: React.PropTypes.bool,
computableFormat: React.PropTypes.string,
date: React.PropTypes.any,
minDate: React.PropTypes.any,
maxDate: React.PropTypes.any,
format: React.PropTypes.string,
inputFieldId: React.PropTypes.string,
minView: React.PropTypes.number,
onBlur: React.PropTypes.func,
onChange: React.PropTypes.func,
placeholder: React.PropTypes.string,
hideTouchKeyboard: React.PropTypes.bool,
},
getDefaultProps: function() {
return {
calendarIcon: 'Toggle calendar',
iconClass: false
}
},
getInitialState: function() {
var date = this.props.date ? moment(toDate(this.props.date)) : null,
minDate = this.props.minDate ? moment(toDate(this.props.minDate)) : null,
maxDate = this.props.maxDate ? moment(toDate(this.props.maxDate)) : null,
inputFieldId = this.props.inputFieldId ? this.props.inputFieldId : null,
format = this.props.format || 'MM-DD-YYYY',
minView = parseInt(this.props.minView, 10) || 0,
computableFormat = this.props.computableFormat || 'MM-DD-YYYY';
return {
date: date,
minDate: minDate,
maxDate: maxDate,
format: format,
computableFormat: computableFormat,
inputValue: date ? date.format(format) : null,
views: ['days', 'months', 'years'],
minView: minView,
currentView: minView || 0,
isVisible: false
};
},
componentDidMount: function() {
document.addEventListener('click', this.documentClick);
},
componentWillUnmount: function() {
document.removeEventListener('click', this.documentClick);
},
componentWillReceiveProps: function(nextProps) {
this.setState({
date: nextProps.date ? moment(toDate(nextProps.date)) : this.state.date,
inputValue: nextProps.date ? moment(toDate(nextProps.date)).format(this.state.format) : null
});
},
keyDown: function (e) {
_keyDownActions.call(this, e.keyCode);
},
checkIfDateDisabled: function (date) {
if (this.state.minDate && date.isBefore(this.state.minDate)) {
return true;
}
if (this.state.maxDate && date.isAfter(this.state.maxDate)) {
return true;
}
return false;
},
nextView: function () {
if (this.checkIfDateDisabled(this.state.date)) {
return;
}
this.setState({
currentView: ++this.state.currentView
});
},
prevView: function (date) {
if (this.state.minDate && date.isBefore(this.state.minDate)) {
date = this.state.minDate.clone();
}
if (this.state.maxDate && date.isAfter(this.state.maxDate)) {
date = this.state.maxDate.clone();
}
if (this.state.currentView === this.state.minView) {
this.setState({
date: date,
inputValue: date.format(this.state.format),
isVisible: false
});
if (this.props.onChange) {
this.props.onChange(date.format(this.state.computableFormat));
}
} else {
this.setState({
date: date,
currentView: --this.state.currentView
});
}
},
setDate: function (date, isDayView) {
if (this.checkIfDateDisabled(date)) {
return;
}
this.setState({
date: date,
inputValue: date.format(this.state.format),
isVisible: this.props.closeOnSelect && isDayView ? !this.state.isVisible : this.state.isVisible
});
if (this.props.onChange) {
this.props.onChange(date.format(this.state.computableFormat));
}
},
changeDate: function (e) {
this.setState({
inputValue: e.target.value
})
},
inputBlur: function (e) {
var date = this.state.inputValue,
newDate = null,
computableDate = null,
format = this.state.format;
if (date) {
// format, with strict parsing true, so we catch bad dates
newDate = moment(date, format, true);
// if the new date didn't match our format, see if the native
// js date can parse it
if (!newDate.isValid()) {
var d = new Date(date);
// if native js cannot parse, just make a new date
if (isNaN(d.getTime())) {
d = new Date();
}
newDate = moment(d);
}
computableDate = newDate.format(this.state.computableFormat);
}
this.setState({
date: newDate,
inputValue: newDate ? newDate.format(format) : null
});
if (this.props.onChange) {
this.props.onChange(computableDate);
}
if (this.props.onBlur) {
this.props.onBlur(e, computableDate);
}
},
//small hack for hide calendar
isCalendar: false,
documentClick: function () {
if (!this.isCalendar) {
this.setVisibility(false);
}
this.isCalendar = false;
},
calendarClick: function () {
this.isCalendar = true;
},
todayClick: function () {
var today = moment().startOf('day');
if (this.checkIfDateDisabled(today)) return;
this.setState({
date: today,
inputValue: today.format(this.state.format),
currentView: this.state.minView
});
if (this.props.onChange) {
this.props.onChange(today.format(this.state.computableFormat));
}
},
toggleClick: function () {
this.isCalendar = true;
this.setVisibility();
},
setVisibility: function (val) {
var value = val !== undefined ? val : !this.state.isVisible;
var eventMethod = value ? 'addEventListener' : 'removeEventListener';
document[eventMethod]('keydown', this.keyDown);
if(this.state.isVisible !== value){
this.setState({
isVisible: value
});
}
},
render: function () {
// its ok for this.state.date to be null, but we should never
// pass null for the date into the calendar pop up, as we want
// it to just start on todays date if there is no date set
var calendarDate = this.state.date || moment();
var view;
switch (this.state.currentView) {
case 0:
view = <DaysView
date={calendarDate}
minDate={this.state.minDate}
maxDate={this.state.maxDate}
setDate={this.setDate}
nextView={this.nextView} />;
break;
case 1:
view = <MonthsView
date={calendarDate}
minDate={this.state.minDate}
maxDate={this.state.maxDate}
setDate={this.setDate}
nextView={this.nextView}
prevView={this.prevView} />;
break;
case 2:
view = <YearsView
date={calendarDate}
minDate={this.state.minDate}
maxDate={this.state.maxDate}
setDate={this.setDate}
prevView={this.prevView} />;
break;
}
var todayText = 'Today';
if(moment.locale() === 'de')
todayText = 'Heute';
var calendar = !this.state.isVisible ? '' :
<div className="input-calendar-wrapper" onClick={this.calendarClick}>
{view}
<span
className={"today-btn" + (this.checkIfDateDisabled(moment().startOf('day')) ? " disabled" : "")}
onClick={this.todayClick}>
{todayText}
</span>
</div>;
var readOnly = false;
var srClass = 'sr-only';
if(this.props.hideTouchKeyboard) {
// do not break server side rendering:
try {
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
readOnly = true;
}
} catch (e) {
}
}
return (
<div className="input-calendar">
<input type="text"
id={this.props.inputFieldId}
className="form-control"
value={this.state.inputValue}
onBlur={this.inputBlur}
onChange={this.changeDate}
onFocus={this.props.openOnInputFocus ? this.toggleClick : ''}
placeholder={this.props.placeholder}
readOnly={readOnly} />
<span onClick={this.toggleClick} className="icon-wrapper calendar-icon">
{this.props.iconClass &&
<i className={this.props.iconClass}></i>}
{ !this.props.iconClass && <span>
{this.props.calendarIcon}
</span>}
</span>
{calendar}
</div>
);
}
});
module.exports = Calendar;