Reputation: 1175
(in this example we use primefaces components, but question is not related with Primefaces)
Introduction:
In order to have ajax support for composite component elements we just use element:
Let's look at this situation:
<composite:interface>
<composite:clientBehavior name="dateSelect" event="dateSelect" targets="id111 id222" />
</composite:interface>
<composite:implementation>
<p:calendar id="id111" value="#{csController.selectedDate}" mode="inline"
mindate="#{csController.minDate(2013,9)}"
maxdate="#{csController.maxDate(2013,9)}"/>
<p:calendar id="id222" value="#{csController.selectedDate}" mode="inline"
mindate="#{csController.minDate(2013,10)}"
maxdate="#{csController.maxDate(2013,10)}"/>
</composite:implementation>
We we have two elements and ajax support for these two elements. This works as expected. We could even change to this interface:
<composite:interface>
<composite:clientBehavior name="dateSelect" event="dateSelect" targets="id111" />
<composite:clientBehavior name="dateSelect" event="dateSelect" targets="id222" />
</composite:interface>
and this still works fine.
Problem:
I would like to change implementation to show custom number of calendars, not exaclty two like shown above. So I tried this:
<composite:interface>
<composite:attribute name="firstMonth" default="1"/>
<composite:attribute name="lastMonth" default="12"/>
<c:forEach var="i" begin="#{cc.attrs.firstMonth}" end="#{cc.attrs.lastMonth}">
<composite:clientBehavior name="dateSelect" event="dateSelect" targets="#{cc.clientId}#{i}"/>
</c:forEach>
</composite:interface>
<composite:implementation>
<c:forEach var="i" begin="#{cc.attrs.firstMonth}" end="#{cc.attrs.lastMonth}">
<p:calendar id="#{cc.clientId}#{i}" value="#{csController.selectedDate}" mode="inline"
mindate="#{csController.minDate(2013,i)}"
maxdate="#{csController.maxDate(2013,i)}"/>
</c:forEach>
</composite:implementation>
This of course doesn't work.
Question:
How to get ajax support for all elements generated by c:forEach loop?
Upvotes: 2
Views: 681
Reputation: 31649
Actually you don't need to follow that procedure to add ajax functionality for your components. Just use f:ajax
into your calendars. As c:forEach
works before it, that's not a problem:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<cc:interface>
<cc:attribute name="val" />
</cc:interface>
<cc:implementation>
<c:forEach var="date" items="#{cc.attrs.val}">
<p:calendar value="#{date}" mode="inline">
<f:ajax event="dateSelect" listener="#{bean.printCurrentValues}" />
</p:calendar>
</c:forEach>
</cc:implementation>
</html>
Being this the main page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:composite="http://java.sun.com/jsf/composite/composites">
<h:head />
<h:body>
<h:form>
<composite:comp val="#{bean.calendars}" />
</h:form>
</h:body>
</html>
I added this listener method to the bean to check current dates are really changing:
@ManagedBean
@ViewScoped
public class Bean {
public List<Date> calendars = Arrays.asList(new Date(), new Date());
public List<Date> getCalendars() {
return calendars;
}
/**
* Just prints the updated values ;-)
*/
public void printCurrentValues() {
System.out.println("Current selected dates: " + calendars);
}
}
For your specific case you only would need to add a list of minDates
and other one of maxDates
in order to bind them to your calendar
properties. Alternatively, you could also create your own class which wraps them to have everything packed. That's your own choice.
Solution above will work for dateSelect
event and a provided listener. What if we want to have different events with different listeners?
Just define them on the interface and allow-deny them with JSTL's c:if
. Here you have the composite implementation for a single input that can launch a listener method, also a dynamic number of p:calendar
components, which will notify a user-defined method. By default none of them is launched.
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<cc:interface>
<cc:attribute name="dateSelect" default="false" />
<cc:attribute name="click" default="false" />
<cc:attribute name="dateSelectListener" method-signature="void f()" />
<cc:attribute name="inputClickedListener" method-signature="void f()" />
<cc:attribute name="val" />
</cc:interface>
<cc:implementation>
<h:inputText>
<c:if test="#{cc.attrs.click}">
<f:ajax event="click" listener="#{cc.attrs.inputClickedListener}" />
</c:if>
</h:inputText>
<c:forEach var="date" items="#{cc.attrs.val}">
<p:calendar value="#{date}" mode="inline">
<c:if test="#{cc.attrs.dateSelect}">
<f:ajax event="dateSelect" listener="#{cc.attrs.dateSelectListener}" />
</c:if>
</p:calendar>
</c:forEach>
</cc:implementation>
</html>
And here the main page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:composite="http://java.sun.com/jsf/composite/composites">
<h:head />
<h:body>
<h:form>
<composite:comp val="#{bean.calendars}" dateSelect="true"
dateSelectListener="#{bean.printCurrentValues}" click="true"
inputClickedListener="#{bean.inputClicked}" />
</h:form>
</h:body>
</html>
Tested with Mojarra JSF 2.1.25 & Tomcat 7.
Upvotes: 1