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) {