diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html index 3cdf0a807e1..9f55b0820c6 100644 --- a/src/demo-app/datepicker/datepicker-demo.html +++ b/src/demo-app/datepicker/datepicker-demo.html @@ -4,22 +4,26 @@ Filter odd months and dates Start in year view Disable datepicker + Disable input

- + - +

- + @@ -37,6 +41,7 @@ [min]="minDate" [max]="maxDate" [mdDatepickerFilter]="filterOdd ? dateFilter : null" + [disabled]="inputDisabled" placeholder="Pick a date" (dateInput)="onDateInput($event)" (dateChange)="onDateChange($event)"> @@ -64,6 +69,7 @@ [(ngModel)]="date" [min]="minDate" [max]="maxDate" + [disabled]="inputDisabled" [mdDatepickerFilter]="filterOdd ? dateFilter : null" placeholder="Pick a date"> @@ -81,7 +87,7 @@ diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index 8c0571d27f7..7847a51b0bb 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -153,7 +153,12 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces @Input() get disabled() { return this._disabled; } set disabled(value: any) { - this._disabled = coerceBooleanProperty(value); + const newValue = coerceBooleanProperty(value); + + if (this._disabled !== newValue) { + this._disabled = newValue; + this._disabledChange.emit(newValue); + } } private _disabled: boolean; @@ -166,6 +171,9 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces /** Emits when the value changes (either due to user input or programmatic change). */ _valueChange = new EventEmitter(); + /** Emits when the disabled state has changed */ + _disabledChange = new EventEmitter(); + _onTouched = () => {}; private _cvaOnChange: (value: any) => void = () => {}; @@ -237,6 +245,8 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces ngOnDestroy() { this._datepickerSubscription.unsubscribe(); + this._valueChange.complete(); + this._disabledChange.complete(); } registerOnValidatorChange(fn: () => void): void { diff --git a/src/lib/datepicker/datepicker-toggle.ts b/src/lib/datepicker/datepicker-toggle.ts index 78c19ec890e..44698e2fbd1 100644 --- a/src/lib/datepicker/datepicker-toggle.ts +++ b/src/lib/datepicker/datepicker-toggle.ts @@ -12,12 +12,16 @@ import { Input, ViewEncapsulation, OnDestroy, + OnChanges, + SimpleChanges, ChangeDetectorRef, } from '@angular/core'; import {MdDatepicker} from './datepicker'; import {MdDatepickerIntl} from './datepicker-intl'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {Subscription} from 'rxjs/Subscription'; +import {merge} from 'rxjs/observable/merge'; +import {of as observableOf} from 'rxjs/observable/of'; @Component({ @@ -30,8 +34,8 @@ import {Subscription} from 'rxjs/Subscription'; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MdDatepickerToggle implements OnDestroy { - private _intlChanges: Subscription; +export class MdDatepickerToggle implements OnChanges, OnDestroy { + private _stateChanges = Subscription.EMPTY; /** Datepicker instance that the button will toggle. */ @Input('for') datepicker: MdDatepicker; @@ -46,12 +50,26 @@ export class MdDatepickerToggle implements OnDestroy { } private _disabled: boolean; - constructor(public _intl: MdDatepickerIntl, changeDetectorRef: ChangeDetectorRef) { - this._intlChanges = _intl.changes.subscribe(() => changeDetectorRef.markForCheck()); + constructor( + public _intl: MdDatepickerIntl, + private _changeDetectorRef: ChangeDetectorRef) {} + + ngOnChanges(changes: SimpleChanges) { + if (changes.datepicker) { + const datepicker: MdDatepicker = changes.datepicker.currentValue; + const datepickerDisabled = datepicker ? datepicker._disabledChange : observableOf(); + const inputDisabled = datepicker && datepicker._datepickerInput ? + datepicker._datepickerInput._disabledChange : + observableOf(); + + this._stateChanges.unsubscribe(); + this._stateChanges = merge(this._intl.changes, datepickerDisabled, inputDisabled) + .subscribe(() => this._changeDetectorRef.markForCheck()); + } } ngOnDestroy() { - this._intlChanges.unsubscribe(); + this._stateChanges.unsubscribe(); } _open(event: Event): void { diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index 631adefbb19..02158c0bbc6 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -552,11 +552,28 @@ describe('MdDatepicker', () => { it('should not open calendar when toggle clicked if datepicker is disabled', () => { testComponent.datepicker.disabled = true; fixture.detectChanges(); + const toggle = fixture.debugElement.query(By.css('button')).nativeElement; + expect(toggle.hasAttribute('disabled')).toBe(true); expect(document.querySelector('md-dialog-container')).toBeNull(); - let toggle = fixture.debugElement.query(By.css('button')); - dispatchMouseEvent(toggle.nativeElement, 'click'); + dispatchMouseEvent(toggle, 'click'); + fixture.detectChanges(); + + expect(document.querySelector('md-dialog-container')).toBeNull(); + }); + + it('should not open calendar when toggle clicked if input is disabled', () => { + expect(testComponent.datepicker.disabled).toBeUndefined(); + + testComponent.input.disabled = true; + fixture.detectChanges(); + const toggle = fixture.debugElement.query(By.css('button')).nativeElement; + + expect(toggle.hasAttribute('disabled')).toBe(true); + expect(document.querySelector('md-dialog-container')).toBeNull(); + + dispatchMouseEvent(toggle, 'click'); fixture.detectChanges(); expect(document.querySelector('md-dialog-container')).toBeNull(); @@ -1075,6 +1092,7 @@ class DatepickerWithFormControl { }) class DatepickerWithToggle { @ViewChild('d') datepicker: MdDatepicker; + @ViewChild(MdDatepickerInput) input: MdDatepickerInput; touchUI = true; } diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index 7f8ed7d49bf..6e87b996dee 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -44,6 +44,7 @@ import {MdDialog} from '../dialog/dialog'; import {MdDialogRef} from '../dialog/dialog-ref'; import {MdDatepickerInput} from './datepicker-input'; import {Subscription} from 'rxjs/Subscription'; +import {Subject} from 'rxjs/Subject'; import {DateAdapter} from '../core/datetime/index'; import {createMissingDateImplError} from './datepicker-errors'; import {MdCalendar} from './calendar'; @@ -150,7 +151,12 @@ export class MdDatepicker implements OnDestroy { return this._disabled === undefined ? this._datepickerInput.disabled : this._disabled; } set disabled(value: any) { - this._disabled = coerceBooleanProperty(value); + const newValue = coerceBooleanProperty(value); + + if (newValue !== this._disabled) { + this._disabled = newValue; + this._disabledChange.next(newValue); + } } private _disabled: boolean; @@ -194,14 +200,17 @@ export class MdDatepicker implements OnDestroy { /** A portal containing the calendar for this datepicker. */ private _calendarPortal: ComponentPortal>; - /** The input element this datepicker is associated with. */ - private _datepickerInput: MdDatepickerInput; - /** The element that was focused before the datepicker was opened. */ private _focusedElementBeforeOpen: HTMLElement | null = null; private _inputSubscription = Subscription.EMPTY; + /** The input element this datepicker is associated with. */ + _datepickerInput: MdDatepickerInput; + + /** Emits when the datepicker is disabled. */ + _disabledChange = new Subject(); + constructor(private _dialog: MdDialog, private _overlay: Overlay, private _ngZone: NgZone, @@ -218,6 +227,7 @@ export class MdDatepicker implements OnDestroy { ngOnDestroy() { this.close(); this._inputSubscription.unsubscribe(); + this._disabledChange.complete(); if (this._popupRef) { this._popupRef.dispose();