Skip to content
This repository has been archived by the owner on Dec 8, 2022. It is now read-only.

Contrib > Added truncate and truncateAfter options to numeric pipe #1856

Merged
merged 7 commits into from
Jul 31, 2018
16 changes: 16 additions & 0 deletions src/demos/numeric/numeric-demo.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@
{{1234567 | skyNumeric}}
</sky-definition-list-value>
</p>
<p>
<sky-definition-list-label>
1234567 without truncation
</sky-definition-list-label>
<sky-definition-list-value>
{{1234567 | skyNumeric:{truncate: false} }}
</sky-definition-list-value>
</p>
<p>
<sky-definition-list-label>
15501 with truncation after 10000 and 1 digit
</sky-definition-list-label>
<sky-definition-list-value>
{{15501 | skyNumeric:{digits: 1, truncateAfter: 10000} }}
</sky-definition-list-value>
</p>
<p>
<sky-definition-list-label>
15.50 as US dollar currency with 2 digits:
Expand Down
2 changes: 2 additions & 0 deletions src/modules/numeric/numeric.options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ export class NumericOptions {
public digits = 1;
public format = 'number';
public iso = 'USD';
public truncate = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we want this to default to true? Won't that immediately truncate/"break" anyone currently using the skyNumeric filter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blackbaud-conorwright The “truncate” boolean is a new option, so we wouldn’t have to worry about a breaking change with that setting in particular.

Also, the pipe as it stands today truncates automatically; this would allow users to turn off the truncate ability altogether.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh alright. I know it's a new option and was worried that having a new option default to applying its effect would be a breaking change. Though if it was always truncating beforehand then this is correct. All good then 👍

public truncateAfter = 0;
}
62 changes: 62 additions & 0 deletions src/modules/numeric/numeric.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { TestBed } from '@angular/core/testing';
import { SkyNumericModule } from './numeric.module';
import { SkyNumericService } from './numeric.service';
import { CurrencyPipe, DecimalPipe } from '@angular/common';
import { SkyNumericPipe } from './numeric.pipe';

describe('Numeric pipe', () => {
let skyNumericPipe: SkyNumericPipe;
let skyNumeric = new SkyNumericService(
new CurrencyPipe('en-US'),
new DecimalPipe('en-US')
);

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
SkyNumericModule
],
providers: [
SkyNumericPipe,
{
provide: SkyNumericService,
useValue: skyNumeric
}
]
});
});

beforeEach(() => {
skyNumericPipe = TestBed.get(SkyNumericPipe);
});

it('should pass all options to the sky numeric service', () => {

let options: any = {
digits: 2,
format: 'currency',
iso: 'USD',
truncate: true,
truncateAfter: 10000
};
expect(skyNumericPipe.transform(10001.00, options)).toBe('$10K');
});

it(`doesn''t require options to be set`, () => {
expect(skyNumericPipe.transform(42.87, undefined)).toBe('42.9');
});

it(`doesn''t require option properties to be set`, () => {
let options: any = {
random: false
};
expect(skyNumericPipe.transform(42.87, options)).toBe('42.9');
});

it(`default digits to zero if truncate set to false`, () => {
let options: any = {
truncate: false
};
expect(skyNumericPipe.transform(42.87, options)).toBe('43');
});
});
37 changes: 23 additions & 14 deletions src/modules/numeric/numeric.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,32 @@ import { NumericOptions } from './numeric.options';

/*
* Shortens numbers to 1K, 1M, 1B, 1T and can format for currency
* All three arguments in the options object, Digits, format and ISO, are optional,
* defaulting to 1, number and USD respectively
* All five arguments in the options object, Digits, format, iso, truncate, truncateAfter are optional,
* defaulting to 1, number and USD, true, 0 respectively
* Usage:
* number_expression | skyNumeric[:numbericOptions]]]
*
* options is an object to be passed in with the following parameters:
* digits
* format
* iso
* truncate
* truncateAfter
* Example:
* {{ 1075 | skyNumeric:{digits: 1, format: 'currency', iso: 'USD'} }}
* formats to: $1.1K
* Example:
* {{ 2075000 | skyNumeric:{digits: 2} }}
* formats to: 2.08M
* Example:
* {{ 2075000 | skyNumeric:{truncate: false} }}
* formats to: 2,075,000
* Example:
* {{ 9500 | skyNumeric:{truncateAfter: 10000} }}
* formats to: 9,500
* Example:
* {{ 10001 | skyNumeric:{truncateAfter: 10000} }}
* formats to: 10K
* Note: Be sure you have a space between the curly bracket surrounding the options object
* and the two curly brackets closing the pipe or it will not work. Thanks angular pipes.
*/
Expand All @@ -31,19 +42,17 @@ export class SkyNumericPipe implements PipeTransform {
private readonly skyNumeric: SkyNumericService
) { }

