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