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