feat(pipes): add number (decimal, percent, currency) pipes
This commit is contained in:

committed by
Tobias Bosch

parent
b54e7214f0
commit
3143d188ae
@ -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
|
||||
});
|
||||
|
||||
/**
|
||||
|
132
modules/angular2/src/change_detection/pipes/number_pipe.ts
Normal file
132
modules/angular2/src/change_detection/pipes/number_pipe.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
42
modules/angular2/src/facade/intl.dart
Normal file
42
modules/angular2/src/facade/intl.dart
Normal file
@ -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);
|
||||
}
|
||||
}
|
73
modules/angular2/src/facade/intl.ts
Normal file
73
modules/angular2/src/facade/intl.ts
Normal file
@ -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;
|
||||
|
Reference in New Issue
Block a user