org.omnifaces.component.validator.ValidateMultipleFields Maven / Gradle / Ivy
Show all versions of omnifaces Show documentation
/*
* Copyright OmniFaces
*
* Licensed 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
*
* https://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 org.omnifaces.component.validator;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static org.omnifaces.util.Components.findComponentsInChildren;
import static org.omnifaces.util.Components.getLabel;
import static org.omnifaces.util.Components.getValue;
import static org.omnifaces.util.Components.isEditable;
import static org.omnifaces.util.Components.validateHasNoChildren;
import static org.omnifaces.util.Components.validateHasParent;
import static org.omnifaces.util.Messages.addError;
import static org.omnifaces.util.Messages.addGlobalError;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import jakarta.faces.component.FacesComponent;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIForm;
import jakarta.faces.component.UIInput;
import jakarta.faces.component.UISelectBoolean;
import jakarta.faces.context.FacesContext;
import org.omnifaces.config.OmniFaces;
import org.omnifaces.util.State;
import org.omnifaces.validator.MultiFieldValidator;
/**
* Base class which is to be shared between all multi field validators. The implementors have to call the super
* constructor with the default message. The implementors have to override the
* {@link #validateValues(FacesContext, List, List)} method to perform the actual validation.
*
* General usage of all multiple field validators
*
* This validator must be placed inside the same UIForm
as the UIInput
components in question.
* The UIInput
components must be referenced by a space separated collection of their client IDs in the
* components
attribute. Since version 3.13 you can also specify a single client ID of a common parent.
* This validator can be placed anywhere in the form, but keep in mind that the
* components will be converted and validated in the order as they appear in the form. So if this validator is been
* placed before all of the components, then it will be executed before any of the component's own converters and
* validators. If this validator fails, then the component's own converters and validators will not be fired. If this
* validator is been placed after all of the components, then it will be executed after any of the component's own
* converters and validators. If any of them fails, then this validator will not be exeucted. It is not recommended to
* put this validator somewhere in between the referenced components as the resulting behaviour may be confusing, for
* example because only the values of preceding components are converted and the values of following components are not
* converted. Put this validator either before or after all of the components, depending on how you would like to
* prioritize the validation.
*
* <o:validateMultipleFields id="myId" components="foo bar baz" />
* <h:message for="myId" />
* <h:inputText id="foo" />
* <h:inputText id="bar" />
* <h:inputText id="baz" />
*
*
* When a target component is disabled="true"
, readonly="true"
or rendered="false"
,
* then the values
argument of {@link #validateValues(FacesContext, List, List)}, will instead contain the
* initial model value. This is quite useful when you need to validate against an existing model.
*
* By default, in an invalidating case, all of the referenced components will be marked invalid and a faces message will
* be added on the client ID of this validator component. The default message can be changed by the message
* attribute. Any "{0}" placeholder in the message will be substituted with a comma separated string of labels of the
* referenced input components.
*
* <o:validateMultipleFields components="foo bar baz" message="{0} are wrong!" />
*
*
* You can also change the default message in the message bundle file as identified by
* <application><message-bundle>
in faces-config.xml
. The message key is just
* the component type as identified by COMPONENT_TYPE
constant of the validator component. For example,
* {@link ValidateAll} has a {@link ValidateAll#COMPONENT_TYPE} value of
* org.omnifaces.component.validator.ValidateAll
. Use exactly this value as message bundle key:
*
* org.omnifaces.component.validator.ValidateAll = {0} are wrong!
*
*
* You can use invalidateAll="false"
to mark only those components which are actually invalid as invalid.
* In case of for example "input all" or "input all or none" validation, that would be only the fields which are left
* empty.
*
* <o:validateMultipleFields components="foo bar baz" message="{0} are wrong!" invalidateAll="false" />
*
*
* The faces message can also be shown for all of the referenced components using showMessageFor="@all"
.
*
* <o:validateMultipleFields components="foo bar baz" message="This is wrong!" showMessageFor="@all" />
* <h:inputText id="foo" />
* <h:message for="foo" />
* <h:inputText id="bar" />
* <h:message for="bar" />
* <h:inputText id="baz" />
* <h:message for="baz" />
*
*
* The faces message can also be shown for only the invalidated components using showMessageFor="@invalid"
.
*
* <o:validateMultipleFields components="foo bar baz" message="This is wrong!" showMessageFor="@invalid" />
*
*
* The faces message can also be shown as global message using showMessageFor="@global"
.
*
* <o:validateMultipleFields components="foo bar baz" message="This is wrong!" showMessageFor="@global" />
*
*
* The faces message can also be shown for specific components referenced by a space separated collection of their
* client IDs in showMessageFor
attribute.
*
* <o:validateMultipleFields components="foo bar baz" message="This is wrong!" showMessageFor="foo baz" />
*
*
* The showMessageFor
attribute defaults to @this
.
*
* The validator can be disabled by the disabled
attribute. It accepts a request based EL expression.
*
* <o:validateMultipleFields components="foo bar baz" disabled="#{param.validationDisabled}" />
*
*
* There is a read-only validationFailed
attribute which can be used to determine if the validation by
* this component has failed.
*
* <o:validateMultipleFields id="myId" binding="#{myId}" components="foo bar baz" />
* <h:panelGroup rendered="#{myId.validationFailed}">
* Validation has failed! <h:message for="myId" />
* </h:panelGroup>
*
*
* TODO: support for immediate="true".
*
* @author Bauke Scholtz
*/
public abstract class ValidateMultipleFields extends ValidatorFamily implements MultiFieldValidator {
// Private constants ----------------------------------------------------------------------------------------------
private static final String DEFAULT_SHOWMESSAGEFOR = "@this";
private static final Boolean DEFAULT_INVALIDATEALL = TRUE;
private static final Boolean DEFAULT_DISABLED = FALSE;
private static final String ERROR_MISSING_COMPONENTS =
"%s attribute 'components' must be specified.";
private static final String ERROR_UNKNOWN_COMPONENT =
"%s attribute '%s' must refer existing client IDs. Client ID '%s' cannot be found.";
private static final String ERROR_INVALID_COMPONENT =
"%s attribute '%s' must refer UIInput client IDs. Client ID '%s' is of type '%s'.";
private static final String ERROR_INVALID_COMPONENTS =
"%s attribute '%s' must refer UIInput client IDs or any UIComponent which contains UIInput children."
+ " Client ID '%s' is of type '%s' and does not contain UIInput children.";
private enum PropertyKeys {
// Cannot be uppercased. They have to exactly match the attribute names.
components, invalidateAll, message, showMessageFor, disabled;
}
// Variables ------------------------------------------------------------------------------------------------------
private final State state = new State(getStateHelper());
// Properties -----------------------------------------------------------------------------------------------------
private String defaultMessage;
private boolean validationFailed;
// Constructors ---------------------------------------------------------------------------------------------------
/**
* The default constructor sets the default message and sets the renderer type to null
.
*/
protected ValidateMultipleFields() {
defaultMessage = OmniFaces.getMessage(getClass().getAnnotation(FacesComponent.class).value());
setRendererType(null);
}
// Actions --------------------------------------------------------------------------------------------------------
/**
* Validate our component hierarchy.
* @throws IllegalStateException When there is no parent of type {@link UIForm}, or when there are any children.
*/
@Override
protected void validateHierarchy() {
validateHasParent(this, UIForm.class);
validateHasNoChildren(this);
}
/**
* If the validation is not disabled, collect the components, if it is not empty, then collect their values and
* delegate to {@link #validateValues(FacesContext, List, List)}. If it returns false
, then mark all
* inputs and the faces context invalid and finally delegate to {@link #showMessage(FacesContext, List)} to show
* the message.
*/
@Override
protected void validateComponents(FacesContext context) {
if (isDisabled()) {
return;
}
List inputs = collectComponents();
if (inputs.isEmpty()) {
return;
}
List