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
*
* @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 {string=} symbol Currency symbol or identifier to be displayed.
* @returns {string} Formated number.
*
* @css ng-format-negative
@ -47,24 +49,28 @@
<doc:example>
<doc:source>
<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:scenario>
it('should init with 1234.56', function(){
expect(binding('amount | currency')).toBe('$1,234.56');
expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
});
it('should update', function(){
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')).
toMatch(/ng-format-negative/);
});
</doc:scenario>
</doc:example>
*/
angularFilter.currency = function(amount){
angularFilter.currency = function(amount, currencySymbol){
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
*
* @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)=} [fractionSize=2] Number of decimal places to round the number to.
@ -92,59 +98,104 @@ angularFilter.currency = function(amount){
</doc:source>
<doc:scenario>
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:4')).toBe('-1,234.5679');
});
it('should update', function(){
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:4')).toBe('-3,374.3330');
});
</doc:scenario>
</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,
pow = Math.pow(10, fractionSize),
whole = '' + number,
type = type || 0, // 0 is decimal pattern, 1 is currency pattern
pattern = NUMBER_FORMATS.PATTERNS[type];
number = Math.abs(number);
var numStr = number + '',
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;
fraction = ('' + number).split('.');
whole = fraction[0];
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 += ',';
//determine fractionSize if it is not specified
if (isUndefined(fractionSize)) {
fractionSize = Math.min(Math.max(pattern[1], fractionLen), pattern[2]);
}
formatedText += whole.charAt(i);
}
if (fractionSize) {
var pow = Math.pow(10, 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) {
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) {
var neg = '';

View file

@ -28,16 +28,16 @@ describe('filter', function() {
});
describe('currency', function() {
it('should do basic filter', function() {
it('should do basic currency filtering', function() {
var html = jqLite('<span/>');
var context = {$element:html};
var currency = bind(context, filter.currency);
expect(currency(0)).toEqual('$0.00');
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(currency(1234.5678)).toEqual('$1,234.57');
expect(currency(1234.5678, "USD$")).toEqual('USD$1,234.57');
expect(html.hasClass('ng-format-negative')).toBeFalsy();
});
});
@ -46,13 +46,14 @@ describe('filter', function() {
it('should do basic filter', function() {
var context = {jqElement:jqLite('<span/>')};
var number = bind(context, filter.number);
expect(number(0, 0)).toEqual('0');
expect(number(0)).toEqual('0.00');
expect(number(-999)).toEqual('-999.00');
expect(number(1234.5678)).toEqual('1,234.57');
expect(number(-999)).toEqual('-999');
expect(number(123)).toEqual('123');
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("1234.5678")).toEqual('1,234.57');
expect(number("1234.5678")).toEqual('1,234.568');
expect(number(1/0)).toEqual("");
expect(number(1, 2)).toEqual("1.00");
expect(number(.1, 2)).toEqual("0.10");
@ -64,11 +65,17 @@ describe('filter', function() {
expect(number(.99, 2)).toEqual("0.99");
expect(number(.999, 3)).toEqual("0.999");
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, 1)).toEqual("1,234.6");
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 () {