Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(datepicker): calendar should update when input changes #3824

Merged
merged 6 commits into from
Apr 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you had something like this:

<input [value]="val" (valueChange)="doSomething()" [mdDatepicker]="dp">

Would setting the value programmatically fire the doSomething() method? It looks like valueChange is emitted in both directions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, isn't that what we want? Otherwise if I set the value programmatically the calendar won't be updated to reflect the change in the input

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I see how it's solving that problem. Just concerned that users will be confused because typically when you have a model to view change, you don't expect the view to model handler to fire as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I admit its a little weird and I had to add that equals check to stop it from infinite looping, maybe we can talk about if there's a better way

}
}
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