0
votes

I have the following code:

<h:form>
    <h:inputText id="inputField" value="#{bean.myProperty}">
        <f:validateLongRange
            minimum="#{bean.minimum}"
            maximum="#{bean.maximum}"/>
    </h:inputText>
    <h:commandButton id="submit" value="Submit" >
        <f:ajax execute="inputField" render="outputField errors" />
    </h:commandButton>
    <h:outputText id="outputField" value="#{bean.myPropertyFormatted}"/>
    <h:message id="errors" for="inputField"/>
</h:form>

When validation fails on the inputText, I would like to remove/hide the outputText from the user. What is the most elegant and future-refactoring-proof way to do this?

I tried setting the attribute rendered="#{!facesContext.validationFailed}" on the outputText element, but that only determines whether or not the outputText element is RE-rendered, as opposed to leaving the old text unchanged. However, when the validateLongRange validation fails, I want to completely remove/hide the outputText from the user, since the user will be seing the validation error message and is not interested in seing an old output message based on a previous, valid input which is still the value stored in the bean.

3
I don't understand why rendered doesn't do the job. If EL inside it returns false outputText element will not be rendered, which means you will not have HTML of that element in browser at all. Isn't that what you need? - partlov
@partlov: Yes, that's what I would need, but that's not the behavior I'm seeing. Instead, when I have a validation error, the outputText just stays the same, unchanged. I want it to disappear instead. If I invert the EL output (without the "!"), it's easier to see what's going on. With rendered="#{facesContext.validationFailed}" I don't get any update on the outputText for valid values, but I get an update as soon as I give an invalid value which results in a validation error. i.e. upon validation error I see the last valid value I entered before entering an invalid value. - Student
@partlov: could the reason my code is not behaving as you would have expected be tied to the use of the f:ajax element? See also the answers below. - Student

3 Answers

3
votes

The problem is that when you set rendered attribute to an object and its value is false, the component won't be in the component tree because is not rendered and you can't re-render (or update) it. The solution is to set the component inside an UIContainer and render the container:

<h:commandButton id="submit" value="Submit" >
    <f:ajax execute="inputField" render="pnlResult" />
</h:commandButton>
<h:panelGrid id="pnlResult">
    <h:outputText id="outputField" value="#{bean.myPropertyFormatted}"
        rendered="#{not facesContext.validationFailed}" />
    <h:message id="errors" for="inputField"/>
</h:panelGrid>
5
votes

As a rule of thumb in JSF, if you want to conditionally render anything, you should wrap it in a container component (<foo:panel/>) and make the container component the target of the ajax update.

<h:form>
   <h:inputText id="inputField" value="#{bean.myProperty}">
      <f:validateLongRange
        minimum="#{bean.minimum}"
        maximum="#{bean.maximum}"/>
   </h:inputText>
   <h:commandButton id="submit" value="Submit" >
      <f:ajax execute="inputField" render="thePanel errors" />
   </h:commandButton>
   <h:panelGrid id="thePanel">
   <h:outputText rendered="#{!facesContext.validationFailed}" id="outputField" value="#{bean.myPropertyFormatted}"/>
   </h:panelGrid>
   <h:message id="errors" for="inputField"/>
</h:form>

For a component to be ajax-updated, it must already be in the DOM in the browser, this is the way ajax works. So when your view is initially rendered, outputField doesn't exist due to the rendered condition evaluating to false. So any subsequent request to ajax update it will fail, because of the reason aforementioned. The container component strategy is to ensure that there's a blanket update of a region of markup, regardless of whatever was there prior

1
votes

I ended up doing the following, based on the excellent answers of kolossus and Luiggi Mendoza:

<h:form>
    <h:inputText id="inputField" value="#{bean.myProperty}">
        <f:validateLongRange
            minimum="#{bean.minimum}"
            maximum="#{bean.maximum}"/>
    </h:inputText>
    <h:commandButton id="submit" value="Submit" >
        <f:ajax execute="inputField" render="outputField errors" />
    </h:commandButton>
    <h:panelGroup layout="block" id="outputGroup">
        <h:outputText id="outputField" value="#{bean.myPropertyFormatted}" rendered="#{not facesContext.validationFailed}"/>
        <h:message id="errors" for="inputField"/>
    </h:panelGroup>
</h:form>

I opted for h:panelGroup instead of h:panelGrid because my web developer background makes me shudder at the idea of table-based layout. It seems together with layout="block", h:panelGroup is rendered as <div/>, whereas h:panelGrid is rendered as a <table/>.