Skip to content

Commit

Permalink
feat(datepicker): ability to display several months
Browse files Browse the repository at this point in the history
BREAKING CHANGES: datepicker navigation now must be hidden with `navigation='none'`and not `[showNavigation]='false'` as prevoiusly

Closes #977
  • Loading branch information
maxokorokov authored and pkozlowski-opensource committed Nov 3, 2016
1 parent 951e538 commit a65cc30
Show file tree
Hide file tree
Showing 17 changed files with 380 additions and 138 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 @@ -18,6 +18,9 @@ import {DEMO_SNIPPETS} from './demos';
<ngbd-example-box demoTitle="Datepicker in a popup" [snippets]="snippets" component="datepicker" demo="popup">
<ngbd-datepicker-popup></ngbd-datepicker-popup>
</ngbd-example-box>
<ngbd-example-box demoTitle="Multiple months" [snippets]="snippets" component="datepicker" demo="multiple">
<ngbd-datepicker-multiple></ngbd-datepicker-multiple>
</ngbd-example-box>
<ngbd-example-box demoTitle="Disabled datepicker" [snippets]="snippets" component="datepicker" demo="disabled">
<ngbd-datepicker-disabled></ngbd-datepicker-disabled>
</ngbd-example-box>
Expand Down
8 changes: 7 additions & 1 deletion demo/src/app/components/datepicker/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {NgbdDatepickerI18n} from './i18n/datepicker-i18n';
import {NgbdDatepickerDisabled} from './disabled/datepicker-disabled';
import {NgbdDatepickerPopup} from './popup/datepicker-popup';
import {NgbdDatepickerCustomday} from './customday/datepicker-customday';
import {NgbdDatepickerMultiple} from './multiple/datepicker-multiple';

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

