Skip to content

Commit

Permalink
feat(datepicker): add support for disabled datepicker
Browse files Browse the repository at this point in the history
fix #702
  • Loading branch information
jnizet authored and pkozlowski-opensource committed Sep 13, 2016
1 parent d0c51fc commit 64ec99c
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 6 deletions.
3 changes: 3 additions & 0 deletions demo/src/app/components/datepicker/datepicker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {DEMO_SNIPPETS} from './demos';
<ngbd-example-box demoTitle="Basic datepicker" [htmlSnippet]="snippets.basic.markup" [tsSnippet]="snippets.basic.code">
<ngbd-datepicker-basic></ngbd-datepicker-basic>
</ngbd-example-box>
<ngbd-example-box demoTitle="Disabled datepicker" [htmlSnippet]="snippets.disabled.markup" [tsSnippet]="snippets.disabled.code">
<ngbd-datepicker-disabled></ngbd-datepicker-disabled>
</ngbd-example-box>
<ngbd-example-box demoTitle="Internationalization of datepickers"
[htmlSnippet]="snippets.i18n.markup"
[tsSnippet]="snippets.i18n.code">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<p>Disabled datepicker</p>

<ngb-datepicker [(ngModel)]="model" [disabled]="disabled"></ngb-datepicker>

<hr/>

<button class="btn btn-sm btn-outline-{{disabled ? 'danger' : 'success'}}" (click)="disabled = !disabled">
{{ disabled ? "disabled" : "enabled"}}
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Component} from '@angular/core';

const now = new Date();

@Component({
selector: 'ngbd-datepicker-disabled',
templateUrl: './datepicker-disabled.html'
})
export class NgbdDatepickerDisabled {

model = {year: now.getFullYear(), month: now.getMonth(), day: now.getDate()};
disabled = true;
}
7 changes: 6 additions & 1 deletion demo/src/app/components/datepicker/demos/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import {NgbdDatepickerBasic} from './basic/datepicker-basic';
import {NgbdDatepickerConfig} from './config/datepicker-config';
import {NgbdDatepickerI18n} from './i18n/datepicker-i18n';
import {NgbdDatepickerDisabled} from './disabled/datepicker-disabled';

export const DEMO_DIRECTIVES = [NgbdDatepickerBasic, NgbdDatepickerI18n, NgbdDatepickerConfig];
export const DEMO_DIRECTIVES = [NgbdDatepickerBasic, NgbdDatepickerDisabled, NgbdDatepickerI18n, NgbdDatepickerConfig];

export const DEMO_SNIPPETS = {
basic: {
code: require('!!prismjs?lang=typescript!./basic/datepicker-basic'),
markup: require('!!prismjs?lang=markup!./basic/datepicker-basic.html')
},
disabled: {
code: require('!!prismjs?lang=typescript!./disabled/datepicker-disabled'),
markup: require('!!prismjs?lang=markup!./disabled/datepicker-disabled.html')
},
i18n: {
code: require('!!prismjs?lang=typescript!./i18n/datepicker-i18n'),
markup: require('!!prismjs?lang=markup!./i18n/datepicker-i18n.html')
Expand Down
11 changes: 11 additions & 0 deletions src/datepicker/datepicker-day-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ describe('ngbDatepickerDayView', () => {
fixture.detectChanges();
expect(el).toHaveCssClass('bg-primary');
});

it('should not apply muted style if disabled but selected', () => {
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.disabled = true;
fixture.componentInstance.selected = true;
fixture.detectChanges();

const el = getElement(fixture.nativeElement);
expect(el).toHaveCssClass('bg-primary');
expect(el).not.toHaveCssClass('text-muted');
});
});

