All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.flow.component.textfield.BigDecimalField Maven / Gradle / Ivy

There is a newer version: 24.4.12
Show newest version
/**
 * Copyright 2000-2024 Vaadin Ltd.
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See  {@literal }  for the full
 * license.
 */
package com.vaadin.flow.component.textfield;

import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.CompositionNotifier;
import com.vaadin.flow.component.HasHelper;
import com.vaadin.flow.component.HasLabel;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasValidation;
import com.vaadin.flow.component.InputNotifier;
import com.vaadin.flow.component.KeyNotifier;
import com.vaadin.flow.component.Synchronize;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.dependency.JavaScript;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.shared.ClientValidationUtil;
import com.vaadin.flow.component.shared.HasClientValidation;
import com.vaadin.flow.component.shared.ValidationUtil;
import com.vaadin.flow.data.binder.HasValidator;
import com.vaadin.flow.data.binder.ValidationResult;
import com.vaadin.flow.data.binder.ValidationStatusChangeEvent;
import com.vaadin.flow.data.binder.ValidationStatusChangeListener;
import com.vaadin.flow.data.binder.Validator;
import com.vaadin.flow.data.value.HasValueChangeMode;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.function.SerializableBiFunction;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.shared.Registration;

/**
 * Server-side component for the {@code vaadin-big-decimal-field} element. This
 * field uses {@link BigDecimal} as the server-side value type, which allows
 * handling decimal numbers with high precision. The component also prevents
 * users from entering characters which can't be used in a decimal number, such
 * as alphabets.
 * 

* When setting values from the server-side, the {@code scale} of the provided * {@link BigDecimal} is preserved in the presentation format shown to the user, * as described in {@link #setValue(BigDecimal)}. * * @author Vaadin Ltd. */ @Tag("vaadin-big-decimal-field") @JavaScript("frontend://vaadin-big-decimal-field.js") @JsModule("./vaadin-big-decimal-field.js") public class BigDecimalField extends GeneratedVaadinTextField implements HasSize, HasValidation, HasValueChangeMode, HasPrefixAndSuffix, InputNotifier, KeyNotifier, CompositionNotifier, HasAutocomplete, HasAutocapitalize, HasAutocorrect, HasHelper, HasLabel, HasValidator, HasClientValidation { private ValueChangeMode currentMode; private boolean isConnectorAttached; private int valueChangeTimeout = DEFAULT_CHANGE_TIMEOUT; private boolean required; private Locale locale; private static final SerializableBiFunction PARSER = ( field, valueFromClient) -> { if (valueFromClient == null || valueFromClient.isEmpty()) { return null; } try { return new BigDecimal( valueFromClient.replace(field.getDecimalSeparator(), '.')); } catch (NumberFormatException e) { return null; } }; private static final SerializableBiFunction FORMATTER = ( field, valueFromModel) -> valueFromModel == null ? "" : valueFromModel.toPlainString().replace('.', field.getDecimalSeparator()); /** * Constructs an empty {@code BigDecimalField}. */ public BigDecimalField() { super(null, null, String.class, PARSER, FORMATTER); setLocale(Optional.ofNullable(UI.getCurrent()).map(UI::getLocale) .orElse(Locale.ROOT)); // workaround for https://github.com/vaadin/flow/issues/3496 setInvalid(false); setValueChangeMode(ValueChangeMode.ON_CHANGE); addValueChangeListener(e -> validate()); if (isEnforcedFieldValidationEnabled()) { addClientValidatedEventListener(e -> validate()); } } /** * Constructs an empty {@code BigDecimalField} with the given label. * * @param label * the text to set as the label */ public BigDecimalField(String label) { this(); setLabel(label); } /** * Constructs an empty {@code BigDecimalField} with the given label and * placeholder text. * * @param label * the text to set as the label * @param placeholder * the placeholder text to set */ public BigDecimalField(String label, String placeholder) { this(label); setPlaceholder(placeholder); } /** * Constructs a {@code BigDecimalField} with the given label, an initial * value and placeholder text. * * @param label * the text to set as the label * @param initialValue * the initial value * @param placeholder * the placeholder text to set * @see #setValue(Object) * @see #setPlaceholder(String) */ public BigDecimalField(String label, BigDecimal initialValue, String placeholder) { this(label); setValue(initialValue); setPlaceholder(placeholder); } /** * Constructs an empty {@code BigDecimalField} with a value change listener. * * @param listener * the value change listener * @see #addValueChangeListener(com.vaadin.flow.component.HasValue.ValueChangeListener) */ public BigDecimalField( ValueChangeListener> listener) { this(); addValueChangeListener(listener); } /** * Constructs an empty {@code BigDecimalField} with a label and a value * change listener. * * @param label * the text to set as the label * @param listener * the value change listener * @see #setLabel(String) * @see #addValueChangeListener(com.vaadin.flow.component.HasValue.ValueChangeListener) */ public BigDecimalField(String label, ValueChangeListener> listener) { this(label); addValueChangeListener(listener); } /** * Constructs an empty {@code BigDecimalField} with a label,a value change * listener and an initial value. * * @param label * the text to set as the label * @param initialValue * the initial value * @param listener * the value change listener * @see #setLabel(String) * @see #setValue(Object) * @see #addValueChangeListener(com.vaadin.flow.component.HasValue.ValueChangeListener) */ public BigDecimalField(String label, BigDecimal initialValue, ValueChangeListener> listener) { this(label); setValue(initialValue); addValueChangeListener(listener); } /** * {@inheritDoc} *

* The default value is {@link ValueChangeMode#ON_CHANGE}. */ @Override public ValueChangeMode getValueChangeMode() { return currentMode; } @Override public void setValueChangeMode(ValueChangeMode valueChangeMode) { currentMode = valueChangeMode; setSynchronizedEvent( ValueChangeMode.eventForMode(valueChangeMode, "value-changed")); applyChangeTimeout(); } @Override public void setValueChangeTimeout(int valueChangeTimeout) { this.valueChangeTimeout = valueChangeTimeout; applyChangeTimeout(); } @Override public int getValueChangeTimeout() { return valueChangeTimeout; } private void applyChangeTimeout() { ValueChangeMode.applyChangeTimeout(getValueChangeMode(), getValueChangeTimeout(), getSynchronizationRegistration()); } @Override public String getErrorMessage() { return super.getErrorMessageString(); } @Override public void setErrorMessage(String errorMessage) { super.setErrorMessage(errorMessage); } @Override public boolean isInvalid() { return isInvalidBoolean(); } @Override public void setInvalid(boolean invalid) { super.setInvalid(invalid); } /** * Sets the label for this component. * * @param label * value for the {@code label} property in the webcomponent */ @Override public void setLabel(String label) { super.setLabel(label); } /** * String used for the label element. * * @return the {@code label} property from the webcomponent */ @Override public String getLabel() { return getLabelString(); } @Override public void setPlaceholder(String placeholder) { super.setPlaceholder(placeholder); } /** * A hint to the user of what can be entered in the component. * * @return the {@code placeholder} property from the webcomponent */ public String getPlaceholder() { return getPlaceholderString(); } /** * Specifies if the field value gets automatically selected when the field * gains focus. * * @return true if autoselect is active, false * otherwise */ public boolean isAutoselect() { return super.isAutoselectBoolean(); } /** * Set to true to always have the field value automatically * selected when the field gains focus, false otherwise. * * @param autoselect * true to set auto select on, false * otherwise */ @Override public void setAutoselect(boolean autoselect) { super.setAutoselect(autoselect); } /** * Gets the visibility state of the button which clears the field. * * @return true if the button is visible, false * otherwise */ public boolean isClearButtonVisible() { return isClearButtonVisibleBoolean(); } /** * Set to false to hide the clear button which clears the text * field. * * @param clearButtonVisible * true to set the button visible, * false otherwise */ @Override public void setClearButtonVisible(boolean clearButtonVisible) { super.setClearButtonVisible(clearButtonVisible); } @Override public void setAutofocus(boolean autofocus) { super.setAutofocus(autofocus); } /** * Specify that this control should have input focus when the page loads. * * @return the {@code autofocus} property from the webcomponent */ public boolean isAutofocus() { return isAutofocusBoolean(); } /** * The text usually displayed in a tooltip popup when the mouse is over the * field. * * @return the {@code title} property from the webcomponent */ public String getTitle() { return super.getTitleString(); } @Override public void setTitle(String title) { super.setTitle(title); } @Override public BigDecimal getEmptyValue() { return null; } /** * Sets the value of this field. If the new value is not equal to * {@code getValue()}, fires a value change event. *

* You can adjust how the value is presented in the field with the APIs * provided by the value type {@link BigDecimal}. For example, you can * change the number of decimal places with * {@link BigDecimal#setScale(int)}. This doesn't however restrict the user * from entering values with different number of decimals. Note that * BigDecimals are immutable, so their methods will return new instances * instead of editing the existing ones. Scientific notation (such as 1e9) * is turned into plain number format for the presentation. * * @param value * the new value */ @Override public void setValue(BigDecimal value) { BigDecimal oldValue = getValue(); super.setValue(value); if (Objects.equals(oldValue, getEmptyValue()) && Objects.equals(value, getEmptyValue()) && isInputValuePresent()) { // Clear the input element from possible bad input. getElement().executeJs("this._inputElementValue = ''"); getElement().setProperty("_hasInputValue", false); fireEvent(new ClientValidatedEvent(this, false)); } else { // Restore the input element's value in case it was cleared // in the above branch. That can happen when setValue(null) // and setValue(...) are subsequently called within one round-trip // and there was bad input. getElement().executeJs("this._inputElementValue = this.value"); } } /** * Returns the current value of the field. By default, the empty * BigDecimalField will return {@code null}. * * @return the current value. */ @Override public BigDecimal getValue() { return super.getValue(); } /** * Performs server-side validation of the current value. This is needed * because it is possible to circumvent the client-side validation * constraints using browser development tools. */ @Override protected void validate() { BigDecimal value = getValue(); boolean isRequired = isRequiredIndicatorVisible(); ValidationResult requiredValidation = ValidationUtil .checkRequired(isRequired, value, getEmptyValue()); setInvalid( requiredValidation.isError() || checkValidity(value).isError()); } private ValidationResult checkValidity(BigDecimal value) { boolean hasNonParsableValue = Objects.equals(value, getEmptyValue()) && isInputValuePresent(); if (hasNonParsableValue) { return ValidationResult.error(""); } return ValidationResult.ok(); } @Override public Validator getDefaultValidator() { return (value, context) -> checkValidity(value); } @Override public Registration addValidationStatusChangeListener( ValidationStatusChangeListener listener) { if (isEnforcedFieldValidationEnabled()) { return addClientValidatedEventListener( event -> listener.validationStatusChanged( new ValidationStatusChangeEvent(this, !isInvalid()))); } return null; } /** * Returns whether the input element has a value or not. * * @return true if the input element's value is populated, * false otherwise */ @Synchronize(property = "_hasInputValue", value = "has-input-value-changed") private boolean isInputValuePresent() { return getElement().getProperty("_hasInputValue", false); } /** * Sets the locale for this BigDecimalField. It is used to determine which * decimal separator (the radix point) should be used. * * @param locale * the locale to set, not {@code null} */ public void setLocale(Locale locale) { Objects.requireNonNull(locale, "Locale to set can't be null."); this.locale = locale; setDecimalSeparator( new DecimalFormatSymbols(locale).getDecimalSeparator()); } /** * Gets the locale used by this BigDecimalField. It is used to determine * which decimal separator (the radix point) should be used. * * @return the locale of this field, never {@code null} */ @Override public Locale getLocale() { return locale; } /** * Updates two things at client-side: changes the decimal separator in the * current input value, and updates the invalid input prevention to accept * the new decimal separator. */ private void setDecimalSeparator(char decimalSeparator) { getElement().setProperty("_decimalSeparator", decimalSeparator + ""); } private char getDecimalSeparator() { return getElement().getProperty("_decimalSeparator").charAt(0); } @Override protected void onAttach(AttachEvent attachEvent) { super.onAttach(attachEvent); if (isEnforcedFieldValidationEnabled()) { ClientValidationUtil .preventWebComponentFromModifyingInvalidState(this); } else { FieldValidationUtil.disableClientValidation(this); } } /** * Whether the full experience validation is enforced for the component. *

* Exposed with protected visibility to support mocking *

* The method requires the {@code VaadinSession} instance to obtain the * application configuration properties, otherwise, the feature is * considered disabled. * * @return {@code true} if enabled, {@code false} otherwise. */ protected boolean isEnforcedFieldValidationEnabled() { VaadinSession session = VaadinSession.getCurrent(); if (session == null) { return false; } DeploymentConfiguration configuration = session.getConfiguration(); if (configuration == null) { return false; } return configuration.isEnforcedFieldValidationEnabled(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy