Skip to content

Commit 3143d18

Browse files
piloopintbosch
authored andcommitted
feat(pipes): add number (decimal, percent, currency) pipes
1 parent b54e721 commit 3143d18

File tree

11 files changed

+368
-1
lines changed

11 files changed

+368
-1
lines changed

modules/angular2/pipes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export {ObservablePipe} from './src/change_detection/pipes/observable_pipe';
1212
export {JsonPipe} from './src/change_detection/pipes/json_pipe';
1313
export {IterableChanges} from './src/change_detection/pipes/iterable_changes';
1414
export {KeyValueChanges} from './src/change_detection/pipes/keyvalue_changes';
15+
export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/change_detection/pipes/number_pipe';
1516
export {LimitToPipe} from './src/change_detection/pipes/limit_to_pipe';

modules/angular2/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies:
1414
code_transformers: '^0.2.8'
1515
dart_style: '^0.1.8'
1616
html: '^0.12.0'
17+
intl: '^0.12.4'
1718
logging: '>=0.9.0 <0.12.0'
1819
source_span: '^1.0.0'
1920
stack_trace: '^1.1.1'

modules/angular2/src/change_detection/change_detection.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {UpperCaseFactory} from './pipes/uppercase_pipe';
1111
import {LowerCaseFactory} from './pipes/lowercase_pipe';
1212
import {JsonPipe} from './pipes/json_pipe';
1313
import {LimitToPipeFactory} from './pipes/limit_to_pipe';
14+
import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
1415
import {NullPipeFactory} from './pipes/null_pipe';
1516
import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
1617
import {Inject, Injectable, OpaqueToken, Optional} from 'angular2/di';
@@ -76,14 +77,41 @@ export const json: List<PipeFactory> =
7677
export const limitTo: List<PipeFactory> =
7778
CONST_EXPR([CONST_EXPR(new LimitToPipeFactory()), CONST_EXPR(new NullPipeFactory())]);
7879

80+
/**
81+
* Number number transform.
82+
*
83+
* @exportedAs angular2/pipes
84+
*/
85+
export const decimal: List<PipeFactory> =
86+
CONST_EXPR([CONST_EXPR(new DecimalPipe()), CONST_EXPR(new NullPipeFactory())]);
87+
88+
/**
89+
* Percent number transform.
90+
*
91+
* @exportedAs angular2/pipes
92+
*/
93+
export const percent: List<PipeFactory> =
94+
CONST_EXPR([CONST_EXPR(new PercentPipe()), CONST_EXPR(new NullPipeFactory())]);
95+
96+
/**
97+
* Currency number transform.
98+
*
99+
* @exportedAs angular2/pipes
100+
*/
101+
export const currency: List<PipeFactory> =
102+
CONST_EXPR([CONST_EXPR(new CurrencyPipe()), CONST_EXPR(new NullPipeFactory())]);
103+
79104
export const defaultPipes = CONST_EXPR({
80105
"iterableDiff": iterableDiff,
81106
"keyValDiff": keyValDiff,
82107
"async": async,
83108
"uppercase": uppercase,
84109
"lowercase": lowercase,
85110
"json": json,
86-
"limitTo": limitTo
111+
"limitTo": limitTo,
112+
"number": decimal,
113+
"percent": percent,
114+
"currency": currency
87115
});
88116

