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