Skip to content

Commit

Permalink
remove numbro
Browse files Browse the repository at this point in the history
  • Loading branch information
olmobrutall committed Sep 20, 2020
1 parent b209617 commit e2de807
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 65 deletions.
11 changes: 5 additions & 6 deletions Signum.React/Scripts/Finder.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from "react";
import { DateTime } from 'luxon'
import numbro from "numbro"
import * as AppContext from "./AppContext"
import * as Navigator from "./Navigator"
import { Dic, classes } from './Globals'
Expand All @@ -21,7 +20,7 @@ import { TypeEntity, QueryEntity } from './Signum.Entities.Basics';

import {
Type, IType, EntityKind, QueryKey, getQueryNiceName, getQueryKey, isQueryDefined, TypeReference,
getTypeInfo, tryGetTypeInfos, getEnumInfo, toLuxonFormat, toNumbroFormat, PseudoType, EntityData,
getTypeInfo, tryGetTypeInfos, getEnumInfo, toLuxonFormat, toNumberFormat, PseudoType, EntityData,
TypeInfo, PropertyRoute, QueryTokenString, getTypeInfos, tryGetTypeInfo, onReloadTypesActions
} from './Reflection';

Expand Down Expand Up @@ -1642,16 +1641,16 @@ export const formatRules: FormatRule[] = [
name: "Number",
isApplicable: col => col.token!.filterType == "Integer" || col.token!.filterType == "Decimal",
formatter: col => {
const numbroFormat = toNumbroFormat(col.token!.format);
return new CellFormatter((cell: number | undefined) => cell == undefined ? "" : <span>{numbro(cell).format(numbroFormat)}</span>, "numeric-cell");
const numberFormat = toNumberFormat(col.token!.format);
return new CellFormatter((cell: number | undefined) => cell == undefined ? "" : <span>{numberFormat.format(cell)}</span>, "numeric-cell");
}
},
{
name: "Number with Unit",
isApplicable: col => (col.token!.filterType == "Integer" || col.token!.filterType == "Decimal") && !!col.token!.unit,
formatter: col => {
const numbroFormat = toNumbroFormat(col.token!.format);
return new CellFormatter((cell: number | undefined) => cell == undefined ? "" : <span>{numbro(cell).format(numbroFormat) + "\u00a0" + col.token!.unit}</span>, "numeric-cell");
const numberFormat = toNumberFormat(col.token!.format);
return new CellFormatter((cell: number | undefined) => cell == undefined ? "" : <span>{numberFormat.format(cell) + "\u00a0" + col.token!.unit}</span>, "numeric-cell");
}
},
{
Expand Down
48 changes: 33 additions & 15 deletions Signum.React/Scripts/Lines/ValueLine.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as React from 'react'
import { DateTime } from 'luxon'
import numbro from 'numbro'
import * as DateTimePicker from 'react-widgets/lib/DateTimePicker'
import { Dic, addClass, classes } from '../Globals'
import { MemberInfo, getTypeInfo, TypeReference, toLuxonFormat, toDurationFormat, toNumbroFormat, isTypeEnum, durationToString, TypeInfo } from '../Reflection'
import { MemberInfo, getTypeInfo, TypeReference, toLuxonFormat, toDurationFormat, toNumberFormat, isTypeEnum, durationToString, TypeInfo } from '../Reflection'
import { LineBaseController, LineBaseProps, useController } from '../Lines/LineBase'
import { FormGroup } from '../Lines/FormGroup'
import { FormControlReadonly } from '../Lines/FormControlReadonly'
Expand All @@ -12,6 +11,7 @@ import TextArea from '../Components/TextArea';
import 'react-widgets/dist/css/react-widgets.css';
import { KeyCodes } from '../Components/Basic';
import { format } from 'd3';
import { isPrefix } from '../FindOptions'

export interface ValueLineProps extends LineBaseProps {
valueLineType?: ValueLineType;
Expand Down Expand Up @@ -455,14 +455,14 @@ ValueLineRenderers.renderers["Decimal" as ValueLineType] = (vl) => {
function numericTextBox(vl: ValueLineController, validateKey: (e: React.KeyboardEvent<any>) => boolean) {
const s = vl.props

const numbroFormat = toNumbroFormat(s.formatText);
const numberFormat = toNumberFormat(s.formatText);

if (s.ctx.readOnly)
return (
<FormGroup ctx={s.ctx} labelText={s.labelText} helpText={s.helpText} htmlAttributes={{ ...vl.baseHtmlAttributes(), ...s.formGroupHtmlAttributes }} labelHtmlAttributes={s.labelHtmlAttributes}>
{vl.withItemGroup(
<FormControlReadonly htmlAttributes={vl.props.valueHtmlAttributes} ctx={s.ctx} className="numeric" innerRef={vl.inputElement}>
{s.ctx.value == null ? "" : numbro(s.ctx.value).format(numbroFormat)}
{s.ctx.value == null ? "" : numberFormat.format(s.ctx.value)}
</FormControlReadonly>)}
</FormGroup>
);
Expand Down Expand Up @@ -498,7 +498,7 @@ function numericTextBox(vl: ValueLineController, validateKey: (e: React.Keyboard
onChange={handleOnChange}
formControlClass={classes(s.ctx.formControlClass, vl.mandatoryClass)}
validateKey={validateKey}
format={numbroFormat}
format={numberFormat}
innerRef={vl.inputElement as React.RefObject<HTMLInputElement>}
/>
)}
Expand All @@ -510,7 +510,7 @@ export interface NumericTextBoxProps {
value: number | null;
onChange: (newValue: number | null) => void;
validateKey: (e: React.KeyboardEvent<any>) => boolean;
format?: string;
format: Intl.NumberFormat;
formControlClass?: string;
htmlAttributes?: React.HTMLAttributes<HTMLInputElement>;
innerRef?: ((ta: HTMLInputElement | null) => void) | React.RefObject<HTMLInputElement>;
Expand All @@ -522,7 +522,7 @@ export function NumericTextBox(p: NumericTextBoxProps) {


const value = text != undefined ? text :
p.value != undefined ? numbro(p.value).format(p.format) :
p.value != undefined ? p.format?.format(p.value) :
"";

return <input ref={p.innerRef} {...p.htmlAttributes}
Expand Down Expand Up @@ -552,15 +552,10 @@ export function NumericTextBox(p: NumericTextBoxProps) {

let value = ValueLineController.autoFixString(input.value, false);

if (numbro.languageData().delimiters.decimal == ',' && !value.contains(",") && value.trim().length > 0) //Numbro transforms 1.000 to 1,0 in spanish or german
value = value + ",00";

if (p.format && p.format.endsWith("%")) {
if (value && !value.endsWith("%"))
value += "%";
}
//if (numbro.languageData().delimiters.decimal == ',' && !value.contains(",") && value.trim().length > 0) //Numbro transforms 1.000 to 1,0 in spanish or german
// value = value + ",00";

const result = value == undefined || value.length == 0 ? null : numbro.unformat(value, p.format);
const result = value == undefined || value.length == 0 ? null : unformat(p.format, value);
setText(undefined);
if (result != p.value)
p.onChange(result);
Expand All @@ -569,6 +564,29 @@ export function NumericTextBox(p: NumericTextBoxProps) {
p.htmlAttributes.onBlur(e);
}

function unformat(format: Intl.NumberFormat, str: string): number {
var isPercentage = format.resolvedOptions().style == "percent";
if (isPercentage) {
format = new Intl.NumberFormat(format.resolvedOptions().locale);
}

const thousandSeparator = format.format(1111).replace(/1/g, '');
const decimalSeparator = format.format(1.1).replace(/1/g, '');

if (thousandSeparator)
str = str.replace(new RegExp('\\' + thousandSeparator, 'g'), '');

if (decimalSeparator)
str = str.replace(new RegExp('\\' + decimalSeparator), '.');

var result = parseFloat(str);

if (isPercentage)
return result / 100;

return result;
}

function handleOnChange(e: React.SyntheticEvent<any>) {
const input = e.currentTarget as HTMLInputElement;
setText(input.value);
Expand Down
73 changes: 57 additions & 16 deletions Signum.React/Scripts/Reflection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { DateTime} from 'luxon';
import numbro from 'numbro';
import { Dic } from './Globals';
import { ModifiableEntity, Entity, Lite, MListElement, ModelState, MixinEntity } from './Signum.Entities'; //ONLY TYPES or Cyclic problems in Webpack!
import { ajaxGet } from './Services';
Expand Down Expand Up @@ -120,38 +119,80 @@ export function toDurationFormat(format: string | undefined): string | undefined
return format.replace("\\:", ":");
}

export function toNumbroFormat(format: string | undefined) {
export namespace NumberFormatSettings {
export let defaultNumberFormatLocale: string = null!;
}

//https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings
export function toNumberFormat(format: string | undefined, locale?: string): Intl.NumberFormat {
return new Intl.NumberFormat(locale ?? NumberFormatSettings.defaultNumberFormatLocale, toNumberFormatOptions(format));
}

export function toNumberFormatOptions(format: string | undefined): Intl.NumberFormatOptions | undefined {

if (format == undefined)
return undefined;

const f = format.toUpperCase();

if (f.startsWith("C"))
return "0." + "0".repeat(parseInt(f.after("C") || "2"));
if (f.startsWith("C")) //unit comes separated
return {
style: "decimal",
minimumFractionDigits: parseInt(f.after("C")) || 2,
maximumFractionDigits: parseInt(f.after("C")) || 2,
useGrouping: true,
}

if (f.startsWith("N"))
return "0,0." + "0".repeat(parseInt(f.after("N") || "2"));
return {
style: "decimal",
minimumFractionDigits: parseInt(f.after("N")) || 2,
maximumFractionDigits: parseInt(f.after("N")) || 2,
useGrouping: true,
}

if (f.startsWith("D"))
return "0".repeat(parseInt(f.after("D") || "1"));
return {
style: "decimal",
maximumFractionDigits: 0,
minimumIntegerDigits: parseInt(f.after("D")) || 1,
useGrouping: false,
}

if (f.startsWith("F"))
return "0." + "0".repeat(parseInt(f.after("F") || "2"));
return {
style: "decimal",
minimumFractionDigits: parseInt(f.after("F")) || 2,
maximumFractionDigits: parseInt(f.after("F")) || 2,
useGrouping: false,
}

if (f.startsWith("E"))
return "0." + "0".repeat(parseInt(f.after("E") || "2"));
return {
style: "decimal",
notation: "scientific",
minimumFractionDigits: parseInt(f.after("E")) || 6,
maximumFractionDigits: parseInt(f.after("E")) || 6,
useGrouping: false,
} as any;

if (f.startsWith("P"))
return "0." + "0".repeat(parseInt(f.after("P") || "2")) + "%";
return {
style: "percent",
minimumFractionDigits: parseInt(f.after("P")) || 2,
maximumFractionDigits: parseInt(f.after("P")) || 2,
useGrouping: false,
}

if (f.contains("#"))
format = format
.replaceAll(".#", "[.]0")
.replaceAll(",#", "[,]0")
.replaceAll("#", "0");

return format;
//simple euristic
var afterDot = f.tryAfter(".") ?? "";
return {
style: "decimal",
minimumFractionDigits: afterDot.trimStart("#").length,
maximumFractionDigits: afterDot.length,
useGrouping: f.contains(","),
}
}

export function valToString(val: any) {
Expand All @@ -165,7 +206,7 @@ export function numberToString(val: any, format?: string) {
if (val == null)
return "";

return numbro(val).format(toNumbroFormat(format));
return toNumberFormat(format).format(val);
}

export function dateToString(val: any, format?: string) {
Expand Down
6 changes: 4 additions & 2 deletions Signum.React/Scripts/SearchControl/FilterBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FilterOptionParsed, QueryDescription, QueryToken, SubTokensOptions, fil
import { SearchMessage } from '../Signum.Entities'
import { isNumber } from '../Lines/ValueLine'
import { ValueLine, EntityLine, EntityCombo, StyleContext, FormControlReadonly } from '../Lines'
import { Binding, IsByAll, tryGetTypeInfos, toLuxonFormat, getTypeInfos } from '../Reflection'
import { Binding, IsByAll, tryGetTypeInfos, toLuxonFormat, getTypeInfos, toNumberFormat } from '../Reflection'
import { TypeContext } from '../TypeContext'
import QueryTokenBuilder from './QueryTokenBuilder'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
Expand Down Expand Up @@ -582,8 +582,10 @@ export function PinnedFilterEditor(p: PinnedFilterEditorProps) {
if (p.readonly)
return <span className="numeric form-control form-control-xs" style={{ width: "60px" }}>{val}</span>;

var numberFormat = toNumberFormat("0");

return (
<NumericTextBox value={val == undefined ? null : val} onChange={n => { binding.setValue(n == null ? undefined : n); p.onChange(); }}
<NumericTextBox value={val == undefined ? null : val} format={numberFormat} onChange={n => { binding.setValue(n == null ? undefined : n); p.onChange(); }}
validateKey={isNumber} formControlClass="form-control form-control-xs" htmlAttributes={{ placeholder: title, style: { width: "60px" } }} />
);
}
Expand Down
16 changes: 7 additions & 9 deletions Signum.React/Scripts/SearchControl/PaginationSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as React from 'react'
import numbro from 'numbro'
import * as Finder from '../Finder'
import { classes } from '../Globals'
import { ResultTable, Pagination, PaginationMode, PaginateMath } from '../FindOptions'
import { SearchMessage } from '../Signum.Entities'
import "./PaginationSelector.css"
import { toNumberFormat } from '../Reflection'

interface PaginationSelectorProps {
resultTable?: ResultTable;
Expand Down Expand Up @@ -32,32 +32,30 @@ export default function PaginationSelector(p: PaginationSelectorProps) {

const pagination = p.pagination;

function format(num: number): string {
return numbro(num).format("0,0");
}
var numberFormat = toNumberFormat("0")

switch (pagination.mode) {

case "All":
return (
<span>{SearchMessage._0Results_N.niceToString().forGenderAndNumber(resultTable.totalElements).formatHtml(
<span className="sf-pagination-strong" key={1}>{resultTable.totalElements && format(resultTable.totalElements)}</span>)
<span className="sf-pagination-strong" key={1}>{resultTable.totalElements && numberFormat.format(resultTable.totalElements)}</span>)
}</span>
);

case "Firsts":
return (
<span>{SearchMessage.First0Results_N.niceToString().forGenderAndNumber(resultTable.rows.length).formatHtml(
<span className={"sf-pagination-strong" + (resultTable.rows.length == resultTable.pagination.elementsPerPage ? " sf-pagination-overflow" : "")} key={1}>{format(resultTable.rows.length)}</span>)
<span className={"sf-pagination-strong" + (resultTable.rows.length == resultTable.pagination.elementsPerPage ? " sf-pagination-overflow" : "")} key={1}>{numberFormat.format(resultTable.rows.length)}</span>)
}</span>
);

case "Paginate":
return (
<span>{SearchMessage._01of2Results_N.niceToString().forGenderAndNumber(resultTable.totalElements).formatHtml(
<span className={"sf-pagination-strong"} key={1}>{format(PaginateMath.startElementIndex(pagination))}</span>,
<span className={"sf-pagination-strong"} key={2}>{format(PaginateMath.endElementIndex(pagination, resultTable.rows.length))}</span>,
<span className={"sf-pagination-strong"} key={3}>{resultTable.totalElements && format(resultTable.totalElements)}</span>)
<span className={"sf-pagination-strong"} key={1}>{numberFormat.format(PaginateMath.startElementIndex(pagination))}</span>,
<span className={"sf-pagination-strong"} key={2}>{numberFormat.format(PaginateMath.endElementIndex(pagination, resultTable.rows.length))}</span>,
<span className={"sf-pagination-strong"} key={3}>{resultTable.totalElements && numberFormat.format(resultTable.totalElements)}</span>)
}</span>
);
default:
Expand Down
7 changes: 3 additions & 4 deletions Signum.React/Scripts/SearchControl/ValueSearchControl.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as React from 'react'
import numbro from 'numbro'
import { DateTime } from 'luxon'
import { classes } from '../Globals'
import * as Navigator from '../Navigator'
import * as Finder from '../Finder'
import { FindOptions, FindOptionsParsed, SubTokensOptions, QueryToken, QueryValueRequest } from '../FindOptions'
import { Lite, Entity, getToString, EmbeddedEntity } from '../Signum.Entities'
import { getQueryKey, toNumbroFormat, toLuxonFormat, getEnumInfo, QueryTokenString, getTypeInfo, getTypeName } from '../Reflection'
import { getQueryKey, toNumberFormat, toLuxonFormat, getEnumInfo, QueryTokenString, getTypeInfo, getTypeName } from '../Reflection'
import { AbortableRequest } from "../Services";
import { SearchControlProps } from "./SearchControl";
import { BsColor } from '../Components';
Expand Down Expand Up @@ -282,8 +281,8 @@ export default class ValueSearchControl extends React.Component<ValueSearchContr
switch (token.filterType) {
case "Integer":
case "Decimal":
const numbroFormat = toNumbroFormat(this.props.format ?? token.format);
return numbro(value).format(numbroFormat);
const numbroFormat = toNumberFormat(this.props.format ?? token.format);
return numbroFormat.format(value);
case "DateTime":
const momentFormat = toLuxonFormat(this.props.format ?? token.format);
return DateTime.fromISO(value).toFormat(momentFormat);
Expand Down
1 change: 0 additions & 1 deletion Signum.React/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"@types/react-router-dom": "5.1.5",
"@types/react-transition-group": "4.4.0",
"@types/react-widgets": "4.4.2",
"numbro": "2.3.0",
"@types/luxon": "1.24.4",
"luxon": "1.25.0",
"popper.js": "1.16.1",
Expand Down
12 changes: 0 additions & 12 deletions Signum.React/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,6 @@
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=

bignumber.js@^8.1.1:
version "8.1.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.1.1.tgz#4b072ae5aea9c20f6730e4e5d529df1271c4d885"
integrity sha512-QD46ppGintwPGuL1KqmwhR0O+N2cZUg8JG/VzwI2e28sM9TqHjQB10lI4QAaMHVbLzwVLLAwEglpKPViWX+5NQ==

classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
Expand Down Expand Up @@ -421,13 +416,6 @@ luxon@1.25.0:
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.25.0.tgz#d86219e90bc0102c0eb299d65b2f5e95efe1fe72"
integrity sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ==

numbro@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/numbro/-/numbro-2.3.0.tgz#a2cdcf164346833fded1128dfc5056a882b3097a"
integrity sha512-KGa4qVveFGC0HgKaJnmKYqyC9CX7jQONxEfREVwc/8UwTJtcEt60F8j/NCKgZH/IFW/Z9uibhCDqpJiRxuXdsA==
dependencies:
bignumber.js "^8.1.1"

object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
Expand Down

1 comment on commit e2de807

@olmobrutall
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Replace numbro.js by Intl.NumberFormat

numbro.js was a simple but somehow complementary library to the now removed moment.js, so it's now also fallen out of grace.

The alternative now is just use the the native Intl.NumberFormat directly.

There is no need to add an external dependency just for formatting numbers, since Luxon is mainly about adding a modern and inmmutable API to manipulate dates (DateTime) on top of the native one (Date), and not so much about formatting. Numbers are just OK the way they are.

How to migrate

Check this commit in Southwind: signumsoftware/southwind@3b138c6

Please sign in to comment.