feat(pipes): add number (decimal, percent, currency) pipes
This commit is contained in:
parent
b54e7214f0
commit
3143d188ae
|
@ -12,4 +12,5 @@ export {ObservablePipe} from './src/change_detection/pipes/observable_pipe';
|
|||
export {JsonPipe} from './src/change_detection/pipes/json_pipe';
|
||||
export {IterableChanges} from './src/change_detection/pipes/iterable_changes';
|
||||
export {KeyValueChanges} from './src/change_detection/pipes/keyvalue_changes';
|
||||
export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/change_detection/pipes/number_pipe';
|
||||
export {LimitToPipe} from './src/change_detection/pipes/limit_to_pipe';
|
||||
|
|
|
@ -14,6 +14,7 @@ dependencies:
|
|||
code_transformers: '^0.2.8'
|
||||
dart_style: '^0.1.8'
|
||||
html: '^0.12.0'
|
||||
intl: '^0.12.4'
|
||||
logging: '>=0.9.0 <0.12.0'
|
||||
source_span: '^1.0.0'
|
||||
stack_trace: '^1.1.1'
|
||||
|
|
|
@ -11,6 +11,7 @@ import {UpperCaseFactory} from './pipes/uppercase_pipe';
|
|||
import {LowerCaseFactory} from './pipes/lowercase_pipe';
|
||||
import {JsonPipe} from './pipes/json_pipe';
|
||||
import {LimitToPipeFactory} from './pipes/limit_to_pipe';
|
||||
import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
|
||||
import {NullPipeFactory} from './pipes/null_pipe';
|
||||
import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
|
||||
import {Inject, Injectable, OpaqueToken, Optional} from 'angular2/di';
|
||||
|
@ -76,6 +77,30 @@ export const json: List<PipeFactory> =
|
|||
export const limitTo: List<PipeFactory> =
|
||||
CONST_EXPR([CONST_EXPR(new LimitToPipeFactory()), CONST_EXPR(new NullPipeFactory())]);
|
||||
|
||||
/**
|
||||
* Number number transform.
|
||||
*
|
||||
* @exportedAs angular2/pipes
|
||||
*/
|
||||
export const decimal: List<PipeFactory> =
|
||||
CONST_EXPR([CONST_EXPR(new DecimalPipe()), CONST_EXPR(new NullPipeFactory())]);
|
||||
|
||||
/**
|
||||
* Percent number transform.
|
||||
*
|
||||
* @exportedAs angular2/pipes
|
||||
*/
|
||||
export const percent: List<PipeFactory> =
|
||||
CONST_EXPR([CONST_EXPR(new PercentPipe()), CONST_EXPR(new NullPipeFactory())]);
|
||||
|
||||
/**
|
||||
* Currency number transform.
|
||||
*
|
||||
* @exportedAs angular2/pipes
|
||||
*/
|
||||
export const currency: List<PipeFactory> =
|
||||
CONST_EXPR([CONST_EXPR(new CurrencyPipe()), CONST_EXPR(new NullPipeFactory())]);
|
||||
|
||||
export const defaultPipes = CONST_EXPR({
|
||||
"iterableDiff": iterableDiff,
|
||||
"keyValDiff": keyValDiff,
|
||||
|
@ -83,7 +108,10 @@ export const defaultPipes = CONST_EXPR({
|
|||
"uppercase": uppercase,
|
||||
"lowercase": lowercase,
|
||||
"json": json,
|
||||
"limitTo": limitTo
|
||||
"limitTo": limitTo,
|
||||
"number": decimal,
|
||||
"percent": percent,
|
||||
"currency": currency
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
import {
|
||||
isNumber,
|
||||
isPresent,
|
||||
isBlank,
|
||||
StringWrapper,
|
||||
NumberWrapper,
|
||||
RegExpWrapper,
|
||||
BaseException,
|
||||
CONST,
|
||||
FunctionWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Pipe, BasePipe, PipeFactory} from './pipe';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
|
||||
var defaultLocale: string = 'en-US';
|
||||
var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
|
||||
|
||||
@CONST()
|
||||
export class NumberPipe extends BasePipe implements PipeFactory {
|
||||
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
|
||||
currencyAsSymbol: boolean = false): string {
|
||||
var minInt = 1, minFraction = 0, maxFraction = 3;
|
||||
if (isPresent(digits)) {
|
||||
var parts = RegExpWrapper.firstMatch(_re, digits);
|
||||
if (isBlank(parts)) {
|
||||
throw new BaseException(`${digits} is not a valid digit info for number pipes`);
|
||||
}
|
||||
if (isPresent(parts[1])) { // min integer digits
|
||||
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
||||
}
|
||||
if (isPresent(parts[3])) { // min fraction digits
|
||||
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||
}
|
||||
if (isPresent(parts[5])) { // max fraction digits
|
||||
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||
}
|
||||
}
|
||||
return NumberFormatter.format(value, defaultLocale, style, {
|
||||
minimumIntegerDigits: minInt,
|
||||
minimumFractionDigits: minFraction,
|
||||
maximumFractionDigits: maxFraction,
|
||||
currency: currency,
|
||||
currencyAsSymbol: currencyAsSymbol
|
||||
});
|
||||
}
|
||||
|
||||
supports(obj): boolean { return isNumber(obj); }
|
||||
|
||||
create(cdRef: ChangeDetectorRef): Pipe { return this }
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number as local text. i.e. group sizing and seperator and other locale-specific
|
||||
* configurations are based on the active locale.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* expression | number[:digitInfo]
|
||||
*
|
||||
* where `expression` is a number and `digitInfo` has the following format:
|
||||
*
|
||||
* {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
|
||||
*
|
||||
* - minIntegerDigits is the minimum number of integer digits to use. Defaults to 1.
|
||||
* - minFractionDigits is the minimum number of digits after fraction. Defaults to 0.
|
||||
* - maxFractionDigits is the maximum number of digits after fraction. Defaults to 3.
|
||||
*
|
||||
* For more information on the acceptable range for each of these numbers and other
|
||||
* details see your native internationalization library.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* {{ 123 | number }} // output is 123
|
||||
* {{ 123.1 | number: '.2-3' }} // output is 123.10
|
||||
* {{ 1 | number: '2.2' }} // output is 01.00
|
||||
*
|
||||
* @exportedAs angular2/pipes
|
||||
*/
|
||||
@CONST()
|
||||
export class DecimalPipe extends NumberPipe {
|
||||
transform(value, args: any[]): string {
|
||||
var digits: string = ListWrapper.first(args);
|
||||
return NumberPipe._format(value, NumberFormatStyle.DECIMAL, digits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number as local percent.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* expression | percent[:digitInfo]
|
||||
*
|
||||
* For more information about `digitInfo` see {@link DecimalPipe}
|
||||
*
|
||||
* @exportedAs angular2/pipes
|
||||
*/
|
||||
@CONST()
|
||||
export class PercentPipe extends NumberPipe {
|
||||
transform(value, args: any[]): string {
|
||||
var digits: string = ListWrapper.first(args);
|
||||
return NumberPipe._format(value, NumberFormatStyle.PERCENT, digits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number as local currency.
|
||||
*
|
||||
* # Usage
|
||||
*
|
||||
* expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]
|
||||
*
|
||||
* where `currencyCode` is the ISO 4217 currency code, such as "USD" for the US dollar and
|
||||
* "EUR" for the euro. `symbolDisplay` is a boolean indicating whether to use the currency
|
||||
* symbol (e.g. $) or the currency code (e.g. USD) in the output. The default for this value
|
||||
* is `false`.
|
||||
* For more information about `digitInfo` see {@link DecimalPipe}
|
||||
*
|
||||
* @exportedAs angular2/pipes
|
||||
*/
|
||||
@CONST()
|
||||
export class CurrencyPipe extends NumberPipe {
|
||||
transform(value, args: any[]): string {
|
||||
var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD';
|
||||
var symbolDisplay: boolean = isPresent(args) && args.length > 1 ? args[1] : false;
|
||||
var digits: string = isPresent(args) && args.length > 2 ? args[2] : null;
|
||||
return NumberPipe._format(value, NumberFormatStyle.CURRENCY, digits, currencyCode,
|
||||
symbolDisplay);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
library facade.intl;
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
String _normalizeLocale(String locale) => locale.replaceAll('-', '_');
|
||||
|
||||
enum NumberFormatStyle {
|
||||
DECIMAL,
|
||||
PERCENT,
|
||||
CURRENCY
|
||||
}
|
||||
|
||||
class NumberFormatter {
|
||||
static String format(num number, String locale, NumberFormatStyle style,
|
||||
{int minimumIntegerDigits: 1,
|
||||
int minimumFractionDigits: 0,
|
||||
int maximumFractionDigits: 3,
|
||||
String currency,
|
||||
bool currencyAsSymbol: false}) {
|
||||
locale = _normalizeLocale(locale);
|
||||
NumberFormat formatter;
|
||||
switch (style) {
|
||||
case NumberFormatStyle.DECIMAL:
|
||||
formatter = new NumberFormat.decimalPattern(locale);
|
||||
break;
|
||||
case NumberFormatStyle.PERCENT:
|
||||
formatter = new NumberFormat.percentPattern(locale);
|
||||
break;
|
||||
case NumberFormatStyle.CURRENCY:
|
||||
if (currencyAsSymbol) {
|
||||
// See https://github.com/dart-lang/intl/issues/59.
|
||||
throw new Exception('Displaying currency as symbol is not supported.');
|
||||
}
|
||||
formatter = new NumberFormat.currencyPattern(locale, currency);
|
||||
break;
|
||||
}
|
||||
formatter.minimumIntegerDigits = minimumIntegerDigits;
|
||||
formatter.minimumFractionDigits = minimumFractionDigits;
|
||||
formatter.maximumFractionDigits = maximumFractionDigits;
|
||||
return formatter.format(number);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
|
||||
// Modified version of internal Typescript intl.d.ts.
|
||||
// TODO(piloopin): remove when https://github.com/Microsoft/TypeScript/issues/3521 is shipped.
|
||||
declare module Intl {
|
||||
interface NumberFormatOptions {
|
||||
localeMatcher?: string;
|
||||
style?: string;
|
||||
currency?: string;
|
||||
currencyDisplay?: string;
|
||||
useGrouping?: boolean;
|
||||
}
|
||||
|
||||
interface NumberFormat {
|
||||
format(value: number): string;
|
||||
}
|
||||
|
||||
var NumberFormat: {
|
||||
new (locale?: string, options?: NumberFormatOptions): NumberFormat;
|
||||
}
|
||||
|
||||
interface DateTimeFormatOptions {
|
||||
localeMatcher?: string;
|
||||
weekday?: string;
|
||||
era?: string;
|
||||
year?: string;
|
||||
month?: string;
|
||||
day?: string;
|
||||
hour?: string;
|
||||
minute?: string;
|
||||
second?: string;
|
||||
timeZoneName?: string;
|
||||
formatMatcher?: string;
|
||||
hour12?: boolean;
|
||||
}
|
||||
|
||||
interface DateTimeFormat {
|
||||
format(date?: Date | number): string;
|
||||
}
|
||||
|
||||
var DateTimeFormat: {
|
||||
new (locale?: string, options?: DateTimeFormatOptions): DateTimeFormat;
|
||||
}
|
||||
}
|
||||
|
||||
export enum NumberFormatStyle {
|
||||
DECIMAL,
|
||||
PERCENT,
|
||||
CURRENCY
|
||||
}
|
||||
|
||||
export class NumberFormatter {
|
||||
static format(number: number, locale: string, style: NumberFormatStyle,
|
||||
{minimumIntegerDigits = 1, minimumFractionDigits = 0, maximumFractionDigits = 3,
|
||||
currency, currencyAsSymbol = false}: {
|
||||
minimumIntegerDigits?: int,
|
||||
minimumFractionDigits?: int,
|
||||
maximumFractionDigits?: int,
|
||||
currency?: string,
|
||||
currencyAsSymbol?: boolean
|
||||
} = {}): string {
|
||||
var intlOptions: Intl.NumberFormatOptions = {
|
||||
minimumIntegerDigits: minimumIntegerDigits,
|
||||
minimumFractionDigits: minimumFractionDigits,
|
||||
maximumFractionDigits: maximumFractionDigits
|
||||
};
|
||||
intlOptions.style = NumberFormatStyle[style].toLowerCase();
|
||||
if (style == NumberFormatStyle.CURRENCY) {
|
||||
intlOptions.currency = currency;
|
||||
intlOptions.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
|
||||
}
|
||||
return new Intl.NumberFormat(locale, intlOptions).format(number);
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ bool isType(obj) => obj is Type;
|
|||
bool isStringMap(obj) => obj is Map;
|
||||
bool isArray(obj) => obj is List;
|
||||
bool isPromise(obj) => obj is Future;
|
||||
bool isNumber(obj) => obj is num;
|
||||
|
||||
String stringify(obj) => obj.toString();
|
||||
|
||||
|
|
|
@ -89,6 +89,10 @@ export function isArray(obj): boolean {
|
|||
return Array.isArray(obj);
|
||||
}
|
||||
|
||||
export function isNumber(obj): boolean {
|
||||
return typeof obj === 'number';
|
||||
}
|
||||
|
||||
export function stringify(token): string {
|
||||
if (typeof token === 'string') {
|
||||
return token;
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
|
||||
|
||||
import {
|
||||
DecimalPipe,
|
||||
PercentPipe,
|
||||
CurrencyPipe
|
||||
} from 'angular2/src/change_detection/pipes/number_pipe';
|
||||
|
||||
export function main() {
|
||||
describe("DecimalPipe", () => {
|
||||
var pipe;
|
||||
|
||||
beforeEach(() => { pipe = new DecimalPipe(); });
|
||||
|
||||
describe("supports", () => {
|
||||
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
|
||||
|
||||
it("should not support other objects", () => {
|
||||
expect(pipe.supports(new Object())).toBe(false);
|
||||
expect(pipe.supports('str')).toBe(false);
|
||||
expect(pipe.supports(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transform", () => {
|
||||
it('should return correct value', () => {
|
||||
expect(pipe.transform(12345, [])).toEqual('12,345');
|
||||
expect(pipe.transform(123, ['.2'])).toEqual('123.00');
|
||||
expect(pipe.transform(1, ['3.'])).toEqual('001');
|
||||
expect(pipe.transform(1.1, ['3.4-5'])).toEqual('001.1000');
|
||||
expect(pipe.transform(1.123456, ['3.4-5'])).toEqual('001.12346');
|
||||
expect(pipe.transform(1.1234, [])).toEqual('1.123');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("PercentPipe", () => {
|
||||
var pipe;
|
||||
|
||||
beforeEach(() => { pipe = new PercentPipe(); });
|
||||
|
||||
describe("supports", () => {
|
||||
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
|
||||
|
||||
it("should not support other objects", () => {
|
||||
expect(pipe.supports(new Object())).toBe(false);
|
||||
expect(pipe.supports('str')).toBe(false);
|
||||
expect(pipe.supports(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transform", () => {
|
||||
it('should return correct value', () => {
|
||||
expect(pipe.transform(1.23, [])).toEqual('123%');
|
||||
expect(pipe.transform(1.2, ['.2'])).toEqual('120.00%');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("CurrencyPipe", () => {
|
||||
var pipe;
|
||||
|
||||
beforeEach(() => { pipe = new CurrencyPipe(); });
|
||||
|
||||
describe("supports", () => {
|
||||
it("should support numbers", () => { expect(pipe.supports(123.0)).toBe(true); });
|
||||
|
||||
it("should not support other objects", () => {
|
||||
expect(pipe.supports(new Object())).toBe(false);
|
||||
expect(pipe.supports('str')).toBe(false);
|
||||
expect(pipe.supports(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transform", () => {
|
||||
it('should return correct value', () => {
|
||||
expect(pipe.transform(123, [])).toEqual('USD123');
|
||||
expect(pipe.transform(12, ['EUR', false, '.2'])).toEqual('EUR12.00');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -7,6 +7,8 @@ dependencies:
|
|||
dev_dependencies:
|
||||
angular2:
|
||||
path: ../angular2
|
||||
dependency_overrides:
|
||||
intl: '^0.12.4' # angular depends on an older version of intl.
|
||||
transformers:
|
||||
- angular:
|
||||
$exclude: "web/e2e_test"
|
||||
|
|
|
@ -3,5 +3,6 @@ environment:
|
|||
sdk: '>=1.9.0 <2.0.0'
|
||||
dev_dependencies:
|
||||
guinness: '^0.1.17'
|
||||
intl: '^0.12.4'
|
||||
unittest: '^0.11.5+4'
|
||||
quiver: '^0.21.4'
|
||||
|
|
Loading…
Reference in New Issue