marquex
2015-08-20 62fd2f65bd45d143116bceba919c25e9c713f503
commit | author | age
c7776a 1 'use strict';
417bf4 2
3ab634 3 require('classlist-polyfill');
MK 4
c7776a 5 var assign = require('object-assign'),
4ad788 6     React = require('react'),
d76f7b 7     DaysView = require('./src/DaysView'),
M 8     MonthsView = require('./src/MonthsView'),
9     YearsView = require('./src/YearsView'),
10     TimeView = require('./src/TimeView'),
4ad788 11     moment = require('moment')
c7776a 12 ;
417bf4 13
c37f80 14 var TYPES = React.PropTypes;
9fb8e8 15 var Datetime = React.createClass({
c7776a 16     mixins: [
M 17         require('react-onclickoutside')
18     ],
19     viewComponents: {
20         days: DaysView,
21         months: MonthsView,
22         years: YearsView,
23         time: TimeView
24     },
25     propTypes: {
0d9dc7 26         // value: TYPES.object | TYPES.string,
M 27         // defaultValue: TYPES.object | TYPES.string,
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,
c37f80 37         minDate: TYPES.object,
M 38         maxDate: TYPES.object
c7776a 39     },
8abb28 40
c7776a 41     getDefaultProps: function() {
4e9d38 42         var nof = function(){};
c7776a 43         return {
8abb28 44             className: '',
62fd2f 45             defaultValue: '',
c7776a 46             viewMode: 'days',
d76f7b 47             inputProps: {},
a3a33b 48             input: true,
4e9d38 49             onBlur: nof,
cbe644 50             onChange: nof,
839cd8 51             timeFormat: true,
cbe644 52             dateFormat: true
c7776a 53         };
M 54     },
c658ad 55
c7776a 56     getInitialState: function() {
c658ad 57         var state = this.getStateFromProps( this.props );
M 58
59         state.open = !this.props.input;
0d9dc7 60         state.currentView = this.props.dateFormat ? this.props.viewMode : 'time';
c658ad 61
M 62         return state;
63     },
64
65     getStateFromProps: function( props ){
66         var formats = this.getFormats( props ),
67             date = props.value || props.defaultValue,
62fd2f 68             selectedDate, viewDate
c7776a 69         ;
3515a4 70
62fd2f 71         if( date && typeof date == 'string' )
c658ad 72             selectedDate = this.localMoment( date, formats.datetime );
62fd2f 73         else if( date )
c658ad 74             selectedDate = this.localMoment( date );
62fd2f 75
M 76         if( selectedDate && !selectedDate.isValid() )
77             selectedDate = null;
78
79         viewDate = selectedDate ?
80             selectedDate.clone().startOf("month") :
81             this.localMoment().startOf("month")
82         ;
3515a4 83
c7776a 84         return {
M 85             inputFormat: formats.datetime,
62fd2f 86             viewDate: viewDate,
0d9dc7 87             selectedDate: selectedDate,
62fd2f 88             inputValue: selectedDate ? selectedDate.format( formats.datetime ) : (date || '')
c7776a 89         };
M 90     },
aca70a 91
c7776a 92     getFormats: function( props ){
M 93         var formats = {
839cd8 94                 date: props.dateFormat || '',
M 95                 time: props.timeFormat || ''
c37f80 96             },
M 97             locale = this.localMoment( props.date ).localeData()
98         ;
5e870c 99
3515a4 100         if( formats.date === true ){
M 101             formats.date = locale.longDateFormat('L');
c7776a 102         }
3515a4 103         if( formats.time === true ){
M 104             formats.time = locale.longDateFormat('LT');
c7776a 105         }
5e870c 106
0d9dc7 107         formats.datetime = formats.date && formats.time ?
M 108             formats.date + ' ' + formats.time :
109             formats.date || formats.time
110         ;
c7776a 111
M 112         return formats;
113     },
114
115     componentWillReceiveProps: function(nextProps) {
c658ad 116         var formats = this.getFormats( nextProps ),
M 117             update = {}
c37f80 118         ;
M 119
c658ad 120         if( nextProps.value ){
M 121             update = this.getStateFromProps( nextProps );
122         }
123         if ( formats.datetime !== this.getFormats( this.props ).datetime ) {
124             update.inputFormat = formats.datetime;
c7776a 125         }
M 126
c658ad 127         this.setState( update );
M 128     },
129
130     onInputChange: function( e ) {
131         var value = e.target == null ? e : e.target.value,
132             localMoment = this.localMoment( value, this.state.inputFormat ),
133             update = { inputValue: value }
134         ;
135
136         if ( localMoment.isValid() && !this.props.value ) {
137             update.selectedDate = localMoment;
138             update.viewDate = localMoment.clone().startOf("month");
139         }
62fd2f 140         else {
M 141             update.selectedDate = null;
142         }
c658ad 143
M 144         return this.setState( update, function() {
62fd2f 145             return this.props.onChange( localMoment.isValid() ? localMoment : this.state.inputValue );
c7776a 146         });
M 147     },
148
149     showView: function( view ){
150         var me = this;
151         return function( e ){
152             me.setState({ currentView: view });
153         };
154     },
155
156     setDate: function( type ){
157         var me = this,
4ad788 158             nextViews = {
M 159                 month: 'days',
160                 year: 'months'
161             }
c7776a 162         ;
M 163         return function( e ){
164             me.setState({
d4a1e8 165                 viewDate: me.state.viewDate.clone()[ type ]( parseInt(e.target.getAttribute('data-value')) ).startOf( type ),
c7776a 166                 currentView: nextViews[ type ]
M 167             });
9fb8e8 168         };
c7776a 169     },
M 170
171     addTime: function( amount, type, toSelected ){
172         return this.updateTime( 'add', amount, type, toSelected );
173     },
9fb8e8 174
c7776a 175     subtractTime: function( amount, type, toSelected ){
M 176         return this.updateTime( 'subtract', amount, type, toSelected );
177     },
9fb8e8 178
c7776a 179     updateTime: function( op, amount, type, toSelected ){
M 180         var me = this;
181
182         return function(){
183             var update = {},
184                 date = toSelected ? 'selectedDate' : 'viewDate'
185             ;
186
187             update[ date ] = me.state[ date ].clone()[ op ]( amount, type );
188
189             me.setState( update );
190         };
191     },
192
193     allowedSetTime: ['hours','minutes','seconds', 'milliseconds'],
194     setTime: function( type, value ){
195         var index = this.allowedSetTime.indexOf( type ) + 1,
62fd2f 196             state = this.state,
M 197             date = (state.selectedDate || state.viewDate).clone(),
c7776a 198             nextType
M 199         ;
200
4ad788 201         // It is needed to set all the time properties
M 202         // to not to reset the time
c7776a 203         date[ type ]( value );
M 204         for (; index < this.allowedSetTime.length; index++) {
205             nextType = this.allowedSetTime[index];
206             date[ nextType ]( date[nextType]() );
207         }
4ad788 208
c658ad 209         if( !this.props.value ){
M 210             this.setState({
211                 selectedDate: date,
62fd2f 212                 inputValue: date.format( state.inputFormat )
c658ad 213             });
M 214         }
4e9d38 215         this.props.onChange( date );
c7776a 216     },
M 217
c658ad 218     updateSelectedDate: function( e ) {
c7776a 219         var target = e.target,
c37f80 220             modifier = 0,
62fd2f 221             viewDate = this.state.viewDate,
M 222             currentDate = this.state.selectedDate || viewDate,
c37f80 223             date
c7776a 224         ;
M 225
226         if(target.className.indexOf("new") != -1)
227             modifier = 1;
228         else if(target.className.indexOf("old") != -1)
229             modifier = -1;
230
62fd2f 231         date = viewDate.clone()
M 232             .month( viewDate.month() + modifier )
d4a1e8 233             .date( parseInt( target.getAttribute('data-value') ) )
c7776a 234             .hours( currentDate.hours() )
M 235             .minutes( currentDate.minutes() )
236             .seconds( currentDate.seconds() )
237             .milliseconds( currentDate.milliseconds() )
238         ;
239
c658ad 240         if( !this.props.value ){
M 241             this.setState({
242                 selectedDate: date,
243                 viewDate: date.clone().startOf('month'),
244                 inputValue: date.format( this.state.inputFormat )
245             });
246         }
4e9d38 247
M 248         this.props.onChange( date );
c7776a 249     },
M 250
251     openCalendar: function() {
a3a33b 252         this.setState({ open: true });
c7776a 253     },
M 254
255     handleClickOutside: function(){
62fd2f 256         if( this.props.input && this.state.open ){
a3a33b 257             this.setState({ open: false });
62fd2f 258             this.props.onBlur( this.state.selectedDate || this.state.inputValue );
M 259         }
c7776a 260     },
M 261
c658ad 262     localMoment: function( date, format ){
M 263         var m = moment( date, format );
c37f80 264         if( this.props.locale )
M 265             m.locale( this.props.locale );
266         return m;
267     },
268
c7776a 269     componentProps: {
c658ad 270         fromProps: ['value', 'isValidDate', 'renderDay', 'renderMonth', 'renderYear'],
9fb8e8 271         fromState: ['viewDate', 'selectedDate' ],
c658ad 272         fromThis: ['setDate', 'setTime', 'showView', 'addTime', 'subtractTime', 'updateSelectedDate', 'localMoment']
c7776a 273     },
M 274
275     getComponentProps: function(){
276         var me = this,
a3a33b 277             formats = this.getFormats( this.props ),
M 278             props = {dateFormat: formats.date, timeFormat: formats.time}
c7776a 279         ;
M 280
281         this.componentProps.fromProps.forEach( function( name ){
282             props[ name ] = me.props[ name ];
283         });
284         this.componentProps.fromState.forEach( function( name ){
285             props[ name ] = me.state[ name ];
286         });
287         this.componentProps.fromThis.forEach( function( name ){
288             props[ name ] = me[ name ];
289         });
290
291         return props;
292     },
293
294     render: function() {
d76f7b 295         var Component = this.viewComponents[ this.state.currentView ],
a3a33b 296             DOM = React.DOM,
8abb28 297             className = 'rdt ' + this.props.className,
a3a33b 298             children = []
M 299         ;
300
301         if( this.props.input ){
302             children = [ DOM.input( assign({
18dc17 303                 key: 'i',
d76f7b 304                 type:'text',
2bb9ca 305                 className: 'form-control',
d76f7b 306                 onFocus: this.openCalendar,
c658ad 307                 onChange: this.onInputChange,
d76f7b 308                 value: this.state.inputValue
a3a33b 309             }, this.props.inputProps ))];
M 310         }
311         else {
312             className += ' rdtStatic';
313         }
d76f7b 314
a3a33b 315         if( this.state.open )
M 316             className += ' rdtOpen';
317
318         return DOM.div({className: className}, children.concat(
319             DOM.div(
320                 { key: 'dt', className: 'rdtPicker' },
321                 React.createElement( Component, this.getComponentProps())
d76f7b 322             )
a3a33b 323         ));
c7776a 324     }
47e834 325 });
LC 326
cc4dda 327 // Make moment accessible through the Datetime class
M 328 Datetime.moment = moment;
329
9fb8e8 330 module.exports = Datetime;