Struggling with currency/amount input fields (Money)

Hello all,

I have a form with one price input field.
In order to follow industry standards (and for learning purpose) I’m using Money.
Currently I’m only dealing with only one currency (EUR), so I’m not into currency conversions etc.
I intended to use it only for the ease of formatting purpose as well as for the precision handling.

That being said, outputting a Money field is nice when displaying the value.
But I’m struggling for getting the value from a form.
At first I was not sure if I should use a text input field or an number input field.
It turns out that by the Money type is considered as a string.
But I had to disable the currency display since I already have a custom css displaying the currency in a styled way. So I’m basically using only the amount part.

But in that case, I wonder that I can simply go with a number field with the benefit to display a dedicated number keyboard in mobile.
The problem is that if I’m using a phone that has an English local, I cannot use commas but only dots as decimal separator.
And in this case if I put 12.34 I am ending up with 1234.00

When I’m using a text field it correctly handles the comma but not the dot.

Is it possible to set both the comma and the dot as the decimal separator?

How do you guys handle those pesky formatting problems?

Thanks for any comments…

ex_money (I am the author) can parse input numbers in any of its known locales using the locale-specific separators. Including parsing amounts with the currency sign since, as you point out, the display output most commonly uses that. Here are some example that might help:

# Default to EUR since thats the default currency for Germany
iex> Money.parse "10.000,43", locale: "de"                
#Money<:EUR, 10000.43>

# Parse an accounting amount
iex> Money.parse "(10.000,43)", locale: "de"    
#Money<:EUR, -10000.43>

# With a symbol
iex> Money.parse "€10.000,43", locale: "de" 
#Money<:EUR, 10000.43>

# Currency is specified
iex> Money.parse "CHF 10.000,43", locale: "de"
#Money<:CHF, 10000.43>
4 Likes

Thanks for taking the time to answer!
In another project I was using your package (not the latest version).
But with this new project I tried with your latest version but got some dependency errors (with ex_money_sql).
So I’m using the other package… But I guess that regarding this part they work the same, right?

However, my problem is more about the fact that if I want to grab a decimal value from a number input field in the HTLM, the locale is not taken into account.

I mean, the browser validation only allows the decimal symbol of the user’s locale. For example in my devices (phone or computer) I have English locale, so I cannot input the comma in the input field.

I can only use the dot as the decimal separator. But then I don’t know why, I receive the value as if there was no decimal separator.

I don’t have any issue when I use parse in controllers (or in IEx).
But in my case I defined the type in the schema and the changeset as well as the default locale, so everything just happens. But unfortunately, not the way I want when using a number_input field.
I wonder if I should divide the received value by 100, if so where?

Sorry to hear you has an issue with ex_momey_sql. Please do raise an issue on github or DM me. I have had no issues reported so I want to quash this asap.

I believe the number input field in most (all?) browsers don’t allow the input of the separator, only the decimal point that is in the currently set locale for the browser. Most issues I’ve seen come from the server rendering a formatted money amount that includes currency symbols and/or separators that cannot then be edited correctly because of the restrictions on a number field.

1 Like

No problem! It seems to be a version bumping issue…
If I look here ex_money_sql | Hex it seems that ex_money_sql is wanting ex_money 5.0 instead of 5.2.

My bad, when I was saying decimal separator, I was talking about the decimal point separator.

Indeed, if we keep the field as a number field, the browser only expect a string with bare numbers (no currency symbols) so I can use the the amount part of the Money.

But still, if I put using anEnglish locale 9.99 (meaning ~10), the dot is not taken into account and I got in the changeset 999,00 (so ~ around 1000).

I’m still struggling to find how to do that simple task…

ex_money_sql indicates that it will work with ex_money from 5.0 and hex will retrieve the latest version which matches which will be 5.2.

Can you show the HTML that is generated for the input field (or the template that generates it)?

1 Like

Hello @kip,

Thank you for your last answer.
But because of the pandemic I had to stop working in that project.
Meanwhile, I switched back to a regular text_input so I don’t get that decimal point error depending on if it’s a dot . or a comma ,.

However, I’m just facing another error related to the display of the currency symbol…

Let’s say I have the following very simple schema (where it’s assumed that both fields are required):

schema "products" do
  field :name, :string
  field :price, Money.Ecto.Composite.Type
end

I also have the following Cldr config:

defmodule MyApp.Cldr do
  use Cldr,
    locales: ["fr"],
    default_locale: "fr",
    providers: [Cldr.Number, Money]
end

