Skip to content

Commit 31b59ef

Browse files
Di PengIgorMinar
authored andcommitted
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
1 parent 1725137 commit 31b59ef

File tree

2 files changed

+102
-44
lines changed

2 files changed

+102
-44
lines changed

src/filters.js

Lines changed: 86 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
* @function
3636
*
3737
* @description
38-
* Formats a number as a currency (ie $1,234.56).
38+
* Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
39+
* symbol for current locale is used.
3940
*
4041
* @param {number} amount Input to filter.
42+
* @param {string=} symbol Currency symbol or identifier to be displayed.
4143
* @returns {string} Formated number.
4244
*
4345
* @css ng-format-negative
@@ -47,24 +49,28 @@
4749
<doc:example>
4850
<doc:source>
4951
<input type="text" name="amount" value="1234.56"/> <br/>
50-
{{amount | currency}}
52+
default currency symbol ($): {{amount | currency}}<br/>
53+
custom currency identifier (USD$): {{amount | currency:"USD$"}}
5154
</doc:source>
5255
<doc:scenario>
5356
it('should init with 1234.56', function(){
5457
expect(binding('amount | currency')).toBe('$1,234.56');
58+
expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
5559
});
5660
it('should update', function(){
5761
input('amount').enter('-1234');
58-
expect(binding('amount | currency')).toBe('$-1,234.00');
62+
expect(binding('amount | currency')).toBe('($1,234.00)');
63+
expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
5964
expect(element('.doc-example-live .ng-binding').attr('className')).
6065
toMatch(/ng-format-negative/);
6166
});
6267
</doc:scenario>
6368
</doc:example>
6469
*/
65-
angularFilter.currency = function(amount){
70+
angularFilter.currency = function(amount, currencySymbol){
6671
this.$element.toggleClass('ng-format-negative', amount < 0);
67-
return '$' + angularFilter.number.apply(this, [amount, 2]);
72+
if (isUndefined(currencySymbol)) currencySymbol = NUMBER_FORMATS.CURRENCY_SYM;
73+
return formatNumber(amount, 2, 1).replace(/\u00A4/g, currencySymbol);
6874
};
6975

7076
/**
@@ -74,9 +80,9 @@ angularFilter.currency = function(amount){
7480
* @function
7581
*
7682
* @description
77-
* Formats a number as text.
83+
* Formats a number as text.
7884
*
79-
* If the input is not a number empty string is returned.
85+
* If the input is not a number empty string is returned.
8086
*
8187
* @param {number|string} number Number to format.
8288
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
@@ -92,59 +98,104 @@ angularFilter.currency = function(amount){
9298
</doc:source>
9399
<doc:scenario>
94100
it('should format numbers', function(){
95-
expect(binding('val | number')).toBe('1,234.57');
101+
expect(binding('val | number')).toBe('1,234.568');
96102
expect(binding('val | number:0')).toBe('1,235');
97103
expect(binding('-val | number:4')).toBe('-1,234.5679');
98104
});
99105
100106
it('should update', function(){
101107
input('val').enter('3374.333');
102-
expect(binding('val | number')).toBe('3,374.33');
108+
expect(binding('val | number')).toBe('3,374.333');
103109
expect(binding('val | number:0')).toBe('3,374');
104110
expect(binding('-val | number:4')).toBe('-3,374.3330');
105111
});
106112
</doc:scenario>
107113
</doc:example>
108114
*/
109-
angularFilter.number = function(number, fractionSize){
110-
if (isNaN(number) || !isFinite(number)) {
111-
return '';
112-
}
113-
fractionSize = isUndefined(fractionSize)? 2 : fractionSize;
114115

116+
// PATTERNS[0] is an array for Decimal Pattern, PATTERNS[1] is an array Currency Pattern
117+
// Following is the order in each pattern array:
118+
// 0: minInteger,
119+
// 1: minFraction,
120+
// 2: maxFraction,
121+
// 3: positivePrefix,
122+
// 4: positiveSuffix,
123+
// 5: negativePrefix,
124+
// 6: negativeSuffix,
125+
// 7: groupSize,
126+
// 8: lastGroupSize
127+
var NUMBER_FORMATS = {
128+
DECIMAL_SEP: '.',
129+
GROUP_SEP: ',',
130+
PATTERNS: [[1, 0, 3, '', '', '-', '', 3, 3],[1, 2, 2, '\u00A4', '', '(\u00A4', ')', 3, 3]],
131+
CURRENCY_SYM: '$'
132+
};
133+
var DECIMAL_SEP = '.';
134+
135+
angularFilter.number = function(number, fractionSize) {
136+
if (isNaN(number) || !isFinite(number)) return '';
137+
return formatNumber(number, fractionSize, 0);
138+
}
139+
140+
function formatNumber(number, fractionSize, type) {
115141
var isNegative = number < 0,
116-
pow = Math.pow(10, fractionSize),
117-
whole = '' + number,
142+
type = type || 0, // 0 is decimal pattern, 1 is currency pattern
143+
pattern = NUMBER_FORMATS.PATTERNS[type];
144+
145+
number = Math.abs(number);
146+
var numStr = number + '',
118147
formatedText = '',
119-
i, fraction;
148+
parts = [];
120149

121-
if (whole.indexOf('e') > -1) return whole;
150+
if (numStr.indexOf('e') !== -1) {
151+
var formatedText = numStr;
152+
} else {
153+
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
122154

123-
number = Math.round(number * pow) / pow;
124-
fraction = ('' + number).split('.');
125-
whole = fraction[0];
126-
fraction = fraction[1] || '';
127-
if (isNegative) {
128-
formatedText = '-';
129-
whole = whole.substring(1);
130-
}
155+
//determine fractionSize if it is not specified
156+
if (isUndefined(fractionSize)) {
157+
fractionSize = Math.min(Math.max(pattern[1], fractionLen), pattern[2]);
158+
}
131159

160+
var pow = Math.pow(10, fractionSize);
161+
number = Math.round(number * pow) / pow;
162+
var fraction = ('' + number).split(DECIMAL_SEP);
163+
var whole = fraction[0];
164+
fraction = fraction[1] || '';
165+
166+
var pos = 0,
167+
lgroup = pattern[8],
168+
group = pattern[7];
169+
170+
if (whole.length >= (lgroup + group)) {
171+
pos = whole.length - lgroup;
172+
for (var i = 0; i < pos; i++) {
173+
if ((pos - i)%group === 0 && i !== 0) {
174+
formatedText += NUMBER_FORMATS.GROUP_SEP;
175+
}
176+
formatedText += whole.charAt(i);
177+
}
178+
}
132179

133-
for (i = 0; i < whole.length; i++) {
134-
if ((whole.length - i)%3 === 0 && i !== 0) {
135-
formatedText += ',';
180+
for (i = pos; i < whole.length; i++) {
181+
if ((whole.length - i)%lgroup === 0 && i !== 0) {
182+
formatedText += NUMBER_FORMATS.GROUP_SEP;
183+
}
184+
formatedText += whole.charAt(i);
136185
}
137-
formatedText += whole.charAt(i);
138-
}
139-
if (fractionSize) {
186+
187+
// format fraction part.
140188
while(fraction.length < fractionSize) {
141189
fraction += '0';
142190
}
143-
formatedText += '.' + fraction.substring(0, fractionSize);
191+
if (fractionSize) formatedText += NUMBER_FORMATS.DECIMAL_SEP + fraction.substr(0, fractionSize);
144192
}
145-
return formatedText;
146-
};
147193

194+
parts.push(isNegative ? pattern[5] : pattern[3]);
195+
parts.push(formatedText);
196+
parts.push(isNegative ? pattern[6] : pattern[4]);
197+
return parts.join('');
198+
}
148199

149200
function padNumber(num, digits, trim) {
150201
var neg = '';

test/FiltersSpec.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ describe('filter', function() {
2828
});
2929

3030
describe('currency', function() {
31-
it('should do basic filter', function() {
31+
it('should do basic currency filtering', function() {
3232
var html = jqLite('<span/>');
3333
var context = {$element:html};
3434
var currency = bind(context, filter.currency);
3535

3636
expect(currency(0)).toEqual('$0.00');
3737
expect(html.hasClass('ng-format-negative')).toBeFalsy();
38-
expect(currency(-999)).toEqual('$-999.00');
38+
expect(currency(-999)).toEqual('($999.00)');
3939
expect(html.hasClass('ng-format-negative')).toBeTruthy();
40-
expect(currency(1234.5678)).toEqual('$1,234.57');
40+
expect(currency(1234.5678, "USD$")).toEqual('USD$1,234.57');
4141
expect(html.hasClass('ng-format-negative')).toBeFalsy();
4242
});
4343
});
@@ -46,13 +46,14 @@ describe('filter', function() {
4646
it('should do basic filter', function() {
4747
var context = {jqElement:jqLite('<span/>')};
4848
var number = bind(context, filter.number);
49-
5049
expect(number(0, 0)).toEqual('0');
51-
expect(number(0)).toEqual('0.00');
52-
expect(number(-999)).toEqual('-999.00');
53-
expect(number(1234.5678)).toEqual('1,234.57');
50+
expect(number(-999)).toEqual('-999');
51+
expect(number(123)).toEqual('123');
52+
expect(number(1234567)).toEqual('1,234,567');
53+
expect(number(1234)).toEqual('1,234');
54+
expect(number(1234.5678)).toEqual('1,234.568');
5455
expect(number(Number.NaN)).toEqual('');
55-
expect(number("1234.5678")).toEqual('1,234.57');
56+
expect(number("1234.5678")).toEqual('1,234.568');
5657
expect(number(1/0)).toEqual("");
5758
expect(number(1, 2)).toEqual("1.00");
5859
expect(number(.1, 2)).toEqual("0.10");
@@ -64,11 +65,17 @@ describe('filter', function() {
6465
expect(number(.99, 2)).toEqual("0.99");
6566
expect(number(.999, 3)).toEqual("0.999");
6667
expect(number(.9999, 3)).toEqual("1.000");
67-
expect(number(1e50, 0)).toEqual("1e+50");
6868
expect(number(1234.567, 0)).toEqual("1,235");
6969
expect(number(1234.567, 1)).toEqual("1,234.6");
7070
expect(number(1234.567, 2)).toEqual("1,234.57");
7171
});
72+
73+
it('should filter exponential numbers', function() {
74+
var context = {jqElement:jqLite('<span/>')};
75+
var number = bind(context, filter.number);
76+
expect(number(1e50, 0)).toEqual('1e+50');
77+
expect(number(-2e50, 2)).toEqual('-2e+50');
78+
});
7279
});
7380

7481
describe('json', function () {

0 commit comments

Comments
 (0)