From c8bcb1e31fb9de409fe81935f591ff73489b9930 Mon Sep 17 00:00:00 2001 From: jquense Date: Tue, 22 Dec 2015 22:27:47 -0500 Subject: [PATCH] [fixed] correctly localize number when editing --- dev/configure-globalize.js | 10 +++--- dev/configure-simplenumber.js | 3 ++ dev/dev.jsx | 10 ++---- docs/components/pages/i18n.md | 10 ++++++ src/NumberInput.jsx | 63 ++++++++++++++++++--------------- src/NumberPicker.jsx | 2 +- src/localizers/globalize.js | 50 +++++++++++++++++--------- src/localizers/simple-number.js | 44 +++++++++++++++-------- src/util/localizers.js | 12 +++++-- 9 files changed, 127 insertions(+), 77 deletions(-) create mode 100644 dev/configure-simplenumber.js diff --git a/dev/configure-globalize.js b/dev/configure-globalize.js index 0fa09260f..50a036755 100644 --- a/dev/configure-globalize.js +++ b/dev/configure-globalize.js @@ -2,16 +2,16 @@ var Globalize = require('globalize') var localizers = require('../src/localizers/globalize') Globalize.load( - require('cldr-data/main/en-GB/ca-gregorian.json'), - require('cldr-data/main/en-GB/currencies.json'), - require('cldr-data/main/en-GB/dateFields.json'), - require('cldr-data/main/en-GB/numbers.json'), + require('cldr-data/main/sk/ca-gregorian.json'), + require('cldr-data/main/sk/currencies.json'), + require('cldr-data/main/sk/dateFields.json'), + require('cldr-data/main/sk/numbers.json'), require('cldr-data/supplemental/numberingSystems.json'), require('cldr-data/supplemental/currencyData.json'), require('cldr-data/supplemental/likelySubtags.json'), require('cldr-data/supplemental/timeData.json'), require('cldr-data/supplemental/weekData.json') ); -Globalize.locale('en-GB') +Globalize.locale('sk') localizers(Globalize) diff --git a/dev/configure-simplenumber.js b/dev/configure-simplenumber.js new file mode 100644 index 000000000..ced57a47e --- /dev/null +++ b/dev/configure-simplenumber.js @@ -0,0 +1,3 @@ +var localizers = require('../src/localizers/simple-number') + +localizers() diff --git a/dev/dev.jsx b/dev/dev.jsx index 250aa7b2e..955e8ebb0 100644 --- a/dev/dev.jsx +++ b/dev/dev.jsx @@ -19,7 +19,8 @@ var NumberPicker = require('../src/NumberPicker.jsx') // var List = require('../src/List.jsx') require('../src/less/react-widgets.less') -require('./configure-moment') +require('./configure-globalize') +//require('./configure-simplenumber') var chance = new (require('chance')) @@ -96,12 +97,7 @@ var App = React.createClass({ > add - + diff --git a/docs/components/pages/i18n.md b/docs/components/pages/i18n.md index 0cb676ed1..7c635ac3c 100644 --- a/docs/components/pages/i18n.md +++ b/docs/components/pages/i18n.md @@ -246,6 +246,16 @@ function( ) -> String ``` +#### `decimalChar` -> '.' + +Return the decimal separator character. + +``` +function( + format: String|Object, + culture: String? +) -> String +``` #### `precision` diff --git a/src/NumberInput.jsx b/src/NumberInput.jsx index 682840733..b0c6ec253 100644 --- a/src/NumberInput.jsx +++ b/src/NumberInput.jsx @@ -2,7 +2,7 @@ import React from 'react'; import CustomPropTypes from './util/propTypes'; import { number as numberLocalizer } from './util/localizers'; -var format = props => numberLocalizer.getFormat('default', props.format) +let getFormat = props => numberLocalizer.getFormat('default', props.format) export default React.createClass({ @@ -13,6 +13,7 @@ export default React.createClass({ placeholder: React.PropTypes.string, format: CustomPropTypes.numberFormat, + parse: React.PropTypes.func.isRequired, culture: React.PropTypes.string, @@ -25,20 +26,23 @@ export default React.createClass({ getDefaultProps(){ return { value: null, - editing: false, - parse: (number, culture) => numberLocalizer.parse(number, culture) + editing: false } }, - getDefaultState(props){ - var value = props.editing - ? props.value - : formatNumber(props.value, format(props), props.culture) + getDefaultState(props = this.props){ + var value = props.value + , decimal = numberLocalizer.decimalChar(null, props.culture) + , format = getFormat(props); this._beginningWithSign = false; if (value == null || isNaN(props.value)) value = '' + else + value = props.editing + ? ('' + value).replace('.', decimal) + : numberLocalizer.format(value, format, props.culture) return { stringValue: '' + value @@ -46,7 +50,7 @@ export default React.createClass({ }, getInitialState() { - return this.getDefaultState(this.props) + return this.getDefaultState() }, componentWillReceiveProps(nextProps) { @@ -82,7 +86,7 @@ export default React.createClass({ _change(e) { var val = e.target.value - , number = this.props.parse(e.target.value, this.props.culture) + , number = this._parse(e.target.value) , atSign = this.isSign(val.trim()) , startingWithSign = this._beginningWithSign; @@ -93,16 +97,20 @@ export default React.createClass({ return this.props.onChange(null) } - if (this.isFlushable(number, val)) - return this.props.onChange(number) + if (this.isFlushable(number, val)) { + if (number !== this.props.value) + return this.props.onChange(number) + else + this.setState(this.getDefaultState()) // 5. -> 5 + } - if (!isNaN(number) || (atSign && startingWithSign) || this.isAtDelimiter(number, val)) + if ((atSign && startingWithSign) || this.isAtDelimiter(number, val)) this.current(e.target.value) }, _finish() { var str = this.state.stringValue - , number = this.props.parse(str, this.props.culture); + , number = this._parse(str); // if number is below the min // we need to flush low values and decimal stops, onBlur means i'm done inputing @@ -113,19 +121,21 @@ export default React.createClass({ _parse(strVal) { let culture = this.props.culture - , format = this.props.editFormat + , delimChar = numberLocalizer.decimalChar(null, culture) , userParse = this.props.parse; if (userParse) return userParse(strVal, culture) - return format ? numberLocalizer.parse(strVal, culture) : +strVal + strVal = strVal.replace(delimChar, '.') + strVal = parseFloat(strVal); + + return strVal }, isFlushable(num, str) { return ( - this.isValid(num) - && num !== this.props.value + this.isValid(num) && !this.isAtDelimiter(num, str) && !this.isSign(str) ) @@ -135,17 +145,17 @@ export default React.createClass({ return (val || '').trim() === '-'; }, - isAtDelimiter(num, str) { - var next; + isAtDelimiter(num, str, props = this.props) { + var localeChar = numberLocalizer.decimalChar(null, props.culture) + , lastIndex = str.length - 1 + , char; if (str.length <= 1) return false - next = this.props.parse( - str.substr(0, str.length - 1), this.props.culture) + char = str[lastIndex] - return typeof next === 'number' - && !isNaN(next) - && next === num + return char === localeChar + && str.indexOf(char) === lastIndex }, isValid(num) { @@ -160,8 +170,3 @@ export default React.createClass({ } }); - - -function formatNumber(number, format, culture){ - return numberLocalizer.format(number, format, culture) -} diff --git a/src/NumberPicker.jsx b/src/NumberPicker.jsx index e0fdae512..90ccfeb97 100644 --- a/src/NumberPicker.jsx +++ b/src/NumberPicker.jsx @@ -195,7 +195,7 @@ let NumberPicker = React.createClass({ }, @widgetEnabled - _focus(focused, e){ + _focus(focused, e) { focused && compat.findDOMNode(this.refs.input).focus() diff --git a/src/localizers/globalize.js b/src/localizers/globalize.js index 2552ccd63..3d421b88f 100644 --- a/src/localizers/globalize.js +++ b/src/localizers/globalize.js @@ -77,11 +77,12 @@ function newGlobalize(globalize){ propType: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - parse(value, format, culture){ - return locale(culture).parseNumber(value, format) + // TODO major bump consistent ordering + parse(value, culture) { + return locale(culture).parseNumber(value) }, - format(value, format, culture){ + format(value, format, culture) { if (value == null) return value @@ -91,7 +92,12 @@ function newGlobalize(globalize){ return locale(culture).formatNumber(value, format) }, - precision(format){ + decimalChar(format, culture) { + let str = this.format(1.1, { raw: '0.0' }, culture) + return str[str.length - 2] || '.' + }, + + precision(format) { return !format || format.maximumFractionDigits == null ? null : format.maximumFractionDigits } @@ -154,13 +160,26 @@ function oldGlobalize(globalize){ } } + function formatData(format, _culture){ + var culture = getCulture(_culture) + , numFormat = culture.numberFormat + + if (typeof format === 'string') { + if (format.indexOf('p') !== -1) numFormat = numFormat.percent + if (format.indexOf('c') !== -1) numFormat = numFormat.curency + } + + return numFormat + } + var number = { formats: { default: 'D' }, - parse(value, culture){ + // TODO major bump consistent ordering + parse(value, culture) { return globalize.parseFloat(value, 10, culture) }, @@ -168,21 +187,18 @@ function oldGlobalize(globalize){ return globalize.format(value, format, culture) }, - precision(format, _culture){ - var culture = getCulture(_culture) - , numFormat = culture.numberFormat - - if (typeof format === 'string') { - if (format.length > 1) - return parseFloat(format.substr(1)) + decimalChar(format, culture){ + var data = formatData(format, culture) + return data['.'] || '.' + }, - if (format.indexOf('p') !== -1) numFormat = numFormat.percent - if (format.indexOf('c') !== -1) numFormat = numFormat.curency + precision(format, _culture){ + var data = formatData(format, _culture) - return numFormat.decimals || null - } + if (typeof format === 'string' && format.length > 1) + return parseFloat(format.substr(1)) - return null + return data ? data.decimals : null } } diff --git a/src/localizers/simple-number.js b/src/localizers/simple-number.js index 5a5e5a274..ea3534608 100644 --- a/src/localizers/simple-number.js +++ b/src/localizers/simple-number.js @@ -2,24 +2,27 @@ import configure from '../configure' import formatNumber from 'format-number-with-string'; import deconstruct from 'deconstruct-number-format'; -export default function simpleNumber(){ +let defaults = { + decimal: '.', + grouping: ',' +} + let localizer = { formats: { - default: '-#,##0.' + default: `-#${grouping}##0${decimal}`, }, - - parse(value, format){ + + // TODO major bump consistent ordering + parse(value, culture, format) { if (format) { let data = deconstruct(format) - - if (data.negativeLeftPos !== -1) - value = value.substr(data.negativeLeftPos + 1) - - if (data.negativeRightPos !== -1) - value = value.substring(0, data.negativeRightPos) + , negative = (data.negativeLeftSymbol && value.indexOf(data.negativeLeftSymbol) !== -1) + || (data.negativeRightSymbol && value.indexOf(data.negativeRightSymbol) !== -1) value = value + .replace(data.negativeLeftSymbol, '') + .replace(data.negativeRightSymbol, '') .replace(data.prefix, '') .replace(data.suffix, '') @@ -31,18 +34,29 @@ export default function simpleNumber(){ if (data.decimalsSeparator) halves[1] = halves[1].replace(new RegExp('\\' + data.decimalsSeparator, 'g')) - value = halves.join(data.decimalChar) + if (halves[1] === '') halves.pop(); + + value = halves.join('.') + value = +value + + if (negative) + value = -1 * value } - let number = parseFloat(value) + else + value = parseFloat(value) - return number + return isNaN(value) ? null : value }, - format(value, format){ + format(value, format) { return formatNumber(value, format) }, - precision(format){ + decimalChar(format) { + return format && deconstruct(format).decimalsSeparator || '.' + }, + + precision(format) { let data = deconstruct(format) return data.maxRight !== -1 ? data.maxRight : null } diff --git a/src/util/localizers.js b/src/util/localizers.js index 523e13196..d5016c9d3 100644 --- a/src/util/localizers.js +++ b/src/util/localizers.js @@ -42,7 +42,7 @@ function checkFormats(requiredFormats, formats){ let _numberLocalizer = createWrapper('NumberPicker') -export function setNumber({ format, parse, precision = () => null, formats, propType }) { +export function setNumber({ format, parse, decimalChar = () => '.', precision = () => null, formats, propType }) { invariant(typeof format === 'function' , 'number localizer `format(..)` must be a function') invariant(typeof parse === 'function' @@ -50,17 +50,20 @@ export function setNumber({ format, parse, precision = () => null, formats, prop checkFormats(REQUIRED_NUMBER_FORMATS, formats) + formats.editFormat = formats.editFormat || (str => parseFloat(str)); + _numberLocalizer = { formats, precision, + decimalChar, propType: propType || localePropType, format(value, str, culture){ return _format(this, format, value, str, culture) }, - parse(value, culture) { - let result = parse.call(this, value, culture) + parse(value, culture, format) { + let result = parse.call(this, value, culture, format) invariant(result == null || typeof result === 'number' , 'number localizer `parse(..)` must return a number, null, or undefined') return result @@ -107,6 +110,9 @@ export let number = { format(...args){ return _numberLocalizer.format(...args) }, + decimalChar(...args){ + return _numberLocalizer.decimalChar(...args) + }, precision(...args){ return _numberLocalizer.precision(...args) }