Simon Egersand
2018-02-01 80582080ed523fcb9f85300438e066b76a008a4f
commit | author | age
c7776a 1 'use strict';
417bf4 2
c7776a 3 var assign = require('object-assign'),
2bcc09 4     PropTypes = require('prop-types'),
cf3d92 5     createClass = require('create-react-class'),
9f1b61 6     moment = require('moment'),
4ad788 7     React = require('react'),
e09432 8     CalendarContainer = require('./src/CalendarContainer')
cf3d92 9     ;
417bf4 10
0cae6c 11 var TYPES = PropTypes;
84755c 12 var Datetime = createClass({
c7776a 13     propTypes: {
0d9dc7 14         // value: TYPES.object | TYPES.string,
M 15         // defaultValue: TYPES.object | TYPES.string,
aca9e6 16         onFocus: TYPES.func,
0ef08f 17         onBlur: TYPES.func,
c37f80 18         onChange: TYPES.func,
5377a9 19         onViewModeChange: TYPES.func,
c37f80 20         locale: TYPES.string,
049c33 21         utc: TYPES.bool,
c37f80 22         input: TYPES.bool,
cbe644 23         // dateFormat: TYPES.string | TYPES.bool,
M 24         // timeFormat: TYPES.string | TYPES.bool,
c37f80 25         inputProps: TYPES.object,
0b3475 26         timeConstraints: TYPES.object,
c37f80 27         viewMode: TYPES.oneOf(['years', 'months', 'days', 'time']),
e7f876 28         isValidDate: TYPES.func,
692390 29         open: TYPES.bool,
9012e8 30         strictParsing: TYPES.bool,
M 31         closeOnSelect: TYPES.bool,
32         closeOnTab: TYPES.bool
c7776a 33     },
8abb28 34
c7776a 35     getInitialState: function() {
c658ad 36         var state = this.getStateFromProps( this.props );
M 37
462115 38         if ( state.open === undefined )
87c677 39             state.open = !this.props.input;
M 40
92a2c6 41         state.currentView = this.props.dateFormat ? (this.props.viewMode || state.updateOn || 'days') : 'time';
c658ad 42
M 43         return state;
44     },
45
9f1b61 46     getStateFromProps: function( props ) {
c658ad 47         var formats = this.getFormats( props ),
M 48             date = props.value || props.defaultValue,
0eb496 49             selectedDate, viewDate, updateOn, inputValue
cf3d92 50             ;
3515a4 51
462115 52         if ( date && typeof date === 'string' )
c658ad 53             selectedDate = this.localMoment( date, formats.datetime );
462115 54         else if ( date )
c658ad 55             selectedDate = this.localMoment( date );
62fd2f 56
462115 57         if ( selectedDate && !selectedDate.isValid() )
62fd2f 58             selectedDate = null;
M 59
60         viewDate = selectedDate ?
462115 61             selectedDate.clone().startOf('month') :
SE 62             this.localMoment().startOf('month')
62fd2f 63         ;
3515a4 64
d1be3f 65         updateOn = this.getUpdateOn(formats);
SA 66
0eb496 67         if ( selectedDate )
SE 68             inputValue = selectedDate.format(formats.datetime);
69         else if ( date.isValid && !date.isValid() )
70             inputValue = '';
71         else
72             inputValue = date || '';
73
c7776a 74         return {
d1be3f 75             updateOn: updateOn,
c7776a 76             inputFormat: formats.datetime,
62fd2f 77             viewDate: viewDate,
0d9dc7 78             selectedDate: selectedDate,
0eb496 79             inputValue: inputValue,
50a0c2 80             open: props.open
c7776a 81         };
d1be3f 82     },
SA 83
9f1b61 84     getUpdateOn: function( formats ) {
cf3d92 85         if ( formats.date.match(/[lLD]/) ) {
462115 86             return 'days';
2bcc09 87         } else if ( formats.date.indexOf('M') !== -1 ) {
462115 88             return 'months';
2bcc09 89         } else if ( formats.date.indexOf('Y') !== -1 ) {
462115 90             return 'years';
92a2c6 91         }
M 92
93         return 'days';
c7776a 94     },
aca70a 95
9f1b61 96     getFormats: function( props ) {
c7776a 97         var formats = {
839cd8 98                 date: props.dateFormat || '',
M 99                 time: props.timeFormat || ''
c37f80 100             },
64fc6a 101             locale = this.localMoment( props.date, null, props ).localeData()
cf3d92 102             ;
5e870c 103
9f1b61 104         if ( formats.date === true ) {
3515a4 105             formats.date = locale.longDateFormat('L');
c7776a 106         }
9f1b61 107         else if ( this.getUpdateOn(formats) !== 'days' ) {
92a2c6 108             formats.time = '';
M 109         }
110
9f1b61 111         if ( formats.time === true ) {
3515a4 112             formats.time = locale.longDateFormat('LT');
c7776a 113         }
5e870c 114
0d9dc7 115         formats.datetime = formats.date && formats.time ?
M 116             formats.date + ' ' + formats.time :
117             formats.date || formats.time
118         ;
c7776a 119
M 120         return formats;
121     },
122
9f1b61 123     componentWillReceiveProps: function( nextProps ) {
c658ad 124         var formats = this.getFormats( nextProps ),
64fc6a 125             updatedState = {}
c37f80 126         ;
M 127
701646 128         if ( nextProps.value !== this.props.value ||
64fc6a 129             formats.datetime !== this.getFormats( this.props ).datetime ) {
SE 130             updatedState = this.getStateFromProps( nextProps );
c7776a 131         }
M 132
64fc6a 133         if ( updatedState.open === undefined ) {
d0b63b 134             if ( typeof nextProps.open !== 'undefined' ) {
SE 135                 updatedState.open = nextProps.open;
136             } else if ( this.props.closeOnSelect && this.state.currentView !== 'time' ) {
fee412 137                 updatedState.open = false;
SE 138             } else {
139                 updatedState.open = this.state.open;
140             }
433f26 141         }
SE 142
583af6 143         if ( nextProps.viewMode !== this.props.viewMode ) {
64fc6a 144             updatedState.currentView = nextProps.viewMode;
583af6 145         }
a8a17a 146
64fc6a 147         if ( nextProps.locale !== this.props.locale ) {
SE 148             if ( this.state.viewDate ) {
149                 var updatedViewDate = this.state.viewDate.clone().locale( nextProps.locale );
150                 updatedState.viewDate = updatedViewDate;
151             }
152             if ( this.state.selectedDate ) {
153                 var updatedSelectedDate = this.state.selectedDate.clone().locale( nextProps.locale );
154                 updatedState.selectedDate = updatedSelectedDate;
155                 updatedState.inputValue = updatedSelectedDate.format( formats.datetime );
156             }
157         }
158
159         if ( nextProps.utc !== this.props.utc ) {
160             if ( nextProps.utc ) {
161                 if ( this.state.viewDate )
162                     updatedState.viewDate = this.state.viewDate.clone().utc();
163                 if ( this.state.selectedDate ) {
164                     updatedState.selectedDate = this.state.selectedDate.clone().utc();
165                     updatedState.inputValue = updatedState.selectedDate.format( formats.datetime );
166                 }
167             } else {
168                 if ( this.state.viewDate )
169                     updatedState.viewDate = this.state.viewDate.clone().local();
170                 if ( this.state.selectedDate ) {
171                     updatedState.selectedDate = this.state.selectedDate.clone().local();
172                     updatedState.inputValue = updatedState.selectedDate.format(formats.datetime);
173                 }
174             }
175         }
39b827 176         //we should only show a valid date if we are provided a isValidDate function. Removed in 2.10.3
LA 177         /*if (this.props.isValidDate) {
9509fa 178             updatedState.viewDate = updatedState.viewDate || this.state.viewDate;
JC 179             while (!this.props.isValidDate(updatedState.viewDate)) {
180                 updatedState.viewDate = updatedState.viewDate.add(1, 'day');
181             }
39b827 182         }*/
64fc6a 183         this.setState( updatedState );
c658ad 184     },
M 185
186     onInputChange: function( e ) {
462115 187         var value = e.target === null ? e : e.target.value,
c658ad 188             localMoment = this.localMoment( value, this.state.inputFormat ),
M 189             update = { inputValue: value }
cf3d92 190             ;
c658ad 191
M 192         if ( localMoment.isValid() && !this.props.value ) {
193             update.selectedDate = localMoment;
462115 194             update.viewDate = localMoment.clone().startOf('month');
2bcc09 195         } else {
62fd2f 196             update.selectedDate = null;
M 197         }
c658ad 198
M 199         return this.setState( update, function() {
62fd2f 200             return this.props.onChange( localMoment.isValid() ? localMoment : this.state.inputValue );
c7776a 201         });
M 202     },
203
9f1b61 204     onInputKey: function( e ) {
SE 205         if ( e.which === 9 && this.props.closeOnTab ) {
9012e8 206             this.closeCalendar();
M 207         }
c7776a 208     },
M 209
9f1b61 210     showView: function( view ) {
c7776a 211         var me = this;
9f1b61 212         return function() {
5377a9 213             me.state.currentView !== view && me.props.onViewModeChange( view );
c7776a 214             me.setState({ currentView: view });
M 215         };
216     },
217
9f1b61 218     setDate: function( type ) {
c7776a 219         var me = this,
4ad788 220             nextViews = {
M 221                 month: 'days',
222                 year: 'months'
223             }
c7776a 224         ;
9f1b61 225         return function( e ) {
c7776a 226             me.setState({
462115 227                 viewDate: me.state.viewDate.clone()[ type ]( parseInt(e.target.getAttribute('data-value'), 10) ).startOf( type ),
c7776a 228                 currentView: nextViews[ type ]
M 229             });
5377a9 230             me.props.onViewModeChange( nextViews[ type ] );
9fb8e8 231         };
c7776a 232     },
M 233
9f1b61 234     addTime: function( amount, type, toSelected ) {
c7776a 235         return this.updateTime( 'add', amount, type, toSelected );
M 236     },
9fb8e8 237
9f1b61 238     subtractTime: function( amount, type, toSelected ) {
c7776a 239         return this.updateTime( 'subtract', amount, type, toSelected );
M 240     },
9fb8e8 241
9f1b61 242     updateTime: function( op, amount, type, toSelected ) {
c7776a 243         var me = this;
M 244
9f1b61 245         return function() {
c7776a 246             var update = {},
M 247                 date = toSelected ? 'selectedDate' : 'viewDate'
248             ;
249
250             update[ date ] = me.state[ date ].clone()[ op ]( amount, type );
251
252             me.setState( update );
253         };
254     },
255
462115 256     allowedSetTime: ['hours', 'minutes', 'seconds', 'milliseconds'],
9f1b61 257     setTime: function( type, value ) {
c7776a 258         var index = this.allowedSetTime.indexOf( type ) + 1,
62fd2f 259             state = this.state,
M 260             date = (state.selectedDate || state.viewDate).clone(),
c7776a 261             nextType
cf3d92 262             ;
c7776a 263
4ad788 264         // It is needed to set all the time properties
M 265         // to not to reset the time
c7776a 266         date[ type ]( value );
M 267         for (; index < this.allowedSetTime.length; index++) {
268             nextType = this.allowedSetTime[index];
269             date[ nextType ]( date[nextType]() );
270         }
4ad788 271
9f1b61 272         if ( !this.props.value ) {
c658ad 273             this.setState({
M 274                 selectedDate: date,
62fd2f 275                 inputValue: date.format( state.inputFormat )
c658ad 276             });
M 277         }
4e9d38 278         this.props.onChange( date );
c7776a 279     },
M 280
1fdc4e 281     updateSelectedDate: function( e, close ) {
c7776a 282         var target = e.target,
c37f80 283             modifier = 0,
62fd2f 284             viewDate = this.state.viewDate,
M 285             currentDate = this.state.selectedDate || viewDate,
c37f80 286             date
cf3d92 287             ;
c7776a 288
9f1b61 289         if (target.className.indexOf('rdtDay') !== -1) {
462115 290             if (target.className.indexOf('rdtNew') !== -1)
d1be3f 291                 modifier = 1;
462115 292             else if (target.className.indexOf('rdtOld') !== -1)
d1be3f 293                 modifier = -1;
c7776a 294
d1be3f 295             date = viewDate.clone()
SA 296                 .month( viewDate.month() + modifier )
462115 297                 .date( parseInt( target.getAttribute('data-value'), 10 ) );
9f1b61 298         } else if (target.className.indexOf('rdtMonth') !== -1) {
d1be3f 299             date = viewDate.clone()
462115 300                 .month( parseInt( target.getAttribute('data-value'), 10 ) )
SE 301                 .date( currentDate.date() );
9f1b61 302         } else if (target.className.indexOf('rdtYear') !== -1) {
d1be3f 303             date = viewDate.clone()
SA 304                 .month( currentDate.month() )
305                 .date( currentDate.date() )
462115 306                 .year( parseInt( target.getAttribute('data-value'), 10 ) );
d1be3f 307         }
SA 308
309         date.hours( currentDate.hours() )
c7776a 310             .minutes( currentDate.minutes() )
M 311             .seconds( currentDate.seconds() )
462115 312             .milliseconds( currentDate.milliseconds() );
c7776a 313
9f1b61 314         if ( !this.props.value ) {
794700 315             var open = !( this.props.closeOnSelect && close );
SE 316             if ( !open ) {
317                 this.props.onBlur( date );
318             }
319
c658ad 320             this.setState({
M 321                 selectedDate: date,
322                 viewDate: date.clone().startOf('month'),
50a0c2 323                 inputValue: date.format( this.state.inputFormat ),
794700 324                 open: open
c658ad 325             });
462115 326         } else {
794700 327             if ( this.props.closeOnSelect && close ) {
50a0c2 328                 this.closeCalendar();
M 329             }
c658ad 330         }
4e9d38 331
M 332         this.props.onChange( date );
c7776a 333     },
M 334
689227 335     openCalendar: function( e ) {
SE 336         if ( !this.state.open ) {
f72983 337             this.setState({ open: true }, function() {
689227 338                 this.props.onFocus( e );
f72983 339             });
aca9e6 340         }
c7776a 341     },
M 342
1fdc4e 343     closeCalendar: function() {
f72983 344         this.setState({ open: false }, function () {
GV 345             this.props.onBlur( this.state.selectedDate || this.state.inputValue );
346         });
1fdc4e 347     },
EC 348
9f1b61 349     handleClickOutside: function() {
SE 350         if ( this.props.input && this.state.open && !this.props.open ) {
f72983 351             this.setState({ open: false }, function() {
GV 352                 this.props.onBlur( this.state.selectedDate || this.state.inputValue );
353             });
62fd2f 354         }
c7776a 355     },
M 356
64fc6a 357     localMoment: function( date, format, props ) {
SE 358         props = props || this.props;
359         var momentFn = props.utc ? moment.utc : moment;
360         var m = momentFn( date, format, props.strictParsing );
361         if ( props.locale )
362             m.locale( props.locale );
c37f80 363         return m;
M 364     },
365
c7776a 366     componentProps: {
0b3475 367         fromProps: ['value', 'isValidDate', 'renderDay', 'renderMonth', 'renderYear', 'timeConstraints'],
d1be3f 368         fromState: ['viewDate', 'selectedDate', 'updateOn'],
11612b 369         fromThis: ['setDate', 'setTime', 'showView', 'addTime', 'subtractTime', 'updateSelectedDate', 'localMoment', 'handleClickOutside']
c7776a 370     },
M 371
9f1b61 372     getComponentProps: function() {
c7776a 373         var me = this,
a3a33b 374             formats = this.getFormats( this.props ),
M 375             props = {dateFormat: formats.date, timeFormat: formats.time}
cf3d92 376             ;
c7776a 377
9f1b61 378         this.componentProps.fromProps.forEach( function( name ) {
c7776a 379             props[ name ] = me.props[ name ];
M 380         });
9f1b61 381         this.componentProps.fromState.forEach( function( name ) {
c7776a 382             props[ name ] = me.state[ name ];
M 383         });
9f1b61 384         this.componentProps.fromThis.forEach( function( name ) {
c7776a 385             props[ name ] = me[ name ];
M 386         });
387
388         return props;
389     },
390
391     render: function() {
f4e62d 392         // TODO: Make a function or clean up this code,
SE 393         // logic right now is really hard to follow
a50b2e 394         var className = 'rdt' + (this.props.className ?
386942 395                   ( Array.isArray( this.props.className ) ?
SE 396                   ' ' + this.props.className.join( ' ' ) : ' ' + this.props.className) : ''),
cf3d92 397             children = [];
a3a33b 398
9f1b61 399         if ( this.props.input ) {
b6f2dd 400             var finalInputProps = assign({
DF 401                 type: 'text',
402                 className: 'form-control',
403                 onClick: this.openCalendar,
404                 onFocus: this.openCalendar,
405                 onChange: this.onInputChange,
406                 onKeyDown: this.onInputKey,
407                 value: this.state.inputValue,
408             }, this.props.inputProps);
b8a9a7 409             if ( this.props.renderInput ) {
0484e2 410                 children = [ React.createElement('div', { key: 'i' }, this.props.renderInput( finalInputProps, this.openCalendar, this.closeCalendar )) ];
b8a9a7 411             } else {
b6f2dd 412                 children = [ React.createElement('input', assign({ key: 'i' }, finalInputProps ))];
b8a9a7 413             }
462115 414         } else {
a3a33b 415             className += ' rdtStatic';
M 416         }
d76f7b 417
462115 418         if ( this.state.open )
a3a33b 419             className += ' rdtOpen';
M 420
f37c3f 421         return React.createElement( 'div', { className: className }, children.concat(
SE 422             React.createElement( 'div',
a3a33b 423                 { key: 'dt', className: 'rdtPicker' },
f37c3f 424                 React.createElement( CalendarContainer, { view: this.state.currentView, viewProps: this.getComponentProps(), onClickOutside: this.handleClickOutside })
d76f7b 425             )
a3a33b 426         ));
c7776a 427     }
47e834 428 });
LC 429
e6c8d2 430 Datetime.defaultProps = {
MM 431     className: '',
432     defaultValue: '',
433     inputProps: {},
434     input: true,
435     onFocus: function() {},
436     onBlur: function() {},
437     onChange: function() {},
438     onViewModeChange: function() {},
439     timeFormat: true,
440     timeConstraints: {},
441     dateFormat: true,
442     strictParsing: true,
443     closeOnSelect: false,
444     closeOnTab: true,
445     utc: false
446 };
447
cc4dda 448 // Make moment accessible through the Datetime class
M 449 Datetime.moment = moment;
450
9fb8e8 451 module.exports = Datetime;