Skip to content

Commit

Permalink
fix(datepicker): calendar should update when input changes (#3824)
Browse files Browse the repository at this point in the history
* fix(datepicker): calendar selected date should change when input changes

* startAt fix

* fix tests

* address comments

* fix unsubscribe

* make valueChange on datepicker-input internal
  • Loading branch information
mmalerba committed Apr 14, 2017
1 parent ae1b1c3 commit dba2b68
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 22 deletions.
7 changes: 7 additions & 0 deletions src/lib/core/datetime/simple-date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export class SimpleDate {
return SimpleDate.fromNativeDate(new Date());
}

/**
* Checks whether the given dates are equal. Null dates are considered equal to other null dates.
*/
static equals(first: SimpleDate, second: SimpleDate): boolean {
return first && second ? !first.compare(second) : first == second;
}

/** The native JS Date. */
private _date: Date;

Expand Down
2 changes: 1 addition & 1 deletion src/lib/datepicker/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class MdCalendar implements AfterContentInit {

/** Handles date selection in the month view. */
_dateSelected(date: SimpleDate): void {
if ((!date || !this.selected) && date != this.selected || date.compare(this.selected)) {
if (!SimpleDate.equals(date, this.selected)) {
this.selectedChange.emit(date);
}
}
Expand Down
30 changes: 23 additions & 7 deletions src/lib/datepicker/datepicker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
AfterContentInit,
Directive,
ElementRef,
EventEmitter,
forwardRef,
Input,
OnDestroy,
Expand All @@ -15,6 +16,7 @@ import {CalendarLocale} from '../core/datetime/calendar-locale';
import {Subscription} from 'rxjs/Subscription';
import {MdInputContainer} from '../input/input-container';
import {DOWN_ARROW} from '../core/keyboard/keycodes';
import {Observable} from 'rxjs/Observable';


export const MD_DATEPICKER_VALUE_ACCESSOR: any = {
Expand All @@ -34,7 +36,7 @@ export const MD_DATEPICKER_VALUE_ACCESSOR: any = {
'[attr.aria-owns]': '_datepicker?.id',
'[min]': '_min?.toNativeDate()',
'[max]': '_max?.toNativeDate()',
'(input)': '_onChange($event.target.value)',
'(input)': '_onInput($event.target.value)',
'(blur)': '_onTouched()',
'(keydown)': '_onKeydown($event)',
}
Expand All @@ -56,14 +58,17 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor
/** The value of the input. */
@Input()
get value(): SimpleDate {
return this._value;
return this._locale.parseDate(this._elementRef.nativeElement.value);
}
set value(value: SimpleDate) {
this._value = this._locale.parseDate(value);
const stringValue = this._value == null ? '' : this._locale.formatDate(this._value);
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', stringValue);
let date = this._locale.parseDate(value);
let oldDate = this.value;
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value',
date ? this._locale.formatDate(date) : '');
if (!SimpleDate.equals(oldDate, date)) {
this._valueChangeEmitter.emit(date);
}
}
private _value: SimpleDate;

/** The minimum valid date. */
@Input()
Expand All @@ -77,6 +82,11 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor
set max(value: SimpleDate) { this._max = this._locale.parseDate(value); }
private _max: SimpleDate;

private _valueChangeEmitter = new EventEmitter<SimpleDate>();

/** Emits when the value changes (either due to user input or programmatic change). */
_valueChange: Observable<SimpleDate> = this._valueChangeEmitter.asObservable();

_onChange = (value: any) => {};

_onTouched = () => {};
Expand Down Expand Up @@ -120,7 +130,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor

// Implemented as part of ControlValueAccessor
registerOnChange(fn: (value: any) => void): void {
this._onChange = value => fn(this._locale.parseDate(value));
this._onChange = fn;
}

// Implemented as part of ControlValueAccessor
Expand All @@ -139,4 +149,10 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAccessor
event.preventDefault();
}
}

_onInput(value: string) {
let date = this._locale.parseDate(value);
this._onChange(date);
this._valueChangeEmitter.emit(date);
}
}
3 changes: 2 additions & 1 deletion src/lib/datepicker/datepicker.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[minDate]="_minDate"
[maxDate]="_maxDate"
[dateFilter]="dateFilter"
[(selected)]="_selected">
[selected]="_selected"
(selectedChange)="_selectAndClose($event)">
</md-calendar>
</ng-template>
12 changes: 6 additions & 6 deletions src/lib/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,13 @@ describe('MdDatepicker', () => {
expect(document.querySelector('md-dialog-container')).not.toBeNull();
expect(testComponent.datepickerInput.value).toEqual(new SimpleDate(2020, 0, 1));

let selected = new SimpleDate(2017, 0, 1);
testComponent.datepicker._selected = selected;
let cells = document.querySelectorAll('.mat-calendar-table-cell');
dispatchMouseEvent(cells[1], 'click');
fixture.detectChanges();

fixture.whenStable().then(() => {
expect(document.querySelector('md-dialog-container')).toBeNull();
expect(testComponent.datepickerInput.value).toEqual(selected);
expect(testComponent.datepickerInput.value).toEqual(new SimpleDate(2020, 0, 2));
});
}));

