Loading...
Important Notice - Forums is archived

To simplify things and help our users to be more productive, we have archived the current forum and focus our efforts on helping developers on Stack Overflow. You can post new questions on Stack Overflow or join our Discord channel.

Product icon
TUTORIAL

Vaadin lets you build secure, UX-first PWAs entirely in Java.
Free ebook & tutorial.

extending TextField to handle BigDecimal values

Marco Zanon
9 years ago May 13, 2012 5:39pm
Johannes Häyry
9 years ago May 14, 2012 7:20am
Marco Zanon
9 years ago May 14, 2012 7:11pm
Jens Jansson
9 years ago May 15, 2012 6:22am
Marco Zanon
9 years ago May 15, 2012 12:13pm
Marco Zanon
9 years ago May 15, 2012 9:38pm
Charles Anthony
9 years ago May 16, 2012 7:50am
Marco Zanon
9 years ago May 16, 2012 8:45pm
Fabiano Bonin
9 years ago Mar 20, 2013 8:27pm

Hi,

There is a better way to achieve this, extending the class CustomField. This way you will not have to represent your BigDecimal value as a String.

Here is a working implementation. This class can be adapted to edit any numeric type (just replace BigDecimal with the type).

It also formats the number according to the system Locale. You can also input the formatted number, and it will parse it correctly.

package br.com.personalsoft.engine.aplicacao.vaadin.controles;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

import com.vaadin.data.util.converter.Converter.ConversionException;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomField;
import com.vaadin.ui.TextField;

public class BigDecimalFieldPS extends CustomField<BigDecimal> {

	private static final long serialVersionUID = -5150605101121179296L;
	private TextField content;
	private NumberFormat numberFormat;
	private int recursions = 0;

	public BigDecimalFieldPS() {
		DecimalFormat numberFormat = (DecimalFormat) DecimalFormat.getNumberInstance(Locale
				.getDefault());
		numberFormat.setParseBigDecimal(true);

		this.numberFormat = numberFormat;

		init();
	}

	public BigDecimalFieldPS(NumberFormat numberFormat) {
		this.numberFormat = numberFormat;

		init();
	}

	private void init() {
		content = new TextField();
		content.setNullSettingAllowed(true);
		content.setNullRepresentation("");
		content.addValueChangeListener(new ValueChangeListener() {
			private static final long serialVersionUID = -4994220730867294055L;

			@Override
			public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
				if (recursions == 0) {
					recursions++;
					try {
						if (event.getProperty().getValue() == null)
							BigDecimalFieldPS.this.setValue(null);
						else {
							try {
								BigDecimal bigDecimal = (BigDecimal) numberFormat.parse((String) event
										.getProperty().getValue());
								BigDecimalFieldPS.this.setValue(bigDecimal);
							} catch (com.vaadin.data.Property.ReadOnlyException | ConversionException
									| ParseException e) {
							}
							if (BigDecimalFieldPS.this.getValue() == null)
								content.setValue(null);
							else
								content.setValue(numberFormat.format(BigDecimalFieldPS.this.getValue()));
						}
					} finally {
						recursions--;
					}
				}
			}
		});

		setImmediate(true);
	}

	@Override
	protected Component initContent() {
		return content;
	}

	@Override
	public Class<? extends BigDecimal> getType() {
		return BigDecimal.class;
	}

	@Override
	protected void setInternalValue(BigDecimal newValue) {
		super.setInternalValue(newValue);

		recursions++;
		try {
			if (newValue == null)
				content.setValue(null);
			else
				content.setValue(numberFormat.format(newValue));
		} finally {
			recursions--;
		}
	}

	@Override
	public void setImmediate(boolean immediate) {
		super.setImmediate(immediate);
		content.setImmediate(immediate);
	}

	@Override
	public void setSizeFull() {
		super.setSizeFull();
		content.setSizeFull();
	}

	@Override
	public void setSizeUndefined() {
		super.setSizeUndefined();
		content.setSizeUndefined();
	}

	@Override
	public void setWidth(String width) {
		super.setWidth(width);
		content.setWidth(width);
	}

	@Override
	public void setHeight(String height) {
		super.setHeight(height);
		content.setHeight(height);
	}

	public void selectAll() {
		content.selectAll();
	}

}
Last updated on Mar, 20th 2013
Henri Sara
9 years ago Mar 21, 2013 7:47am
Fabiano Bonin
9 years ago Apr 01, 2013 1:16pm
Marco Zanon
8 years ago May 04, 2013 2:12pm
David Wall
8 years ago May 28, 2013 10:03pm

I find that Converters are not doing the function of field validation correctly. One problem is that the exception text included in a ConversionException is not shown to the user, so they can a built-in meaningless exception like "Could not convert value to BigDecimal".

It seems like a Validator should be used on the field to first ensure the input data is reasonable before creating the BigDecimal object. By trying to create a BigDecimal using unvalidated input, you get the odd converter exception without any better reasoning shown to the user. In general, creating an object by initializing it with unvalidated input is not a good choice when a validator has been set to check this first, but unfortunately the conversion takes place before the validation, which is just not right, so the validator is never called to give the user a nice error for invalid input (the purpose of the validator in the first place).

Clearly, it should validate the input using the presentation object (String), and only attempt to create the model object (Integer) if it passes that test. In general, validators are added to fields, but converters are more general purpose, so it makes sense that the user error should come from the field's validator and not from the common model converter exception.

First, run the validator if set. If it passes, then convert that input into the model object.

Charles Anthony
8 years ago May 29, 2013 7:07am
David Wall
8 years ago May 29, 2013 5:22pm

Hey Charles, I guess if they fix so the converter exception message appears to the user, it will help resolve this issue for me. I can at least get rid of the user-unfriendly error message like "Could not convert value to EsfIPAddr".... or BigDecimal or anything other class name that is leaked to the UI but has no meaning to the user.

I am all for the presentation validation, though, if so many people rely on using the model objects to be invalidly constructed before they the test to see if the model object is valid. I really think there is no good reason to say that unvalidated user input should be passed into the constructor of a model object, though. This is the definition of an attack vector, applying unvalidated data directly into your business logic. Most model objects have no concept of holding an "invalid" value, only valid values, so if the data entered by the user is not correct, attempting to create the model based on it is not correct.

I am more than happy to switch to PresentationValidators, but alas there is no such thing.

Sacha Piccardi
7 years ago Apr 18, 2014 4:55pm
member sound
7 years ago May 30, 2014 3:01pm