Skip to content

Commit

Permalink
feat(datepicker): allow overriding day, week number and year numerals
Browse files Browse the repository at this point in the history
Useful for calendars that do not use arabic numerals like Hebrew

Closes #2593
  • Loading branch information
maxokorokov committed Aug 10, 2018
1 parent 1e68401 commit 91c04e9
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 25 deletions.
8 changes: 5 additions & 3 deletions src/datepicker/datepicker-day-view.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {TestBed} from '@angular/core/testing';

import {Component} from '@angular/core';
import {NgbDatepickerModule} from './datepicker.module';
import {NgbDatepickerDayView} from './datepicker-day-view';
import {NgbDate} from './ngb-date';
import {NgbDatepickerI18n, NgbDatepickerI18nDefault} from './datepicker-i18n';

function getElement(element: HTMLElement): HTMLElement {
return <HTMLElement>element.querySelector('[ngbDatepickerDayView]');
Expand All @@ -12,8 +12,10 @@ function getElement(element: HTMLElement): HTMLElement {
describe('ngbDatepickerDayView', () => {

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

it('should display date', () => {
Expand Down
5 changes: 4 additions & 1 deletion src/datepicker/datepicker-day-view.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {NgbDate} from './ngb-date';
import {NgbDatepickerI18n} from './datepicker-i18n';

@Component({
selector: '[ngbDatepickerDayView]',
Expand All @@ -25,7 +26,7 @@ import {NgbDate} from './ngb-date';
'[class.outside]': 'isMuted()',
'[class.active]': 'focused'
},
template: `{{ date.day }}`
template: `{{ i18n.getDayNumerals(date) }}`
})
export class NgbDatepickerDayView {
@Input() currentMonth: number;
Expand All @@ -34,5 +35,7 @@ export class NgbDatepickerDayView {
@Input() focused: boolean;
@Input() selected: boolean;

constructor(public i18n: NgbDatepickerI18n) {}

isMuted() { return !this.selected && (this.date.month !== this.currentMonth || this.disabled); }
}
23 changes: 20 additions & 3 deletions src/datepicker/datepicker-i18n.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {NgbDatepickerI18nDefault} from './datepicker-i18n';
import {TestBed} from '@angular/core/testing';
import {LOCALE_ID} from '@angular/core';
import {NgbDate} from './ngb-date';

describe('ngb-datepicker-i18n-default', () => {

let i18n: NgbDatepickerI18nDefault;

beforeEach(() => {
const locale: string = TestBed.get(LOCALE_ID);
i18n = new NgbDatepickerI18nDefault(locale);
TestBed.configureTestingModule({providers: [NgbDatepickerI18nDefault]});
i18n = TestBed.get(NgbDatepickerI18nDefault);
});

it('should return abbreviated month name', () => {
Expand All @@ -32,4 +32,21 @@ describe('ngb-datepicker-i18n-default', () => {
expect(i18n.getWeekdayShortName(8)).toBe(undefined);
});

it('should generate aria label for a date',
() => { expect(i18n.getDayAriaLabel(new NgbDate(2010, 10, 8))).toBe('Friday, October 8, 2010'); });

it('should generate week number numerals', () => {
expect(i18n.getWeekNumerals(1)).toBe('1');
expect(i18n.getWeekNumerals(55)).toBe('55');
});

it('should generate day numerals', () => {
expect(i18n.getDayNumerals(new NgbDate(2010, 10, 1))).toBe('1');
expect(i18n.getDayNumerals(new NgbDate(2010, 10, 31))).toBe('31');
});

it('should generate year numerals', () => {
expect(i18n.getYearNumerals(0)).toBe('0');
expect(i18n.getYearNumerals(2000)).toBe('2000');
});
});
16 changes: 16 additions & 0 deletions src/datepicker/datepicker-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ export abstract class NgbDatepickerI18n {
* @since 2.0.0
*/
abstract getDayAriaLabel(date: NgbDateStruct): string;

/**
* Returns the textual representation of a day that is rendered in a day cell
*/
getDayNumerals(date: NgbDateStruct): string { return `${date.day}`; }

/**
* Returns the textual representation of a week number rendered by date picker
*/
getWeekNumerals(weekNumber: number): string { return `${weekNumber}`; }

/**
* Returns the textual representation of a year that is rendered
* in date picker year select box
*/
getYearNumerals(year: number): string { return `${year}`; }
}

@Injectable()
Expand Down
74 changes: 58 additions & 16 deletions src/datepicker/datepicker-integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {TestBed} from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Component} from '@angular/core';
import {NgbDatepickerModule} from './datepicker.module';
import {NgbDatepickerModule, NgbDateStruct} from './datepicker.module';
import {NgbCalendar, NgbCalendarGregorian} from './ngb-calendar';
import {NgbDate} from './ngb-date';
import {getMonthSelect, getYearSelect} from '../test/datepicker/common';
Expand Down Expand Up @@ -31,27 +31,69 @@ describe('ngb-datepicker integration', () => {
expect(getYearSelect(fixture.nativeElement).value).toBe('2000');
});

it('should allow overriding datepicker i18n', () => {
describe('i18n', () => {

const MONTHS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'];
const ALPHABET = 'ABCDEFGHIJKLMNOPRSTQUVWXYZ';

class AlphabetMonthsI18n extends NgbDatepickerI18nDefault {
getMonthShortName(month: number) { return MONTHS[month - 1]; }
class CustomI18n extends NgbDatepickerI18nDefault {
// alphabetic months: Jan -> A, Feb -> B, etc
getMonthShortName(month: number) { return ALPHABET[month - 1]; }

// alphabetic days: 1 -> A, 2 -> B, etc
getDayNumerals(date: NgbDateStruct) { return ALPHABET[date.day - 1]; }

// alphabetic week numbers: 1 -> A, 2 -> B, etc
getWeekNumerals(week: number) { return ALPHABET[week - 1]; }

// reversed years: 1998 -> 9881
getYearNumerals(year: number) { return `${year}`.split('').reverse().join(''); }
}

TestBed.overrideComponent(TestComponent, {
set: {
template: `<ngb-datepicker></ngb-datepicker>`,
providers: [{provide: NgbDatepickerI18n, useClass: AlphabetMonthsI18n}]
}
let fixture: ComponentFixture<TestComponent>;

beforeEach(() => {
TestBed.overrideComponent(TestComponent, {
set: {
template: `
<ngb-datepicker [startDate]="{year: 2018, month: 1}"
[minDate]="{year: 2017, month: 1, day: 1}"
[maxDate]="{year: 2019, month: 12, day: 31}"
[showWeekNumbers]="true"
></ngb-datepicker>`,
providers: [{provide: NgbDatepickerI18n, useClass: CustomI18n}]
}
});

fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
});

it('should allow overriding month names', () => {
const monthOptions = getMonthSelect(fixture.nativeElement).querySelectorAll('option');
const months = Array.from(monthOptions).map(o => o.innerHTML);
expect(months.join('')).toEqual(ALPHABET.slice(0, 12));
});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();

const monthOptionsText =
Array.from(getMonthSelect(fixture.nativeElement).querySelectorAll('option')).map(o => o.innerHTML);
it('should allow overriding week number numerals', () => {
// month view that displays JAN 2018 starts directly with week 01
const weekNumberElements = fixture.nativeElement.querySelectorAll('.ngb-dp-week-number');
const weekNumbers = Array.from(weekNumberElements).map((o: HTMLElement) => o.innerHTML);
expect(weekNumbers.join('')).toEqual(ALPHABET.slice(0, 6));
});

it('should allow overriding day numerals', () => {
// month view that displays JAN 2018 starts directly with 01 JAN
const daysElements = fixture.nativeElement.querySelectorAll('.ngb-dp-day > div');
const days = Array.from(daysElements).map((o: HTMLElement) => o.innerHTML);
expect(days.slice(0, 26).join('')).toEqual(ALPHABET);
});

expect(monthOptionsText).toEqual(MONTHS);
it('should allow overriding year numerals', () => {
// we have only 2017, 2018 and 2019 in the select box
const yearOptions = getYearSelect(fixture.nativeElement).querySelectorAll('option');
const years = Array.from(yearOptions).map(o => o.innerText);
expect(years).toEqual(['7102', '8102', '9102']);
});
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/datepicker/datepicker-month-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {DayTemplateContext} from './datepicker-day-template-context';
</div>
<ng-template ngFor let-week [ngForOf]="month.weeks">
<div *ngIf="!week.collapsed" class="ngb-dp-week" role="row">
<div *ngIf="showWeekNumbers" class="ngb-dp-week-number small text-muted">{{ week.number }}</div>
<div *ngIf="showWeekNumbers" class="ngb-dp-week-number small text-muted">{{ i18n.getWeekNumerals(week.number) }}</div>
<div *ngFor="let day of week.days" (click)="doSelect(day)" class="ngb-dp-day" role="gridcell"
[class.disabled]="day.context.disabled"
[tabindex]="day.tabindex"
Expand Down
2 changes: 1 addition & 1 deletion src/datepicker/datepicker-navigation-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {NgbDatepickerI18n} from './datepicker-i18n';
class="custom-select"
[value]="date?.year"
(change)="changeYear($event.target.value)">
<option *ngFor="let y of years" [value]="y">{{ y }}</option>
<option *ngFor="let y of years" [value]="y">{{ i18n.getYearNumerals(y) }}</option>
</select>
`
})
Expand Down

0 comments on commit 91c04e9

Please sign in to comment.