fix(datepicker): toggle not reacting to disabled state changes in datepicker or input (#6964)

Fixes the datepicker toggle not reacting to changes in the disabled state of the associated datepicker and datepicker input.
This commit is contained in:
Kristiyan Kostadinov
2017-09-12 02:20:30 +02:00
committed by mmalerba
parent 9fe6386eec
commit 85993d3a96
5 changed files with 78 additions and 16 deletions
+10 -4
View File
@@ -4,22 +4,26 @@
<md-checkbox [(ngModel)]="filterOdd">Filter odd months and dates</md-checkbox>
<md-checkbox [(ngModel)]="yearView">Start in year view</md-checkbox>
<md-checkbox [(ngModel)]="datepickerDisabled">Disable datepicker</md-checkbox>
<md-checkbox [(ngModel)]="inputDisabled">Disable input</md-checkbox>
</p>
<p>
<md-form-field>
<input mdInput [mdDatepicker]="minDatePicker" [(ngModel)]="minDate" placeholder="Min date">
<input mdInput [mdDatepicker]="minDatePicker" [(ngModel)]="minDate" placeholder="Min date"
[disabled]="inputDisabled">
<md-datepicker-toggle mdSuffix [for]="minDatePicker"></md-datepicker-toggle>
<md-datepicker #minDatePicker [touchUi]="touch" [disabled]="datepickerDisabled"></md-datepicker>
</md-form-field>
<md-form-field>
<input mdInput [mdDatepicker]="maxDatePicker" [(ngModel)]="maxDate" placeholder="Max date">
<input mdInput [mdDatepicker]="maxDatePicker" [(ngModel)]="maxDate" placeholder="Max date"
[disabled]="inputDisabled">
<md-datepicker-toggle mdSuffix [for]="maxDatePicker"></md-datepicker-toggle>
<md-datepicker #maxDatePicker [touchUi]="touch" [disabled]="datepickerDisabled"></md-datepicker>
</md-form-field>
</p>
<p>
<md-form-field>
<input mdInput [mdDatepicker]="startAtPicker" [(ngModel)]="startAt" placeholder="Start at date">
<input mdInput [mdDatepicker]="startAtPicker" [(ngModel)]="startAt" placeholder="Start at date"
[disabled]="inputDisabled">
<md-datepicker-toggle mdSuffix [for]="startAtPicker"></md-datepicker-toggle>
<md-datepicker #startAtPicker [touchUi]="touch" [disabled]="datepickerDisabled"></md-datepicker>
</md-form-field>
@@ -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">
<md-datepicker-toggle [for]="resultPicker2"></md-datepicker-toggle>
@@ -81,7 +87,7 @@
<md-datepicker-toggle [for]="datePicker1"></md-datepicker-toggle>
<md-form-field>
<input mdInput [mdDatepicker]="datePicker1" [(ngModel)]="date" [min]="minDate" [max]="maxDate"
[mdDatepickerFilter]="filterOdd ? dateFilter : null" [disabled]="true"
[mdDatepickerFilter]="filterOdd ? dateFilter : null" disabled
placeholder="Input disabled">
<md-datepicker #datePicker1 [touchUi]="touch" [startAt]="startAt"
[startView]="yearView ? 'year' : 'month'"></md-datepicker>
+11 -1
View File
@@ -153,7 +153,12 @@ export class MdDatepickerInput<D> 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<D> implements AfterContentInit, ControlValueAcces
/** Emits when the value changes (either due to user input or programmatic change). */
_valueChange = new EventEmitter<D|null>();
/** Emits when the disabled state has changed */
_disabledChange = new EventEmitter<boolean>();
_onTouched = () => {};
private _cvaOnChange: (value: any) => void = () => {};
@@ -237,6 +245,8 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
ngOnDestroy() {
this._datepickerSubscription.unsubscribe();
this._valueChange.complete();
this._disabledChange.complete();
}
registerOnValidatorChange(fn: () => void): void {
+23 -5
View File
@@ -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<D> implements OnDestroy {
private _intlChanges: Subscription;
export class MdDatepickerToggle<D> implements OnChanges, OnDestroy {
private _stateChanges = Subscription.EMPTY;
/** Datepicker instance that the button will toggle. */
@Input('for') datepicker: MdDatepicker<D>;
@@ -46,12 +50,26 @@ export class MdDatepickerToggle<D> 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<D> = 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 {
+20 -2
View File
@@ -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<Date>;
@ViewChild(MdDatepickerInput) input: MdDatepickerInput<Date>;
touchUI = true;
}
+14 -4
View File
@@ -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<D> 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<D> implements OnDestroy {
/** A portal containing the calendar for this datepicker. */
private _calendarPortal: ComponentPortal<MdDatepickerContent<D>>;
/** The input element this datepicker is associated with. */
private _datepickerInput: MdDatepickerInput<D>;
/** 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<D>;
/** Emits when the datepicker is disabled. */
_disabledChange = new Subject<boolean>();
constructor(private _dialog: MdDialog,
private _overlay: Overlay,
private _ngZone: NgZone,
@@ -218,6 +227,7 @@ export class MdDatepicker<D> implements OnDestroy {
ngOnDestroy() {
this.close();
this._inputSubscription.unsubscribe();
this._disabledChange.complete();
if (this._popupRef) {
this._popupRef.dispose();