export const DEMO_SNIPPETS = {
Expand All @@ -30,6 +32,10 @@ export const DEMO_SNIPPETS = {
code: require('!!prismjs?lang=typescript!./customday/datepicker-customday'),
markup: require('!!prismjs?lang=markup!./customday/datepicker-customday.html')
},
multiple: {
code: require('!!prismjs?lang=typescript!./multiple/datepicker-multiple'),
markup: require('!!prismjs?lang=markup!./multiple/datepicker-multiple.html')
},
config: {
code: require('!!prismjs?lang=typescript!./config/datepicker-config'),
markup: require('!!prismjs?lang=markup!./config/datepicker-config.html')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

<ngb-datepicker [displayMonths]="displayMonths" [navigation]="navigation"></ngb-datepicker>

<hr/>

<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input class="form-control" placeholder="yyyy-mm-dd"
name="dp" [displayMonths]="displayMonths" [navigation]="navigation" ngbDatepicker #d="ngbDatepicker">
<div class="input-group-addon" (click)="d.toggle()" >
<img src="img/calendar-icon.svg" style="width: 1.2rem; height: 1rem; cursor: pointer;"/>
</div>
</div>
</div>
</form>

<hr/>

<select class="custom-select" [(ngModel)]="displayMonths">
<option [value]="1">One month</option>
<option [value]="2">Two months</option>
<option [value]="3">Three months</option>
</select>

<select class="custom-select" [(ngModel)]="navigation">
<option value="none">Without navigation</option>
<option value="select">With select boxes</option>
<option value="arrows">Without select boxes</option>
</select>

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Component} from '@angular/core';

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

displayMonths = 2;
navigation = 'select';
}
3 changes: 2 additions & 1 deletion src/datepicker/datepicker-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ describe('ngb-datepicker-config', () => {
const config = new NgbDatepickerConfig();

expect(config.dayTemplate).toBeUndefined();
expect(config.displayMonths).toBe(1);
expect(config.firstDayOfWeek).toBe(1);
expect(config.markDisabled).toBeUndefined();
expect(config.minDate).toBeUndefined();
expect(config.maxDate).toBeUndefined();
expect(config.navigation).toBe('select');
expect(config.outsideDays).toBe('visible');
expect(config.showNavigation).toBe(true);
expect(config.showWeekdays).toBe(true);
expect(config.showWeekNumbers).toBe(false);
expect(config.startDate).toBeUndefined();
Expand Down
3 changes: 2 additions & 1 deletion src/datepicker/datepicker-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {NgbDateStruct} from './ngb-date-struct';
@Injectable()
export class NgbDatepickerConfig {
dayTemplate: TemplateRef<DayTemplateContext>;
displayMonths = 1;
firstDayOfWeek = 1;
markDisabled: (date: NgbDateStruct, current: {year: number, month: number}) => boolean;
minDate: NgbDateStruct;
maxDate: NgbDateStruct;
navigation: 'select' | 'arrows' | 'none' = 'select';
outsideDays: 'visible' | 'collapsed' | 'hidden' = 'visible';
showNavigation = true;
showWeekdays = true;
showWeekNumbers = false;
startDate: {year: number, month: number};
Expand Down
17 changes: 14 additions & 3 deletions src/datepicker/datepicker-input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ describe('NgbInputDatepicker', () => {
expect(dp.dayTemplate).toBeDefined();
});

it('should propagate the "displayMonths" option', () => {
const fixture = createTestCmpt(`<input ngbDatepicker [displayMonths]="3">`);
const dpInput = fixture.debugElement.query(By.directive(NgbInputDatepicker)).injector.get(NgbInputDatepicker);

dpInput.open();
fixture.detectChanges();

const dp = fixture.debugElement.query(By.css('ngb-datepicker')).injector.get(NgbDatepicker);
expect(dp.displayMonths).toBe(3);
});

it('should propagate the "firstDayOfWeek" option', () => {
const fixture = createTestCmpt(`<input ngbDatepicker [firstDayOfWeek]="5">`);
const dpInput = fixture.debugElement.query(By.directive(NgbInputDatepicker)).injector.get(NgbInputDatepicker);
Expand Down Expand Up @@ -212,15 +223,15 @@ describe('NgbInputDatepicker', () => {
expect(dp.outsideDays).toEqual('collapsed');
});

it('should propagate the "showNavigation" option', () => {
const fixture = createTestCmpt(`<input ngbDatepicker [showNavigation]="true">`);
it('should propagate the "navigation" option', () => {
const fixture = createTestCmpt(`<input ngbDatepicker navigation="none">`);
const dpInput = fixture.debugElement.query(By.directive(NgbInputDatepicker)).injector.get(NgbInputDatepicker);

dpInput.open();
fixture.detectChanges();

const dp = fixture.debugElement.query(By.css('ngb-datepicker')).injector.get(NgbDatepicker);
expect(dp.showNavigation).toBeTruthy();
expect(dp.navigation).toBe('none');
});

it('should propagate the "showWeekdays" option', () => {
Expand Down
20 changes: 13 additions & 7 deletions src/datepicker/datepicker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export class NgbInputDatepicker implements ControlValueAccessor {
*/
@Input() dayTemplate: TemplateRef<DayTemplateContext>;

/**
* Number of months to display
*/
@Input() displayMonths: number;

/**
* First day of the week. With default calendar we use ISO 8601: 1=Mon ... 7=Sun
*/
Expand All @@ -68,15 +73,16 @@ export class NgbInputDatepicker implements ControlValueAccessor {
@Input() maxDate: NgbDateStruct;

/**
* The way to display days that don't belong to current month: `visible` (default),
* `hidden` (not displayed) or `collapsed` (not displayed with empty space collapsed)
* Navigation type: `select` (default with select boxes for month and year), `arrows`
* (without select boxes, only navigation arrows) or `none` (no navigation at all)
*/
@Input() outsideDays: 'visible' | 'collapsed' | 'hidden';
@Input() navigation: 'select' | 'arrows' | 'none';

/**
* Whether to display navigation
* The way to display days that don't belong to current month: `visible` (default),
* `hidden` (not displayed) or `collapsed` (not displayed with empty space collapsed)
*/
@Input() showNavigation: boolean;
@Input() outsideDays: 'visible' | 'collapsed' | 'hidden';

/**
* Whether to display days of the week
Expand Down Expand Up @@ -192,8 +198,8 @@ export class NgbInputDatepicker implements ControlValueAccessor {
onBlur() { this._onTouched(); }

private _applyDatepickerInputs(datepickerInstance: NgbDatepicker): void {
['dayTemplate', 'firstDayOfWeek', 'markDisabled', 'minDate', 'maxDate', 'outsideDays', 'showNavigation',
'showWeekdays', 'showWeekNumbers']
['dayTemplate', 'displayMonths', 'firstDayOfWeek', 'markDisabled', 'minDate', 'maxDate', 'navigation',
'outsideDays', 'showNavigation', 'showWeekdays', 'showWeekNumbers']
.forEach((optionName: string) => {
if (this[optionName] !== undefined) {
datepickerInstance[optionName] = this[optionName];
Expand Down
36 changes: 18 additions & 18 deletions src/datepicker/datepicker-month-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ function expectDates(element: HTMLElement, dates: string[]) {
expect(result).toEqual(dates);
}

describe('ngbDatepickerMonthView', () => {
describe('ngb-datepicker-month-view', () => {

beforeEach(() => {
TestBed.overrideModule(NgbDatepickerModule, {set: {exports: [NgbDatepickerMonthView, NgbDatepickerDayView]}});
TestBed.configureTestingModule({declarations: [TestComponent], imports: [NgbDatepickerModule.forRoot()]});
});

it('should show/hide weekdays', () => {
const fixture =
createTestComponent('<tbody ngbDatepickerMonthView [month]="month" [showWeekdays]="showWeekdays"></tbody>');
const fixture = createTestComponent(
'<ngb-datepicker-month-view [month]="month" [showWeekdays]="showWeekdays"></ngb-datepicker-month-view>');

expectWeekdays(fixture.nativeElement, ['Mo']);

Expand All @@ -59,7 +59,7 @@ describe('ngbDatepickerMonthView', () => {

it('should show/hide week numbers', () => {
const fixture = createTestComponent(
'<tbody ngbDatepickerMonthView [month]="month" [showWeekNumbers]="showWeekNumbers"></tbody>');
'<ngb-datepicker-month-view [month]="month" [showWeekNumbers]="showWeekNumbers"></ngb-datepicker-month-view>');

expectWeekNumbers(fixture.nativeElement, ['2']);

Expand All @@ -71,15 +71,15 @@ describe('ngbDatepickerMonthView', () => {
it('should use custom template to display dates', () => {
const fixture = createTestComponent(`
<template #tpl let-date="date">{{ date.day }}</template>
<tbody ngbDatepickerMonthView [month]="month" [dayTemplate]="tpl"></tbody>
<ngb-datepicker-month-view [month]="month" [dayTemplate]="tpl"></ngb-datepicker-month-view>
`);
expectDates(fixture.nativeElement, ['22', '23']);
});

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

spyOn(fixture.componentInstance, 'onClick');
Expand All @@ -93,7 +93,7 @@ describe('ngbDatepickerMonthView', () => {
it('should not send date selection events for disabled dates', () => {
const fixture = createTestComponent(`
<template #tpl let-date="date">{{ date.day }}</template>
<tbody ngbDatepickerMonthView [month]="month" [dayTemplate]="tpl" (select)="onClick($event)"></tbody>
<ngb-datepicker-month-view [month]="month" [dayTemplate]="tpl" (select)="onClick($event)"></ngb-datepicker-month-view>
`);

fixture.componentInstance.month.weeks[0].days[0].disabled = true;
Expand All @@ -110,7 +110,8 @@ describe('ngbDatepickerMonthView', () => {
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>
<ngb-datepicker-month-view [month]="month" [dayTemplate]="tpl" [disabled]="true" (select)="onClick($event)">
</ngb-datepicker-month-view>
`);

fixture.detectChanges();
Expand All @@ -127,8 +128,7 @@ describe('ngbDatepickerMonthView', () => {
it('should set cursor to pointer', () => {
const fixture = createTestComponent(`
<template #tpl let-date="date">{{ date.day }}</template>
<table><tbody ngbDatepickerMonthView [month]="month" [dayTemplate]="tpl"
(change)="onClick($event)"></tbody></table>
<ngb-datepicker-month-view [month]="month" [dayTemplate]="tpl" (change)="onClick($event)"></ngb-datepicker-month-view>
`);

const dates = getDates(fixture.nativeElement);
Expand All @@ -140,8 +140,7 @@ describe('ngbDatepickerMonthView', () => {
it('should set default cursor for disabled dates', () => {
const fixture = createTestComponent(`
<template #tpl let-date="date">{{ date.day }}</template>
<table><tbody ngbDatepickerMonthView [month]="month" [dayTemplate]="tpl"
(change)="onClick($event)"></tbody></table>
<ngb-datepicker-month-view [month]="month" [dayTemplate]="tpl" (change)="onClick($event)"></ngb-datepicker-month-view>
`);

fixture.componentInstance.month.weeks[0].days[0].disabled = true;
Expand All @@ -154,8 +153,8 @@ describe('ngbDatepickerMonthView', () => {
it('should set default 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>
<ngb-datepicker-month-view [month]="month" [dayTemplate]="tpl" (change)="onClick($event)" [disabled]="true">
</ngb-datepicker-month-view>
`);

fixture.detectChanges();
Expand All @@ -165,8 +164,8 @@ describe('ngbDatepickerMonthView', () => {
});

it('should set default cursor for other months days', () => {
const fixture =
createTestComponent('<tbody ngbDatepickerMonthView [month]="month" [outsideDays]="outsideDays"></tbody>');
const fixture = createTestComponent(
'<ngb-datepicker-month-view [month]="month" [outsideDays]="outsideDays"></ngb-datepicker-month-view>');

const dates = getDates(fixture.nativeElement);
expect(window.getComputedStyle(dates[1]).getPropertyValue('cursor')).toBe('pointer');
Expand All @@ -182,8 +181,8 @@ describe('ngbDatepickerMonthView', () => {
}

it('should apply proper visibility to other months days', () => {
const fixture =
createTestComponent('<tbody ngbDatepickerMonthView [month]="month" [outsideDays]="outsideDays"></tbody>');
const fixture = createTestComponent(
'<ngb-datepicker-month-view [month]="month" [outsideDays]="outsideDays"></ngb-datepicker-month-view>');

let dates = getDates(fixture.nativeElement);
expect(dates[0]).not.toHaveCssClass('hidden');
Expand Down Expand Up @@ -211,6 +210,7 @@ describe('ngbDatepickerMonthView', () => {
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
month: MonthViewModel = {
firstDate: new NgbDate(2016, 7, 22),
year: 2016,
number: 7,
weekdays: [1],
Expand Down
43 changes: 22 additions & 21 deletions src/datepicker/datepicker-month-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import {NgbDatepickerI18n} from './datepicker-i18n';
import {DayTemplateContext} from './datepicker-day-template-context';

@Component({
selector: '[ngbDatepickerMonthView]',
selector: 'ngb-datepicker-month-view',
styles: [`
.weekday {
padding-bottom: 0.25rem;
}
.weeknumber {
.weeknumber {
}
.day {
padding: 0;
Expand All @@ -20,30 +19,32 @@ import {DayTemplateContext} from './datepicker-day-template-context';
.day.disabled, .day.hidden, .day.collapsed {
cursor: default;
}
:host/deep/.day.collapsed > * {
:host/deep/.day.collapsed > * {
display: none;
}
:host/deep/.day.hidden > * {
visibility: hidden;
}
}
`],
template: `
<tr *ngIf="showWeekdays">
<td *ngIf="showWeekNumbers"></td>
<td *ngFor="let w of month.weekdays" class="weekday text-xs-center font-weight-bold">{{ i18n.getWeekdayName(w) }}</td>
</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]="isDisabled(day)"
[class.collapsed]="isCollapsed(day)" [class.hidden]="isHidden(day)">
<template [ngTemplateOutlet]="dayTemplate"
[ngOutletContext]="{date: {year: day.date.year, month: day.date.month, day: day.date.day},
currentMonth: month.number,
disabled: isDisabled(day),
selected: isSelected(day.date)}">
</template>
</td>
</tr>
<table>
<tr *ngIf="showWeekdays">
<td *ngIf="showWeekNumbers"></td>
<td *ngFor="let w of month.weekdays" class="weekday text-xs-center font-weight-bold">{{ i18n.getWeekdayName(w) }}</td>
</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]="isDisabled(day)"
[class.collapsed]="isCollapsed(day)" [class.hidden]="isHidden(day)">
<template [ngTemplateOutlet]="dayTemplate"
[ngOutletContext]="{date: {year: day.date.year, month: day.date.month, day: day.date.day},
currentMonth: month.number,
disabled: isDisabled(day),
selected: isSelected(day.date)}">
</template>
</td>
</tr>
</table>
`
})
export class NgbDatepickerMonthView {
Expand Down
Loading

0 comments on commit a65cc30

Please sign in to comment.