Enforce two digits with BigDecimalField (Vaadin 14.1.17)

Hi, I’m designing an input form for bookkeeping.
So it’s important to align all numbers the same and always show monetary amounts with two decimal places after the comma.
How can I reach this using BigDecimalField in Vaadin 14.1.17?

I tried calling BigDecimal.setScale in a Converter class and also in the setter- and getter-methods in the bound datamodel-class.

Behaviour stays the same: when the user changes e.g. “123,00” to “123”, then the BigDecimalField just shows “123”, but unfortunately not “123,00”.
18110480.png

I’m not aware of any good way of doing what you describe, so I created https://github.com/vaadin/vaadin-text-field-flow/issues/282 a while ago.

The workaround I ended up with was a value change listener on the field that check the scale() of the new value and does setValue again if the scale is not as expected. This approach does, however, have a couple of unwanted side effects as well :frowning:

Another possible solution is to use JavaScript to target the input field of the vaadin-big-decimal-field component, like so:

Page page = UI.getCurrent().getPage();
page.executeJs("var matches = document.querySelectorAll(\"vaadin-big-decimal-field\");\n" + 
				"	var i;\n" + 
				"	for (i = 0; i < matches.length; i++) {\n" + 
				"		matches[i]
.shadowRoot.querySelector(\"input\")\n" + 
				"				.setAttribute(\"onchange\",\n" + 
				"						\"(function(el){el.value=parseFloat(el.value).toFixed(2);})(this)\");\n" + 
				"	}");

There is currently no easy way to achieve this behaviour with BigDecimalField. There’s an issue about it here https://github.com/vaadin/vaadin-text-field-flow/issues/282

I think the BigDecimal.setScale only affects how it is displayed when the value is set from Java as it is converted to string simply via BigDecimal.toPlainString() (see the [formatter method source]
(https://github.com/vaadin/vaadin-text-field-flow/blob/master/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/BigDecimalField.java#L80-L83)).

As a workaround for now you should be able to get this behaviour by adding an event listener which would update the value with the correct format every time the user changes it. Should be doable at least on the client side by adding a listener to the change event of the field with some JS code for modifying the value. Might also be possible using a server side ValueChangeListener and having it do something like setValue(value.setScale(2)) (though I’m not sure if this server side approach works).

EDIT: Oops I guess I had this tab open a bit too long since I didn’t see those previous answers before posting mine.

Tarek Oraby:
Another possible solution is to use JavaScript to target the input field of the vaadin-big-decimal-field component, like so:

Your solution won’t work for any vaadin-big-decimal-field elements that might be inside shadow roots (e.g if you’re defining your layout with PolymerTemplate). Also your code with parseFloat won’t parse the number correctly in all cases depending on the Locale like in the example in original post with comma as a decimal separator (it would just drop the decimals).

Here’s an extension of BigDecimalField which should work to give the behaviour you need.

my-big-decimal-field.js:

(function() {

  const changeListener = function(e) {
    this.inputElement.value = parseFloat(this.value.split(this._decimalSeparator).join('.')).toFixed(2).split('.').join(this._decimalSeparator);
  };

  customElements.whenDefined('vaadin-big-decimal-field').then(() => {

    class MyBigDecimalField extends customElements.get('vaadin-big-decimal-field') {

      static get is() {
        return 'my-big-decimal-field';
      }

      ready() {
        super.ready();
        this.addEventListener('change', changeListener);
      }

    }

    customElements.define(MyBigDecimalField.is, MyBigDecimalField);

  });
})();

MyBigDecimalField.java:

@Tag("my-big-decimal-field")
@JsModule("./my-big-decimal-field.js")
class MyBigDecimalField extends BigDecimalField {
}

Then just use MyBigDecimalField instead of BigDecimalField.

@Kari: This works like a charm :slight_smile:

Tested with Vaadin 14.1.17, in Firefox 73 and Chromium 80.0.3987.116.

Great. Thank you!

I would suggest

class MyBigDecimalField extends BigDecimalField {

    MyBigDecimalField(final int descendants){
        setPattern(calcPattern(descendants));
        setPreventInvalidInput(true);
    }

    private String calcPattern(final int descendants){
        String pattern = "[0-9]
*";
        if (descendants > 0){
            pattern += "(,|.)?";
        }
        pattern+="[0-9]
{0,"+descendants+"}";
       return pattern;
    }

}

and maybe also

grid.addEditColumn(vp).custom(field, (item, value) -> {item.setDecimal(value.setScale(2, RoundingMode.HALF_UP));} ).setHeader("BigDecimal");

to use it in a GridPro.

Thinking about it, it does not exactly nail the OP’s question, but the question people often search the web for and then find this thread. The question that goes with my answer would really be “I need BigDecimalField to allow only certain numeric inputs, such that there’s only a fixed number of digits right of the decimal separator (also known as descendants)”.