Search code examples
ajaxjsfjsf-2lifecycle

p:ajax immediate="true" on UIInput does not skip validation


TL;DR

<h:form>
    <p:inputText required="true">
        <p:ajax event="click" immediate="true" process="@this" update="@this" />
    </p:inputText>
</h:form>

click causes validation, even if ajax is immediate. Why?


I can't make finally made this work out:

<h:form>
    <p:dataGrid var="elem" value="#{immediateTestBean.elements}" columns="3">
        <f:facet name="footer">
            <p:commandButton action="#{immediateTestBean.newElement}" immediate="true"
                process="@namingcontainer" update="@namingcontainer" value="#{bundle.add}" />
        </f:facet>

        <h:panelGrid columns="3">
            <p:selectOneMenu value="#{elem.type}">
                <p:ajax immediate="true" process="@form" update="@form" />
                <f:selectItem itemValue="text" itemLabel="#{bundle.text}" />
                <f:selectItem itemValue="date" itemLabel="#{bundle.date}" />
            </p:selectOneMenu>

            <h:panelGroup id="detail">
                <p:inputText value="#{elem.text}" required="true" rendered="#{elem.type == 'text'}"
                    validator="#{immediateTestBean.testValidate}" label="#{bundle.text}">
                    <f:validateLength minimum="4" />
                </p:inputText>
                <p:calendar value="#{elem.date}" required="true" rendered="#{elem.type == 'date'}"
                    pattern="dd/MM/yyyy" readonlyInput="true" showOn="button"
                    validator="#{immediateTestBean.testValidate}" label="#{bundle.date}" />
            </h:panelGroup>

            <p:commandButton action="#{immediateTestBean.removeElement(elem)}" immediate="true"
                process="@namingcontainer" update="@namingcontainer" value="#{bundle.remove}" />
        </h:panelGrid>
    </p:dataGrid>

    <p:spacer height="10" style="display: block" />

    <p:commandButton process="@form" update="@form" value="#{bundle.submit}" />
</h:form>

Everything works fine, except <p:ajax immediate="true" process="@form" update="@form" /> which does not behave as UICommands do: validation occurs (but in APPLY_REQUEST_VALUES phase).

I tried to dig inside JSF source code and noted that UICommands call facesContext.renderResponse() in default ActionListenerImpl.processAction(), so I tried with:

<p:ajax listener="#{facesContext.renderResponse}" immediate="true" process="@form" update="@form" />

Now, when I select 'date', validation is skipped, but <h:panelGroup id="detail"> is not updated.

Then, I tought that skipping validation caused skip of update model too, so I called:

<p:ajax listener="#{immediateTestBean.skip}" immediate="true" process="@form" update="@form" />

And finally it works.

But two questions arose:

  1. Why is this so complicated?
  2. Is there a simpler way to achieve the same behavior?

However, here is the code of the bean:

@ManagedBean
@ViewScoped
public class ImmediateTestBean implements Serializable
{
    private static final long serialVersionUID = 1L;

    private static final Logger logger = LoggerFactory.getLogger(ImmediateTestBean.class);

    public class Element
    {
        private String type = "text";

        private String text;

        private Date date;

        public String getType()
        {
            return type;
        }

        public void setType(String type)
        {
            this.type = type;
        }

        public String getText()
        {
            return text;
        }

        public void setText(String text)
        {
            this.text = text;
        }

        public Date getDate()
        {
            return date;
        }

        public void setDate(Date date)
        {
            this.date = date;
        }
    }

    private final List<Element> elements = new ArrayList<>();

    public void testValidate(FacesContext context, UIComponent component, Object value)
    {
        logger.debug("phase: {}", context.getCurrentPhaseId());
    }

    public void skip()
    {
        logger.debug("Faces.getCurrentPhaseId(): {}", Faces.getCurrentPhaseId());

        UIInput component = (UIInput) Components.getCurrentComponent();
        logger.debug("component: {}", component);

        FacesContext context = Faces.getContext();
        component.validate(context);
        if(component.isValid())
        {
            component.updateModel(context);
        }

        Faces.renderResponse();
    }

    public void newElement()
    {
        elements.add(new Element());
    }

    public void removeElement(Element elem)
    {
        elements.remove(elem);
    }

    public List<Element> getElements()
    {
        return elements;
    }
}

Solution

  • This is a lack of JSF, which cannot be simulated with immediate="true" in every case.

    Just use OmniFaces SkipValidators