'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.props.disableOnClickOutside ) {
|
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;
|