feat(widget): add ng:pluralize as an Angular widget

This commit is contained in:
Di Peng 2011-08-16 16:00:16 -07:00 committed by Igor Minar
parent 0da4902e9d
commit e068addadb
2 changed files with 318 additions and 0 deletions

View file

@ -1445,3 +1445,195 @@ angularWidget('ng:view', function(element) {
compiler.directives(true);
}
});
/**
* @ngdoc widget
* @name angular.widget.ng:pluralize
*
* @description
* # Overview
* ng:pluralize is a widget that displays messages according to en-US localization rules.
* These rules are bundled with angular.js and the rules can be overridden
* (see {@link guide/dev_guide.i18n Angular i18n} dev guide). You configure ng:pluralize by
* specifying the mappings between
* {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
* plural categories} and the strings to be displayed.
*
* # Plural categories and explicit number rules
* There are two
* {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
* plural categories} in Angular's default en-US locale: "one" and "other".
*
* While a pural category may match many numbers (for example, in en-US locale, "other" can match
* any number that is not 1), an explicit number rule can only match one number. For example, the
* explicit number rule for "3" matches the number 3. You will see the use of plural categories
* and explicit number rules throughout later parts of this documentation.
*
* # Configuring ng:pluralize
* You configure ng:pluralize 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 {@link guide/dev_guide.expressions
* Angular expression}; these are evaluated on the current scope for its binded 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 so that Angular
* can interpret it correctly.
*
* The following example shows how to configure ng:pluralize:
*
* <pre>
* <ng:pluralize count="personCount"
when="{'0': 'Nobody is viewing.',
* 'one': '1 person is viewing.',
* 'other': '{} people are viewing.'}">
* </ng:pluralize>
*</pre>
*
* 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
* <span ng:non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
* for <span ng:non-bindable>{{numberExpression}}</span>.
*
* # Configuring ng:pluralize 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:
*
* <pre>
* <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>
* </pre>
*
* 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
* plural categories "one" and "other".
*
* @param {string|expression} count The variable to be bounded to.
* @param {string} when The mapping between plural category to its correspoding strings.
* @param {number=} offset Offset to deduct from the total number.
*
* @example
<doc:example>
<doc:source>
Person 1:<input type="text" name="person1" value="Igor" /><br/>
Person 2:<input type="text" name="person2" value="Misko" /><br/>
Number of People:<input type="text" name="personCount" value="1" /><br/>
<!--- Example with simple pluralization rules for en locale --->
Without Offset:
<ng:pluralize count="personCount"
when="{'0': 'Nobody is viewing.',
'one': '1 person is viewing.',
'other': '{} people are viewing.'}">
</ng:pluralize><br>
<!--- Example with offset --->
With Offset(2):
<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>
</doc:source>
<doc:scenario>
it('should show correct pluralized string', function(){
expect(element('.doc-example-live .ng-pluralize:first').text()).
toBe('1 person is viewing.');
expect(element('.doc-example-live .ng-pluralize:last').text()).
toBe('Igor is viewing.');
using('.doc-example-live').input('personCount').enter('0');
expect(element('.doc-example-live .ng-pluralize:first').text()).
toBe('Nobody is viewing.');
expect(element('.doc-example-live .ng-pluralize:last').text()).
toBe('Nobody is viewing.');
using('.doc-example-live').input('personCount').enter('2');
expect(element('.doc-example-live .ng-pluralize:first').text()).
toBe('2 people are viewing.');
expect(element('.doc-example-live .ng-pluralize:last').text()).
toBe('Igor and Misko are viewing.');
using('.doc-example-live').input('personCount').enter('3');
expect(element('.doc-example-live .ng-pluralize:first').text()).
toBe('3 people are viewing.');
expect(element('.doc-example-live .ng-pluralize:last').text()).
toBe('Igor, Misko and one other person are viewing.');
using('.doc-example-live').input('personCount').enter('4');
expect(element('.doc-example-live .ng-pluralize:first').text()).
toBe('4 people are viewing.');
expect(element('.doc-example-live .ng-pluralize:last').text()).
toBe('Igor, Misko and 2 other people are viewing.');
});
it('should show data-binded names', function(){
using('.doc-example-live').input('personCount').enter('4');
expect(element('.doc-example-live .ng-pluralize:last').text()).
toBe('Igor, Misko and 2 other people are viewing.');
using('.doc-example-live').input('person1').enter('Di');
using('.doc-example-live').input('person2').enter('Vojta');
expect(element('.doc-example-live .ng-pluralize:last').text()).
toBe('Di, Vojta and 2 other people are viewing.');
});
</doc:scenario>
</doc:example>
*/
angularWidget('ng:pluralize', function(element) {
var numberExp = element.attr('count'),
whenExp = element.attr('when'),
offset = element.attr('offset') || 0;
return annotate('$locale', function($locale, element) {
var scope = this,
whens = scope.$eval(whenExp),
whensExpFns = {};
forEach(whens, function(expression, key) {
whensExpFns[key] = compileBindTemplate(expression.replace(/{}/g,
'{{' + numberExp + '-' + offset + '}}'));
});
scope.$watch(function() {
var value = parseFloat(scope.$eval(numberExp));
if (!isNaN(value)) {
//if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise,
//check it against pluralization rules in $locale service
if (!whens[value]) value = $locale.pluralCat(value - offset);
return whensExpFns[value](scope, element, true);
} else {
return '';
}
}, function(scope, newVal) {
element.text(newVal);
});
});
});

