From 2294ea28fcea90d1424213c33338c07d1a0c4834 Mon Sep 17 00:00:00 2001 From: Ji Won Shin Date: Sat, 8 Jul 2017 22:20:02 -0700 Subject: [PATCH] fix(datepicker): allow disabling calendar popup (#5305) * Add ability to disable datepicker pop-up in read-only mode. * Change readOnly to disabled * Added ability to disable datepicker toggle when the input is disabled. * Lint edits * Failed test fixes * Allow disabled input to disable popup unless specified otherwise for datepicker. * Minor comment edits * Added host bindings and changed delegation logic. * Added tests for cascade and removed async testing. --- src/demo-app/datepicker/datepicker-demo.html | 36 +++++++++-- src/demo-app/datepicker/datepicker-demo.ts | 2 + src/lib/datepicker/datepicker-input.ts | 10 ++++ src/lib/datepicker/datepicker-toggle.ts | 14 ++++- src/lib/datepicker/datepicker.spec.ts | 63 ++++++++++++++++---- src/lib/datepicker/datepicker.ts | 13 +++- 6 files changed, 121 insertions(+), 17 deletions(-) diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html index 1f6f5a882802..0aab603d2ba2 100644 --- a/src/demo-app/datepicker/datepicker-demo.html +++ b/src/demo-app/datepicker/datepicker-demo.html @@ -3,25 +3,26 @@

Options

Use touch UI Filter odd months and dates Start in year view + Disable datepicker

- + - +

- +

Result

@@ -44,12 +45,14 @@

Result

- Result

+ +

Input disabled datepicker

+

+ + + + + +

+ +

Input disabled, datepicker popup enabled

+

+ + + + + +

diff --git a/src/demo-app/datepicker/datepicker-demo.ts b/src/demo-app/datepicker/datepicker-demo.ts index 0b2264e380dd..b80c11c6f830 100644 --- a/src/demo-app/datepicker/datepicker-demo.ts +++ b/src/demo-app/datepicker/datepicker-demo.ts @@ -11,6 +11,8 @@ export class DatepickerDemo { touch: boolean; filterOdd: boolean; yearView: boolean; + inputDisabled: boolean; + datepickerDisabled: boolean; minDate: Date; maxDate: Date; startAt: Date; diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index 59bd785d6bf5..524ca4dea34b 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -35,6 +35,7 @@ import {DOWN_ARROW} from '../core/keyboard/keycodes'; import {DateAdapter} from '../core/datetime/index'; import {createMissingDateImplError} from './datepicker-errors'; import {MD_DATE_FORMATS, MdDateFormats} from '../core/datetime/date-formats'; +import {coerceBooleanProperty} from '@angular/cdk'; export const MD_DATEPICKER_VALUE_ACCESSOR: any = { @@ -61,6 +62,7 @@ export const MD_DATEPICKER_VALIDATORS: any = { '[attr.aria-owns]': '_datepicker?.id', '[attr.min]': 'min ? _dateAdapter.getISODateString(min) : null', '[attr.max]': 'max ? _dateAdapter.getISODateString(max) : null', + '[disabled]': 'disabled', '(input)': '_onInput($event.target.value)', '(blur)': '_onTouched()', '(keydown)': '_onKeydown($event)', @@ -124,6 +126,14 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces } private _max: D; + /** Whether the datepicker-input is disabled. */ + @Input() + get disabled() { return this._disabled; } + set disabled(value: any) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled: boolean; + /** Emits when the value changes (either due to user input or programmatic change). */ _valueChange = new EventEmitter(); diff --git a/src/lib/datepicker/datepicker-toggle.ts b/src/lib/datepicker/datepicker-toggle.ts index c3f4a5944540..9f7591e015eb 100644 --- a/src/lib/datepicker/datepicker-toggle.ts +++ b/src/lib/datepicker/datepicker-toggle.ts @@ -9,6 +9,7 @@ import {ChangeDetectionStrategy, Component, Input, ViewEncapsulation} from '@angular/core'; import {MdDatepicker} from './datepicker'; import {MdDatepickerIntl} from './datepicker-intl'; +import {coerceBooleanProperty} from '@angular/cdk'; @Component({ @@ -20,6 +21,7 @@ import {MdDatepickerIntl} from './datepicker-intl'; 'type': 'button', 'class': 'mat-datepicker-toggle', '[attr.aria-label]': '_intl.openCalendarLabel', + '[disabled]': 'disabled', '(click)': '_open($event)', }, encapsulation: ViewEncapsulation.None, @@ -33,10 +35,20 @@ export class MdDatepickerToggle { get _datepicker() { return this.datepicker; } set _datepicker(v: MdDatepicker) { this.datepicker = v; } + /** Whether the toggle button is disabled. */ + @Input() + get disabled() { + return this._disabled === undefined ? this.datepicker.disabled : this._disabled; + } + set disabled(value) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled: boolean; + constructor(public _intl: MdDatepickerIntl) {} _open(event: Event): void { - if (this.datepicker) { + if (this.datepicker && !this.disabled) { this.datepicker.open(); event.stopPropagation(); } diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index ea7f05288c33..5acbd8fe3d0b 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -65,16 +65,16 @@ describe('MdDatepicker', () => { fixture.detectChanges(); })); - it('open non-touch should open popup', async(() => { + it('open non-touch should open popup', () => { expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); testComponent.datepicker.open(); fixture.detectChanges(); expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull(); - })); + }); - it('open touch should open dialog', async(() => { + it('open touch should open dialog', () => { testComponent.touch = true; fixture.detectChanges(); @@ -84,9 +84,36 @@ describe('MdDatepicker', () => { fixture.detectChanges(); expect(document.querySelector('md-dialog-container')).not.toBeNull(); - })); + }); + + it('open in disabled mode should not open the calendar', () => { + testComponent.disabled = true; + fixture.detectChanges(); + + expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); + expect(document.querySelector('md-dialog-container')).toBeNull(); + + testComponent.datepicker.open(); + fixture.detectChanges(); + + expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); + expect(document.querySelector('md-dialog-container')).toBeNull(); + }); + + it('disabled datepicker input should open the calendar if datepicker is enabled', () => { + testComponent.datepicker.disabled = false; + testComponent.datepickerInput.disabled = true; + fixture.detectChanges(); + + expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); - it('close should close popup', async(() => { + testComponent.datepicker.open(); + fixture.detectChanges(); + + expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull(); + }); + + it('close should close popup', () => { testComponent.datepicker.open(); fixture.detectChanges(); @@ -100,7 +127,7 @@ describe('MdDatepicker', () => { fixture.whenStable().then(() => { expect(parseInt(getComputedStyle(popup).height as string)).toBe(0); }); - })); + }); it('should close the popup when pressing ESCAPE', () => { testComponent.datepicker.open(); @@ -119,7 +146,7 @@ describe('MdDatepicker', () => { .toBe(true, 'Expected default ESCAPE action to be prevented.'); }); - it('close should close dialog', async(() => { + it('close should close dialog', () => { testComponent.touch = true; fixture.detectChanges(); @@ -134,9 +161,9 @@ describe('MdDatepicker', () => { fixture.whenStable().then(() => { expect(document.querySelector('md-dialog-container')).toBeNull(); }); - })); + }); - it('setting selected should update input and close calendar', async(() => { + it('setting selected should update input and close calendar', () => { testComponent.touch = true; fixture.detectChanges(); @@ -154,7 +181,7 @@ describe('MdDatepicker', () => { expect(document.querySelector('md-dialog-container')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); }); - })); + }); it('startAt should fallback to input value', () => { expect(testComponent.datepicker.startAt).toEqual(new Date(2020, JAN, 1)); @@ -433,6 +460,19 @@ describe('MdDatepicker', () => { expect(document.querySelector('md-dialog-container')).not.toBeNull(); }); + it('should not open calendar when toggle clicked if datepicker is disabled', () => { + testComponent.datepicker.disabled = true; + fixture.detectChanges(); + + expect(document.querySelector('md-dialog-container')).toBeNull(); + + let toggle = fixture.debugElement.query(By.css('button')); + dispatchMouseEvent(toggle.nativeElement, 'click'); + fixture.detectChanges(); + + expect(document.querySelector('md-dialog-container')).toBeNull(); + }); + it('should set the `button` type on the trigger to prevent form submissions', () => { let toggle = fixture.debugElement.query(By.css('button')).nativeElement; expect(toggle.getAttribute('type')).toBe('button'); @@ -724,11 +764,12 @@ describe('MdDatepicker', () => { @Component({ template: ` - + `, }) class StandardDatepicker { touch = false; + disabled = false; date = new Date(2020, JAN, 1); @ViewChild('d') datepicker: MdDatepicker; @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index 7d4bedf51d4d..9523f57c92e9 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -38,6 +38,7 @@ import {createMissingDateImplError} from './datepicker-errors'; import {ESCAPE} from '../core/keyboard/keycodes'; import {MdCalendar} from './calendar'; import {first} from '../core/rxjs/index'; +import {coerceBooleanProperty} from '@angular/cdk'; /** Used to generate a unique ID for each datepicker instance. */ @@ -115,6 +116,16 @@ export class MdDatepicker implements OnDestroy { */ @Input() touchUi = false; + /** Whether the datepicker pop-up should be disabled. */ + @Input() + get disabled() { + return this._disabled === undefined ? this._datepickerInput.disabled : this._disabled; + } + set disabled(value: any) { + this._disabled = coerceBooleanProperty(value); + } + private _disabled: boolean; + /** Emits new selected date when selected date changes. */ @Output() selectedChanged = new EventEmitter(); @@ -205,7 +216,7 @@ export class MdDatepicker implements OnDestroy { /** Open the calendar. */ open(): void { - if (this.opened) { + if (this.opened || this.disabled) { return; } if (!this._datepickerInput) {