feat(number/currency filter): format numbers and currency using pattern

both numbers and currency need to be formatted using a generic pattern
which can be replaced for a different pattern when angular is working in
a non en-US locale

for now only en-US locale is supported, but that will change in the
future
This commit is contained in:
Di Peng 2011-07-23 17:05:20 -07:00 committed by Igor Minar
parent 17251372b1
commit 31b59efa96
2 changed files with 104 additions and 46 deletions

View file

@ -35,9 +35,11 @@
* @function * @function
* *
* @description * @description
* Formats a number as a currency (ie $1,234.56). * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
* symbol for current locale is used.
* *
* @param {number} amount Input to filter. * @param {number} amount Input to filter.
* @param {string=} symbol Currency symbol or identifier to be displayed.
* @returns {string} Formated number. * @returns {string} Formated number.
* *
* @css ng-format-negative * @css ng-format-negative
@ -47,24 +49,28 @@
<doc:example> <doc:example>
<doc:source> <doc:source>
<input type="text" name="amount" value="1234.56"/> <br/> <input type="text" name="amount" value="1234.56"/> <br/>
{{amount | currency}} default currency symbol ($): {{amount | currency}}<br/>
custom currency identifier (USD$): {{amount | currency:"USD$"}}
</doc:source> </doc:source>
<doc:scenario> <doc:scenario>
it('should init with 1234.56', function(){ it('should init with 1234.56', function(){
expect(binding('amount | currency')).toBe('$1,234.56'); expect(binding('amount | currency')).toBe('$1,234.56');
expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
}); });
it('should update', function(){ it('should update', function(){
input('amount').enter('-1234'); input('amount').enter('-1234');
expect(binding('amount | currency')).toBe('$-1,234.00'); expect(binding('amount | currency')).toBe('($1,234.00)');
expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
expect(element('.doc-example-live .ng-binding').attr('className')). expect(element('.doc-example-live .ng-binding').attr('className')).
toMatch(/ng-format-negative/); toMatch(/ng-format-negative/);
}); });
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
angularFilter.currency = function(amount){ angularFilter.currency = function(amount, currencySymbol){
this.$element.toggleClass('ng-format-negative', amount < 0); this.$element.toggleClass('ng-format-negative', amount < 0);
return '$' + angularFilter.number.apply(this, [amount, 2]); if (isUndefined(currencySymbol)) currencySymbol = NUMBER_FORMATS.CURRENCY_SYM;
return formatNumber(amount, 2, 1).replace(/\u00A4/g, currencySymbol);
}; };
/** /**
@ -74,9 +80,9 @@ angularFilter.currency = function(amount){
* @function * @function
* *
* @description * @description
* Formats a number as text. * Formats a number as text.
* *
* If the input is not a number empty string is returned. * If the input is not a number empty string is returned.
* *
* @param {number|string} number Number to format. * @param {number|string} number Number to format.
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to. * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
@ -92,59 +98,104 @@ angularFilter.currency = function(amount){
</doc:source> </doc:source>
<doc:scenario> <doc:scenario>
it('should format numbers', function(){ it('should format numbers', function(){
expect(binding('val | number')).toBe('1,234.57'); expect(binding('val | number')).toBe('1,234.568');
expect(binding('val | number:0')).toBe('1,235'); expect(binding('val | number:0')).toBe('1,235');
expect(binding('-val | number:4')).toBe('-1,234.5679'); expect(binding('-val | number:4')).toBe('-1,234.5679');
}); });
it('should update', function(){ it('should update', function(){
input('val').enter('3374.333'); input('val').enter('3374.333');
expect(binding('val | number')).toBe('3,374.33'); expect(binding('val | number')).toBe('3,374.333');
expect(binding('val | number:0')).toBe('3,374'); expect(binding('val | number:0')).toBe('3,374');
expect(binding('-val | number:4')).toBe('-3,374.3330'); expect(binding('-val | number:4')).toBe('-3,374.3330');
}); });
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
angularFilter.number = function(number, fractionSize){
if (isNaN(number) || !isFinite(number)) {
return '';
}
fractionSize = isUndefined(fractionSize)? 2 : fractionSize;
// PATTERNS[0] is an array for Decimal Pattern, PATTERNS[1] is an array Currency Pattern
// Following is the order in each pattern array:
// 0: minInteger,
// 1: minFraction,
// 2: maxFraction,
// 3: positivePrefix,
// 4: positiveSuffix,
// 5: negativePrefix,
// 6: negativeSuffix,
// 7: groupSize,
// 8: lastGroupSize
var NUMBER_FORMATS = {
DECIMAL_SEP: '.',
GROUP_SEP: ',',
PATTERNS: [[1, 0, 3, '', '', '-', '', 3, 3],[1, 2, 2, '\u00A4', '', '(\u00A4', ')', 3, 3]],
CURRENCY_SYM: '$'
};
var DECIMAL_SEP = '.';
angularFilter.number = function(number, fractionSize) {
if (isNaN(number) || !isFinite(number)) return '';
return formatNumber(number, fractionSize, 0);
}
function formatNumber(number, fractionSize, type) {
var isNegative = number < 0, var isNegative = number < 0,
pow = Math.pow(10, fractionSize), type = type || 0, // 0 is decimal pattern, 1 is currency pattern
whole = '' + number, pattern = NUMBER_FORMATS.PATTERNS[type];
number = Math.abs(number);
var numStr = number + '',
formatedText = '', formatedText = '',
i, fraction; parts = [];
if (whole.indexOf('e') > -1) return whole; if (numStr.indexOf('e') !== -1) {
var formatedText = numStr;
} else {
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
number = Math.round(number * pow) / pow; //determine fractionSize if it is not specified
fraction = ('' + number).split('.'); if (isUndefined(fractionSize)) {
whole = fraction[0]; fractionSize = Math.min(Math.max(pattern[1], fractionLen), pattern[2]);
fraction = fraction[1] || '';
if (isNegative) {
formatedText = '-';
whole = whole.substring(1);
}
for (i = 0; i < whole.length; i++) {
if ((whole.length - i)%3 === 0 && i !== 0) {
formatedText += ',';
} }
formatedText += whole.charAt(i);
} var pow = Math.pow(10, fractionSize);
if (fractionSize) { number = Math.round(number * pow) / pow;
var fraction = ('' + number).split(DECIMAL_SEP);
var whole = fraction[0];
fraction = fraction[1] || '';
var pos = 0,
lgroup = pattern[8],
group = pattern[7];
if (whole.length >= (lgroup + group)) {
pos = whole.length - lgroup;
for (var i = 0; i < pos; i++) {
if ((pos - i)%group === 0 && i !== 0) {
formatedText += NUMBER_FORMATS.GROUP_SEP;
}
formatedText += whole.charAt(i);
}
}
for (i = pos; i < whole.length; i++) {
if ((whole.length - i)%lgroup === 0 && i !== 0) {
formatedText += NUMBER_FORMATS.GROUP_SEP;
}
formatedText += whole.charAt(i);
}
// format fraction part.
while(fraction.length < fractionSize) { while(fraction.length < fractionSize) {
fraction += '0'; fraction += '0';
} }
formatedText += '.' + fraction.substring(0, fractionSize); if (fractionSize) formatedText += NUMBER_FORMATS.DECIMAL_SEP + fraction.substr(0, fractionSize);
} }
return formatedText;
};
parts.push(isNegative ? pattern[5] : pattern[3]);
parts.push(formatedText);
parts.push(isNegative ? pattern[6] : pattern[4]);
return parts.join('');
}
function padNumber(num, digits, trim) { function padNumber(num, digits, trim) {
var neg = ''; var neg = '';

View file

@ -28,16 +28,16 @@ describe('filter', function() {
}); });
describe('currency', function() { describe('currency', function() {
it('should do basic filter', function() { it('should do basic currency filtering', function() {
var html = jqLite('<span/>'); var html = jqLite('<span/>');
var context = {$element:html}; var context = {$element:html};
var currency = bind(context, filter.currency); var currency = bind(context, filter.currency);
expect(currency(0)).toEqual('$0.00'); expect(currency(0)).toEqual('$0.00');
expect(html.hasClass('ng-format-negative')).toBeFalsy(); expect(html.hasClass('ng-format-negative')).toBeFalsy();
expect(currency(-999)).toEqual('$-999.00'); expect(currency(-999)).toEqual('($999.00)');
expect(html.hasClass('ng-format-negative')).toBeTruthy(); expect(html.hasClass('ng-format-negative')).toBeTruthy();
expect(currency(1234.5678)).toEqual('$1,234.57'); expect(currency(1234.5678, "USD$")).toEqual('USD$1,234.57');
expect(html.hasClass('ng-format-negative')).toBeFalsy(); expect(html.hasClass('ng-format-negative')).toBeFalsy();
}); });
}); });
@ -46,13 +46,14 @@ describe('filter', function() {
it('should do basic filter', function() { it('should do basic filter', function() {
var context = {jqElement:jqLite('<span/>')}; var context = {jqElement:jqLite('<span/>')};
var number = bind(context, filter.number); var number = bind(context, filter.number);
expect(number(0, 0)).toEqual('0'); expect(number(0, 0)).toEqual('0');
expect(number(0)).toEqual('0.00'); expect(number(-999)).toEqual('-999');
expect(number(-999)).toEqual('-999.00'); expect(number(123)).toEqual('123');
expect(number(1234.5678)).toEqual('1,234.57'); expect(number(1234567)).toEqual('1,234,567');
expect(number(1234)).toEqual('1,234');
expect(number(1234.5678)).toEqual('1,234.568');
expect(number(Number.NaN)).toEqual(''); expect(number(Number.NaN)).toEqual('');
expect(number("1234.5678")).toEqual('1,234.57'); expect(number("1234.5678")).toEqual('1,234.568');
expect(number(1/0)).toEqual(""); expect(number(1/0)).toEqual("");
expect(number(1, 2)).toEqual("1.00"); expect(number(1, 2)).toEqual("1.00");
expect(number(.1, 2)).toEqual("0.10"); expect(number(.1, 2)).toEqual("0.10");
@ -64,11 +65,17 @@ describe('filter', function() {
expect(number(.99, 2)).toEqual("0.99"); expect(number(.99, 2)).toEqual("0.99");
expect(number(.999, 3)).toEqual("0.999"); expect(number(.999, 3)).toEqual("0.999");
expect(number(.9999, 3)).toEqual("1.000"); expect(number(.9999, 3)).toEqual("1.000");
expect(number(1e50, 0)).toEqual("1e+50");
expect(number(1234.567, 0)).toEqual("1,235"); expect(number(1234.567, 0)).toEqual("1,235");
expect(number(1234.567, 1)).toEqual("1,234.6"); expect(number(1234.567, 1)).toEqual("1,234.6");
expect(number(1234.567, 2)).toEqual("1,234.57"); expect(number(1234.567, 2)).toEqual("1,234.57");
}); });
it('should filter exponential numbers', function() {
var context = {jqElement:jqLite('<span/>')};
var number = bind(context, filter.number);
expect(number(1e50, 0)).toEqual('1e+50');
expect(number(-2e50, 2)).toEqual('-2e+50');
});
}); });
describe('json', function () { describe('json', function () {