diff --git a/demo/src/app/components/datepicker/datepicker.module.ts b/demo/src/app/components/datepicker/datepicker.module.ts
index ce6a34b19c..36b28867e3 100644
--- a/demo/src/app/components/datepicker/datepicker.module.ts
+++ b/demo/src/app/components/datepicker/datepicker.module.ts
@@ -23,6 +23,8 @@ import { NgbdDatepickerFooterTemplateModule } from './demos/footertemplate/datep
import { NgbdDatepickerFootertemplate } from './demos/footertemplate/datepicker-footertemplate';
import { NgbdDatepickerI18n } from './demos/i18n/datepicker-i18n';
import { NgbdDatepickerI18nModule } from './demos/i18n/datepicker-i18n.module';
+import { NgbdDatepickerCustommonth } from './demos/custommonth/datepicker-custommonth';
+import { NgbdDatepickerCustommonthModule } from './demos/custommonth/datepicker-custommonth.module';
import { NgbdDatepickerMultiple } from './demos/multiple/datepicker-multiple';
import { NgbdDatepickerMultipleModule } from './demos/multiple/datepicker-multiple.module';
import { NgbdDatepickerPopup } from './demos/popup/datepicker-popup';
@@ -46,6 +48,7 @@ const OVERVIEW = {
'limiting-dates': 'Disabling and limiting dates',
'day-template': 'Day display customization',
today: 'Today\'s date',
+ 'content-template': 'Content Template',
'footer-template': 'Custom footer',
range: 'Range selection',
i18n: 'Internationalization',
@@ -107,6 +110,12 @@ const DEMOS = {
code: require('!!raw-loader!./demos/customday/datepicker-customday').default,
markup: require('!!raw-loader!./demos/customday/datepicker-customday.html').default
},
+ custommonth: {
+ title: 'Custom month layout',
+ type: NgbdDatepickerCustommonth,
+ code: require('!!raw-loader!./demos/custommonth/datepicker-custommonth').default,
+ markup: require('!!raw-loader!./demos/custommonth/datepicker-custommonth.html').default
+ },
footertemplate: {
title: 'Footer template',
type: NgbdDatepickerFootertemplate,
@@ -165,6 +174,7 @@ export const ROUTES = [
NgbdDatepickerRangePopupModule,
NgbdDatepickerAdapterModule,
NgbdDatepickerKeyboardModule,
+ NgbdDatepickerCustommonthModule,
...DEMO_CALENDAR_MODULES
],
declarations: [
diff --git a/demo/src/app/components/datepicker/demos/custommonth/datepicker-custommonth.html b/demo/src/app/components/datepicker/demos/custommonth/datepicker-custommonth.html
new file mode 100644
index 0000000000..cb2a91816a
--- /dev/null
+++ b/demo/src/app/components/datepicker/demos/custommonth/datepicker-custommonth.html
@@ -0,0 +1,14 @@
+
This datepicker uses a custom month layout.
+
+
diff --git a/demo/src/app/components/datepicker/overview/datepicker-overview.component.ts b/demo/src/app/components/datepicker/overview/datepicker-overview.component.ts
index e04a2d8adc..f9849550da 100644
--- a/demo/src/app/components/datepicker/overview/datepicker-overview.component.ts
+++ b/demo/src/app/components/datepicker/overview/datepicker-overview.component.ts
@@ -119,6 +119,21 @@ export class NgbdDatepickerOverviewComponent {
`,
}),
+ contentTemplate: Snippet({
+ lang: 'html',
+ code: `
+
+
+
+ {{i18n.getMonthFullName(monthStruct.month)}} {{monthStruct.year}}
+
+
+
+
+ `,
+ }),
todayHTML: Snippet({
lang: 'html',
code: `
diff --git a/src/datepicker/datepicker-input.spec.ts b/src/datepicker/datepicker-input.spec.ts
index 4c18bb1ac7..e66164a276 100644
--- a/src/datepicker/datepicker-input.spec.ts
+++ b/src/datepicker/datepicker-input.spec.ts
@@ -289,7 +289,7 @@ describe('NgbInputDatepicker', () => {
expect(input.disabled).toBeTruthy();
expect(buttonInDatePicker.disabled).toBeTruthy();
- const dayElements = fixture.nativeElement.querySelectorAll('ngb-datepicker-month-view .ngb-dp-day');
+ const dayElements = fixture.nativeElement.querySelectorAll('ngb-datepicker-month .ngb-dp-day');
expect(dayElements[1]).toHaveCssClass('disabled');
expect(dayElements[11]).toHaveCssClass('disabled');
expect(dayElements[21]).toHaveCssClass('disabled');
@@ -303,7 +303,7 @@ describe('NgbInputDatepicker', () => {
expect(input.disabled).toBeFalsy();
expect(buttonInDatePicker.disabled).toBeFalsy();
- const dayElements2 = fixture.nativeElement.querySelectorAll('ngb-datepicker-month-view .ngb-dp-day');
+ const dayElements2 = fixture.nativeElement.querySelectorAll('ngb-datepicker-month .ngb-dp-day');
expect(dayElements2[1]).not.toHaveCssClass('disabled');
expect(dayElements2[11]).not.toHaveCssClass('disabled');
expect(dayElements2[21]).not.toHaveCssClass('disabled');
@@ -329,7 +329,7 @@ describe('NgbInputDatepicker', () => {
expect(input.disabled).toBeTruthy();
expect(buttonInDatePicker.disabled).toBeTruthy();
- const dayElements = fixture.nativeElement.querySelectorAll('ngb-datepicker-month-view .ngb-dp-day');
+ const dayElements = fixture.nativeElement.querySelectorAll('ngb-datepicker-month .ngb-dp-day');
expect(dayElements[1]).toHaveCssClass('disabled');
expect(dayElements[11]).toHaveCssClass('disabled');
expect(dayElements[21]).toHaveCssClass('disabled');
@@ -341,7 +341,7 @@ describe('NgbInputDatepicker', () => {
expect(input.disabled).toBeFalsy();
expect(buttonInDatePicker.disabled).toBeFalsy();
- const dayElements2 = fixture.nativeElement.querySelectorAll('ngb-datepicker-month-view .ngb-dp-day');
+ const dayElements2 = fixture.nativeElement.querySelectorAll('ngb-datepicker-month .ngb-dp-day');
expect(dayElements2[1]).not.toHaveCssClass('disabled');
expect(dayElements2[11]).not.toHaveCssClass('disabled');
expect(dayElements2[21]).not.toHaveCssClass('disabled');
diff --git a/src/datepicker/datepicker-integration.spec.ts b/src/datepicker/datepicker-integration.spec.ts
index 6481b13379..34d21f987b 100644
--- a/src/datepicker/datepicker-integration.spec.ts
+++ b/src/datepicker/datepicker-integration.spec.ts
@@ -8,6 +8,7 @@ import {getMonthSelect, getYearSelect} from '../test/datepicker/common';
import {NgbDatepickerI18n, NgbDatepickerI18nDefault} from './datepicker-i18n';
import {NgbDatepicker} from './datepicker';
import {NgbDatepickerKeyboardService} from './datepicker-keyboard-service';
+import {NgbDatepickerMonth} from './datepicker-month';
import {Key} from '../util/key';
describe('ngb-datepicker integration', () => {
@@ -116,18 +117,18 @@ describe('ngb-datepicker integration', () => {
@Injectable()
class CustomKeyboardService extends NgbDatepickerKeyboardService {
- processKey(event: KeyboardEvent, service: NgbDatepicker, calendar: NgbCalendar) {
+ processKey(event: KeyboardEvent, service: NgbDatepicker) {
const state = service.state;
// tslint:disable-next-line:deprecation
switch (event.which) {
case Key.PageUp:
- service.focusDate(calendar.getPrev(state.focusedDate, event.altKey ? 'y' : 'm', 1));
+ service.focusDate(service.calendar.getPrev(state.focusedDate, event.altKey ? 'y' : 'm', 1));
break;
case Key.PageDown:
- service.focusDate(calendar.getNext(state.focusedDate, event.altKey ? 'y' : 'm', 1));
+ service.focusDate(service.calendar.getNext(state.focusedDate, event.altKey ? 'y' : 'm', 1));
break;
default:
- super.processKey(event, service, calendar);
+ super.processKey(event, service);
return;
}
event.preventDefault();
@@ -136,8 +137,8 @@ describe('ngb-datepicker integration', () => {
}
let fixture: ComponentFixture;
- let dp: NgbDatepicker;
- let ngbCalendar: NgbCalendar;
+ let calendar: NgbCalendar;
+ let mv: NgbDatepickerMonth;
let startDate: NgbDateStruct = new NgbDate(2018, 1, 1);
beforeEach(() => {
@@ -151,22 +152,54 @@ describe('ngb-datepicker integration', () => {
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
+ calendar = fixture.debugElement.query(By.css('ngb-datepicker')).injector.get(NgbDatepicker).calendar;
+ mv = fixture.debugElement.query(By.css('ngb-datepicker-month')).injector.get(NgbDatepickerMonth);
- dp = fixture.debugElement.query(By.css('ngb-datepicker')).injector.get(NgbDatepicker);
- ngbCalendar = fixture.debugElement.query(By.css('ngb-datepicker')).injector.get(NgbCalendar as Type);
-
- spyOn(ngbCalendar, 'getPrev');
+ spyOn(calendar, 'getPrev');
});
it('should allow customize keyboard navigation', () => {
- dp.onKeyDown({which: Key.PageUp, altKey: true, preventDefault: () => {}, stopPropagation: () => {}});
- expect(ngbCalendar.getPrev).toHaveBeenCalledWith(startDate, 'y', 1);
- dp.onKeyDown({which: Key.PageUp, shiftKey: true, preventDefault: () => {}, stopPropagation: () => {}});
- expect(ngbCalendar.getPrev).toHaveBeenCalledWith(startDate, 'm', 1);
+ mv.onKeyDown({which: Key.PageUp, altKey: true, preventDefault: () => {}, stopPropagation: () => {}});
+ expect(calendar.getPrev).toHaveBeenCalledWith(startDate, 'y', 1);
+ mv.onKeyDown({which: Key.PageUp, shiftKey: true, preventDefault: () => {}, stopPropagation: () => {}});
+ expect(calendar.getPrev).toHaveBeenCalledWith(startDate, 'm', 1);
});
it('should allow access to default keyboard navigation', () => {
- dp.onKeyDown({which: Key.ArrowUp, altKey: true, preventDefault: () => {}, stopPropagation: () => {}});
+ mv.onKeyDown({which: Key.ArrowUp, altKey: true, preventDefault: () => {}, stopPropagation: () => {}});
+ expect(calendar.getPrev).toHaveBeenCalledWith(startDate, 'd', 7);
+ });
+
+ });
+
+ describe('ngb-datepicker-month', () => {
+ let fixture: ComponentFixture;
+ let mv: NgbDatepickerMonth;
+ let startDate: NgbDateStruct = new NgbDate(2018, 1, 1);
+ let ngbCalendar: NgbCalendar;
+
+ beforeEach(() => {
+ TestBed.overrideComponent(TestComponent, {
+ set: {
+ template: `
+
+
+
+
+ `
+ }
+ });
+
+ fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+ mv = fixture.debugElement.query(By.css('ngb-datepicker-month')).injector.get(NgbDatepickerMonth);
+ ngbCalendar = fixture.debugElement.query(By.css('ngb-datepicker')).injector.get(NgbCalendar as Type);
+
+ spyOn(ngbCalendar, 'getPrev');
+ });
+
+ it('should preserve the functionality of keyboard service', () => {
+ mv.onKeyDown({which: Key.ArrowUp, altKey: true, preventDefault: () => {}, stopPropagation: () => {}});
expect(ngbCalendar.getPrev).toHaveBeenCalledWith(startDate, 'd', 7);
});
});
diff --git a/src/datepicker/datepicker-keyboard-service.spec.ts b/src/datepicker/datepicker-keyboard-service.spec.ts
index ba1bb61134..30b661fcb9 100644
--- a/src/datepicker/datepicker-keyboard-service.spec.ts
+++ b/src/datepicker/datepicker-keyboard-service.spec.ts
@@ -14,7 +14,7 @@ describe('ngb-datepicker-keyboard-service', () => {
let service: NgbDatepickerKeyboardService;
let calendar: NgbCalendar;
let mock: Partial;
- let processKey = function(e: KeyboardEvent) { service.processKey(e, mock as NgbDatepicker, calendar); };
+ let processKey = function(e: KeyboardEvent) { service.processKey(e, mock as NgbDatepicker); };
let state: NgbDatepickerState = Object.assign({focusedDate: {day: 1, month: 1, year: 2018}});
beforeEach(() => {
@@ -23,7 +23,7 @@ describe('ngb-datepicker-keyboard-service', () => {
calendar = TestBed.get(NgbCalendar as Type);
service = TestBed.get(NgbDatepickerKeyboardService);
- mock = {state, focusDate: () => {}, focusSelect: () => {}};
+ mock = {state, focusDate: () => {}, focusSelect: () => {}, calendar};
spyOn(mock, 'focusDate');
spyOn(mock, 'focusSelect');
diff --git a/src/datepicker/datepicker-keyboard-service.ts b/src/datepicker/datepicker-keyboard-service.ts
index a304529d76..5c1b9d927a 100644
--- a/src/datepicker/datepicker-keyboard-service.ts
+++ b/src/datepicker/datepicker-keyboard-service.ts
@@ -1,5 +1,4 @@
import {Injectable} from '@angular/core';
-import {NgbCalendar} from './ngb-calendar';
import {NgbDatepicker} from './datepicker';
import {Key} from '../util/key';
@@ -15,8 +14,8 @@ export class NgbDatepickerKeyboardService {
/**
* Processes a keyboard event.
*/
- processKey(event: KeyboardEvent, datepicker: NgbDatepicker, calendar: NgbCalendar) {
- const state = datepicker.state;
+ processKey(event: KeyboardEvent, datepicker: NgbDatepicker) {
+ const {state, calendar} = datepicker;
// tslint:disable-next-line:deprecation
switch (event.which) {
case Key.PageUp:
diff --git a/src/datepicker/datepicker-month-view.spec.ts b/src/datepicker/datepicker-month-view.spec.ts
deleted file mode 100644
index cd0a3e8f02..0000000000
--- a/src/datepicker/datepicker-month-view.spec.ts
+++ /dev/null
@@ -1,346 +0,0 @@
-import {TestBed, ComponentFixture} from '@angular/core/testing';
-import {createGenericTestComponent} from '../test/common';
-
-import {Component} from '@angular/core';
-
-import {NgbDatepickerModule} from './datepicker.module';
-import {NgbDatepickerMonthView} from './datepicker-month-view';
-import {MonthViewModel} from './datepicker-view-model';
-import {NgbDate} from './ngb-date';
-import {NgbDatepickerDayView} from './datepicker-day-view';
-
-const createTestComponent = (html: string) =>
- createGenericTestComponent(html, TestComponent) as ComponentFixture;
-
-function getWeekdays(element: HTMLElement): HTMLElement[] {
- return Array.from(element.querySelectorAll('.ngb-dp-weekday'));
-}
-
-function getWeekNumbers(element: HTMLElement): HTMLElement[] {
- return Array.from(element.querySelectorAll('.ngb-dp-week-number'));
-}
-
-function getDates(element: HTMLElement): HTMLElement[] {
- return Array.from(element.querySelectorAll('.ngb-dp-day'));
-}
-
-function expectWeekdays(element: HTMLElement, weekdays: string[]) {
- const result = getWeekdays(element).map(td => td.innerText.trim());
- expect(result).toEqual(weekdays);
-}
-
-function expectWeekNumbers(element: HTMLElement, weeknumbers: string[]) {
- const result = getWeekNumbers(element).map(td => td.innerText.trim());
- expect(result).toEqual(weeknumbers);
-}
-
-function expectDates(element: HTMLElement, dates: string[]) {
- const result = getDates(element).map(td => td.innerText.trim());
- expect(result).toEqual(dates);
-}
-
-describe('ngb-datepicker-month-view', () => {
-
- beforeEach(() => {
- TestBed.overrideModule(NgbDatepickerModule, {set: {exports: [NgbDatepickerMonthView, NgbDatepickerDayView]}});
- TestBed.configureTestingModule({declarations: [TestComponent], imports: [NgbDatepickerModule]});
- });
-
- it('should show/hide weekdays', () => {
- const fixture = createTestComponent(
- '');
-
- expectWeekdays(fixture.nativeElement, ['Mo', 'Tu']);
-
- fixture.componentInstance.showWeekdays = false;
- fixture.detectChanges();
- expectWeekdays(fixture.nativeElement, []);
- });
-
- it('should show/hide week numbers', () => {
- const fixture = createTestComponent(
- '');
-
- expectWeekNumbers(fixture.nativeElement, ['1', '2', '3']);
-
- fixture.componentInstance.showWeekNumbers = false;
- fixture.detectChanges();
- expectWeekNumbers(fixture.nativeElement, []);
- });
-
- it('should use custom template to display dates', () => {
- const fixture = createTestComponent(`
- {{ date.day }}
-
- `);
- expectDates(fixture.nativeElement, ['', '1', '2', '3', '4', '']);
- });
-
- it('should use "date" as an implicit value for the template', () => {
- const fixture = createTestComponent(`
- {{ d.day }}
-
- `);
- expectDates(fixture.nativeElement, ['', '1', '2', '3', '4', '']);
- });
-
- it('should send date selection events', () => {
- const fixture = createTestComponent(`
- {{ date.day }}
-
- `);
-
- spyOn(fixture.componentInstance, 'onClick');
-
- const dates = getDates(fixture.nativeElement);
- dates[1].click();
-
- expect(fixture.componentInstance.onClick).toHaveBeenCalledWith(new NgbDate(2016, 8, 1));
- });
-
- it('should not send date selection events for hidden and disabled dates', () => {
- const fixture = createTestComponent(`
- {{ date.day }}
-
- `);
-
- spyOn(fixture.componentInstance, 'onClick');
-
- const dates = getDates(fixture.nativeElement);
- dates[0].click(); // hidden
- dates[2].click(); // disabled
-
- expect(fixture.componentInstance.onClick).not.toHaveBeenCalled();
- });
-
- it('should set cursor to pointer or default', () => {
- const fixture = createTestComponent(`
- {{ date.day }}
-
- `);
-
- const dates = getDates(fixture.nativeElement);
- // hidden
- expect(window.getComputedStyle(dates[0]).getPropertyValue('cursor')).toBe('default');
- // normal
- expect(window.getComputedStyle(dates[1]).getPropertyValue('cursor')).toBe('pointer');
- // disabled
- expect(window.getComputedStyle(dates[2]).getPropertyValue('cursor')).toBe('default');
- });
-
- it('should apply correct CSS classes to days', () => {
- const fixture = createTestComponent(`
- {{ date.day }}
-
- `);
-
- let dates = getDates(fixture.nativeElement);
- // hidden
- expect(dates[0]).toHaveCssClass('hidden');
- expect(dates[0]).not.toHaveCssClass('disabled');
- expect(dates[0]).not.toHaveCssClass('ngb-dp-today');
- // normal
- expect(dates[1]).not.toHaveCssClass('hidden');
- expect(dates[1]).not.toHaveCssClass('disabled');
- expect(dates[1]).not.toHaveCssClass('ngb-dp-today');
- // disabled
- expect(dates[2]).not.toHaveCssClass('hidden');
- expect(dates[2]).toHaveCssClass('disabled');
- expect(dates[2]).toHaveCssClass('ngb-dp-today');
- });
-
- it('should not display collapsed weeks', () => {
- const fixture = createTestComponent(`
- {{ date.day }}
-
-
- `);
-
- expectDates(fixture.nativeElement, ['', '1', '2', '3', '4', '']);
- });
-
- it('should add correct aria-label attribute', () => {
- const fixture = createTestComponent(`
- {{ date.day }}
-
- `);
-
- let dates = getDates(fixture.nativeElement);
- expect(dates[0].getAttribute('aria-label')).toBe('Monday');
- });
-});
-
-@Component({selector: 'test-cmp', template: ''})
-class TestComponent {
- month: MonthViewModel = {
- firstDate: new NgbDate(2016, 8, 1),
- lastDate: new NgbDate(2016, 8, 31),
- year: 2016,
- number: 8,
- weekdays: [1, 2],
- weeks: [
- // month: 7, 8
- {
- number: 1,
- days: [
- {
- date: new NgbDate(2016, 7, 4),
- context: {
- currentMonth: 8,
- currentYear: 2016,
- $implicit: new NgbDate(2016, 7, 4),
- date: new NgbDate(2016, 7, 4),
- disabled: false,
- focused: false,
- selected: false,
- today: false
- },
- tabindex: -1,
- ariaLabel: 'Monday',
- hidden: true
- },
- {
- date: new NgbDate(2016, 8, 1),
- context: {
- currentMonth: 8,
- currentYear: 2016,
- $implicit: new NgbDate(2016, 8, 1),
- date: new NgbDate(2016, 8, 1),
- disabled: false,
- focused: false,
- selected: false,
- today: false
- },
- tabindex: -1,
- ariaLabel: 'Monday',
- hidden: false
- }
- ],
- collapsed: false
- },
- // month: 8, 8
- {
- number: 2,
- days: [
- {
- date: new NgbDate(2016, 8, 2),
- context: {
- currentMonth: 8,
- currentYear: 2016,
- $implicit: new NgbDate(2016, 8, 2),
- date: new NgbDate(2016, 8, 2),
- disabled: true,
- focused: false,
- selected: false,
- today: true
- },
- tabindex: -1,
- ariaLabel: 'Friday',
- hidden: false
- },
- {
- date: new NgbDate(2016, 8, 3),
- context: {
- currentMonth: 8,
- currentYear: 2016,
- $implicit: new NgbDate(2016, 8, 3),
- date: new NgbDate(2016, 8, 3),
- disabled: false,
- focused: false,
- selected: false,
- today: false
- },
- tabindex: -1,
- ariaLabel: 'Saturday',
- hidden: false
- }
- ],
- collapsed: false
- },
- // month: 8, 9
- {
- number: 3,
- days: [
- {
- date: new NgbDate(2016, 8, 4),
- context: {
- currentMonth: 8,
- currentYear: 2016,
- $implicit: new NgbDate(2016, 8, 4),
- date: new NgbDate(2016, 8, 4),
- disabled: false,
- focused: false,
- selected: false,
- today: false
- },
- tabindex: -1,
- ariaLabel: 'Sunday',
- hidden: false
- },
- {
- date: new NgbDate(2016, 9, 1),
- context: {
- currentMonth: 8,
- currentYear: 2016,
- $implicit: new NgbDate(2016, 9, 1),
- date: new NgbDate(2016, 9, 1),
- disabled: false,
- focused: false,
- selected: false,
- today: false
- },
- tabindex: -1,
- ariaLabel: 'Saturday',
- hidden: true
- }
- ],
- collapsed: false
- },
- // month: 9, 9 -> to collapse
- {
- number: 4,
- days: [
- {
- date: new NgbDate(2016, 9, 2),
- context: {
- currentMonth: 8,
- currentYear: 2016,
- $implicit: new NgbDate(2016, 9, 2),
- date: new NgbDate(2016, 9, 2),
- disabled: false,
- focused: false,
- selected: false,
- today: false
- },
- tabindex: -1,
- ariaLabel: 'Sunday',
- hidden: true
- },
- {
- date: new NgbDate(2016, 9, 3),
- context: {
- currentMonth: 8,
- currentYear: 2016,
- $implicit: new NgbDate(2016, 9, 3),
- date: new NgbDate(2016, 9, 3),
- disabled: false,
- focused: false,
- selected: false,
- today: false
- },
- tabindex: -1,
- ariaLabel: 'Monday',
- hidden: true
- }
- ],
- collapsed: true
- }
- ]
- };
-
- showWeekdays = true;
- showWeekNumbers = true;
- outsideDays = 'visible';
-
- onClick = () => {};
-}
diff --git a/src/datepicker/datepicker-month-view.ts b/src/datepicker/datepicker-month-view.ts
deleted file mode 100644
index 8005b3b5d1..0000000000
--- a/src/datepicker/datepicker-month-view.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import {Component, Input, TemplateRef, Output, EventEmitter, ViewEncapsulation} from '@angular/core';
-import {MonthViewModel, DayViewModel} from './datepicker-view-model';
-import {NgbDate} from './ngb-date';
-import {NgbDatepickerI18n} from './datepicker-i18n';
-import {DayTemplateContext} from './datepicker-day-template-context';
-
-@Component({
- selector: 'ngb-datepicker-month-view',
- host: {'role': 'grid'},
- encapsulation: ViewEncapsulation.None,
- styleUrls: ['./datepicker-month-view.scss'],
- template: `
-
-
-
- {{ i18n.getWeekdayShortName(w) }}
-
-
-
-
-
{{ i18n.getWeekNumerals(week.number) }}
-
-
-
-
-
-
-
- `
-})
-export class NgbDatepickerMonthView {
- @Input() dayTemplate: TemplateRef;
- @Input() month: MonthViewModel;
- @Input() showWeekdays;
- @Input() showWeekNumbers;
-
- @Output() select = new EventEmitter();
-
- constructor(public i18n: NgbDatepickerI18n) {}
-
- doSelect(day: DayViewModel) {
- if (!day.context.disabled && !day.hidden) {
- this.select.emit(day.date);
- }
- }
-}
diff --git a/src/datepicker/datepicker-month-view.scss b/src/datepicker/datepicker-month.scss
similarity index 95%
rename from src/datepicker/datepicker-month-view.scss
rename to src/datepicker/datepicker-month.scss
index 5ac47e299c..a8e4a7c0f2 100644
--- a/src/datepicker/datepicker-month-view.scss
+++ b/src/datepicker/datepicker-month.scss
@@ -1,4 +1,4 @@
-ngb-datepicker-month-view {
+ngb-datepicker-month {
display: block;
}
diff --git a/src/datepicker/datepicker-month.spec.ts b/src/datepicker/datepicker-month.spec.ts
new file mode 100644
index 0000000000..b1bcf48bc8
--- /dev/null
+++ b/src/datepicker/datepicker-month.spec.ts
@@ -0,0 +1,403 @@
+import {TestBed, ComponentFixture} from '@angular/core/testing';
+import {createGenericTestComponent} from '../test/common';
+
+import {Component, Injectable} from '@angular/core';
+
+import {NgbDatepickerModule} from './datepicker.module';
+import {NgbDatepicker, NgbDatepickerMonths, NGB_DATEPICKER_VALUE_ACCESSOR} from './datepicker';
+import {NgbDatepickerKeyboardService} from './datepicker-keyboard-service';
+import {NgbDatepickerService} from './datepicker-service';
+import {NgbDatepickerMonth} from './datepicker-month';
+import {NgbDate} from './ngb-date';
+import {NgbDateStruct} from './ngb-date-struct';
+import {NgbDatepickerDayView} from './datepicker-day-view';
+
+const createTestComponent = () => createGenericTestComponent(
+ `
+
+ {{ date.day }}
+
+`,
+ TestComponent) as ComponentFixture;
+
+function getWeekdays(element: HTMLElement): HTMLElement[] {
+ return Array.from(element.querySelectorAll('.ngb-dp-weekday'));
+}
+
+function getWeekNumbers(element: HTMLElement): HTMLElement[] {
+ return Array.from(element.querySelectorAll('.ngb-dp-week-number'));
+}
+
+function getDates(element: HTMLElement): HTMLElement[] {
+ return Array.from(element.querySelectorAll('.ngb-dp-day'));
+}
+
+function expectWeekdays(element: HTMLElement, weekdays: string[]) {
+ const result = getWeekdays(element).map(td => td.innerText.trim());
+ expect(result).toEqual(weekdays);
+}
+
+function expectWeekNumbers(element: HTMLElement, weeknumbers: string[]) {
+ const result = getWeekNumbers(element).map(td => td.innerText.trim());
+ expect(result).toEqual(weeknumbers);
+}
+
+function expectDates(element: HTMLElement, dates: string[]) {
+ const result = getDates(element).map(td => td.innerText.trim());
+ expect(result).toEqual(dates);
+}
+
+@Injectable()
+class MockDatepickerService extends NgbDatepickerService {
+ getMonth(struct: NgbDateStruct) {
+ return {
+ firstDate: new NgbDate(2016, 8, 1),
+ lastDate: new NgbDate(2016, 8, 31),
+ year: 2016,
+ number: 8,
+ weekdays: [1, 2],
+ weeks: [
+ // month: 7, 8
+ {
+ number: 1,
+ days: [
+ {
+ date: new NgbDate(2016, 7, 4),
+ context: {
+ currentMonth: 8,
+ currentYear: 2016,
+ $implicit: new NgbDate(2016, 7, 4),
+ date: new NgbDate(2016, 7, 4),
+ disabled: false,
+ focused: false,
+ selected: false,
+ today: false
+ },
+ tabindex: -1,
+ ariaLabel: 'Monday',
+ hidden: true
+ },
+ {
+ date: new NgbDate(2016, 8, 1),
+ context: {
+ currentMonth: 8,
+ currentYear: 2016,
+ $implicit: new NgbDate(2016, 8, 1),
+ date: new NgbDate(2016, 8, 1),
+ disabled: false,
+ focused: false,
+ selected: false,
+ today: false
+ },
+ tabindex: -1,
+ ariaLabel: 'Monday',
+ hidden: false
+ }
+ ],
+ collapsed: false
+ },
+ // month: 8, 8
+ {
+ number: 2,
+ days: [
+ {
+ date: new NgbDate(2016, 8, 2),
+ context: {
+ currentMonth: 8,
+ currentYear: 2016,
+ $implicit: new NgbDate(2016, 8, 2),
+ date: new NgbDate(2016, 8, 2),
+ disabled: true,
+ focused: false,
+ selected: false,
+ today: true
+ },
+ tabindex: -1,
+ ariaLabel: 'Friday',
+ hidden: false
+ },
+ {
+ date: new NgbDate(2016, 8, 3),
+ context: {
+ currentMonth: 8,
+ currentYear: 2016,
+ $implicit: new NgbDate(2016, 8, 3),
+ date: new NgbDate(2016, 8, 3),
+ disabled: false,
+ focused: false,
+ selected: false,
+ today: false
+ },
+ tabindex: -1,
+ ariaLabel: 'Saturday',
+ hidden: false
+ }
+ ],
+ collapsed: false
+ },
+ // month: 8, 9
+ {
+ number: 3,
+ days: [
+ {
+ date: new NgbDate(2016, 8, 4),
+ context: {
+ currentMonth: 8,
+ currentYear: 2016,
+ $implicit: new NgbDate(2016, 8, 4),
+ date: new NgbDate(2016, 8, 4),
+ disabled: false,
+ focused: false,
+ selected: false,
+ today: false
+ },
+ tabindex: -1,
+ ariaLabel: 'Sunday',
+ hidden: false
+ },
+ {
+ date: new NgbDate(2016, 9, 1),
+ context: {
+ currentMonth: 8,
+ currentYear: 2016,
+ $implicit: new NgbDate(2016, 9, 1),
+ date: new NgbDate(2016, 9, 1),
+ disabled: false,
+ focused: false,
+ selected: false,
+ today: false
+ },
+ tabindex: -1,
+ ariaLabel: 'Saturday',
+ hidden: true
+ }
+ ],
+ collapsed: false
+ },
+ // month: 9, 9 -> to collapse
+ {
+ number: 4,
+ days: [
+ {
+ date: new NgbDate(2016, 9, 2),
+ context: {
+ currentMonth: 8,
+ currentYear: 2016,
+ $implicit: new NgbDate(2016, 9, 2),
+ date: new NgbDate(2016, 9, 2),
+ disabled: false,
+ focused: false,
+ selected: false,
+ today: false
+ },
+ tabindex: -1,
+ ariaLabel: 'Sunday',
+ hidden: true
+ },
+ {
+ date: new NgbDate(2016, 9, 3),
+ context: {
+ currentMonth: 8,
+ currentYear: 2016,
+ $implicit: new NgbDate(2016, 9, 3),
+ date: new NgbDate(2016, 9, 3),
+ disabled: false,
+ focused: false,
+ selected: false,
+ today: false
+ },
+ tabindex: -1,
+ ariaLabel: 'Monday',
+ hidden: true
+ }
+ ],
+ collapsed: true
+ }
+ ]
+ };
+ }
+}
+
+describe('ngb-datepicker-month', () => {
+
+ beforeEach(() => {
+ TestBed.overrideModule(
+ NgbDatepickerModule,
+ {set: {exports: [NgbDatepicker, NgbDatepickerMonths, NgbDatepickerMonth, NgbDatepickerDayView]}});
+ TestBed.overrideComponent(NgbDatepicker, {
+ set: {
+ providers: [
+ NGB_DATEPICKER_VALUE_ACCESSOR, {provide: NgbDatepickerService, useClass: MockDatepickerService},
+ NgbDatepickerKeyboardService
+ ]
+ }
+ });
+ TestBed.configureTestingModule({
+ declarations: [TestComponent],
+ imports: [NgbDatepickerModule],
+ providers: [{provide: NgbDatepickerService, useClass: MockDatepickerService}]
+ });
+ });
+
+ it('should show/hide weekdays', () => {
+ const fixture = createTestComponent();
+ fixture.componentInstance.showWeekNumbers = false;
+ fixture.detectChanges();
+
+ expectWeekdays(fixture.nativeElement, ['Mo', 'Tu']);
+
+ fixture.componentInstance.showWeekdays = false;
+ fixture.detectChanges();
+ expectWeekdays(fixture.nativeElement, []);
+ });
+
+ it('should show/hide week numbers', () => {
+ const fixture = createTestComponent();
+
+ expectWeekNumbers(fixture.nativeElement, ['1', '2', '3']);
+
+ fixture.componentInstance.showWeekNumbers = false;
+ fixture.detectChanges();
+ expectWeekNumbers(fixture.nativeElement, []);
+ });
+
+ it('should use custom template to display dates', () => {
+ const fixture = createTestComponent();
+ expectDates(fixture.nativeElement, ['', '1', '2', '3', '4', '']);
+ });
+
+ it('should use "date" as an implicit value for the template', () => {
+ const fixture = createTestComponent();
+ expectDates(fixture.nativeElement, ['', '1', '2', '3', '4', '']);
+ });
+
+ it('should send date selection events', () => {
+ const fixture = createTestComponent();
+
+ spyOn(fixture.componentInstance, 'onClick');
+
+ const dates = getDates(fixture.nativeElement);
+ dates[1].click();
+
+ expect(fixture.componentInstance.onClick).toHaveBeenCalledWith(new NgbDate(2016, 8, 1));
+ });
+
+ it('should not send date selection events for hidden and disabled dates', () => {
+ const fixture = createTestComponent();
+
+ spyOn(fixture.componentInstance, 'onClick');
+
+ const dates = getDates(fixture.nativeElement);
+ dates[0].click(); // hidden
+ dates[2].click(); // disabled
+
+ expect(fixture.componentInstance.onClick).not.toHaveBeenCalled();
+ });
+
+ it('should set cursor to pointer or default', () => {
+ const fixture = createTestComponent();
+
+ const dates = getDates(fixture.nativeElement);
+ // hidden
+ expect(window.getComputedStyle(dates[0]).getPropertyValue('cursor')).toBe('default');
+ // normal
+ expect(window.getComputedStyle(dates[1]).getPropertyValue('cursor')).toBe('pointer');
+ // disabled
+ expect(window.getComputedStyle(dates[2]).getPropertyValue('cursor')).toBe('default');
+ });
+
+ it('should apply correct CSS classes to days', () => {
+ const fixture = createTestComponent();
+
+ let dates = getDates(fixture.nativeElement);
+ // hidden
+ expect(dates[0]).toHaveCssClass('hidden');
+ expect(dates[0]).not.toHaveCssClass('disabled');
+ expect(dates[0]).not.toHaveCssClass('ngb-dp-today');
+ // normal
+ expect(dates[1]).not.toHaveCssClass('hidden');
+ expect(dates[1]).not.toHaveCssClass('disabled');
+ expect(dates[1]).not.toHaveCssClass('ngb-dp-today');
+ // disabled
+ expect(dates[2]).not.toHaveCssClass('hidden');
+ expect(dates[2]).toHaveCssClass('disabled');
+ expect(dates[2]).toHaveCssClass('ngb-dp-today');
+ });
+
+ it('should not display collapsed weeks', () => {
+ const fixture = createTestComponent();
+
+ expectDates(fixture.nativeElement, ['', '1', '2', '3', '4', '']);
+ });
+
+ it('should add correct aria-label attribute', () => {
+ const fixture = createTestComponent();
+
+ let dates = getDates(fixture.nativeElement);
+ expect(dates[0].getAttribute('aria-label')).toBe('Monday');
+ });
+
+ it('should render custom month layout', () => {
+ const fixture = createGenericTestComponent(
+ `
+
+
+
+
+ `,
+ TestComponent) as ComponentFixture;
+ expectDates(fixture.nativeElement, ['', '1', '2', '3', '4', '']);
+ });
+
+ it('should render custom month template', () => {
+ const fixture = createGenericTestComponent(
+ `
+
+ Custom Content
+
+ `,
+ TestComponent) as ComponentFixture;
+ expectDates(fixture.nativeElement, []);
+ expect(fixture.nativeElement.querySelectorAll('.customClass').length).toEqual(1);
+ expect(fixture.nativeElement.querySelectorAll('.customClass')[0].innerText.trim()).toEqual('Custom Content');
+ });
+
+ it('should handle keyboard events with custom month template', () => {
+ const fixture = createGenericTestComponent(
+ `
+
+ Custom Content
+
+ `,
+ TestComponent) as ComponentFixture;
+ expectDates(fixture.nativeElement, []);
+ expect(fixture.nativeElement.querySelectorAll('.customClass').length).toEqual(1);
+ expect(fixture.nativeElement.querySelectorAll('.customClass')[0].innerText.trim()).toEqual('Custom Content');
+ });
+});
+
+@Component({selector: 'test-cmp', template: ''})
+class TestComponent {
+ showWeekdays = true;
+ showWeekNumbers = true;
+ outsideDays = 'visible';
+
+ onClick = () => {};
+}
diff --git a/src/datepicker/datepicker-month.ts b/src/datepicker/datepicker-month.ts
new file mode 100644
index 0000000000..fd6a1c7e3e
--- /dev/null
+++ b/src/datepicker/datepicker-month.ts
@@ -0,0 +1,57 @@
+import {Component, Input, ViewEncapsulation} from '@angular/core';
+import {NgbDatepicker} from './datepicker';
+import {NgbDatepickerI18n} from './datepicker-i18n';
+import {NgbDatepickerKeyboardService} from './datepicker-keyboard-service';
+import {NgbDatepickerService} from './datepicker-service';
+import {MonthViewModel, DayViewModel} from './datepicker-view-model';
+import {NgbDateStruct} from './ngb-date-struct';
+
+@Component({
+ selector: 'ngb-datepicker-month',
+ host: {'role': 'grid', '(keydown)': 'onKeyDown($event)'},
+ encapsulation: ViewEncapsulation.None,
+ styleUrls: ['./datepicker-month.scss'],
+ template: `
+
+
+
+ {{ i18n.getWeekdayShortName(w) }}
+
+
+
+
+
{{ i18n.getWeekNumerals(week.number) }}
+
+
+
+
+
+
+
+ `
+})
+export class NgbDatepickerMonth {
+ @Input()
+ set month(month: NgbDateStruct) {
+ this.viewModel = this._service.getMonth(month);
+ }
+
+ viewModel: MonthViewModel;
+
+ constructor(
+ public i18n: NgbDatepickerI18n, public datepicker: NgbDatepicker,
+ private _keyboardService: NgbDatepickerKeyboardService, private _service: NgbDatepickerService) {}
+
+ onKeyDown(event: KeyboardEvent) { this._keyboardService.processKey(event, this.datepicker); }
+
+ doSelect(day: DayViewModel) {
+ if (!day.context.disabled && !day.hidden) {
+ this.datepicker.onDateSelect(day.date);
+ }
+ }
+}
diff --git a/src/datepicker/datepicker-service.spec.ts b/src/datepicker/datepicker-service.spec.ts
index 62f11c56d0..1552874ce4 100644
--- a/src/datepicker/datepicker-service.spec.ts
+++ b/src/datepicker/datepicker-service.spec.ts
@@ -649,6 +649,13 @@ describe('ngb-datepicker-service', () => {
expect(model.selectBoxes.months).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
});
+ it(`should throw if registers a month outside range`, () => {
+ expect(() => {
+ service.set({minDate: new NgbDate(2017, 5, 1)});
+ service.getMonth(new NgbDate(2015, 5, 1));
+ }).toThrowError();
+ });
+
it(`should rebuild 'months' and 'years' only when year change`, () => {
service.focus(new NgbDate(2010, 5, 1));
let months = model.selectBoxes.months;
diff --git a/src/datepicker/datepicker-service.ts b/src/datepicker/datepicker-service.ts
index d942a18d7e..8bfac4889a 100644
--- a/src/datepicker/datepicker-service.ts
+++ b/src/datepicker/datepicker-service.ts
@@ -159,6 +159,15 @@ export class NgbDatepickerService {
return this._calendar.isValid(ngbDate) ? ngbDate : defaultValue;
}
+ getMonth(struct: NgbDateStruct) {
+ for (let month of this._state.months) {
+ if (struct.month === month.number && struct.year === month.year) {
+ return month;
+ }
+ }
+ throw new Error(`month ${struct.month} of year ${struct.year} not found`);
+ }
+
private _nextState(patch: Partial) {
const newState = this._updateState(patch);
this._patchContexts(newState);
diff --git a/src/datepicker/datepicker.module.ts b/src/datepicker/datepicker.module.ts
index 8feb350a8c..4397d23e83 100644
--- a/src/datepicker/datepicker.module.ts
+++ b/src/datepicker/datepicker.module.ts
@@ -1,14 +1,14 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
-import {NgbDatepicker} from './datepicker';
-import {NgbDatepickerMonthView} from './datepicker-month-view';
+import {NgbDatepicker, NgbDatepickerMonths} from './datepicker';
+import {NgbDatepickerMonth} from './datepicker-month';
import {NgbDatepickerNavigation} from './datepicker-navigation';
import {NgbInputDatepicker} from './datepicker-input';
import {NgbDatepickerDayView} from './datepicker-day-view';
import {NgbDatepickerNavigationSelect} from './datepicker-navigation-select';
-export {NgbDatepicker, NgbDatepickerNavigateEvent, NgbDatepickerState} from './datepicker';
+export {NgbDatepicker, NgbDatepickerMonths, NgbDatepickerNavigateEvent, NgbDatepickerState} from './datepicker';
export {NgbInputDatepicker} from './datepicker-input';
export {NgbCalendar, NgbPeriod, NgbCalendarGregorian} from './ngb-calendar';
export {NgbCalendarIslamicCivil} from './hijri/ngb-calendar-islamic-civil';
@@ -16,7 +16,7 @@ export {NgbCalendarIslamicUmalqura} from './hijri/ngb-calendar-islamic-umalqura'
export {NgbCalendarPersian} from './jalali/ngb-calendar-persian';
export {NgbCalendarHebrew} from './hebrew/ngb-calendar-hebrew';
export {NgbDatepickerI18nHebrew} from './hebrew/datepicker-i18n-hebrew';
-export {NgbDatepickerMonthView} from './datepicker-month-view';
+export {NgbDatepickerMonth} from './datepicker-month';
export {NgbDatepickerDayView} from './datepicker-day-view';
export {NgbDatepickerNavigation} from './datepicker-navigation';
export {NgbDatepickerNavigationSelect} from './datepicker-navigation-select';
@@ -33,10 +33,10 @@ export {NgbDatepickerKeyboardService} from './datepicker-keyboard-service';
@NgModule({
declarations: [
- NgbDatepicker, NgbDatepickerMonthView, NgbDatepickerNavigation, NgbDatepickerNavigationSelect, NgbDatepickerDayView,
- NgbInputDatepicker
+ NgbDatepicker, NgbDatepickerMonths, NgbDatepickerMonth, NgbDatepickerNavigation, NgbDatepickerNavigationSelect,
+ NgbDatepickerDayView, NgbInputDatepicker
],
- exports: [NgbDatepicker, NgbInputDatepicker],
+ exports: [NgbDatepicker, NgbDatepickerMonths, NgbInputDatepicker, NgbDatepickerMonth],
imports: [CommonModule, FormsModule],
entryComponents: [NgbDatepicker]
})
diff --git a/src/datepicker/datepicker.scss b/src/datepicker/datepicker.scss
index 0cf3ef5619..74af01d39b 100644
--- a/src/datepicker/datepicker.scss
+++ b/src/datepicker/datepicker.scss
@@ -3,7 +3,7 @@ ngb-datepicker {
border-radius: .25rem;
display: inline-block;
- &-month-view {
+ &-month {
pointer-events: auto;
}
diff --git a/src/datepicker/datepicker.spec.ts b/src/datepicker/datepicker.spec.ts
index 900e64fe36..fcd243faae 100644
--- a/src/datepicker/datepicker.spec.ts
+++ b/src/datepicker/datepicker.spec.ts
@@ -6,14 +6,13 @@ import {Component, TemplateRef, DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
import {FormsModule, ReactiveFormsModule, FormGroup, FormControl, Validators} from '@angular/forms';
-import {NgbCalendar} from './ngb-calendar';
import {NgbDatepickerModule, NgbDatepickerNavigateEvent} from './datepicker.module';
import {NgbDate} from './ngb-date';
import {NgbDatepickerConfig} from './datepicker-config';
import {NgbDatepicker, NgbDatepickerState} from './datepicker';
import {DayTemplateContext} from './datepicker-day-template-context';
import {NgbDateStruct} from './ngb-date-struct';
-import {NgbDatepickerMonthView} from './datepicker-month-view';
+import {NgbDatepickerMonth} from './datepicker-month';
import {NgbDatepickerDayView} from './datepicker-day-view';
import {NgbDatepickerKeyboardService} from './datepicker-keyboard-service';
import {NgbDatepickerNavigationSelect} from './datepicker-navigation-select';
@@ -66,7 +65,7 @@ function triggerKeyDown(element: DebugElement, keyCode: number, shiftKey = false
}
function getMonthContainer(datepicker: DebugElement) {
- return datepicker.query(By.css('div.ngb-dp-months'));
+ return datepicker.query(By.css('ngb-datepicker-month'));
}
function expectSelectedDate(element: DebugElement, selectedDate: NgbDate) {
@@ -407,12 +406,12 @@ describe('ngb-datepicker', () => {
it('should display multiple months', () => {
const fixture = createTestComponent(``);
- let months = fixture.debugElement.queryAll(By.directive(NgbDatepickerMonthView));
+ let months = fixture.debugElement.queryAll(By.directive(NgbDatepickerMonth));
expect(months.length).toBe(1);
fixture.componentInstance.displayMonths = 3;
fixture.detectChanges();
- months = fixture.debugElement.queryAll(By.directive(NgbDatepickerMonthView));
+ months = fixture.debugElement.queryAll(By.directive(NgbDatepickerMonth));
expect(months.length).toBe(3);
});
@@ -1216,10 +1215,9 @@ describe('ngb-datepicker', () => {
let mockState: NgbDatepickerState;
let dp: NgbDatepicker;
- const mockKeyboardService: NgbDatepickerKeyboardService = {
- processKey(event: KeyboardEvent, datepicker: NgbDatepicker, calendar: NgbCalendar) {
- mockState = datepicker.state;
- }
+ let mv: NgbDatepickerMonth;
+ const mockKeyboardService: Partial = {
+ processKey(event: KeyboardEvent, datepicker: NgbDatepicker) { mockState = datepicker.state; }
};
beforeEach(() => {
@@ -1231,59 +1229,68 @@ describe('ngb-datepicker', () => {
``);
fixture.detectChanges();
dp = fixture.debugElement.query(By.directive(NgbDatepicker)).componentInstance;
+ mv = fixture.debugElement.query(By.css('ngb-datepicker-month')).injector.get(NgbDatepickerMonth);
});
it('should provide an defensive copy of minDate', () => {
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(mockState.firstDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 1}));
expect(mockState.lastDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 31}));
expect(mockState.minDate).toEqual(NgbDate.from({year: 2010, month: 1, day: 1}));
expect(mockState.maxDate).toEqual(NgbDate.from({year: 2020, month: 12, day: 31}));
Object.assign(mockState, {minDate: undefined});
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(dp.model.minDate).toEqual(NgbDate.from({year: 2010, month: 1, day: 1}));
});
it('should provide an defensive copy of maxDate', () => {
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(mockState.firstDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 1}));
expect(mockState.lastDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 31}));
expect(mockState.minDate).toEqual(NgbDate.from({year: 2010, month: 1, day: 1}));
expect(mockState.maxDate).toEqual(NgbDate.from({year: 2020, month: 12, day: 31}));
Object.assign(mockState, {maxDate: undefined});
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(dp.model.maxDate).toEqual(NgbDate.from({year: 2020, month: 12, day: 31}));
});
it('should provide an defensive copy of firstDate', () => {
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(mockState.firstDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 1}));
expect(mockState.lastDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 31}));
expect(mockState.minDate).toEqual(NgbDate.from({year: 2010, month: 1, day: 1}));
expect(mockState.maxDate).toEqual(NgbDate.from({year: 2020, month: 12, day: 31}));
Object.assign(mockState, {firstDate: undefined});
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(dp.model.firstDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 1}));
});
it('should provide an defensive copy of lastDate', () => {
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(mockState.firstDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 1}));
expect(mockState.lastDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 31}));
expect(mockState.minDate).toEqual(NgbDate.from({year: 2010, month: 1, day: 1}));
expect(mockState.maxDate).toEqual(NgbDate.from({year: 2020, month: 12, day: 31}));
Object.assign(mockState, {lastDate: undefined});
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(dp.model.lastDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 31}));
});
it('should provide an defensive copy of focusedDate', () => {
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(mockState.focusedDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 1}));
Object.assign(mockState, {focusedDate: undefined});
- dp.onKeyDown({});
+ mv.onKeyDown({});
expect(dp.model.focusDate).toEqual(NgbDate.from({year: 2016, month: 8, day: 1}));
});
+
+ it('should prevent overriding of calendar', () => {
+ try {
+ (dp)['calendar'] = null;
+ } catch (e) {
+ }
+ expect(dp.calendar).toBeTruthy();
+ });
});
});
diff --git a/src/datepicker/datepicker.ts b/src/datepicker/datepicker.ts
index 8869549e3f..4d32bdaec4 100644
--- a/src/datepicker/datepicker.ts
+++ b/src/datepicker/datepicker.ts
@@ -1,10 +1,11 @@
import {fromEvent, merge, Subject} from 'rxjs';
import {filter, take, takeUntil} from 'rxjs/operators';
import {
- AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
+ ContentChild,
+ Directive,
ElementRef,
EventEmitter,
forwardRef,
@@ -23,7 +24,6 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {NgbCalendar} from './ngb-calendar';
import {NgbDate} from './ngb-date';
import {DatepickerServiceInputs, NgbDatepickerService} from './datepicker-service';
-import {NgbDatepickerKeyboardService} from './datepicker-keyboard-service';
import {DatepickerViewModel, NavigationEvent} from './datepicker-view-model';
import {DayTemplateContext} from './datepicker-day-template-context';
import {NgbDatepickerConfig} from './datepicker-config';
@@ -33,7 +33,7 @@ import {NgbDatepickerI18n} from './datepicker-i18n';
import {isChangedDate, isChangedMonth} from './datepicker-tools';
import {hasClassName} from '../util/util';
-const NGB_DATEPICKER_VALUE_ACCESSOR = {
+export const NGB_DATEPICKER_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgbDatepicker),
multi: true
@@ -93,6 +93,19 @@ export interface NgbDatepickerState {
* The date currently focused by the datepicker
*/
readonly focusedDate: NgbDate;
+
+ /**
+ * The first dates of months in range of current datepicker instance
+ */
+ readonly months: NgbDate[];
+}
+
+/*
+ * A directive that marks the content template that customizes the way datepicker months are displayed
+ */
+@Directive({selector: 'ng-template[ngbDatepickerMonths]'})
+export class NgbDatepickerMonths {
+ constructor(public templateRef: TemplateRef) {}
}
/**
@@ -107,7 +120,7 @@ export interface NgbDatepickerState {
encapsulation: ViewEncapsulation.None,
styleUrls: ['./datepicker.scss'],
template: `
-
+
+
+
+
1 && navigation === 'select')" class="ngb-dp-month-name">
+ {{ i18n.getMonthFullName(month.number, month.year) }} {{ i18n.getYearNumerals(month.year) }}
+
+
+
+
+
-
-
-
-
1 && navigation === 'select')"
- class="ngb-dp-month-name">
- {{ i18n.getMonthFullName(month.number, month.year) }} {{ i18n.getYearNumerals(month.year) }}
-
-
-
-
-
+
+
@@ -154,10 +162,13 @@ export interface NgbDatepickerState {
providers: [NGB_DATEPICKER_VALUE_ACCESSOR, NgbDatepickerService]
})
export class NgbDatepicker implements OnDestroy,
- OnChanges, OnInit, AfterViewInit, ControlValueAccessor {
+ OnChanges, OnInit, ControlValueAccessor {
model: DatepickerViewModel;
+ @ViewChild('defaultDayTemplate', {static: true}) private _defaultDayTemplate: TemplateRef
;
@ViewChild('months', {static: true}) private _monthsEl: ElementRef;
+ @ContentChild(NgbDatepickerMonths, {static: true}) monthsTemplate: NgbDatepickerMonths;
+
private _controlValue: NgbDate;
private _destroyed$ = new Subject();
private _publicState: NgbDatepickerState = {};
@@ -294,9 +305,8 @@ export class NgbDatepicker implements OnDestroy,
constructor(
private _service: NgbDatepickerService, private _calendar: NgbCalendar, public i18n: NgbDatepickerI18n,
- config: NgbDatepickerConfig, private _keyboardService: NgbDatepickerKeyboardService, cd: ChangeDetectorRef,
- private _elementRef: ElementRef, private _ngbDateAdapter: NgbDateAdapter,
- private _ngZone: NgZone) {
+ config: NgbDatepickerConfig, cd: ChangeDetectorRef, private _elementRef: ElementRef,
+ private _ngbDateAdapter: NgbDateAdapter, private _ngZone: NgZone) {
['dayTemplate', 'dayTemplateData', 'displayMonths', 'firstDayOfWeek', 'footerTemplate', 'markDisabled', 'minDate',
'maxDate', 'navigation', 'outsideDays', 'showWeekdays', 'showWeekNumbers', 'startDate']
.forEach(input => this[input] = config[input]);
@@ -313,7 +323,8 @@ export class NgbDatepicker implements OnDestroy,
minDate: model.minDate,
firstDate: model.firstDate,
lastDate: model.lastDate,
- focusedDate: model.focusDate
+ focusedDate: model.focusDate,
+ months: model.months.map(viewModel => viewModel.firstDate)
};
let navigationPrevented = false;
@@ -361,6 +372,11 @@ export class NgbDatepicker implements OnDestroy,
*/
get state(): NgbDatepickerState { return this._publicState; }
+ /**
+ * Returns the calendar service used in the specific datepicker instance.
+ */
+ get calendar(): NgbCalendar { return this._calendar; }
+
/**
* Focuses on given date.
*/
@@ -424,6 +440,9 @@ export class NgbDatepicker implements OnDestroy,
this.navigateTo(this.startDate);
}
+ if (!this.dayTemplate) {
+ this.dayTemplate = this._defaultDayTemplate;
+ }
}
ngOnChanges(changes: SimpleChanges) {
@@ -447,8 +466,6 @@ export class NgbDatepicker implements OnDestroy,
this._service.select(date, {emitEvent: true});
}
- onKeyDown(event: KeyboardEvent) { this._keyboardService.processKey(event, this, this._calendar); }
-
onNavigateDateSelect(date: NgbDate) { this._service.open(date); }
onNavigateEvent(event: NavigationEvent) {
diff --git a/src/index.ts b/src/index.ts
index abdd8fa326..874a31a71a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -60,10 +60,12 @@ export {
NgbDatepicker,
NgbDatepickerConfig,
NgbInputDatepickerConfig,
+ NgbDatepickerMonths,
NgbDatepickerI18n,
NgbDatepickerI18nHebrew,
NgbDatepickerKeyboardService,
NgbDatepickerModule,
+ NgbDatepickerMonth,
NgbDatepickerNavigateEvent,
NgbDatepickerState,
NgbDateStruct,