From 5711642104dbe0aff8b4bda70f063af51e87d0ff Mon Sep 17 00:00:00 2001 From: Mark Porter Date: Wed, 9 Aug 2017 16:05:15 -0400 Subject: [PATCH] Added onViewDateChange callback for when the viewed date changes --- README.md | 1 + src/DateTime.js | 34 +++++++++++++--------- test/tests.spec.js | 66 +++++++++++++++++++++++++++++++++++++++++++ typings/DateTime.d.ts | 5 ++++ 4 files changed, 92 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 867e40d95..4b31c24eb 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Below we have all the props that we can use with the `` component. The | **onBeforeNavigate** | `function` | ( nextView, currentView, viewDate ) => nextView | Allows to intercept a change of the calendar view. The accepted function receives the view that it's supposed to navigate to, the view that is showing currently and the date currently shown in the view. Return a viewMode ( default ones are `years`, `months`, `days` or `time`) to navigate to it. If the function returns a "falsy" value, the navigation is stopped and we will remain in the current view. | | **onNavigateBack** | `function` | empty function | Callback trigger when the user navigates to the previous month, year or decade. The callback receives the amount and type ('month', 'year') as parameters. | | **onNavigateForward** | `function` | empty function | Callback trigger when the user navigates to the next month, year or decade. The callback receives the amount and type ('month', 'year') as parameters. | +| **onViewDateChange** | `function` | empty function | Callback trigger for when the user changes the currently viewed date. The callback receives the current view date `moment` object as only parameter. | | **className** | `string` or `string array` | `''` | Extra class name for the outermost markup element. | | **inputProps** | `object` | `undefined` | Defines additional attributes for the input element of the component. For example: `onClick`, `placeholder`, `disabled`, `required`, `name` and `className` (`className` *sets* the class attribute for the input element). See [Customize the Input Appearance](#customize-the-input-appearance). | | **isValidDate** | `function` | `() => true` | Define the dates that can be selected. The function receives `(currentDate, selectedDate)` and shall return a `true` or `false` whether the `currentDate` is valid or not. See [selectable dates](#selectable-dates).| diff --git a/src/DateTime.js b/src/DateTime.js index 88a72bfb0..ae33f4aad 100644 --- a/src/DateTime.js +++ b/src/DateTime.js @@ -31,6 +31,7 @@ export default class Datetime extends React.Component { onBeforeNavigate: TYPES.func, onNavigateBack: TYPES.func, onNavigateForward: TYPES.func, + onViewDateChange: TYPES.func, updateOnView: TYPES.string, locale: TYPES.string, utc: TYPES.bool, @@ -62,6 +63,7 @@ export default class Datetime extends React.Component { onBeforeNavigate: function(next) { return next; }, onNavigateBack: nofn, onNavigateForward: nofn, + onViewDateChange: nofn, dateFormat: true, timeFormat: true, utc: false, @@ -445,25 +447,29 @@ export default class Datetime extends React.Component { } } - componentDidUpdate( prevProps ) { - if ( prevProps === this.props ) return; + componentDidUpdate( prevProps, prevState ) { + if ( !prevState.viewDate.isSame( this.state.viewDate ) ) { + this.props.onViewDateChange( this.state.viewDate ); + } - let needsUpdate = false; - let thisProps = this.props; + if ( prevProps !== this.props ) { + let needsUpdate = false; + let thisProps = this.props; - ['locale', 'utc', 'displayZone', 'dateFormat', 'timeFormat'].forEach( function(p) { - prevProps[p] !== thisProps[p] && (needsUpdate = true); - }); + ['locale', 'utc', 'displayZone', 'dateFormat', 'timeFormat'].forEach( function(p) { + prevProps[p] !== thisProps[p] && (needsUpdate = true); + }); - if ( needsUpdate ) { - this.regenerateDates(); - } + if ( needsUpdate ) { + this.regenerateDates(); + } - if ( thisProps.value && thisProps.value !== prevProps.value ) { - this.setViewDate( thisProps.value ); - } + if ( thisProps.value && thisProps.value !== prevProps.value ) { + this.setViewDate( thisProps.value ); + } - this.checkTZ(); + this.checkTZ(); + } } regenerateDates() { diff --git a/test/tests.spec.js b/test/tests.spec.js index 498afa2ec..ccedafc31 100644 --- a/test/tests.spec.js +++ b/test/tests.spec.js @@ -1259,6 +1259,72 @@ describe('Datetime', () => { }); }); + describe('onViewDateChange', () => { + it('when increase month', () => { + const date = new Date(2000, 0, 15, 2, 2, 2, 2), + mDate = moment(date), + onViewDateChangeFn = jest.fn(), + component = utils.createDatetime({ initialViewDate: date, initialViewMode: 'months', onViewDateChange: onViewDateChangeFn }); + + utils.clickOnElement(component.find('.rdtNext').at(0)); + expect(onViewDateChangeFn).toHaveBeenCalledTimes(1); + expect(onViewDateChangeFn.mock.calls[0][0].isSame(mDate.add(1, 'month'), 'month')).toBeTruthy(); + }); + + it('when decrease month', () => { + const date = new Date(2000, 0, 15, 2, 2, 2, 2), + mDate = moment(date), + onViewDateChangeFn = jest.fn(), + component = utils.createDatetime({ initialViewDate: date, initialViewMode: 'months', onViewDateChange: onViewDateChangeFn }); + + utils.clickOnElement(component.find('.rdtPrev').at(0)); + expect(onViewDateChangeFn).toHaveBeenCalledTimes(1); + expect(onViewDateChangeFn.mock.calls[0][0].isSame(mDate.subtract(1, 'month'), 'month')).toBeTruthy(); + }); + + it('when pick month in picker', () => { + const date = new Date(2000, 0, 15, 2, 2, 2, 2), + mDate = moment(date), + onViewDateChangeFn = jest.fn(), + component = utils.createDatetime({ initialViewDate: date, initialViewMode: 'months', onViewDateChange: onViewDateChangeFn }); + + utils.clickNthMonth(component, 5); + expect(onViewDateChangeFn).toHaveBeenCalledTimes(1); + expect(onViewDateChangeFn.mock.calls[0][0].isSame(mDate.add(5, 'month'), 'month')).toBeTruthy(); + }); + + it('when pick same month in picker', () => { + const date = new Date(2000, 0, 15, 2, 2, 2, 2), + onViewDateChangeFn = jest.fn(), + component = utils.createDatetime({ initialViewDate: date, initialViewMode: 'months', onViewDateChange: onViewDateChangeFn }); + + utils.clickNthMonth(component, 0); + expect(onViewDateChangeFn).not.toHaveBeenCalled(); + }); + + it('when next is clicked in month view', () => { + const date = new Date(2000, 0, 15, 2, 2, 2, 2), + mDate = moment(date), + onViewDateChangeFn = jest.fn(), + component = utils.createDatetime({ initialViewDate: date, initialViewMode: 'months', onViewDateChange: onViewDateChangeFn }); + + utils.clickOnElement(component.find('.rdtNext').at(0)); + expect(onViewDateChangeFn).toHaveBeenCalledTimes(1); + expect(onViewDateChangeFn.mock.calls[0][0].isSame(mDate.add(1, 'year'), 'year')).toBeTruthy(); + }); + + it('when prev is clicked in month view', () => { + const date = new Date(2000, 0, 15, 2, 2, 2, 2), + mDate = moment(date), + onViewDateChangeFn = jest.fn(), + component = utils.createDatetime({ initialViewDate: date, initialViewMode: 'months', onViewDateChange: onViewDateChangeFn }); + + utils.clickOnElement(component.find('.rdtPrev').at(0)); + expect(onViewDateChangeFn).toHaveBeenCalledTimes(1); + expect(onViewDateChangeFn.mock.calls[0][0].isSame(mDate.subtract(1, 'year'), 'year')).toBeTruthy(); + }); + }); + describe('onNavigateBack', () => { it('when moving to previous month', () => { const component = utils.createDatetime({ onNavigateBack: (amount, type) => { diff --git a/typings/DateTime.d.ts b/typings/DateTime.d.ts index 99bbc4134..5384ff2a1 100644 --- a/typings/DateTime.d.ts +++ b/typings/DateTime.d.ts @@ -96,6 +96,11 @@ declare namespace ReactDatetimeClass { to the user's local timezone (unless `utc` specified). */ displayTimeZone?: string; + /* + /* + Callback trigger for when the user navigates the calendar + */ + onViewDateChange?: (value: Moment) => void; /* Callback trigger when the date changes. The callback receives the selected `moment` object as only parameter, if the date in the input is valid. If the date in the input is not valid, the