View file

@ -1236,5 +1236,131 @@ describe("widget", function(){
expect(rootScope.log).toEqual(['parent', 'init', 'child']);
});
});
describe('ng:pluralize', function() {
describe('deal with pluralized strings without offset', function() {
beforeEach(function() {
compile('<ng:pluralize count="email"' +
"when=\"{'0': 'You have no new email'," +
"'one': 'You have one new email'," +
"'other': 'You have {} new emails'}\">" +
'</ng:pluralize>');
});
it('should show single/plural strings', function() {
scope.email = 0;
scope.$digest();
expect(element.text()).toBe('You have no new email');
scope.email = '0';
scope.$digest();
expect(element.text()).toBe('You have no new email');
scope.email = 1;
scope.$digest();
expect(element.text()).toBe('You have one new email');
scope.email = 0.01;
scope.$digest();
expect(element.text()).toBe('You have 0.01 new emails');
scope.email = '0.1';
scope.$digest();
expect(element.text()).toBe('You have 0.1 new emails');
scope.email = 2;
scope.$digest();
expect(element.text()).toBe('You have 2 new emails');
scope.email = -0.1;
scope.$digest();
expect(element.text()).toBe('You have -0.1 new emails');
scope.email = '-0.01';
scope.$digest();
expect(element.text()).toBe('You have -0.01 new emails');
scope.email = -2;
scope.$digest();
expect(element.text()).toBe('You have -2 new emails');
});
it('should show single/plural strings with mal-formed inputs', function() {
scope.email = '';
scope.$digest();
expect(element.text()).toBe('');
scope.email = null;
scope.$digest();
expect(element.text()).toBe('');
scope.email = undefined;
scope.$digest();
expect(element.text()).toBe('');
scope.email = 'a3';
scope.$digest();
expect(element.text()).toBe('');
scope.email = '011';
scope.$digest();
expect(element.text()).toBe('You have 11 new emails');
scope.email = '-011';
scope.$digest();
expect(element.text()).toBe('You have -11 new emails');
scope.email = '1fff';
scope.$digest();
expect(element.text()).toBe('You have one new email');
scope.email = '0aa22';
scope.$digest();
expect(element.text()).toBe('You have no new email');
scope.email = '000001';
scope.$digest();
expect(element.text()).toBe('You have one new email');
});
});
describe('deal with pluralized strings with offset', function() {
it('should show single/plural strings with offset', function() {
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>");
scope.p1 = 'Igor';
scope.p2 = 'Misko';
scope.viewCount = 0;
scope.$digest();
expect(element.text()).toBe('Nobody is viewing.');
scope.viewCount = 1;
scope.$digest();
expect(element.text()).toBe('Igor is viewing.');
scope.viewCount = 2;
scope.$digest();
expect(element.text()).toBe('Igor and Misko are viewing.');
scope.viewCount = 3;
scope.$digest();
expect(element.text()).toBe('Igor, Misko and one other person are viewing.');
scope.viewCount = 4;
scope.$digest();
expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.');
});
});
});
});