(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:
composite:clientBehavior
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?
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.
Related
In a very simple way, here is a composite:
<ui:component
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions"
xmlns:p="http://primefaces.org/ui"
>
<cc:interface>
<cc:attribute name="value" />
<cc:attribute name="required" />
<cc:editableValueHolder name="calendar" />
</cc:interface>
<cc:implementation>
<p:calendar id="calendar" value="#{cc.attrs.value}" required="#{cc.attrs.required}" showOn="button"/>
</cc:implementation>
</ui:component>
Here how I use it:
<p:outputLabel for="rat:calendar" value="Hello" />
<my:calendar id="rat" required="true"/>
A required=true icon should be displayed but it does not. In OutputLabelRenderer looks fine, except in line 118, input.isRequired() returns false while I explicitly set true. I don't think it is related to Primefaces but looks like a JSF problem (I'm using Mojarra 2.2.13 and reproduced on Myfaces 2.2.4 as well)
Explanation provided by Myfaces: https://issues.apache.org/jira/browse/MYFACES-4059
I'm developing composite component in JSF. In that I can pass method names as arguments in following way.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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:c="http://java.sun.com/jsp/jstl/core"
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
xmlns:p="http://primefaces.org/ui"
xmlns:composite="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="noOfButtons" type="java.lang.Integer" required="true"/>
<composite:attribute name="actionOnly" type="java.lang.Boolean" required="true"/>
<composite:attribute name="myBean" required="true" type="com.jsf.CRUDBean"/>
<composite:attribute name="myIcons" type="java.lang.String" required="true"/>
<composite:attribute name="myAction" type="java.lang.String" required="true"/>
<composite:attribute name="myDesc" type="java.lang.String" required="true"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<c:forEach begin="0" end="#{cc.attrs.noOfmys - 1}" var="counter"
varStatus="status">
<h:commandButton id="myButton_${counter}"
action="#{cc.attrs.myBean[fn:split(cc.attrs.myAction, ',')[status.index]]}"
image="#{request.contextPath}/resources/images/#{fn:split(cc.attrs.myIcons, ',')[status.index]}"
title="#{fn:split(cc.attrs.myDesc, ',')[status.index]}"
style="padding:2px">
<p:spacer width="5px;" />
</c:forEach>
</composite:implementation>
</html>
This is working perfect.But in my case, I need to pass action in following way.
<c:forEach begin="0" end="#{cc.attrs.noOfmys - 1}" var="counter"
varStatus="status">
<h:commandButton id="myButton_${counter}"
image="#{request.contextPath}/resources/images/#{fn:split(cc.attrs.myIcons, ',')[status.index]}"
title="#{fn:split(cc.attrs.myDesc, ',')[status.index]}"
style="padding:2px">
<f:attribute name="action" value="#{cc.attrs.myBean[fn:split(cc.attrs.myAction, ',')[status.index]]}" />
</h:commandButton>
<p:spacer width="5px;" />
</c:forEach>
But this is not working in my application. Whenever I click commandButton from the page nothing happening at user level. Any help would be thankful.
In the given case, I want to use a facelet with different ManagedBeans, so the regarding action-bean is given as an parameter:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html" >
<h:body>
<ui:include src="ratings.xhtml" >
<ui:param name="createAction" value="#{myOneCreateAction}" />
<ui:param name="ratings" value="#{context.ratings}" />
</ui:include>
</h:body>
</html>
I'm giving the create action as parameter value="#{myOneCreateAction}".
Within that facelet is a component also being used several times on other pages - so I try to refactor it in a composite component.
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:io="http://java.sun.com/jsf/composite/inoutComponents">
<ui:composition>
<rich:dataTable id="ratingTblId"
value="#{ratings}"
var="rating">
<rich:column>
<io:removeButton
id="removeButton"
actionMethod="#{createAction.removeRating}"
immediate="true"
render=":#{rich:clientId('ratingTblId')}" />
<h:commandButton
id="removeButton2"
actionListener="#{createAction.removeRating}"
immediate="true" >
<f:ajax render="ratingTblId" />
</h:commandButton>
</rich:column>
</rich:dataTable>
</ui:composition>
</html>
See, how the method is given as actionMethod="#{createAction.removeRating}" to the component. This component itself looks like following:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:cc="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<cc:interface>
<cc:attribute
name="actionMethod"
targets="remove"
method-signature="void f(javax.faces.event.ActionEvent)"/>
<cc:attribute name="render" required="false" />
</cc:interface>
<!-- IMPLEMENTATION -->
<cc:implementation>
<h:commandButton
id="remove"
actionListener="#{cc.attrs.actionMethod}"
onclick="if (!confirm('Do you really?')) { return false; }">
<f:ajax execute="#this" render="#{cc.attrs.render}" />
</h:commandButton>
</cc:implementation>
</ui:composition>
and last but not least, the managed bean
Name("myOneCreateAction")
#Scope(ScopeType.CONVERSATION)
public class MyOneCreateAction {
...
public void removeRating(ActionEvent ev) {
// do something
}
...
}
Surprisingly, while the removeButton2 correctly jumps into the right function, the composite components version returns a
javax.faces.event.AbortProcessingException: Target Unreachable,
identifier 'createAction' resolved to null
instead. Am using Mojarra JSF 2.1.26 with Seam 2.3.1.CR1. There are no nested composite components. When replacing the composite component parameter to #{myOneCreateAction.removeRating}, it works like expected.
Has anybody seen this before? Am I blind? Any work-arounds known... ? Thanks in advance!
As a work-around I rewrote the component to give action bean and action method as two separate parameters resolving them as following
xhtml:
<io:removeButton
id="removeButton"
actionBean="#{createAction}"
actionMethod="removeRating"
immediate="true"
render=":#{rich:clientId('ratingTblId')}" />
composite component:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:cc="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<cc:interface>
<cc:attribute name="actionBean" />
<cc:attribute name="actionMethod" />
<cc:attribute name="render" required="false" />
</cc:interface>
<!-- IMPLEMENTATION -->
<cc:implementation>
<h:commandButton
id="remove"
action="#{cc.attrs.actionBean[cc.attrs.actionMethod]}"
onclick="if (!confirm('Do you really?')) { return false; }">
<f:ajax execute="#this" render="#{cc.attrs.render}" />
</h:commandButton>
</cc:implementation>
</ui:composition>
also changing the action methods signature to return String instead of void. That does not look that super sexy anymore, but works. :-/
I was having the same issue, found out that it has been fixed on version 2.2.15:
https://github.com/javaserverfaces/mojarra/issues/4271
I've created a composite that contains the PrimeFaces selectManyMenu. I'm attempting to pass the bound selection value into the composite, but it fails.
Composite code:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:kf="http://java.sun.com/jsf/composite/kf"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<composite:interface>
<composite:attribute name="selectedUsers" required="true"/>
<composite:attribute name="users" required="true" type="java.util.List"/>
<composite:attribute name="recommendActionHandler" required="true" method-signature="void listener()"/>
<composite:attribute name="recommendButtonStyle"/>
<composite:clientBehavior name="click" event="change" targets="recommendUsers" default="true"/>
</composite:interface>
<composite:implementation>
<div id="#{cc.clientId}">
<h:form id="recommendForm" prependId="false">
<p:growl id="recommendGrowl" showDetail="true"/>
<p:commandButton id="recommendBtn" value="Recommend" type="button" update=":recommendForm,recommendUsers" style="#{cc.attrs.recommendButtonStyle}"/>
<p:overlayPanel id="recommendPanel" for="recommendBtn" widgetVar="recommendPanel"
dynamic="true"
hideEffect="fade"
showCloseIcon="true">
<p:selectManyMenu id="recommendUsers" value="#{cc.attrs.selectedUsers}" showCheckbox="true"
style="width:150px;height:200px">
<f:selectItems value="#{cc.attrs.users}" var="user" itemLabel="#{user.login}" itemValue="#{user.login}"/>
</p:selectManyMenu>
<p:commandButton id="submitRecommendations"
value="Send"
update="recommendUsers recommendGrowl"
actionListener="#{cc.attrs.recommendActionHandler}"
process="recommendUsers #this"
/>
</p:overlayPanel>
</h:form>
</div>
</composite:implementation>
</html>
View code:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:kf="http://java.sun.com/jsf/composite/kf"
>
<ui:composition template="/WEB-INF/facelet_templates/default.xhtml">
<ui:define name="content">
<kf:recommend id="recommendIt"
selectedUsers="#{recommendViewBean.selectedUsers}"
users="#{recommendViewBean.users}"
recommendActionHandler="#{recommendController.saveRecommendations()}"
/>
</ui:define>
</ui:composition>
</html>
View bean code:
public class RecommendViewBean {
private List<UserType> users = new ArrayList<UserType>();
private List<String> selectedUsers = new ArrayList<String>();
//setters and getters...
}
In the code above, the value selectedUsers is the value in question. I pass the view/backing bean's List value that holds the selections for the selectManyMenu's value attribute. This works great when outside of a composite or if I pass the view bean like this...
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:kf="http://java.sun.com/jsf/composite/kf"
>
<ui:composition template="/WEB-INF/facelet_templates/default.xhtml">
<ui:define name="content">
<kf:recommend id="recommendIt"
selectedUsers="#{recommendViewBean}"
users="#{recommendViewBean.users}"
recommendActionHandler="#{recommendController.saveRecommendations()}"
/>
</ui:define>
</ui:composition>
</html>
...
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:kf="http://java.sun.com/jsf/composite/kf"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<composite:interface>
<composite:attribute name="selectedUsers" required="true"/>
<composite:attribute name="users" required="true" type="java.util.List"/>
<composite:attribute name="recommendActionHandler" required="true" method-signature="void listener()"/>
<composite:attribute name="recommendButtonStyle"/>
<composite:clientBehavior name="click" event="change" targets="recommendUsers" default="true"/>
</composite:interface>
<composite:implementation>
<div id="#{cc.clientId}">
<h:form id="recommendForm" prependId="false">
<p:growl id="recommendGrowl" showDetail="true"/>
<p:commandButton id="recommendBtn" value="Recommend" type="button" update=":recommendForm,recommendUsers" style="#{cc.attrs.recommendButtonStyle}"/>
<p:overlayPanel id="recommendPanel" for="recommendBtn" widgetVar="recommendPanel"
dynamic="true"
hideEffect="fade"
showCloseIcon="true">
<p:selectManyMenu id="recommendUsers" value="#{cc.attrs.selectedUsers.selectedUsers}" showCheckbox="true"
style="width:150px;height:200px">
<f:selectItems value="#{cc.attrs.users}" var="user" itemLabel="#{user.login}" itemValue="#{user.login}"/>
</p:selectManyMenu>
<p:commandButton id="submitRecommendations"
value="Send"
update="recommendUsers recommendGrowl"
actionListener="#{cc.attrs.recommendActionHandler}"
process="recommendUsers #this"
/>
</p:overlayPanel>
</h:form>
</div>
</composite:implementation>
</html>
So, my question is, how would I pass the appropriate bound value for selectManyMenu's value attribute into the composite?
Thanks so much. Let me know if I need to explain more.
I want to use the o:validator tag from the omnifaces library for a composition component:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core" xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"
xmlns:pe="http://primefaces.org/ui/extensions">
<cc:interface>
<cc:attribute name="disabled" />
<cc:attribute name="label" />
<cc:attribute name="validatorMessage" />
<cc:attribute name="required" />
<cc:attribute name="value" required="true" />
<cc:editableValueHolder name="date" targets="textfield"/>
</cc:interface>
<cc:implementation>
<p:outputLabel id="label" for="textfield" value="#{cc.attrs.label}" rendered="#{cc.attrs.label!=null}" />
<p:inputText id="textfield" value="#{cc.attrs.value}" styleClass="dateInputField" required="#{cc.attrs.required}" disabled="#{cc.attrs.disabled}">
<f:convertDateTime locale="de_DE" type="date" />
<!-- some other stuff... -->
</p:inputText>
<p:watermark for="textfield" value="TT.MM.JJJJ" />
<cc:insertChildren/>
</cc:implementation>
The validator gets called like this:
<o:validator validatorId="customGreaterThanValidator" compareTo="#{bean.date}" validatorMessage="given date must be later than #{bean.date}" for="date"/>
But unfortunatly it seems that the validator tag from omnifaces ignores the "for" Attribute. With the original validator tag from JSF the "for" tag works, but not if the value of the "compareTo" tag is a ValueExpression.
I tried to implement a custom taghandler, but that didn't work either.
This is indeed not supported in the current 1.3 <o:validator> (nor <o:converter>). It will come in future 1.4 as per issue 126 (where you can download the snapshot).
The key is to change TagHandler to ValidatorHandler and ConverterHandler respectively so that the TagHandlerDelegate can do its job for composites.