public transform(value: number, optionsObject: any): string {
let options = new NumericOptions();
if (optionsObject) {
if (optionsObject.digits !== undefined) {
options.digits = optionsObject.digits;
}
if (optionsObject.format !== undefined) {
options.format = optionsObject.format;
}
if (optionsObject.iso !== undefined) {
options.iso = optionsObject.iso;
}
public transform(value: number, config: any): string {
const options = new NumericOptions();

// The default number of digits is `1`. When truncate is disabled, set digits
// to `0` to avoid the unnecessary addition of `.0` at the end of the formatted number.
if (config && config.truncate === false && config.digits === undefined) {
config.digits = 0;
}

Object.assign(options, config);

return this.skyNumeric.formatNumber(value, options);
}
}
25 changes: 20 additions & 5 deletions src/modules/numeric/numeric.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,19 @@ export class SkyNumericService {
value: number,
options: NumericOptions
): string {
if (isNaN(value)) {
return '';
}

const decimalPlaceRegExp = /\.0+$|(\.[0-9]*[1-9])0+$/;
const symbol: SkyNumericSymbol = this.symbolIndex.find((si) => {
// Checks both positive and negative of value to ensure
// negative numbers are shortened.
return (value >= si.value || -value >= si.value);
return options.truncate &&
(
(value >= options.truncateAfter && value >= si.value) ||
(-value >= options.truncateAfter && -value >= si.value)
);
});

let output: string;
Expand All @@ -70,6 +78,7 @@ export class SkyNumericService {

this.storeShortenSymbol(output);

let digits: string;
// Checks the string entered for format. Using toLowerCase to ignore case.
switch (options.format.toLowerCase()) {

Expand All @@ -81,7 +90,6 @@ export class SkyNumericService {
const isShortened = (value > this.symbolIndex[this.symbolIndex.length - 1].value);
const isDecimal = (value % 1 !== 0);

let digits: string;
if (!isShortened && isDecimal && options.digits >= 2) {
digits = `1.2-${options.digits}`;
} else {
Expand All @@ -101,15 +109,22 @@ export class SkyNumericService {
// it will be treated like a number.
default:
// Ensures localization of the number to ensure comma and
// decimal separators are correct.
// decimal separator
if (options.truncate) {
digits = `1.0-${options.digits}`;
} else {
digits = `1.${options.digits}-${options.digits}`;
}
output = this.decimalPipe.transform(
parseFloat(output),
`1.0-${options.digits}`
digits
);
break;
}

output = this.replaceShortenSymbol(output);
if (options.truncate) {
output = this.replaceShortenSymbol(output);
}

return output;
}
Expand Down
59 changes: 59 additions & 0 deletions src/modules/numeric/numeric.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ describe('Numeric service', () => {
expect(skyNumeric.formatNumber(value, options)).toBe('0');
});

it('formats undefined as blank', () => {
let value = undefined;
let options = new NumericOptions();
options.digits = 0;

expect(skyNumeric.formatNumber(value, options)).toBe('');
});

it('formats 100 with 0 digits as 100', () => {
const value = 100;
let options = new NumericOptions();
Expand All @@ -40,27 +48,59 @@ describe('Numeric service', () => {
expect(skyNumeric.formatNumber(value, options)).toBe('1K');
});

it('does not truncate 1000 with 2 digits as 1K when truncate is false', () => {
const value = 1000;
let options = new NumericOptions();
options.digits = 2;
options.truncate = false;
expect(skyNumeric.formatNumber(value, options)).toBe('1,000.00');
});

it('formats 1000000 with 0 digits as 1M', () => {
const value = 1000000;
let options = new NumericOptions();
options.digits = 0;
expect(skyNumeric.formatNumber(value, options)).toBe('1M');
});

it('does not truncate 1000000 with 2 digits as 1M when truncate is false', () => {
const value = 1000000;
let options = new NumericOptions();
options.digits = 2;
options.truncate = false;
expect(skyNumeric.formatNumber(value, options)).toBe('1,000,000.00');
});

it('formats 1000000000 with 0 digits as 1B', () => {
const value = 1000000000;
let options = new NumericOptions();
options.digits = 0;
expect(skyNumeric.formatNumber(value, options)).toBe('1B');
});

it('does not truncate 1000000000 with 2 digits as 1B when truncate is false', () => {
const value = 1000000000;
let options = new NumericOptions();
options.digits = 2;
options.truncate = false;
expect(skyNumeric.formatNumber(value, options)).toBe('1,000,000,000.00');
});

it('formats 1000000000000 with 0 digits as 1T', () => {
const value = 1000000000000;
let options = new NumericOptions();
options.digits = 0;
expect(skyNumeric.formatNumber(value, options)).toBe('1T');
});

it('does not truncate 1000000000000 with 2 digits as 1T when truncate is false', () => {
const value = 1000000000000;
let options = new NumericOptions();
options.digits = 2;
options.truncate = false;
expect(skyNumeric.formatNumber(value, options)).toBe('1,000,000,000,000.00');
});

it('formats 999000000 as 999M', () => {
const value = 999000000;
let options = new NumericOptions();
Expand Down Expand Up @@ -124,6 +164,25 @@ describe('Numeric service', () => {
options.format = 'currency';
expect(skyNumeric.formatNumber(value, options)).toBe('£15.50');
});

// Testing ability only after a certain value is specified
// using the truncateAfter configuration property
it('does not truncate 5000 to 5K when truncateAfter set to 10000', () => {
const value = 5000;
let options = new NumericOptions();
options.digits = 0;
options.truncateAfter = 10000;
expect(skyNumeric.formatNumber(value, options)).toBe('5,000');
});

it('formats 10001 to 10K when truncateAfter set to 10000', () => {
const value = 10001;
let options = new NumericOptions();
options.digits = 0;
options.truncateAfter = 10000;
expect(skyNumeric.formatNumber(value, options)).toBe('10K');
});

// Adjusting test to expect either format of a negative. MS browsers use system's Region
// setting for Currency formatting. For Negative currency, the windows default is parentheses
// around the number. All other browsers use a preceeding negative sign (-).
Expand Down