Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <gerwitzm@lovullo.com>2016-11-30 11:03:33 -0500
committerMike Gerwitz <gerwitzm@lovullo.com>2016-12-01 08:43:46 -0500
commit5947e7646e2a0955a4ab799db69bdc909d8d0ea3 (patch)
treefd1555d534622d721cc1bd71585e3d7596f93f52
parent6db99c8632e7e61f6e96404344f2d48b518e8878 (diff)
downloadliza-5947e7646e2a0955a4ab799db69bdc909d8d0ea3.tar.gz
liza-5947e7646e2a0955a4ab799db69bdc909d8d0ea3.tar.bz2
liza-5947e7646e2a0955a4ab799db69bdc909d8d0ea3.zip
Add scale to Number formatter
* src/validate/formatter/Number.js (__mixin): Add mixin ctor. (parse): Handle scale. (styleNumber): Handle scale. (scale, split): Add methods. * test/validate/formatter/NumberTest.js: Modified accordingly.
-rw-r--r--src/validate/formatter/Number.js134
-rw-r--r--test/validate/formatter/NumberTest.js43
2 files changed, 174 insertions, 3 deletions
diff --git a/src/validate/formatter/Number.js b/src/validate/formatter/Number.js
index 260cd1a..07afc0d 100644
--- a/src/validate/formatter/Number.js
+++ b/src/validate/formatter/Number.js
@@ -1,4 +1,5 @@
/**
+ * @license
* Number formatter
*
* Copyright (C) 2016 LoVullo Associates, Inc.
@@ -25,12 +26,84 @@ var Trait = require( 'easejs' ).Trait,
/**
* Formats numbers in en_US locale
+ *
+ * Only whole numbers are permitted by default unless the mixin is
+ * initialized with a scale, where the scale represents the number of
+ * digits of the significand. If the scale is positive, the
+ * significand will be padded with zeroes to meet the scale; if
+ * negative, trailing zeroes will be removed. The significand will be
+ * truncated (not rounded) if it exceeds the scale:
+ *
+ * @example
+ * EchoFormatter.use( Number ).parse( '00003' ) // => 3
+ * EchoFormatter.use( Number( -6 ) ).parse( '3.14159' ) // => 3.14159
+ * EchoFormatter.use( Number( 6 ) ).parse( '3.14159' ) // => 3.141590
+ * EchoFormatter.use( Number( 2 ) ).parse( '3.14159' ) // => 3.14
+ *
+ * Leading zeroes are stripped.
*/
module.exports = Trait( 'Number' )
.implement( ValidatorFormatter )
.extend(
{
/**
+ * Number of digits after the decimal point
+ *
+ * This value should never be negative.
+ *
+ * @type {number}
+ */
+ 'private _scale': 0,
+
+ /**
+ * Pre-computed zero-padding of scale
+ *
+ * This conveniently allows prefixing this padding with a number
+ * and then truncating to scale.
+ *
+ * @type {string}
+ */
+ 'private _scalestr': '',
+
+
+ /**
+ * Initialize optional scale
+ *
+ * The scale SCALE is an optional value that specifies the number
+ * of digits after the decimal point to display. Note that
+ * trailing zeros are _not_ removed, making this ideal for
+ * i.e. currency.
+ *
+ * The "scale" terminology comes from the Unix bc tool. If
+ * positive, the significand will be padded with zeros to meet the
+ * scale. If negative, no padding will take place.
+ *
+ * If the significand has more digits than permitted by SCALE, it
+ * is truncated.
+ *
+ * @param {number} scale number of digits after decimal point
+ */
+ __mixin: function( scale )
+ {
+ this._scale = Math.abs( scale ) || 0;
+ this._scalestr = this._padScale( +scale );
+ },
+
+
+ /**
+ * Create scale padding for significand
+ *
+ * @return {string} string with SCALE zeroes
+ */
+ 'private _padScale': function( scale )
+ {
+ return ( scale > 0 )
+ ? ( new Array( this._scale + 1 ) ).join( '0' )
+ : '';
+ },
+
+
+ /**
* Parse item as a number
*
* @param {string} data data to parse
@@ -39,7 +112,10 @@ module.exports = Trait( 'Number' )
*/
'virtual abstract override public parse': function( data )
{
- return this.__super( data ).replace( /[ ,]/g, '' );
+ var cleaned = this.__super( data ).replace( /[ ,]/g, '' ),
+ parts = this.split( cleaned );
+
+ return parts.integer + this.scale( parts.significand, this._scale );
},
@@ -65,7 +141,9 @@ module.exports = Trait( 'Number' )
*/
'virtual protected styleNumber': function( number )
{
- var i = number.length,
+ var parts = this.split( number );
+
+ var i = parts.integer.length,
ret = [],
chunk = '';
@@ -83,6 +161,56 @@ module.exports = Trait( 'Number' )
ret.unshift( chunk );
} while ( i > 0 );
- return ret.join( ',' );
+ return ret.join( ',' ) +
+ this.scale( parts.significand, this._scale );
+ },
+
+
+ /**
+ * Parse significand and return scaled value
+ *
+ * If the result is non-empty, then the result will be prefixed
+ * with a decimal point.
+ *
+ * Truncation is determined by whether the initial scale was
+ * negative. If so, this method will lack a zero padding string
+ * and return a result without trailing zeroes.
+ *
+ * @param {string} significand value after decimal point
+ * @param {number} scale positive scale
+ *
+ * @return {string} scaled significand with decimal point as needed
+ */
+ 'virtual protected scale': function( significand, scale )
+ {
+ if ( scale <= 0 )
+ {
+ return '';
+ }
+
+ // easy cheat: use the pre-filled scale and truncate
+ var result = ( significand + this._scalestr ).substr( 0, scale );
+
+ return ( result )
+ ? '.' + result
+ : '';
+ },
+
+
+ /**
+ * Split integer from significand in NUMBER
+ *
+ * @param {string} number number to split
+ *
+ * @return {Object.<integer,decimal>} integer and significand
+ */
+ 'virtual protected split': function( number )
+ {
+ var parts = number.split( '.' );
+
+ return {
+ integer: parts[ 0 ].replace( /^0+/, '' ) || '0',
+ significand: ( parts[ 1 ] || '' ).replace( /0+$/, '' ),
+ }
}
} ); \ No newline at end of file
diff --git a/test/validate/formatter/NumberTest.js b/test/validate/formatter/NumberTest.js
index 74d2595..7945b70 100644
--- a/test/validate/formatter/NumberTest.js
+++ b/test/validate/formatter/NumberTest.js
@@ -27,8 +27,10 @@ var liza = require( '../../../' ),
describe( 'validate.formatter.Number', function()
{
+ // default case, no decimal places
common.testValidate( EchoFormatter.use( Sut )(), {
"1": [ "1", "1" ],
+ "001": [ "1", "1" ],
"123": [ "123", "123" ],
"12345": [ "12345", "12,345" ],
"12,345": [ "12345", "12,345" ],
@@ -36,6 +38,47 @@ describe( 'validate.formatter.Number', function()
"12,345,": [ "12345", "12,345" ],
" 12,345 ,": [ "12345", "12,345" ],
" 1, ,": [ "1", "1" ],
+
+ // strip decimals
+ "1.234": [ "1", "1" ],
+ " 1, ,.": [ "1", "1" ],
+ } );
+
+
+ // decimal places
+ common.testValidate( EchoFormatter.use( Sut( 3 ) )(), {
+ "1": [ "1.000", "1.000" ],
+ "001": [ "1.000", "1.000" ],
+ "123": [ "123.000", "123.000" ],
+ "123.1": [ "123.100", "123.100" ],
+ "0123.1": [ "123.100", "123.100" ],
+ "123.155": [ "123.155", "123.155" ],
+ "123.": [ "123.000", "123.000" ],
+ ".123": [ "0.123", "0.123" ],
+
+ // truncate, not round (leave that to another formatter)
+ "123.1554": [ "123.155", "123.155" ],
+ "123.1556": [ "123.155", "123.155" ],
+
+ "12,345": [ "12345.000", "12,345.000" ],
+ " 1, ,.": [ "1.000", "1.000" ],
+ } );
+
+
+ // really long decimals should be unstyled
+ common.testValidate( EchoFormatter.use( Sut( 10 ) )(), {
+ "0.1234567890": [ true, true ],
+ } );
+
+
+ // negative scale strips trailing zeroes
+ common.testValidate( EchoFormatter.use( Sut( -5 ) )(), {
+ "1": [ "1", "1" ],
+ "01": [ "1", "1" ],
+ "1.0": [ "1", "1" ],
+ "1.0100": [ "1.01", "1.01" ],
+ "123.155": [ "123.155", "123.155" ],
+ "123.123456": [ "123.12345", "123.12345" ],
} );