I am building an invoice application. When the user selects the client, then the services associated with the client are re-rendered in a dataTable by an ajax event. But, the ajax event of the selectOneMenu in the dataTable does not fire.
This is the form which lets the user select a client:
<h:form>
<h:selectOneMenu id="clientSelect" value="#{invoiceBean.client}">
<f:ajax execute="clientSelect" listener="#{invoiceServiceListener.processClientValueChange}" render=":test :invoiceData"/>
<f:selectItems value="#{invoiceBean.clientOptions}"/>
</h:selectOneMenu>
</h:form>
This will fire an ajax event which fills the services array in the invoiceBean and re-renders the dataTable which displays the services in another selectOneMenu in the dataTable. This part works properly.
This is the datatable that is re-rendered after selecting the client by the above form:
<h:panelGroup id="test">
<h:dataTable id="invoiceData" value="#{attributes.priceAttributes}" var="loop">
<h:column>
<ui:param name="service" value="service#{loop}" />
<ui:param name="description" value="description#{loop}" />
<ui:param name="price" value="price#{loop}" />
<h:form id="invoiceSelectMenu">
<h:selectOneMenu id="selectMenu" value="#{invoiceBean[service]}">
<f:ajax event="change" execute="selectMenu" listener="#{invoiceServiceListener.procesServiceValueChange}" render="@form" />
<f:attribute name="row" value="#{loop}" />
<f:selectItems value="#{invoiceBean.services}" />
</h:selectOneMenu>
<h:inputText id="description" value="#{invoiceBean[description]}" />
<h:inputText id="price" value="#{invoiceBean[price]}" />
<h:outputText value="#{loop}" />
</h:form>
</h:column>
</h:dataTable>
</h:panelGroup>
The selectOneMenu is filled properly. However, the ajax event in this select one menu does not work after it has been re-rendered by the first form. But, if I manually fill the services array without submitting the first form, then this ajax event executes normally. By the way, this ajax event also sets a value to the description and price input fields, which should be re-rendered when the event happens. The invoiceBean is request scoped.
This is going to be tricky. Due to JSF spec issue 790, whenever you re-render some content containing another
<h:form>using<f:ajax>, then its view state will get lost. To solve this, you’d basically need to reference the full client ID of another<h:form>inside<f:ajax>instead of some containing component. But you’ve basically multiple forms inside a<h:dataTable>which can’t be referenced by an absolute client ID.You’d need to rearrange the code to put the
<h:form>outside the<h:dataTable>and change the<f:ajax>toexecuteonly the necessary components.and then reference it form the first form as follows:
Note that some component libraries such as PrimeFaces have already workarounded/solved this in the component library specific ajax engine. So if it might happen that you’re already using PrimeFaces, you could also replace
<f:ajax>of the first form by<p:ajax>.Unrelated to the concrete problem, the
<f:attribute name="row" value="#{loop}" />may not do what you expect it does. It will setnullbecause<f:xxx>tags run during view build time, not during view render time tag. The#{loop}is not available during view build time. But that’s subject for a different question 🙂