How to change the validator in a TypedTextField<Double>

Hello,

I am trying to dynamically change the validator of a TypedTextField<Double> based on the value of another component. However, it doesn’t seem to be working because the validators appear to be stacking instead of replacing each other, causing multiple validations to be applied simultaneously.

How can I ensure that only one validator is active at a time?

Here is my code:

        dialogs.createInputDialog(this)
                .withHeader("Type de fichier")
                .withParameters(
                        InputParameter.stringParameter("invoiceNumber").withLabel("Numéro de facture").withRequired(true)
                                .withRequiredMessage("Le numéro de facture est obligatoire"),
                        InputParameter.bigDecimalParameter("ht").withLabel("Montant HT").withRequired(true)
                                .withRequiredMessage("Le montant HT est obligatoire"),
                        InputParameter.bigDecimalParameter("ttc").withLabel("Montant TTC").withRequired(true)
                                .withRequiredMessage("Le montant TTC est obligatoire"),
                        parameter("certifType")
                                .withLabel("Type de certification")

                                .withField(() -> {
                                    JmixComboBox<CertificationType> certifTypeCb = uiComponents.create(JmixComboBox.class);
                                    certifTypeCb.setItems(certificationTypes);
                                    certifTypeCb.setWidthFull();
                                    certifTypeCb.setEnabled(!certificationTypes.isEmpty());
                                    certifTypeCb.setRequired(!certificationTypes.isEmpty());
                                    certifTypeCb.setRequiredMessage("Le type de certification est obligatoire");
                                    if (!certificationTypes.isEmpty()) {
                                        certifTypeCb.setValue(certificationTypes.getFirst());
                                    }
                                    return certifTypeCb;
                                }),
                        parameter("certifValue")
                                .withLabel("Valeur")
                                .withField(() -> {
                                    TypedTextField<Double> certifValue = uiComponents.create(TypedTextField.class);
                                    certifValue.setDatatype(datatypeRegistry.get(Double.class));
                                    certifValue.setId("certifValueField");
                                    certifValue.setTooltipText(productCertification.getTonDelivered() + " tonnes");
                                    certifValue.setWidthFull();
                                    certifValue.setRequired(!certificationTypes.isEmpty());
                                    certifValue.setRequiredMessage("Le valeur est obligatoire");
                                    JmixComboBox<String> unitCb = uiComponents.create(JmixComboBox.class);
                                    unitCb.setItems(Arrays.asList("T", "%"));
                                    unitCb.setId("unitCb");
                                    unitCb.setValue("%");
                                    createPourcentageValidator(certifValue);
                                    unitCb.setWidth("70px");
                                    unitCb.setAllowCustomValue(false);

                                    certifValue.setSuffixComponent(unitCb);
                                    certifValue.setEnabled(!certificationTypes.isEmpty());

                                    unitCb.addValueChangeListener(changeEvent -> {
                                        String selectedUnit = changeEvent.getValue();
                                        if ("%".equals(selectedUnit)) {
                                            createPourcentageValidator(certifValue);
                                        } else if ("T".equals(selectedUnit)) {
                                            // Supprimer la validation si l'unité n'est pas '%'
                                            certifValue.addValidator(value -> {
                                                if (value != null && (value < 0 || value > productCertification.getTonDelivered().doubleValue())) {
                                                    throw new ValidationException("La valeur de tonnes certifiées ne peut être supérieure à la quantité livrée.");
                                                }
                                            });
                                            certifValue.setTypedValue(0.0);
                                        } else {
                                            unitCb.setValue("%");
                                        }
                                    });

                                    return certifValue;
                                })
                )
                .withActions(DialogActions.OK_CANCEL)
                .withCloseListener(closeEvent -> validateUpload(closeEvent, certificationTypes, productCertification, fileContent, fileName)).open();
    public void createPourcentageValidator(TypedTextField<Double> certifValue) {
        // Appliquer une restriction entre 0 et 100
        certifValue.addValidator(value -> {
            if (value != null && (value < 0 || value > 100)) {
                throw new ValidationException("La valeur doit être comprise entre 0 et 100 si l'unité est '%'.");
            }
        });
        certifValue.setTypedValue(0.0);
    }

Thank you in advance for your help.
Best regards,
Thomas

Hello!

The addValidator() does not replace previously added validator, it always adds new one. This behavior is by design.

InputDialog can be build with cross-field validator: dialogs.createInputDialog(this).withValidator(); however it is invoked after click on “OK” or “YES” buttons.

In your case, you can create single validator that checks all cases depending on unitCb field value. For instance:

public static class CertifValidator implements Validator<Double> {

    private final JmixComboBox<String> unitCb;

    public CertifValidator(JmixComboBox<String> unitCb) {
        this.unitCb = unitCb;
    }
    @Override
    public void accept(Double value) {
        if ("%".equals(unitCb.getValue())) {
            // validate
        } else if ("T".equals(unitCb.getValue())) {
            // validate
        }
    }
}