/*
* Copyright 2017 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
*
* 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 org.omnifaces.taghandler;
import static javax.faces.event.PhaseId.RESTORE_VIEW;
import static org.omnifaces.util.Components.getClosestParent;
import static org.omnifaces.util.Components.hasInvokedSubmit;
import static org.omnifaces.util.Events.subscribeToRequestAfterPhase;
import java.io.IOException;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.context.FacesContext;
import javax.faces.view.facelets.ComponentHandler;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagConfig;
import javax.faces.view.facelets.TagHandler;
import org.omnifaces.component.input.Form;
import org.omnifaces.util.Callback;
/**
* <p>
* The <code><o:ignoreValidationFailed></code> taghandler allows the developer to ignore validation failures when
* executing an {@link UICommand} action. This taghandler must be placed inside an {@link UICommand} component and the
* parent {@link UIForm} must be an <code><o:form></code>. When executing an ajax action, make sure that the
* parent {@link UIForm} is also included in the <code><f:ajax execute></code>.
*
* <h3>Usage</h3>
* <p>
* For example:
* <pre>
* <o:form>
* ...
* <h:commandButton value="save valid data" action="#{bean.saveValidData}">
* <o:ignoreValidationFailed />
* <f:ajax execute="@form" />
* </h:commandButton>
* </o:form>
* </pre>
* <p>
* Note that the model values will (obviously) only be updated for components which have actually passed the validation.
* Also the validation messages will still be displayed. If you prefer to not display them, then you'd need to exclude
* them from rendering by <code><f:ajax render></code>, or to put a proper condition in the <code>rendered</code>
* attribute.
*
* @author Bauke Scholtz
* @see Form
*/
public class IgnoreValidationFailed extends TagHandler {
// Constants ------------------------------------------------------------------------------------------------------
private static final String ERROR_INVALID_PARENT =
"Parent component of o:ignoreValidationFailed must be an instance of UICommand.";
private static final String ERROR_INVALID_FORM =
"Parent form of o:ignoreValidationFailed must be an o:form, not h:form.";
// Constructors ---------------------------------------------------------------------------------------------------
/**
* The tag constructor.
* @param config The tag config.
*/
public IgnoreValidationFailed(TagConfig config) {
super(config);
}
// Actions --------------------------------------------------------------------------------------------------------
/**
* If the parent component is an instance of {@link UICommand} and is new and we're in the restore view phase of
* a postback, then delegate to {@link #processIgnoreValidationFailed(UICommand)}.
* @throws IllegalStateException When the parent component is not an instance of {@link UICommand}.
*/
@Override
public void apply(FaceletContext context, final UIComponent parent) throws IOException {
if (!(parent instanceof UICommand)) {
throw new IllegalStateException(ERROR_INVALID_PARENT);
}
FacesContext facesContext = context.getFacesContext();
if (!(ComponentHandler.isNew(parent) && facesContext.isPostback() && facesContext.getCurrentPhaseId() == RESTORE_VIEW)) {
return;
}
// We can't use hasInvokedSubmit() before the component is added to view, because the client ID isn't available.
// Hence, we subscribe this check to after phase of restore view.
subscribeToRequestAfterPhase(RESTORE_VIEW, new Callback.Void() {
@Override
public void invoke() {
processIgnoreValidationFailed((UICommand) parent);
}
});
}
/**
* Check if the given command component has been invoked during the current request and if so, then instruct the
* parent <code><o:form></code> to ignore the validation.
* @param command The command component.
* @throws IllegalStateException When the given command component is not inside a <code><o:form></code>.
*/
protected void processIgnoreValidationFailed(UICommand command) {
if (!hasInvokedSubmit(command)) {
return;
}
Form form = getClosestParent(command, Form.class);
if (form == null) {
throw new IllegalStateException(ERROR_INVALID_FORM);
}
form.setIgnoreValidationFailed(true);
}
}