Now let’s consider that I have the following HTML form:

<%= form_for @changeset, ..., fn f -> %>
  <%= text_input f, :name, required: true %>
  <%= text_input f, :price, required: true %>
...
<% end %>

Regardless of if I type 10, 10,0 or 10.0 everything is working like a charm and I will get the correct “value” displayed as 10,00 €.

However, if I type an amount in the price but not in the name, since it’s required I’ll get a changeset error and the form will be displayed again.
The problem, is that the price amount will be displayed as formatted like 10,00 €.
And if I put a name and want to submit again, the name will pass this time, but not the price amount!

ex_money doesn’t accept its own formatted string.
I get the following error: The currency \" €\" is unknown or not supported.

What I am missing?
I’m sure I’m missing something because those simple very common tasks of manipulating money in forms shouldn’t be that complicated.

I guess I can handle the value to display in the form fields manually with the value: attribute, but this seems very overly complicated…

Thank you for any thoughts…

@Sanjibukai, any chance you have a minimal reproducible case? I’m not seeing the parsing issue. For example:

iex> Money.parse "10,00 €", locale: "fr"         
#Money<:EUR, 10.00>

# With "fr" as the default locale
iex> Money.parse "10,00 €"              
#Money<:EUR, 10.00>

Perhaps check you are on ex_money version 5.3.0 (which is the latest version)? How are you validating the price in your changeset?

1 Like

Hi @kip and thanks for the quick reply!

I’m getting a no matching error when I try to get the version 5.3.0. It seems that the latest version is 5.2.1 (which is also what mix shows in the error as well as what’s displayed in hex.pm and in your github repo too).

Anyway, I tried in IEx (while having the server running thanks to iex -S mix phx.server), and indeed it’s working in IEx…
Both commands (with and without the additional locale: "fr" parameter) are working in IEx…
But still not working in my app.

I noticed that despite having a single locale (“fr”) defined in the MyApp.Cldr module, the following message showed up when compiling:

Generating MyApp.Cldr for 4 locales named ["en", "en-001", "fr", "root"] with a default locale named "fr"

I don’t think the other locales might interfere since it’s stated that the default one is “fr”.
But might it be a problem?
While, I’m here, what is “en-001” and “root” and where they came from?

Also, regarding the validation, I’m doing nothing special. I’m casting and validating as usual like so:

product
|> cast(attrs, [:name, :price])
|> validate_required([:name, :price])

Thank you for any direction…

Edit:
The validation error I got is the following:

#Ecto.Changeset<
  action: nil,
  changes: %{name: "ABC"},
  errors: [
    price: {"The currency \" €\" is unknown or not supported",
     [type: Money.Ecto.Composite.Type, validation: :cast]}
  ],
  data: #MyApp.Product<>,
  valid?: false
>

Edit 2:
If I type 10, I receive back the following string: 10,00 €
Something weird that’s happening is that, if I delete the space like so 10,00€, or even weirder, if I select that space (with highlighting with the mouse) and put back a space again (so apparently nothing changes), the error disappear…
:thinking:

Hi @kip,
I think I found the culprit!
I paste that space in a unicode decoder, and indeed it’s not a regular space (U+20) but rather a non breaking space (U+A0).
This is a good thing to not have the symbol wrapping around, but it’s not being parsed correctly.

When I try the same IEx example as above but inserting that no-break space I got the error again!
I’ll try to check in the code base to see how you are parsing the string and maybe adding other white spaces to the literal regular spaces…

Good catch, confirmed! Fix coming in about 30 minutes.

1 Like

I tried with this simple solution (only for the single no-break space A0) and it worked:
Replacing the following line here money/lib/money/parser/combinators.ex at 5698e20fd6d998bc610fdce5ecc1f794b8463cf7 · kipcole9/money · GitHub

  @whitespace [?\s, ?\t]

with this:

  @whitespace [?\s, ?\t, 160]

But I’m not sure if it could be relevant to take into account more whitespace characters

I have added all the whitespace characters as defined by Unicode (the set [:Zs]) in ex_money version 5.3.0 which I will publish in the next 15 minutes.

1 Like

ex_money version 5.3.0 is published to hex. Really appreciate the collaboration and patience, thanks. Here’s the changelog entry:

Bug Fixes

  • Fix parsing money amounts to use Unicode definition of whitespace (set [:Zs:]). Thanks to @Sanjibukai for the report.

Enhancements

  • Add Money.sum/2 to sum a list of Money, converting them if required.

Upgrading

mix deps.update ex_money
4 Likes