This repository has been archived by the owner on Feb 22, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 248
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ng-pluralize): Implement the ng-pluralize directive
- Loading branch information
Showing
3 changed files
with
350 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
part of angular.directive; | ||
|
||
/** | ||
* ## Overview | ||
* `ngPluralize` is a directive that displays messages according to locale rules. | ||
* | ||
* You configure ngPluralize directive by specifying the mappings between plural | ||
* categories and the strings to be displayed. | ||
* | ||
* ## Plural categories and explicit number rules | ||
* The available plural categories are: | ||
* * "zero", | ||
* * "one", | ||
* * "two", | ||
* * "few", | ||
* * "many", | ||
* * "other". | ||
* | ||
* While a plural category may match many numbers, an explicit number rule can only match | ||
* one number. For example, the explicit number rule for "3" matches the number 3. There | ||
* are examples of plural categories and explicit number rules throughout the rest of this | ||
* documentation. | ||
* | ||
* ## Configuring ngPluralize | ||
* You configure ngPluralize by providing 2 attributes: `count` and `when`. | ||
* You can also provide an optional attribute, `offset`. | ||
* | ||
* The value of the `count` attribute can be either a string or an expression; these are | ||
* evaluated on the current scope for its bound value. | ||
* | ||
* The `when` attribute specifies the mappings between plural categories and the actual | ||
* string to be displayed. The value of the attribute should be a JSON object. | ||
* | ||
* The following example shows how to configure ngPluralize: | ||
* | ||
* <ng-pluralize count="personCount" | ||
* when="{'0': 'Nobody is viewing.', | ||
* 'one': '1 person is viewing.', | ||
* 'other': '{} people are viewing.'}"> | ||
* </ng-pluralize> | ||
* | ||
* In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not | ||
* specify this rule, 0 would be matched to the "other" category and "0 people are viewing" | ||
* would be shown instead of "Nobody is viewing". You can specify an explicit number rule for | ||
* other numbers, for example 12, so that instead of showing "12 people are viewing", you can | ||
* show "a dozen people are viewing". | ||
* | ||
* You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted | ||
* into pluralized strings. In the previous example, Angular will replace `{}` with | ||
* `{{personCount}}`. The closed braces `{}` is a placeholder {{numberExpression}}. | ||
* | ||
* ## Configuring ngPluralize with offset | ||
* The `offset` attribute allows further customization of pluralized text, which can result in | ||
* a better user experience. For example, instead of the message "4 people are viewing this document", | ||
* you might display "John, Kate and 2 others are viewing this document". | ||
* The offset attribute allows you to offset a number by any desired value. | ||
* Let's take a look at an example: | ||
* | ||
* <ng-pluralize count="personCount" offset=2 | ||
* when="{'0': 'Nobody is viewing.', | ||
* '1': '{{person1}} is viewing.', | ||
* '2': '{{person1}} and {{person2}} are viewing.', | ||
* 'one': '{{person1}}, {{person2}} and one other person are viewing.', | ||
* 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> | ||
* </ng-pluralize> | ||
* | ||
* Notice that we are still using two plural categories(one, other), but we added | ||
* three explicit number rules 0, 1 and 2. | ||
* When one person, perhaps John, views the document, "John is viewing" will be shown. | ||
* When three people view the document, no explicit number rule is found, so | ||
* an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. | ||
* In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" | ||
* is shown. | ||
* | ||
* Note that when you specify offsets, you must provide explicit number rules for | ||
* numbers from 0 up to and including the offset. If you use an offset of 3, for example, | ||
* you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for | ||
* at least the "other" plural category. | ||
*/ | ||
@NgDirective( | ||
selector: 'ng-pluralize', | ||
map: const { 'count': '=>count' }) | ||
@NgDirective( | ||
selector: '[ng-pluralize]', | ||
map: const { 'count': '=>count' }) | ||
class NgPluralizeDirective { | ||
|
||
final dom.Element element; | ||
final Scope scope; | ||
final Interpolate interpolate; | ||
int offset; | ||
Map<String, String> whens = new Map(); | ||
Map<Symbol, String> whensOffset = new Map(); | ||
static final RegExp IS_WHEN = new RegExp(r'^when-(minus-)?.'); | ||
|
||
NgPluralizeDirective(this.scope, this.element, this.interpolate, NodeAttrs attributes) { | ||
Map<String, String> whens = attributes['when'] == null ? {} : scope.$eval(attributes['when']); | ||
offset = attributes['offset'] == null ? 0 : int.parse(attributes['offset']); | ||
|
||
element.attributes.keys.where((k) => IS_WHEN.hasMatch(k)).forEach((k) { | ||
var rule = k.replaceFirst('when-', '').replaceFirst('minus-', '-'); | ||
whens[rule] = element.attributes[k]; | ||
}); | ||
|
||
if (whens['other'] == null) { | ||
throw "ngPluralize error! The 'other' plural category must always be specified"; | ||
} | ||
|
||
whens.forEach((k, v) { | ||
if (['zero', 'one', 'two', 'few', 'many', 'other'].contains(k)) { | ||
this.whensOffset[new Symbol(k.toString())] = v; | ||
} else { | ||
this.whens[k.toString()] = v; | ||
} | ||
}); | ||
} | ||
|
||
set count(value) { | ||
if (value is! num) { | ||
try { | ||
value = int.parse(value); | ||
} catch(e) { | ||
try { | ||
value = double.parse(value); | ||
} | ||
catch(e) { | ||
element.text = ''; | ||
return; | ||
} | ||
} | ||
} | ||
|
||
String stringValue = value.toString(); | ||
int intValue = value is double ? value.round() : value; | ||
|
||
if (whens[stringValue] != null) { | ||
_setAndWatch(whens[stringValue]); | ||
} else { | ||
intValue -= offset; | ||
var exp = Function.apply(Intl.plural, [intValue], whensOffset); | ||
if (exp != null) { | ||
exp = exp.replaceAll(r'{}', (value - offset).toString()); | ||
_setAndWatch(exp); | ||
} | ||
} | ||
} | ||
|
||
_setAndWatch(expression) { | ||
Interpolation interpolation = interpolate(expression); | ||
interpolation.setter = (text) => element.text = text; | ||
interpolation.setter(expression); | ||
scope.$watchSet(interpolation.watchExpressions, interpolation.call); | ||
} | ||
} |
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,193 @@ | ||
library ng_pluralize_spec; | ||
|
||
import '../_specs.dart'; | ||
|
||
main() { | ||
describe('PluralizeDirective', () { | ||
|
||
describe('deal with pluralized strings without offset', () { | ||
var element; | ||
var elementAlt; | ||
var elt; | ||
TestBed _; | ||
|
||
beforeEach(inject((TestBed tb) { | ||
_ = tb; | ||
|
||
element = _.compile( | ||
'<ng-pluralize count="email"' + | ||
"when=\"{'-1': 'You have negative email. Whohoo!'," + | ||
"'0': 'You have no new email'," + | ||
"'one': 'You have one new email'," + | ||
"'other': 'You have {} new emails'}\">" + | ||
'</ng-pluralize>' | ||
); | ||
|
||
elementAlt = _.compile( | ||
'<p ng-pluralize count="email" ' + | ||
"when-minus-1='You have negative email. Whohoo!' " + | ||
"when-0='You have no new email' " + | ||
"when-one='You have one new email' " + | ||
"when-other='You have {} new emails'>" + | ||
'</p>' | ||
); | ||
})); | ||
|
||
it('should show single/plural strings', () { | ||
_.rootScope.email = '0'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have no new email'); | ||
expect(elementAlt.text).toEqual('You have no new email'); | ||
|
||
_.rootScope.email = '0'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have no new email'); | ||
expect(elementAlt.text).toEqual('You have no new email'); | ||
|
||
_.rootScope.email = 1; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have one new email'); | ||
expect(elementAlt.text).toEqual('You have one new email'); | ||
|
||
_.rootScope.email = 0.01; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have 0.01 new emails'); | ||
expect(elementAlt.text).toEqual('You have 0.01 new emails'); | ||
|
||
_.rootScope.email = '0.1'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have 0.1 new emails'); | ||
expect(elementAlt.text).toEqual('You have 0.1 new emails'); | ||
|
||
_.rootScope.email = 2; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have 2 new emails'); | ||
expect(elementAlt.text).toEqual('You have 2 new emails'); | ||
|
||
_.rootScope.email = -0.1; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have -0.1 new emails'); | ||
expect(elementAlt.text).toEqual('You have -0.1 new emails'); | ||
|
||
_.rootScope.email = '-0.01'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have -0.01 new emails'); | ||
expect(elementAlt.text).toEqual('You have -0.01 new emails'); | ||
|
||
_.rootScope.email = -2; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have -2 new emails'); | ||
expect(elementAlt.text).toEqual('You have -2 new emails'); | ||
|
||
_.rootScope.email = -1; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have negative email. Whohoo!'); | ||
expect(elementAlt.text).toEqual('You have negative email. Whohoo!'); | ||
}); | ||
|
||
it('should show single/plural strings with mal-formed inputs', () { | ||
_.rootScope.email = ''; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual(''); | ||
expect(elementAlt.text).toEqual(''); | ||
|
||
_.rootScope.email = null; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual(''); | ||
expect(elementAlt.text).toEqual(''); | ||
|
||
_.rootScope.email = 'a3'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual(''); | ||
expect(elementAlt.text).toEqual(''); | ||
|
||
_.rootScope.email = '011'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have 11 new emails'); | ||
expect(elementAlt.text).toEqual('You have 11 new emails'); | ||
|
||
_.rootScope.email = '-011'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have -11 new emails'); | ||
expect(elementAlt.text).toEqual('You have -11 new emails'); | ||
|
||
_.rootScope.email = '1fff'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual(''); | ||
expect(elementAlt.text).toEqual(''); | ||
|
||
_.rootScope.email = '0aa22'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual(''); | ||
expect(elementAlt.text).toEqual(''); | ||
|
||
_.rootScope.email = '000001'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('You have one new email'); | ||
expect(elementAlt.text).toEqual('You have one new email'); | ||
}); | ||
}); | ||
|
||
describe('edge cases', () { | ||
it('should be able to handle empty strings as possible values', (inject((TestBed _) { | ||
var element = _.compile( | ||
'<ng-pluralize count="email"' + | ||
"when=\"{'0': ''," + | ||
"'one': 'Some text'," + | ||
"'other': 'Some text'}\">" + | ||
'</ng-pluralize>'); | ||
_.rootScope.email = '0'; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual(''); | ||
}))); | ||
}); | ||
|
||
describe('deal with pluralized strings with offset', () { | ||
it('should show single/plural strings with offset', (inject((TestBed _) { | ||
var element = _.compile( | ||
"<ng-pluralize count='viewCount' offset='2' " + | ||
"when=\"{'0': 'Nobody is viewing.'," + | ||
"'1': '{{p1}} is viewing.'," + | ||
"'2': '{{p1}} and {{p2}} are viewing.'," + | ||
"'one': '{{p1}}, {{p2}} and one other person are viewing.'," + | ||
"'other': '{{p1}}, {{p2}} and {} other people are viewing.'}\">" + | ||
"</ng-pluralize>"); | ||
var elementAlt = _.compile( | ||
"<ng-pluralize count='viewCount' offset='2' " + | ||
"when-0='Nobody is viewing.'" + | ||
"when-1='{{p1}} is viewing.'" + | ||
"when-2='{{p1}} and {{p2}} are viewing.'" + | ||
"when-one='{{p1}}, {{p2}} and one other person are viewing.'" + | ||
"when-other='{{p1}}, {{p2}} and {} other people are viewing.'>" + | ||
"</ng-pluralize>"); | ||
_.rootScope.p1 = 'Igor'; | ||
_.rootScope.p2 = 'Misko'; | ||
|
||
_.rootScope.viewCount = 0; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('Nobody is viewing.'); | ||
expect(elementAlt.text).toEqual('Nobody is viewing.'); | ||
|
||
_.rootScope.viewCount = 1; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('Igor is viewing.'); | ||
expect(elementAlt.text).toEqual('Igor is viewing.'); | ||
|
||
_.rootScope.viewCount = 2; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('Igor and Misko are viewing.'); | ||
expect(elementAlt.text).toEqual('Igor and Misko are viewing.'); | ||
|
||
_.rootScope.viewCount = 3; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('Igor, Misko and one other person are viewing.'); | ||
expect(elementAlt.text).toEqual('Igor, Misko and one other person are viewing.'); | ||
|
||
_.rootScope.viewCount = 4; | ||
_.rootScope.$digest(); | ||
expect(element.text).toEqual('Igor, Misko and 2 other people are viewing.'); | ||
expect(elementAlt.text).toEqual('Igor, Misko and 2 other people are viewing.'); | ||
}))); | ||
}); | ||
}); | ||
} |