Expand Down Expand Up @@ -228,7 +228,7 @@ describe('MdDatepicker', () => {
expect(testComponent.datepickerInput.value).toBeNull();

let selected = new SimpleDate(2017, 0, 1);
testComponent.datepicker._selected = selected;
testComponent.datepicker._selectAndClose(selected);
fixture.detectChanges();

fixture.whenStable().then(() => {
Expand All @@ -255,7 +255,7 @@ describe('MdDatepicker', () => {

expect(inputEl.classList).toContain('ng-pristine');

testComponent.datepicker._selected = new SimpleDate(2017, 0, 1);
testComponent.datepicker._selectAndClose(new SimpleDate(2017, 0, 1));
fixture.detectChanges();

fixture.whenStable().then(() => {
Expand Down Expand Up @@ -330,7 +330,7 @@ describe('MdDatepicker', () => {
expect(testComponent.datepickerInput.value).toBeNull();

let selected = new SimpleDate(2017, 0, 1);
testComponent.datepicker._selected = selected;
testComponent.datepicker._selectAndClose(selected);
fixture.detectChanges();

expect(testComponent.formControl.value).toEqual(selected);
Expand Down
26 changes: 19 additions & 7 deletions src/lib/datepicker/datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {SimpleDate} from '../core/datetime/simple-date';
import {MdDatepickerInput} from './datepicker-input';
import {CalendarLocale} from '../core/datetime/calendar-locale';
import 'rxjs/add/operator/first';
import {Subscription} from 'rxjs/Subscription';


/** Used to generate a unique ID for each datepicker instance. */
Expand Down Expand Up @@ -81,13 +82,7 @@ export class MdDatepicker implements OnDestroy {
id = `md-datepicker-${datepickerUid++}`;

/** The currently selected date. */
get _selected(): SimpleDate {
return this._datepickerInput ? this._datepickerInput.value : null;
}
set _selected(value: SimpleDate) {
this.selectedChanged.emit(value);
this.close();
}
_selected: SimpleDate = null;

/** The minimum selectable date. */
get _minDate(): SimpleDate {
Expand All @@ -114,6 +109,8 @@ export class MdDatepicker implements OnDestroy {
/** The input element this datepicker is associated with. */
private _datepickerInput: MdDatepickerInput;

private _inputSubscription: Subscription;

constructor(private _dialog: MdDialog, private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef, private _locale: CalendarLocale,
@Optional() private _dir: Dir) {}
Expand All @@ -123,6 +120,19 @@ export class MdDatepicker implements OnDestroy {
if (this._popupRef) {
this._popupRef.dispose();
}
if (this._inputSubscription) {
this._inputSubscription.unsubscribe();
}
}

/** Selects the given date and closes the currently open popup or dialog. */
_selectAndClose(date: SimpleDate): void {
let oldValue = this._selected;
this._selected = date;
if (!SimpleDate.equals(oldValue, this._selected)) {
this.selectedChanged.emit(date);
}
this.close();
}

/**
Expand All @@ -134,6 +144,8 @@ export class MdDatepicker implements OnDestroy {
throw new MdError('An MdDatepicker can only be associated with a single input.');
}
this._datepickerInput = input;
this._inputSubscription =
this._datepickerInput._valueChange.subscribe((value: SimpleDate) => this._selected = value);
}

/**
Expand Down

0 comments on commit dba2b68

Please sign in to comment.