89117
/**
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import {
2+
isNumber,
3+
isPresent,
4+
isBlank,
5+
StringWrapper,
6+
NumberWrapper,
7+
RegExpWrapper,
8+
BaseException,
9+
CONST,
10+
FunctionWrapper
11+
} from 'angular2/src/facade/lang';
12+
import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl';
13+
import {ListWrapper} from 'angular2/src/facade/collection';
14+
import {Pipe, BasePipe, PipeFactory} from './pipe';
15+
import {ChangeDetectorRef} from '../change_detector_ref';
16+
17+
var defaultLocale: string = 'en-US';
18+
var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
19+
20+
@CONST()
21+
export class NumberPipe extends BasePipe implements PipeFactory {
22+
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
23+
currencyAsSymbol: boolean = false): string {
24+
var minInt = 1, minFraction = 0, maxFraction = 3;
25+
if (isPresent(digits)) {
26+
var parts = RegExpWrapper.firstMatch(_re, digits);
27+
if (isBlank(parts)) {
28+
throw new BaseException(`${digits} is not a valid digit info for number pipes`);
29+
}
30+
if (isPresent(parts[1])) { // min integer digits
31+
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
32+
}
33+
if (isPresent(parts[3])) { // min fraction digits
34+
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
35+
}
36+
if (isPresent(parts[5])) { // max fraction digits
37+
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
38+
}
39+
}
40+
return NumberFormatter.format(value, defaultLocale, style, {
41+
minimumIntegerDigits: minInt,
42+
minimumFractionDigits: minFraction,
43+
maximumFractionDigits: maxFraction,
44+
currency: currency,
45+
currencyAsSymbol: currencyAsSymbol
46+
});
47+
}
48+
49+
supports(obj): boolean { return isNumber(obj); }
50+
51+
create(cdRef: ChangeDetectorRef): Pipe { return this }
52+
}
53+
54+
/**
55+
* Formats a number as local text. i.e. group sizing and seperator and other locale-specific
56+
* configurations are based on the active locale.
57+
*
58+
* # Usage
59+
*
60+
* expression | number[:digitInfo]
61+
*
62+
* where `expression` is a number and `digitInfo` has the following format:
63+
*
64+
* {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
65+
*
66+
* - minIntegerDigits is the minimum number of integer digits to use. Defaults to 1.
67+
* - minFractionDigits is the minimum number of digits after fraction. Defaults to 0.
68+
* - maxFractionDigits is the maximum number of digits after fraction. Defaults to 3.
69+
*
70+
* For more information on the acceptable range for each of these numbers and other
71+
* details see your native internationalization library.
72+
*
73+
* # Examples
74+
*
75+
* {{ 123 | number }} // output is 123
76+
* {{ 123.1 | number: '.2-3' }} // output is 123.10
77+
* {{ 1 | number: '2.2' }} // output is 01.00
78+
*
79+
* @exportedAs angular2/pipes
80+
*/
81+
@CONST()
82+
export class DecimalPipe extends NumberPipe {
83+
transform(value, args: any[]): string {
84+
var digits: string = ListWrapper.first(args);
85+
return NumberPipe._format(value, NumberFormatStyle.DECIMAL, digits);
86+
}
87+
}
88+
89+
/**
90+
* Formats a number as local percent.
91+
*
92+
* # Usage
93+
*
94+
* expression | percent[:digitInfo]
95+
*
96+
* For more information about `digitInfo` see {@link DecimalPipe}
97+
*
98+
* @exportedAs angular2/pipes
99+
*/
100+
@CONST()
101+
export class PercentPipe extends NumberPipe {
102+
transform(value, args: any[]): string {
103+
var digits: string = ListWrapper.first(args);
104+
return NumberPipe._format(value, NumberFormatStyle.PERCENT, digits);
105+
}
106+
}
107+
108+
/**
109+
* Formats a number as local currency.
110+
*
111+
* # Usage
112+
*
113+
* expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]
114+
*
115+
* where `currencyCode` is the ISO 4217 currency code, such as "USD" for the US dollar and
116+
* "EUR" for the euro. `symbolDisplay` is a boolean indicating whether to use the currency
117+
* symbol (e.g. $) or the currency code (e.g. USD) in the output. The default for this value
118+
* is `false`.
119+
* For more information about `digitInfo` see {@link DecimalPipe}
120+
*
121+
* @exportedAs angular2/pipes
122+
*/
123+
@CONST()
124+
export class CurrencyPipe extends NumberPipe {
125+
transform(value, args: any[]): string {
126+
var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD';
127+
var symbolDisplay: boolean = isPresent(args) && args.length > 1 ? args[1] : false;
128+
var digits: string = isPresent(args) && args.length > 2 ? args[2] : null;
129+
return NumberPipe._format(value, NumberFormatStyle.CURRENCY, digits, currencyCode,
130+
symbolDisplay);
131+
}
132+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
library facade.intl;
2+
3+
import 'package:intl/intl.dart';
4+
5+
String _normalizeLocale(String locale) => locale.replaceAll('-', '_');
6+
7+
enum NumberFormatStyle {
8+
DECIMAL,
9+
PERCENT,
10+
CURRENCY
11+
}
12+
13+
class NumberFormatter {
14+
static String format(num number, String locale, NumberFormatStyle style,
15+
{int minimumIntegerDigits: 1,
16+
int minimumFractionDigits: 0,
17+
int maximumFractionDigits: 3,
18+
String currency,
19+
bool currencyAsSymbol: false}) {
20+
locale = _normalizeLocale(locale);
21+
NumberFormat formatter;
22+
switch (style) {
23+
case NumberFormatStyle.DECIMAL:
24+
formatter = new NumberFormat.decimalPattern(locale);
25+
break;
26+
case NumberFormatStyle.PERCENT:
27+
formatter = new NumberFormat.percentPattern(locale);
28+
break;
29+
case NumberFormatStyle.CURRENCY:
30+
if (currencyAsSymbol) {
31+
// See https://github.com/dart-lang/intl/issues/59.
32+
throw new Exception('Displaying currency as symbol is not supported.');
33+
}
34+
formatter = new NumberFormat.currencyPattern(locale, currency);
35+
break;
36+
}
37+
formatter.minimumIntegerDigits = minimumIntegerDigits;
38+
formatter.minimumFractionDigits = minimumFractionDigits;
39+
formatter.maximumFractionDigits = maximumFractionDigits;
40+
return formatter.format(number);
41+
}
42+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
2+
// Modified version of internal Typescript intl.d.ts.
3+
// TODO(piloopin): remove when https://github.com/Microsoft/TypeScript/issues/3521 is shipped.
4+
declare module Intl {
5+
interface NumberFormatOptions {
6+
localeMatcher?: string;
7+
style?: string;
8+
currency?: string;
9+
currencyDisplay?: string;
10+
useGrouping?: boolean;
11+
}
12+
13+
interface NumberFormat {
14+
format(value: number): string;
15+
}
16+
17+
var NumberFormat: {
18+
new (locale?: string, options?: NumberFormatOptions): NumberFormat;
19+
}
20+
21+
interface DateTimeFormatOptions {
22+
localeMatcher?: string;
23+
weekday?: string;
24+
era?: string;
25+
year?: string;
26+
month?: string;
27+
day?: string;
28+
hour?: string;
29+
minute?: string;
30+
second?: string;
31+
timeZoneName?: string;
32+
formatMatcher?: string;
33+
hour12?: boolean;
34+
}
35+
36+
interface DateTimeFormat {
37+
format(date?: Date | number): string;
38+
}
39+
40+
var DateTimeFormat: {
41+
new (locale?: string, options?: DateTimeFormatOptions): DateTimeFormat;
42+
}
43+
}
44+
45+
export enum NumberFormatStyle {
46+
DECIMAL,
47+
PERCENT,
48+
CURRENCY
49+
}
50+
51+
export class NumberFormatter {
52+
static format(number: number, locale: string, style: NumberFormatStyle,
53+
{minimumIntegerDigits = 1, minimumFractionDigits = 0, maximumFractionDigits = 3,
54+
currency, currencyAsSymbol = false}: {
55+
minimumIntegerDigits?: int,
56+
minimumFractionDigits?: int,
57+
maximumFractionDigits?: int,
58+
currency?: string,
59+
currencyAsSymbol?: boolean
60+
} = {}): string {
61+
var intlOptions: Intl.NumberFormatOptions = {
62+
minimumIntegerDigits: minimumIntegerDigits,
63+
minimumFractionDigits: minimumFractionDigits,
64+
maximumFractionDigits: maximumFractionDigits
65+
};
66+
intlOptions.style = NumberFormatStyle[style].toLowerCase();
67+
if (style == NumberFormatStyle.CURRENCY) {
68+
intlOptions.currency = currency;
69+
intlOptions.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
70+
}
71+
return new Intl.NumberFormat(locale, intlOptions).format(number);
72+
}
73+
}

modules/angular2/src/facade/lang.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ bool isType(obj) => obj is Type;
3232
bool isStringMap(obj) => obj is Map;
3333
bool isArray(obj) => obj is List;
3434
bool isPromise(obj) => obj is Future;
35+
bool isNumber(obj) => obj is num;
3536

3637
String stringify(obj) => obj.toString();
3738

modules/angular2/src/facade/lang.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export function isArray(obj): boolean {
8989
return Array.isArray(obj);
9090
}
9191

92+
export function isNumber(obj): boolean {
93+
return typeof obj === 'number';
94+
}
95+
9296
export function stringify(token): string {
9397
if (typeof token === 'string') {
9498
return token;

0 commit comments

Comments
 (0)