@Component({
Expand Down
2 changes: 1 addition & 1 deletion src/datepicker/datepicker-day-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ export class NgbDatepickerDayView {
@Input() disabled: boolean;
@Input() selected: boolean;

isMuted() { return this.date.month !== this.currentMonth || this.disabled; }
isMuted() { return !this.selected && (this.date.month !== this.currentMonth || this.disabled); }
}
31 changes: 30 additions & 1 deletion src/datepicker/datepicker-month-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,23 @@ describe('ngbDatepickerMonthView', () => {
const dates = getDates(fixture.nativeElement);
dates[0].click();

expect(fixture.componentInstance.onClick).not.toHaveBeenCalledWith();
expect(fixture.componentInstance.onClick).not.toHaveBeenCalled();
});

it('should not send date selection events if disabled', () => {
const fixture = createTestComponent(`
<template #tpl let-date="date">{{ date.day }}</template>
<tbody ngbDatepickerMonthView [month]="month" [dayTemplate]="tpl" [disabled]="true" (select)="onClick($event)"></tbody>
`);

fixture.detectChanges();

spyOn(fixture.componentInstance, 'onClick');

const dates = getDates(fixture.nativeElement);
dates[0].click();

expect(fixture.componentInstance.onClick).not.toHaveBeenCalled();
});

if (!isBrowser('ie9')) {
Expand Down Expand Up @@ -134,6 +150,19 @@ describe('ngbDatepickerMonthView', () => {
const dates = getDates(fixture.nativeElement);
expect(window.getComputedStyle(dates[0]).getPropertyValue('cursor')).toBe('not-allowed');
});

it('should set not-allowed cursor for all dates if disabled', () => {
const fixture = createTestComponent(`
<template #tpl let-date="date">{{ date.day }}</template>
<table><tbody ngbDatepickerMonthView [month]="month" [dayTemplate]="tpl"
(change)="onClick($event)" [disabled]="true"></tbody></table>
`);

fixture.detectChanges();

const dates = getDates(fixture.nativeElement);
dates.forEach((date) => expect(window.getComputedStyle(date).getPropertyValue('cursor')).toBe('not-allowed'));
});
}

});
Expand Down
9 changes: 6 additions & 3 deletions src/datepicker/datepicker-month-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import {DayTemplateContext} from './datepicker-day-template-context';
</tr>
<tr *ngFor="let week of month.weeks">
<td *ngIf="showWeekNumbers" class="weeknumber small text-xs-center">{{ week.number }}</td>
<td *ngFor="let day of week.days" (click)="doSelect(day)" class="day" [class.disabled]="day.disabled">
<td *ngFor="let day of week.days" (click)="doSelect(day)" class="day" [class.disabled]="isDisabled(day)">
<template [ngTemplateOutlet]="dayTemplate"
[ngOutletContext]="{date: {year: day.date.year, month: day.date.month, day: day.date.day},
currentMonth: month.number,
disabled: day.disabled,
disabled: isDisabled(day),
selected: isSelected(day.date)}">
</template>
</td>
Expand All @@ -41,6 +41,7 @@ import {DayTemplateContext} from './datepicker-day-template-context';
})
export class NgbDatepickerMonthView {
@Input() dayTemplate: TemplateRef<DayTemplateContext>;
@Input() disabled: boolean;
@Input() month: MonthViewModel;
@Input() selectedDate: NgbDate;
@Input() showWeekdays;
Expand All @@ -51,10 +52,12 @@ export class NgbDatepickerMonthView {
constructor(public i18n: NgbDatepickerI18n) {}

doSelect(day: DayViewModel) {
if (!day.disabled) {
if (!this.isDisabled(day)) {
this.select.emit(NgbDate.from(day.date));
}
}

isDisabled(day) { return this.disabled || day.disabled; }

isSelected(date: NgbDate) { return this.selectedDate && this.selectedDate.equals(date); }
}
54 changes: 54 additions & 0 deletions src/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,27 @@ describe('ngb-datepicker', () => {
expect(fixture.componentInstance.model).toEqual({year: 2016, month: 7, day: 2});
});

it('should not update model based on calendar clicks when disabled', async(() => {
const fixture = createTestComponent(
`<ngb-datepicker [startDate]="date" [minDate]="minDate" [maxDate]="maxDate" [(ngModel)]="model" [disabled]="true">
</ngb-datepicker>`);

fixture.whenStable()
.then(() => {
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {

const dates = getDates(fixture.nativeElement);
dates[0].click(); // 1 AUG 2016
expect(fixture.componentInstance.model).toBeFalsy();

dates[1].click();
expect(fixture.componentInstance.model).toBeFalsy();
});
}));

it('select calendar date based on model updates', async(() => {
const fixture = createTestComponent(
`<ngb-datepicker [startDate]="date" [minDate]="minDate" [maxDate]="maxDate" [(ngModel)]="model"></ngb-datepicker>`);
Expand Down Expand Up @@ -219,6 +240,22 @@ describe('ngb-datepicker', () => {
expect(getMonthSelect(fixture.nativeElement).value).toBe(`${today.getMonth()}`);
expect(getYearSelect(fixture.nativeElement).value).toBe(`${today.getFullYear()}`);
});

it('should support disabling all dates via the disabled attribute', async(() => {
const fixture = createTestComponent(
`<ngb-datepicker [(ngModel)]="model" [startDate]="date" [disabled]="true"></ngb-datepicker>`);
fixture.detectChanges();
fixture.whenStable()
.then(() => {
fixture.detectChanges();
return fixture.whenStable();
})
.then(() => {
for (let index = 0; index < 31; index++) {
expect(getDay(fixture.nativeElement, index)).toHaveCssClass('text-muted');
}
});
}));
});

describe('forms', () => {
Expand Down Expand Up @@ -281,6 +318,22 @@ describe('ngb-datepicker', () => {
expect(getDatepicker(compiled)).not.toHaveCssClass('ng-invalid');
});
}));

it('should be disabled with reactive forms', async(() => {
const html = `<form [formGroup]="disabledForm">
<ngb-datepicker [startDate]="date" [minDate]="minDate" [maxDate]="maxDate" formControlName="control">
</ngb-datepicker>
</form>`;

const fixture = createTestComponent(html);
fixture.detectChanges();
const dates = getDates(fixture.nativeElement);
dates[0].click(); // 1 AUG 2016
expect(fixture.componentInstance.disabledForm.controls['control'].value).toBeFalsy();
for (let index = 0; index < 31; index++) {
expect(getDay(fixture.nativeElement, index)).toHaveCssClass('text-muted');
}
}));
});

describe('Custom config', () => {
Expand Down Expand Up @@ -325,6 +378,7 @@ class TestComponent {
minDate = {year: 2010, month: 0, day: 1};
maxDate = {year: 2020, month: 11, day: 31};
form = new FormGroup({control: new FormControl('', Validators.required)});
disabledForm = new FormGroup({control: new FormControl({value: null, disabled: true})});
model;
markDisabled = (date: {year: number, month: number, day: number}) => {
return NgbDate.from(date).equals(new NgbDate(2016, 7, 22));
Expand Down
8 changes: 8 additions & 0 deletions src/datepicker/datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const NGB_DATEPICKER_VALUE_ACCESSOR = {
[dayTemplate]="dayTemplate || dt"
[showWeekdays]="showWeekdays"
[showWeekNumbers]="showWeekNumbers"
[disabled]="disabled"
(select)="onDateSelect($event)">
</tbody>
</table>
Expand Down Expand Up @@ -102,6 +103,8 @@ export class NgbDatepicker implements OnChanges,
*/
@Input() startDate: {year: number, month: number};

disabled = false;

onChange = (_: any) => {};
onTouched = () => {};

Expand Down Expand Up @@ -191,6 +194,11 @@ export class NgbDatepicker implements OnChanges,
*/
writeValue(value) { this.model = value ? new NgbDate(value.year, value.month, value.day) : null; }

/**
* @internal
*/
setDisabledState(isDisabled: boolean) { this.disabled = isDisabled; }

private _setDates() {
this._maxDate = NgbDate.from(this.maxDate);
this._minDate = NgbDate.from(this.minDate);
Expand Down

0 comments on commit 64ec99c

Please sign in to comment.