All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.omnifaces.eventlistener.ResetInputAjaxActionListener Maven / Gradle / Ivy

/*
 * Copyright 2012 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.eventlistener;

import static javax.faces.component.visit.VisitContext.createVisitContext;

import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;

import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitHint;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.faces.event.AjaxBehaviorListener;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.SystemEventListener;

import org.omnifaces.util.Hacks;

/**
 * 

* The {@link ResetInputAjaxActionListener} will reset input fields which are not executed during ajax submit, but which * are rendered/updated during ajax response. This will prevent those input fields to remain in an invalidated state * because of a validation failure during a previous request. This is very useful for cases where you need to update one * form from another form by for example a modal dialog, or when you need a cancel/clear button. *

* How does it work? First, here are some JSF facts: *

    *
  • When JSF validation succeeds for a particular input component during the validations phase, then the submitted * value is set to null and the validated value is set as local value of the input component. *
  • When JSF validation fails for a particular input component during the validations phase, then the submitted * value is kept in the input component. *
  • When at least one input component is invalid after the validations phase, then JSF will not update the model * values for any of the input components. JSF will directly proceed to render response phase. *
  • When JSF renders input components, then it will first test if the submitted value is not null and * then display it, else if the local value is not null and then display it, else it will display the * model value. *
  • As long as you're interacting with the same JSF view, you're dealing with the same component state. *
*

* So, when the validation has failed for a particular form submit and you happen to need to update the values of input * fields by a different ajax action or even a different ajax form (e.g. populating a field depending on a dropdown * selection or the result of some modal dialog form, etc), then you basically need to reset the target input * components in order to get JSF to display the model value which was edited during invoke action. Otherwise JSF will * still display its local value as it was during the validation failure and keep them in an invalidated state. *

* The {@link ResetInputAjaxActionListener} is designed to solve exactly this problem. There are basically three ways * to configure and use it: *

    *
  • Register it as <phase-listener> in faces-config.xml. It'll be applied * to every single ajax action throughout the webapp, on both UIInput and * UICommand components. *

     * <lifecycle>
     *     <phase-listener>org.omnifaces.eventlistener.ResetInputAjaxActionListener</phase-listener>
     * </lifecycle>
     * 
    *
  • Or register it as <action-listener> in faces-config.xml. It'll * only be applied to ajax actions which are invoked by an UICommand component such as * <h:commandButton> and <h:commandLink>. *

     * <application>
     *     <action-listener>org.omnifaces.eventlistener.ResetInputAjaxActionListener</action-listener>
     * </application>
     * 
    *
  • Or register it as <f:actionListener> on the invidivual UICommand * components where this action listener is absolutely necessary to solve the concrete problem. Note that it isn't * possible to register it on the individual UIInput components using the standard JSF tags. *

     * <h:commandButton value="Update" action="#{bean.updateOtherInputs}">
     *     <f:ajax execute="currentInputs" render="otherInputs" />
     *     <f:actionListener type="org.omnifaces.eventlistener.ResetInputAjaxActionListener" />
     * </h:commandButton>
     * 
    *
*

* This works with standard JSF, PrimeFaces and RichFaces actions. Only for RichFaces there's a reflection hack, * because its ExtendedPartialViewContextImpl always returns an empty collection for render IDs. * See also RF issue 11112. *

* Design notice: being a phase listener was mandatory in order to be able to hook on every single ajax action as * standard JSF API does not (seem to?) offer any ways to register some kind of {@link AjaxBehaviorListener} in an * application wide basis, let alone on a per <f:ajax> tag basis, so that it also get applied to * ajax actions in UIInput components. There are ways with help of {@link SystemEventListener}, but it * ended up to be too clumsy. * *

See also: *
JSF spec issue 1060 * * @author Bauke Scholtz */ public class ResetInputAjaxActionListener extends DefaultPhaseListener implements ActionListener { // Constants ------------------------------------------------------------------------------------------------------ private static final long serialVersionUID = -5317382021715077662L; private static final Set VISIT_HINTS = EnumSet.of(VisitHint.SKIP_TRANSIENT, VisitHint.SKIP_UNRENDERED); private static final VisitCallback VISIT_CALLBACK = new VisitCallback() { @Override public VisitResult visit(VisitContext context, UIComponent target) { FacesContext facesContext = context.getFacesContext(); Collection executeIds = facesContext.getPartialViewContext().getExecuteIds(); if (executeIds.contains(target.getClientId(facesContext))) { return VisitResult.REJECT; } if (target instanceof EditableValueHolder) { ((EditableValueHolder) target).resetValue(); } else if (context.getIdsToVisit() != VisitContext.ALL_IDS) { // Render ID didn't specifically point an EditableValueHolder. Visit all children as well. target.visitTree(createVisitContext(facesContext, null, context.getHints()), VISIT_CALLBACK); } return VisitResult.ACCEPT; } }; // Variables ------------------------------------------------------------------------------------------------------ private ActionListener wrapped; // Constructors --------------------------------------------------------------------------------------------------- /** * Construct a new reset input ajax action listener. This constructor will be used when specifying the action * listener by <f:actionListener> or when registering as <phase-listener> in * faces-config.xml. */ public ResetInputAjaxActionListener() { this(null); } /** * Construct a new reset input ajax action listener around the given wrapped action listener. This constructor * will be used when registering as <action-listener> in faces-config.xml. * @param wrapped The wrapped action listener. */ public ResetInputAjaxActionListener(ActionListener wrapped) { super(PhaseId.INVOKE_APPLICATION); this.wrapped = wrapped; } // Actions -------------------------------------------------------------------------------------------------------- /** * Delegate to the {@link #processAction(ActionEvent)} method when this action listener is been registered as a * phase listener so that it get applied on all ajax requests. * @see #processAction(ActionEvent) */ @Override public void beforePhase(PhaseEvent event) { processAction(null); } /** * Handle the reset input action as follows, only and only if the current request is an ajax request and the * {@link PartialViewContext#getRenderIds()} does not return an empty collection nor is the same as * {@link PartialViewContext#getExecuteIds()}: find all {@link EditableValueHolder} components based on * {@link PartialViewContext#getRenderIds()} and if the component is not covered by * {@link PartialViewContext#getExecuteIds()}, then invoke {@link EditableValueHolder#resetValue()} on the * component. * @throws IllegalArgumentException When one of the client IDs resolved to a null component. This * would however indicate a bug in the concrete {@link PartialViewContext} implementation which is been used. */ @Override public void processAction(ActionEvent event) throws AbortProcessingException { FacesContext context = FacesContext.getCurrentInstance(); PartialViewContext partialViewContext = context.getPartialViewContext(); if (partialViewContext.isAjaxRequest()) { Collection renderIds = getRenderIds(partialViewContext); if (!renderIds.isEmpty() && !partialViewContext.getExecuteIds().containsAll(renderIds)) { context.getViewRoot().visitTree(createVisitContext(context, renderIds, VISIT_HINTS), VISIT_CALLBACK); } } if (wrapped != null && event != null) { wrapped.processAction(event); } } // Helpers -------------------------------------------------------------------------------------------------------- /** * Helper method with RichFaces4 hack to return the proper render IDs from the given partial view context. * @param partialViewContext The partial view context to return the render IDs for. * @return The render IDs. */ private static Collection getRenderIds(PartialViewContext partialViewContext) { Collection renderIds = partialViewContext.getRenderIds(); // WARNING: START OF HACK! ------------------------------------------------------------------------------------ // HACK for RichFaces4 because its ExtendedPartialViewContextImpl class doesn't return its componentRenderIds // property on getRenderIds() call when the action is executed using a RichFaces-specific command button/link. // See also https://issues.jboss.org/browse/RF-11112 if (renderIds.isEmpty() && Hacks.isRichFacesInstalled()) { renderIds = Hacks.getRichFacesRenderIds(); } // END OF HACK ------------------------------------------------------------------------------------------------ return renderIds; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy