sialcasa / mvvmFX

an Application Framework for implementing the MVVM Pattern with JavaFX
Apache License 2.0
484 stars 105 forks source link

MvvmFX validation with JFoenix controls #593

Open piterso opened 5 years ago

piterso commented 5 years ago

Is it possible to use JFoenix Validators with mvvmFX Validators way?

Using yours Wiki on how to use validation in MvvmFX https://github.com/sialcasa/mvvmFX/wiki/Validation

i create example app but I use controls from JFoenix library and in this library i got very cool way of defining validators.

For example i define TextField in my FXML view file:

<JFXTextField fx:id="username" promptText="%Name" labelFloat="true">
    <validators>
        <RequiredFieldValidator message="%NameRequiredError"/>
    </validators>
</JFXTextField>

Then i create field in Code Behind View @FXML private JFXTextField username;

and to achieve validation and error message i have to write:

emulationName.focusedProperty().addListener(
    (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
        if (!newValue) emulationName.validate();
    });

and when i do all this when field is empty i got message like this:

jfoenix_way

When i follow your article on how to do validation in mvvmFX I end up with this ControlsFX look:

controlsfx_way_way

I would like to have all the validation logic in ViewModel but also would like to stick with JFoenix TextField. Do you have any ideas how i can achieve that?

manuel-mauky commented 5 years ago

I haven't used JFoenix so I don't know the details here. Based on your example code it looks like JFoenix is also mixing up validation logic and visualisation like the standard ControlsFX. If you want to use it with mvvmfx-validation you would only use the visualisation part of JFoenix while implementing the validation logic the mvvmFX-way.

To do this you would need to implement your own ValidationVisualizer with JFoenix. As a reference you could use the ControlsFxVisualizer. As I don't know the details of the JFoenix validation API I don't know how hard or easy this would be or if this is even possible. If you have any questions I'm happy to support.

No3x commented 5 years ago

I have looked into this just out of curiosity. I dynamically add a validator per control, if there is a error and set their error to true so they show up. This is the only way, because validation logic and visualisation are mixed up. The approach is incompatible with the declaration of Validators (<validator>) in the View but I think that's okay because they do not belong there anyway. Visualization of errors works with JFXComboBox, JFXDatePicker, JFXPasswordField, JFXTextArea, JFXTextField, JFXTimePicker only.

image

import com.jfoenix.controls.base.IFXValidatableControl;
import com.jfoenix.validation.RequiredFieldValidator;
import com.jfoenix.validation.base.ValidatorBase;
import de.saxsys.mvvmfx.utils.validation.Severity;
import de.saxsys.mvvmfx.utils.validation.ValidationMessage;
import de.saxsys.mvvmfx.utils.validation.visualization.ValidationVisualizerBase;
import javafx.scene.control.Control;
import org.kordamp.ikonli.javafx.FontIcon;

import java.util.Optional;

import static org.kordamp.ikonli.fontawesome.FontAwesome.EXCLAMATION_TRIANGLE;

/**
 * An implementation of {@link ValidationVisualizer} that uses the third-party library <a
 * href="https://github.com/jfoenixadmin/JFoenix">JFoenix</a> to visualize validation messages.
 * <p>
 * <strong>Please Note:</strong> The library JFoenix is not delivered with the mvvmFX library. If you like to use
 * this visualization you have to add the JFoenix library to your classpath, otherwise you will get
 * {@link NoClassDefFoundError}s and {@link ClassNotFoundException}s. If you are using a build management system like
 * <i>maven</i> or <i>gradle</i> you simply have to add the library as dependency.
 * This implementation is incompatible with validator declarations in the FXML.
 *
 */
public class JFoenixVisualizer extends ValidationVisualizerBase {

    private static final String ERROR = "error";
    private static final String REQUIRED_FIELD_MSG = "Required field";

    @Override
    public void applyRequiredVisualization(Control control, boolean required) {
        if (control instanceof IFXValidatableControl) {
            final IFXValidatableControl ifxValidatableControl = (IFXValidatableControl) control;
            decorateControl(ifxValidatableControl, Severity.ERROR, REQUIRED_FIELD_MSG, required);
        }
    }

    @Override
    public void applyVisualization(Control control, Optional<ValidationMessage> messageOptional, boolean required) {
        if (control instanceof IFXValidatableControl) {
            final IFXValidatableControl ifxValidatableControl = (IFXValidatableControl) control;
            ifxValidatableControl.resetValidation();

            if (messageOptional.isPresent()) {
                final ValidationMessage message = messageOptional.get();
                decorateControl(ifxValidatableControl, message.getSeverity(), message.getMessage(), required);
            } else if (required) {
                applyRequiredVisualization(control, required);
            }
        }
    }

    private void decorateControl(IFXValidatableControl control, Severity severity, String message, boolean required) {
        ValidatorBase validator;
        if (required) {
            validator = new RequiredFieldValidator();
        } else {
            validator = new ValidatorBase() {
                @Override
                protected void eval() {
                    hasErrors.set(true);
                }
            };
        }

        FontIcon warnIcon = new FontIcon(EXCLAMATION_TRIANGLE);
        warnIcon.getStyleClass().add(ERROR);
        validator.setSrcControl((Control) control);
        validator.setIcon(warnIcon);
        validator.setMessage(message);

        // Caution: setValidators is implemented as add by JFX..
        control.resetValidation();
        control.getValidators().setAll(validator);
        control.validate();
    }
}

Requires

compile 'de.saxsys:mvvmfx-validation:1.8.0-modified'
compile 'com.jfoenix:jfoenix:8.0.8'
compile 'org.kordamp.ikonli:ikonli-javafx:1.9.0'
compile 'org.kordamp.ikonli:ikonli-fontawesome-pack:1.9.0'

I had to set the visibility to protected to overwrite ValidationVisualizerBase methods. It's messy and hacked - would not include into mvvmfx.