com.jfoenix.validation.base.ValidatorBase Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.jfoenix.validation.base;
import com.jfoenix.controls.JFXTooltip;
import com.jfoenix.validation.RegexValidator;
import com.jfoenix.validation.RequiredFieldValidator;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Tooltip;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
/**
* An abstract class that defines the basic validation functionality for a certain control.
*
* @author Shadi Shaheen
* @version 1.0
* @since 2016-03-09
*/
public abstract class ValidatorBase {
/**
* This {@link PseudoClass} will be activated when a validation error occurs.
*
* Some components have default styling for this pseudo class. See {@code jfx-text-field.css}
* and {@code jfx-combo-box.css} for examples.
*/
public static final PseudoClass PSEUDO_CLASS_ERROR = PseudoClass.getPseudoClass("error");
/**
* When using {@code Tooltip.install(node, tooltip)}, the given tooltip is stored in the Node's properties
* under this key.
*
* @see Tooltip#install(Node, Tooltip)
*/
private static final String TOOLTIP_PROP_KEY = "javafx.scene.control.Tooltip";
/**
* Default error tooltip style class
*/
public static final String ERROR_TOOLTIP_STYLE_CLASS = "error-tooltip";
/**
* Key used to stash control tooltip upon validation
*/
private static final String TEMP_TOOLTIP_KEY = "stashed-tootlip";
/**
* supported tooltips keys
*/
private static final Set supportedTooltipKeys = new HashSet<>(
Arrays.asList(
"javafx.scene.control.Tooltip",
"jfoenix-tooltip"
)
);
/**
* @param message will be set as the validator's {@link #message}.
* @see #ValidatorBase()
*/
public ValidatorBase(String message) {
this();
this.setMessage(message);
}
/**
* When creating a new validator you need to define the validation condition by implementing {@link #eval()}.
*
* For examples of how you might implement it, see {@link RequiredFieldValidator} and
* {@link RegexValidator}.
*/
public ValidatorBase() {
}
///////////////////////////////////////////////////////////////////////////
// Methods
///////////////////////////////////////////////////////////////////////////
/**
* Will validate the source control.
*
* Calls {@link #eval()} and then {@link #onEval()}.
*/
public void validate() {
eval();
onEval();
}
/**
* Should evaluate the validation condition and set {@link #hasErrors} to true or false. It should
* be true when the value is invalid (it has errors) and false when the value is valid (no errors).
*
* This method is fired once {@link #validate()} is called.
*/
protected abstract void eval();
/**
* This method will update the source control after evaluating the validation condition (see {@link #eval()}).
*
* If the validator isn't "passing" the {@link #PSEUDO_CLASS_ERROR :error} pseudoclass is applied to the
* {@link #srcControl}.
*
* Applies the {@link #PSEUDO_CLASS_ERROR :error} pseudo class and the errorTooltip to
* the {@link #srcControl}.
*/
protected void onEval() {
Node control = getSrcControl();
boolean invalid = hasErrors.get();
control.pseudoClassStateChanged(PSEUDO_CLASS_ERROR, invalid);
Tooltip activeTooltip = getActiveTooltip(control);
if (invalid) {
Tooltip errorTooltip = errorTooltipSupplier.get();
errorTooltip.getStyleClass().add(ERROR_TOOLTIP_STYLE_CLASS);
errorTooltip.setText(getMessage());
install(control, activeTooltip, errorTooltip);
} else {
Tooltip orgTooltip = (Tooltip) control.getProperties().remove(TEMP_TOOLTIP_KEY);
install(control, activeTooltip, orgTooltip);
}
}
private final Tooltip getActiveTooltip(Node node) {
Tooltip tooltip = null;
for (String key : supportedTooltipKeys) {
tooltip = (Tooltip) node.getProperties().get(key);
if (tooltip != null) {
break;
}
}
return tooltip;
}
private void install(Node node, Tooltip oldVal, Tooltip newVal) {
// stash old tooltip if it's not error tooltip
if (oldVal != null && !oldVal.getStyleClass().contains(ERROR_TOOLTIP_STYLE_CLASS)) {
node.getProperties().put(TEMP_TOOLTIP_KEY, oldVal);
}
if (node instanceof Control) {
// uninstall
if (oldVal != null) {
if (oldVal instanceof JFXTooltip) {
JFXTooltip.uninstall(node);
}
if (newVal == null || !(newVal instanceof JFXTooltip)) {
((Control) node).setTooltip(newVal);
return;
}
if (newVal instanceof JFXTooltip) {
((Control) node).setTooltip(null);
}
}
// install
if (newVal instanceof JFXTooltip) {
install(node, newVal);
} else {
((Control) node).setTooltip(newVal);
}
} else {
uninstall(node, oldVal);
install(node, newVal);
}
}
private void uninstall(Node node, Tooltip tooltip) {
if (tooltip instanceof JFXTooltip) {
JFXTooltip.uninstall(node);
} else {
Tooltip.uninstall(node, tooltip);
}
}
private void install(Node node, Tooltip tooltip) {
if (tooltip == null) {
return;
}
if (tooltip instanceof JFXTooltip) {
JFXTooltip.install(node, (JFXTooltip) tooltip);
} else {
Tooltip.install(node, tooltip);
}
}
///////////////////////////////////////////////////////////////////////////
// Properties
///////////////////////////////////////////////////////////////////////////
/**
* The {@link Control}/{@link Node} that the validator is checking the value of.
*
* Supports {@link Node}s because not all things that need validating are {@link Control}s.
*/
protected SimpleObjectProperty srcControl = new SimpleObjectProperty<>();
/**
* @see #srcControl
*/
public void setSrcControl(Node srcControl) {
this.srcControl.set(srcControl);
}
/**
* @see #srcControl
*/
public Node getSrcControl() {
return this.srcControl.get();
}
/**
* @see #srcControl
*/
public ObjectProperty srcControlProperty() {
return this.srcControl;
}
/**
* Tells whether the validator is "passing" or not.
*
* In a validator's implementation of {@link #eval()}, if the value the validator is checking is invalid, it should
* set this to true. If the value is valid, it should set this to false.
*
* When hasErrors is true, the validator will automatically apply the {@link #PSEUDO_CLASS_ERROR :error}
* pseudoclass to the {@link #srcControl}; the {@link #srcControl} will also have a {@link Tooltip} containing the
* {@link #message} applied to it.
*/
protected ReadOnlyBooleanWrapper hasErrors = new ReadOnlyBooleanWrapper(false);
/**
* @see #hasErrors
*/
public boolean getHasErrors() {
return hasErrors.get();
}
/**
* @see #hasErrors
*/
public ReadOnlyBooleanProperty hasErrorsProperty() {
return hasErrors.getReadOnlyProperty();
}
private Supplier errorTooltipSupplier = () -> new Tooltip();
public Supplier getErrorTooltipSupplier() {
return errorTooltipSupplier;
}
public void setErrorTooltipSupplier(Supplier errorTooltipSupplier) {
this.errorTooltipSupplier = errorTooltipSupplier;
}
/**
* The error message to display when the validator is not "passing."
*
* When {@link #hasErrors} is true, this message is displayed near the {@link #srcControl} (usually below);
* it's also displayed in a {@link Tooltip} applied to the {@link #srcControl}.
*/
protected SimpleStringProperty message = new SimpleStringProperty();
/**
* @see #message
*/
public void setMessage(String msg) {
this.message.set(msg);
}
/**
* @see #message
*/
public String getMessage() {
return this.message.get();
}
/**
* @see #message
*/
public StringProperty messageProperty() {
return this.message;
}
/***** Icon *****/
protected SimpleObjectProperty> iconSupplier = new SimpleObjectProperty>();
public void setIconSupplier(Supplier icon) {
this.iconSupplier.set(icon);
}
public SimpleObjectProperty> iconSupplierProperty() {
return this.iconSupplier;
}
public Supplier getIconSupplier() {
return iconSupplier.get();
}
/**
* allow setting icon in FXML
*
* @param icon
*/
public void setIcon(Node icon) {
iconSupplier.set(() -> icon);
}
public Node getIcon() {
if (iconSupplier.get() == null) {
return null;
}
Node icon = iconSupplier.get().get();
if (icon != null) {
icon.getStyleClass().add("error-icon");
}
return icon;
}
}