-
Notifications
You must be signed in to change notification settings - Fork 25.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
315 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { | ||
Directive, | ||
ViewContainerRef, | ||
TemplateRef, | ||
ContentChildren, | ||
QueryList, | ||
Attribute, | ||
AfterContentInit, | ||
Input | ||
} from 'angular2/core'; | ||
import {isPresent, NumberWrapper} from 'angular2/src/facade/lang'; | ||
import {Map} from 'angular2/src/facade/collection'; | ||
import {SwitchView} from './ng_switch'; | ||
|
||
const _CATEGORY_DEFAULT = 'other'; | ||
|
||
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; } | ||
|
||
/** | ||
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression | ||
* value, or failing that, DOM sub-trees that match the switch expression's pluralization category. | ||
* | ||
* To use this directive, you must provide an extension of `NgLocalization` that maps values to | ||
* category names. You then define a container element that sets the `[ngPlural]` attribute to a | ||
* switch expression. | ||
* - Inner elements defined with an `[ngPluralCase]` attribute will display based on their | ||
* expression. | ||
* - If `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value | ||
* matches the switch expression exactly. | ||
* - Otherwise, the view will be treated as a "category match", and will only display if exact | ||
* value matches aren't found and the value maps to its category using the `getPluralCategory` | ||
* function provided. | ||
* | ||
* If no matching views are found for a switch expression, inner elements marked | ||
* `[ngPluralCase]="other"` will be displayed. | ||
* | ||
* ```typescript | ||
* class MyLocalization extends NgLocalization { | ||
* getPluralCategory(value: any) { | ||
* if(value < 5) { | ||
* return 'few'; | ||
* } | ||
* } | ||
* } | ||
* | ||
* @Component({ | ||
* selector: 'app', | ||
* providers: [provide(NgLocalization, {useClass: MyLocalization})] | ||
* }) | ||
* @View({ | ||
* template: ` | ||
* <p>Value = {{value}}</p> | ||
* <button (click)="inc()">Increment</button> | ||
* | ||
* <div [ngPlural]="value"> | ||
* <template ngPluralCase="=0">there is nothing</template> | ||
* <template ngPluralCase="=1">there is one</template> | ||
* <template ngPluralCase="few">there are a few</template> | ||
* <template ngPluralCase="other">there is some number</template> | ||
* </div> | ||
* `, | ||
* directives: [NgPlural, NgPluralCase] | ||
* }) | ||
* export class App { | ||
* value = 'init'; | ||
* | ||
* inc() { | ||
* this.value = this.value === 'init' ? 0 : this.value + 1; | ||
* } | ||
* } | ||
* | ||
* ``` | ||
*/ | ||
|
||
@Directive({selector: '[ngPluralCase]'}) | ||
export class NgPluralCase { | ||
_view: SwitchView; | ||
constructor(@Attribute('ngPluralCase') public value: string, template: TemplateRef, | ||
viewContainer: ViewContainerRef) { | ||
this._view = new SwitchView(viewContainer, template); | ||
} | ||
} | ||
|
||
|
||
@Directive({selector: '[ngPlural]'}) | ||
export class NgPlural implements AfterContentInit { | ||
private _switchValue: number; | ||
private _activeView: SwitchView; | ||
private _caseViews = new Map<any, SwitchView>(); | ||
@ContentChildren(NgPluralCase) cases: QueryList<NgPluralCase> = null; | ||
|
||
constructor(private _localization: NgLocalization) {} | ||
|
||
@Input() | ||
set ngPlural(value: number) { | ||
this._switchValue = value; | ||
this._updateView(); | ||
} | ||
|
||
ngAfterContentInit() { | ||
this.cases.forEach((pluralCase: NgPluralCase): void => { | ||
this._caseViews.set(this._formatValue(pluralCase), pluralCase._view); | ||
}); | ||
this._updateView(); | ||
} | ||
|
||
/** @internal */ | ||
_updateView(): void { | ||
this._clearViews(); | ||
|
||
var view: SwitchView = this._caseViews.get(this._switchValue); | ||
if (!isPresent(view)) view = this._getCategoryView(this._switchValue); | ||
|
||
this._activateView(view); | ||
} | ||
|
||
/** @internal */ | ||
_clearViews() { | ||
if (isPresent(this._activeView)) this._activeView.destroy(); | ||
} | ||
|
||
/** @internal */ | ||
_activateView(view: SwitchView) { | ||
if (!isPresent(view)) return; | ||
this._activeView = view; | ||
this._activeView.create(); | ||
} | ||
|
||
/** @internal */ | ||
_getCategoryView(value: number): SwitchView { | ||
var category: string = this._localization.getPluralCategory(value); | ||
var categoryView: SwitchView = this._caseViews.get(category); | ||
return isPresent(categoryView) ? categoryView : this._caseViews.get(_CATEGORY_DEFAULT); | ||
} | ||
|
||
/** @internal */ | ||
_isValueView(pluralCase: NgPluralCase): boolean { return pluralCase.value[0] === "="; } | ||
|
||
/** @internal */ | ||
_formatValue(pluralCase: NgPluralCase): any { | ||
return this._isValueView(pluralCase) ? this._stripValue(pluralCase.value) : pluralCase.value; | ||
} | ||
|
||
/** @internal */ | ||
_stripValue(value: string): number { return NumberWrapper.parseInt(value.substring(1), 10); } | ||
} |
137 changes: 137 additions & 0 deletions
137
modules/angular2/test/common/directives/ng_plural_spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import { | ||
AsyncTestCompleter, | ||
TestComponentBuilder, | ||
beforeEachProviders, | ||
beforeEach, | ||
ddescribe, | ||
describe, | ||
el, | ||
expect, | ||
iit, | ||
inject, | ||
it, | ||
xit, | ||
} from 'angular2/testing_internal'; | ||
|
||
import {Component, View, Injectable, provide} from 'angular2/core'; | ||
import {NgPlural, NgPluralCase, NgLocalization} from 'angular2/common'; | ||
|
||
export function main() { | ||
describe('switch', () => { | ||
beforeEachProviders(() => [provide(NgLocalization, {useClass: TestLocalizationMap})]); | ||
|
||
it('should display the template according to the exact value', | ||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { | ||
var template = '<div>' + | ||
'<ul [ngPlural]="switchValue">' + | ||
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' + | ||
'<template ngPluralCase="=1"><li>you have one message.</li></template>' + | ||
'</ul></div>'; | ||
|
||
tcb.overrideTemplate(TestComponent, template) | ||
.createAsync(TestComponent) | ||
.then((fixture) => { | ||
fixture.debugElement.componentInstance.switchValue = 0; | ||
fixture.detectChanges(); | ||
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.'); | ||
|
||
fixture.debugElement.componentInstance.switchValue = 1; | ||
fixture.detectChanges(); | ||
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.'); | ||
|
||
async.done(); | ||
}); | ||
})); | ||
|
||
it('should display the template according to the category', | ||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { | ||
var template = | ||
'<div>' + | ||
'<ul [ngPlural]="switchValue">' + | ||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + | ||
'<template ngPluralCase="many"><li>you have many messages.</li></template>' + | ||
'</ul></div>'; | ||
|
||
tcb.overrideTemplate(TestComponent, template) | ||
.createAsync(TestComponent) | ||
.then((fixture) => { | ||
fixture.debugElement.componentInstance.switchValue = 2; | ||
fixture.detectChanges(); | ||
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.'); | ||
|
||
fixture.debugElement.componentInstance.switchValue = 8; | ||
fixture.detectChanges(); | ||
expect(fixture.debugElement.nativeElement).toHaveText('you have many messages.'); | ||
|
||
async.done(); | ||
}); | ||
})); | ||
|
||
it('should default to other when no matches are found', | ||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { | ||
var template = | ||
'<div>' + | ||
'<ul [ngPlural]="switchValue">' + | ||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + | ||
'<template ngPluralCase="other"><li>default message.</li></template>' + | ||
'</ul></div>'; | ||
|
||
tcb.overrideTemplate(TestComponent, template) | ||
.createAsync(TestComponent) | ||
.then((fixture) => { | ||
fixture.debugElement.componentInstance.switchValue = 100; | ||
fixture.detectChanges(); | ||
expect(fixture.debugElement.nativeElement).toHaveText('default message.'); | ||
|
||
async.done(); | ||
}); | ||
})); | ||
|
||
it('should prioritize value matches over category matches', | ||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { | ||
var template = | ||
'<div>' + | ||
'<ul [ngPlural]="switchValue">' + | ||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + | ||
'<template ngPluralCase="=2">you have two messages.</template>' + | ||
'</ul></div>'; | ||
|
||
tcb.overrideTemplate(TestComponent, template) | ||
.createAsync(TestComponent) | ||
.then((fixture) => { | ||
fixture.debugElement.componentInstance.switchValue = 2; | ||
fixture.detectChanges(); | ||
expect(fixture.debugElement.nativeElement).toHaveText('you have two messages.'); | ||
|
||
fixture.debugElement.componentInstance.switchValue = 3; | ||
fixture.detectChanges(); | ||
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.'); | ||
|
||
async.done(); | ||
}); | ||
})); | ||
}); | ||
} | ||
|
||
|
||
@Injectable() | ||
export class TestLocalizationMap extends NgLocalization { | ||
getPluralCategory(value: number): string { | ||
if (value > 1 && value < 4) { | ||
return 'few'; | ||
} else if (value >= 4 && value < 10) { | ||
return 'many'; | ||
} else { | ||
return 'other'; | ||
} | ||
} | ||
} | ||
|
||
|
||
@Component({selector: 'test-cmp'}) | ||
@View({directives: [NgPlural, NgPluralCase]}) | ||
class TestComponent { | ||
switchValue: number; | ||
|
||
constructor() { this.switchValue = null; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters