LiveView server-side validation/formatting of form inputs relying on ex_cldr_numbers

I am not really sure where to post this for it is both LiveView and cross-library related issue so I am posting it here.

What I am trying to do is having form input numbers (amounts) validated and formatted server-side by using ex_cldr_numbers library (which btw, is fantastic, kudos to @kip) while relying on an embedded_schema + changeset based validation.

The limitation I am encountering is as follows. If I use the combination of a text_input tag and a changeset based validation of the input numbers I don’t see how to validate and display them back in a server-side generated, locale-specific number format (e.g. with the thousand separator such as 1,000,000) since if I store them as :decimal (which is their actual data format) then the text_input tag will show them as numbers without the locale specific thousand separator (or any other desired locale-specific number format).

So, in my view (unless I am missing something big time), I can either choose not to use the text_input tag but write my own HTML input code mimicking the text_input output with an additional attribute updated by LiveView and used to override the value attribute in the element JS hook updated callback function, OR I need to have two fields in the schema for each such number (one as a string to read from/write to by the form and the other to store its decimal representation). The latter is so lame that it’s out of the question, so only the former option seems valid but still cumbersome.

Again, unless I am missing something, it would be nice if one of the following two features was added:
a) Either Ecto.Changeset taking an optional transform function when getting a field value (too far fetched maybe?) and having LiveView actually pass it and use it in such a manner, or
b) Have an optional input value-override feature in LiveView to avoid writing own proprietary hooks and manually replicating the text_input html output in each such case, as previously described.

Trying to replicate the feature rich ex_cldr implementation in JS is also out of the question and the only “light” option remaining outside one of those feature requests is to have such numbers stored in the schema as strings, manage their validation and formatting ourselves and transform them via the same cldr library into decimals with a proprietary function in the data module once the data is required for a later use.

Liveview won’t update inputs, which are focused anyways. So no matter what you do you cannot live replace the text in an input as user currently writes to without additional js. Given the latency of liveview I’d also strongly suggest to not even try to format input field values server side. I even had problems with js libraries being quite annoying by trying to format while supplying input (and in the mean time screwing up what I intended to write).

There are imo three options (from most to least prefered in my opinion):

  • Don’t do it
    Just let users supply number is whatever format they want to and apply formatting only afterwards. If you’re worried about incorrect inputs opt for making corrections easier or show a formatted preview next to the input.

  • Use JS library
    There are js libraries created for formatting input with delays or while writing. If they support cldr you might eben be able to supply patterns of ex_cldr to them.

  • Take on the complexity head on
    With client and server doing concurrent edits to text you’re tech. dealing with distributed state. One way to handle that could be using delta-elixir, which slab released recently, and the js counterpart and actually treat the changes made to the text like distributed changes, which need later consolidation. This is overengineering at its finest for your usecase, but I wanted to include it just to show the level of complexity the core of the requested functionality entails.

2 Likes

I like your reply for it shifts focus to the real problem, behind the one I’ve presented here. It did cross my mind that unforeseeable oscillations in latency interleaved with the user typing may cause undesired side effects and thus highly negatively impact the UX, but now that I have a testimony of someone actually trying it before, I am going to retire this idea all together and follow the first suggestion on your list.

Thank you.

You can stash the server rendered in a data attribute, and use a bit of JS to change the input field. This way, you don’t need to deal with different cldr libraries that may disagree with each other on minor details.

Actually, that’s not even necessary if opting not to format the number while the user is typing for which I agree with @LostKobrakai that thriving for the “ideal” solution may seriously impair UX which is why I am now using a focusout listener to detect when form inputs lose their focus and then simply push a proprietary event to my component e.g.:

function maybeFocusoutOfModalInput( me, e) {
  if( !isInput( e.target) || !isOfClass( e.target, MODAL_NUMBER_CLASS)) return false;

  me.__view.pushInput( e.target, e.target.form, 'rerender', e.target, null);
  return true;
}

With the following input tag in the template:

<%= text_input f, :amount, class: “modal-number-input”, value: @changeset.changes[ :amount] && CldrHelpers.amount( @changeset.changes[ :amount]) %>

Note that the ‘rerender’ handle_event is not really a rerender but has the effect of it as it simply recreates a changeset based on the provided form parameters (since, as per LiveView docs, JS is the single source of truth here).

And that’s it. No need for additional JS code.

1 Like

I like the idea of letting the browser format the user’s number natively, and pretty-formatting it for the specific context on focus out. This is the way of least surprise for the user as the number input will behave in the same way as it does in other sites, and other apps when on mobile.

Following this frame of mind I wanted to test the native options for internationalization at our control. The result is the gist HTML5 input number localization study (codepen.io).

I wanted to see how Edge(Chromium) and Firefox behaved while influencing the localization of decimal number inputs.

From the input implementation notes in the HTML Standard (whatwg.org)

The formats shown to the user in date, time, and number controls is independent of the format used for form submission.

Browsers are encouraged to use user interfaces that present dates, times, and numbers according to the conventions of either the locale implied by the input element’s language or the user’s preferred locale. Using the page’s locale will ensure consistency with page-provided data.

The main point of study was how browsers interpret , and . for fractions of a number, and as a bonus how to influence the virtual keyboard presented to the user.

0.1 is a default representations of a floating-point number in the HTML Standard (whatwg.org), the other being exponent notation.

Setting the element’s lang attribute will influence whether value sanitization algorithm (whatwg.org) recognizes or ignores , as a valid decimal. The value must be a valid language tag (WAI) | W3C.

Set up

Configure two number inputs, individually displaying their value attributes and a text representation of their checkValidity() results.

Method

Test out each step in latest Edge and Firefox browsers.

  1. Step each input’s value up by clicking the spinner arrow
  2. Manually overwrite the inputs with 0,1
  3. Manually overwrite the inputs with 0.1

Results

Firefox
  1. Clicking the arrow on input en-CA makes the display use . as decimal, whereas fr-CA defaults to localized display of ,
    image

  2. Both inputs recognise . as decimal
    image

  3. Default language en-CA does not use , as decimal, so value is not valid.
    image

Edge(Chromium)
  1. Changing the value with the spinner causes both values to display with , as decimal?
    image

  2. Both inputs recognise . as decimal
    image

  3. Input value does not care that , is not valid for en-CA, it parses it to a valid float anyway!
    image

Moral of the story is that <input type=number> is not treated equally in different browsers.

Anyone willing to post their results below for other browsers, like Safari, please go ahead.

1 Like

I agree. However, there’s more to it than the eye can see.

Depending on particular application and its localization scope and ambitions, the number formatting per-se may not be sufficient. In our app, there are cases in which we tokenize entire strings made up of numbers, currency symbols, reserved keys, etc. and even though this particular post was not about one such case, but a much simpler one, we want to maintain the same UX throughout our entire application and in doing so propagate the same “flavor” of behavioral intuitivity.
.
In short, I don’t think it’s up to the browsers to implement an arbitrary subset of localization-sensitive behavior for it may be easily show insufficient and therefore subject to not being used at all, but again, that’s just me.