I would like to set up a Date field in my page like this
|hours| h |minutes|
where hours and minutes are in separated inputText.
The bean have this date
import java.util.Date;
...
private Date myDate;
...
and the page is
<h:form>
...
<h:inputText id="myDateHours" maxlength="2" value="#{myBean.myDate}"
<f:convertDateTime pattern="HH" />
</h:inputText>
<h:outputText value=" h " />
<h:inputText id="myDateMinutes" maxlength="2" value="#{myBean.myDate}"
<f:convertDateTime pattern="mm" />
</h:inputText>
...
</h:form>
But the problem is that when I submit the form only the last element is saved.
For instance if I type the hours and then the minutes, the hours are overwritten and the result is
| 00 | h | minutes |
I tried to set
<h:inputText id="myDateHours" value="#{myBean.myDate.hours}></h:inputText>
<h:inputText id="myDateMinutes" value="#{myBean.myDate.minutes}></h:inputText>
but I get a
Cannot convert 01/01/70 01:00 of type class java.util.Date to int
I don't want to replace my date field with two int field (hours and minutes...)
Do you have an idea?
Thanks
This particular case is not possible if you want to use a single model value.
This is however a perfect candidate for a composite component. It allows you to bind a single model value to a group of closely related existing components and perform the processing/conversion in the backing component, fully decoupled from the view and backing bean. One of the examples can be found in this article: composite component with multiple input fields. This example can in for your specific case be altered as follows:
/resources/components/inputTime.xhtml:
<ui:component
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:cc="http://java.sun.com/jsf/composite"
>
<cc:interface componentType="inputTime">
<cc:attribute name="value" type="java.util.Date" shortDescription="The selected time. Defaults to now." />
</cc:interface>
<cc:implementation>
<span id="#{cc.clientId}" style="white-space:nowrap">
<h:inputText id="hour" binding="#{cc.hour}" maxlength="2" converter="javax.faces.Integer" />h
<h:inputText id="minute" binding="#{cc.minute}" maxlength="2" converter="javax.faces.Integer" />
</span>
</cc:implementation>
</ui:component>
com.example.InputTime
#FacesComponent("inputTime")
public class InputTime extends UIInput implements NamingContainer {
private UIInput hour;
private UIInput minute;
/**
* As required by <cc:interface>.
*/
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
/**
* Set initial hour and minute based on model.
*/
#Override
public void encodeBegin(FacesContext context) throws IOException {
Calendar calendar = Calendar.getInstance();
Date date = (Date) getValue();
if (date != null) {
calendar.setTime(date);
}
hour.setValue(calendar.get(Calendar.HOUR_OF_DAY));
minute.setValue(calendar.get(Calendar.MINUTE));
super.encodeBegin(context);
}
/**
* Returns the submitted value in HH-mm format.
*/
#Override
public Object getSubmittedValue() {
return hour.getSubmittedValue() + "-" + minute.getSubmittedValue();
}
/**
* Converts the submitted value to concrete {#link Date} instance.
*/
#Override
protected Object getConvertedValue(FacesContext context, Object submittedValue) {
try {
return new SimpleDateFormat("HH-mm").parse((String) submittedValue);
}
catch (ParseException e) {
throw new ConverterException(e);
}
}
public UIInput getHour() {
return hour;
}
public void setHour(UIInput hour) {
this.hour = hour;
}
public UIInput getMinute() {
return minute;
}
public void setMinute(UIInput minute) {
this.minute = minute;
}
}
Usage:
<html ... xmlns:my="http://java.sun.com/jsf/composite/components">
...
<my:inputTime value="#{bean.date}" />
See also:
When to use <ui:include>, tag files, composite components and/or custom components?
You need two separate setter methods in the bean, and then do the merge in the server.
<h:inputText id="myDateHours" value="#{myBean.hours}></h:inputText>
<h:inputText id="myDateMinutes" value="#{myBean.minutes}></h:inputText>
Both must get date values, so then you can operate with a JAVA Calendar setting both fields, in the action invoked by your form.
Calendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(0);
calendar.set(Calendar.HOUR, getHours().getHours());
calendar.set(Calendar.MINUTE, getMinutes().getMinutes());
Be aware of time zones if required.
Related
This question already has answers here:
JSF doesn't support cross-field validation, is there a workaround?
(2 answers)
Closed 5 years ago.
I have one jsf page containing many h:inputText fields. Before submitting the form using h:commandButton I want to check data in input fields is same or not using f:validator in backing bean. How i can get the values of two inputText fields in backing bean??
The validation mechanism in JSF was designed to validate a single component.
However, in practice, you often need to ensure that related components have reasonable values before letting the values propagate into the model.
For example, it is not a good idea to ask users to enter a date into a single textfield.
Instead, you would use three different textfields, for the day, month, and year.
If the user enters an illegal date, such as February 30, you would like to show a validation error and prevent the illegal data from entering the model.
The trick is to attach the validator to the last of the components. By the time its validator is called, the preceding components passed validation and had their local values set. The last component has passed conversion, and the converted value is passed as the Object parameter of the validation method.
Of course, you need to have access to the other components. You can easily achieve that access by using a backing bean that contains all components of the current form. Simply attach the validation method to the backing bean:
public class BackingBean {
private int day;
private int month;
private int year;
private UIInput dayInput;
private UIInput monthInput;
private UIInput yearInput;
// PROPERTY: day
public int getDay() { return day; }
public void setDay(int newValue) { day = newValue; }
// PROPERTY: month
public int getMonth() { return month; }
public void setMonth(int newValue) { month = newValue; }
// PROPERTY: year
public int getYear() { return year; }
public void setYear(int newValue) { year = newValue; }
// PROPERTY: dayInput
public UIInput getDayInput() { return dayInput; }
public void setDayInput(UIInput newValue) { dayInput = newValue; }
// PROPERTY: monthInput
public UIInput getMonthInput() { return monthInput; }
public void setMonthInput(UIInput newValue) { monthInput = newValue; }
// PROPERTY: yearInput
public UIInput getYearInput() { return yearInput; }
public void setYearInput(UIInput newValue) { yearInput = newValue; }
public void validateDate(FacesContext context, UIComponent component, Object value) {
int d = ((Integer) dayInput.getLocalValue()).intValue();
int m = ((Integer) monthInput.getLocalValue()).intValue();
int y = ((Integer) value).intValue();
if (!isValidDate(d, m, y)) {
throw new ValidatorException(new FacesMessage("Invalid Date"));
}
}
private static boolean isValidDate(int d, int m, int y) {
//DO YOUR VALIDATION HERE
}
}
Here is your JSP
<html>
<%# taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%# taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<head></head>
<body>
<h:form>
<h:panelGrid columns="3">
<h:inputText value="#{bb.day}" binding="#{bb.dayInput}" size="2" required="true"/>
<h:inputText value="#{bb.month}" binding="#{bb.monthInput}" size="2" required="true"/>
<h:inputText value="#{bb.year}" binding="#{bb.yearInput}" size="4" required="true" validator="#{bb.validateDate}"/>
<h:message for="year" styleClass="errorMessage"/>
</h:panelGrid>
<h:commandButton value="Submit" action="submit"/>
</h:form>
</body>
</f:view>
</html>
In JSF & Primefaces web application, I want to pass a value for the complete method of primefaces input text area control. I have tried it as follows.
JSF file
<p:inputTextarea id="txtMicMemoVal"
value="#{patientReportController.memoEnterVal}"
style="min-width: 200px;"
completeMethod="#{investigationItemValueController.completeValues}" >
<f:attribute name="ii" value="#{pv.investigationItem}" />
<f:ajax event="blur" execute="#this"
listener="#{patientReportController.saveMemoVal(pv.id)}" ></f:ajax>
</p:inputTextarea>
Relevant Backing Bean
public List<String> completeValues(String qry) {
System.out.println("completing values");
FacesContext context = FacesContext.getCurrentInstance();
InvestigationItem ii;
try {
ii = (InvestigationItem) UIComponent.getCurrentComponent(context).getAttributes().get("ii");
System.out.println("ii = " + ii);
} catch (Exception e) {
ii = null;
System.out.println("error " + e.getMessage());
}
Map m = new HashMap();
String sql;
sql = "select v.name from InvestigationItemValue v "
+ "where v.investigationItem=:ii and v.retired=false and"
+ " (upper(v.code) like :s or upper(v.name) like :s) order by v.name";
m.put("s","'%"+ qry.toUpperCase()+"%'");
m.put("ii", ii);
List<String> sls = getFacade().findString(sql, m);
System.out.println("sls = " + sls);
return sls;
}
But the backing bean method is not fired when i enter text to input text area. But if I remove the f:attribute, backing bean is fired. But I want that parameter as well for functionality.
Thanks in advance to direct me to over come this issue.
Interesting question. Primefaces bounds you to receive only a String parameter in your completion method, so the only solution I see is evaluating your expression at server side, when completion function gets called.
I suppose you've got an iteration (either ui:repeat or p:dataTable) where each id differs from the previous one. If you don't, you can also use it.
That would be the way to go:
<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:p="http://primefaces.org/ui">
<h:head />
<h:body>
<h:form>
<ui:repeat var="str" value="#{bean.strings}">
<p:inputTextarea value="#{bean.value}" style="min-width: 200px;"
completeMethod="#{bean.complete}" />
</ui:repeat>
</h:form>
</h:body>
</html>
#ManagedBean
#RequestScoped
public class Bean {
public String value;
public List<String> strings = Arrays.asList("param1", "param2", "param3");
public List<String> complete(String query) {
List<String> results = new ArrayList<String>();
//Here we evaluate the current #{str} value and print it out
System.out.println(FacesContext
.getCurrentInstance()
.getApplication()
.evaluateExpressionGet(FacesContext.getCurrentInstance(),
"#{str}", String.class));
if (query.equals("PrimeFaces")) {
results.add("PrimeFaces Rocks!!!");
results.add("PrimeFaces has 100+ components.");
results.add("PrimeFaces is lightweight.");
results.add("PrimeFaces is easy to use.");
results.add("PrimeFaces is developed with passion!");
}
return results;
}
public List<String> getStrings() {
return strings;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Note you're evaluating the current EL result for #{str} when method call performs. You'll get a different evaluation result depending on which p:inputTextArea you write to.
See also:
How to pass parameter to f:ajax in h:inputText? f:param does not work
I'm writing my custom table composite component with Mojarra JSF. I'm also trying to bind that composite to a backing component. The aim is to be able to specify the number of elements the table has in a composite attribute, later on the bound backing component will autogenerate the elements itself before view gets rendered. I've this sample code:
Main page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:comp="http://java.sun.com/jsf/composite/comp">
<h:head />
<body>
<h:form>
<comp:myTable itemNumber="2" />
</h:form>
</body>
</html>
myTable.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html">
<h:body>
<composite:interface componentType="components.myTable">
<composite:attribute name="itemNumber"
type="java.lang.Integer" required="true" />
</composite:interface>
<composite:implementation>
<h:dataTable value="#{cc.values}" var="value">
<h:column headerText="column">
#{value}
<h:commandButton value="Action" action="#{cc.action}" />
</h:column>
</h:dataTable>
</composite:implementation>
</h:body>
</html>
MyTable.java:
#FacesComponent("components.myTable")
public class MyTable extends UINamingContainer {
private List<String> values = new ArrayList<String>();
public void action() {
System.out.println("Called");
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
// Initialize the list according to the element number
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
super.encodeBegin(context);
}
public List<String> getValues() {
return values;
}
}
The issue is table gets rendered properly (in this case with two items), but action method doesn't get called when pressing the button on the lines.
If I follow the wiki page for composite components, I can get it work in that way, but having to initialize the List each time getValues() is called, introducing logic into the getter method :-(.
Any idea about that? It seems to be a trouble related with overriding encodeBegin method. I also tried initializing it on markInitialState, but attributes are not yet available there...
Tested with Mojarra 2.1.27 + Tomcat 6-7 & Mojarra 2.2.5 + Tomcat 7
As to the cause, UIComponent instances are inherently request scoped. The postback effectively creates a brand new instance with properties like values reinitialized to default. In your implementation, it is only filled during encodeXxx(), which is invoked long after decode() wherein the action event needs to be queued and thus too late.
You'd better fill it during the initialization of the component. If you want a #PostConstruct-like hook for UIComponent instances, then the postAddToView event is a good candidate. This is invoked directly after the component instance is added to the component tree.
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
...
</cc:implementation>
with
private List<String> values;
public void init() {
values = new ArrayList<String>();
Integer num = (Integer) getAttributes().get("value");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
}
(and remove the encodeBegin() method if it isn't doing anything useful anymore)
An alternative would be lazy initialization in getValues() method.
A simpler solution would be to store and retrieve values as part of the components state. Storing can happen during encodeBegin, and retrieving could directly happen within the getter:
#FacesComponent("components.myTable")
public class TestTable extends UINamingContainer {
public void action() {
System.out.println("Called");
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
// Initialize the list according to the element number
List<String> values = new ArrayList<>();
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
getStateHelper().put("values",values);
super.encodeBegin(context);
}
public List<String> getValues() {
return (List<String>)getStateHelper().get("values");
}
}
To avoid repeating the logic in getValues(), there could be additional parsing required in more complex cases, there should be a way to process and cache the attributes right after they become available, although I am not sure when and how at this point.
Either way - this seemed to be the simplest way to solve this problem.
all
I've been working on a composite component for a date range. Essentially, my composite component uses two Richfaces 4.3 calendar components underneath to capture the individual date values, generate a date range (a pair of LocalDate objects). I found this blog entry which was the basis for my custom component that combines the two submitted values on the calendar into one pair value.
Everything seems to work fine and the values are getting updated. However, I'm trying to figure out how to propagate the change event to the using xhtml page for a partial render of another component, and I've been unsuccessful. I've tried everything I could think of, but I think I'm missing something.
The page:
<rich:panel>
<f:facet name="header">Calendar Date Range Component</f:facet>
<h:outputText id="out1" binding="#{calendarDateRangeTestBean.component1}"
value="#{calendarDateRangeTestBean.dateRange}" converter="localDatePairConverter" /><br/>
<h:outputText id="out2" value="#{calendarDateRangeTestBean.dateRange}" converter="localDatePairConverter" /><b>NOT WORKING</b>
<yxp:calendarDateRange id="calendarDateRange" value="#{calendarDateRangeTestBean.dateRange}"
dataModel="#{calendarDateRangeTestBean}"
valueChangeListener="#{calendarDateRangeTestBean.processValueChange}">
<f:ajax execute="#all" listener="#{calendarDateRangeTestBean.processBehaviorEvent}"/>
<!-- This doesn't seem to work???? -->
<f:ajax execute="#all" render="out2" />
</yxp:calendarDateRange>
</rich:panel>
My test managed bean:
#ViewScoped
#ManagedBean
public class CalendarDateRangeTestBean extends AbstractCalendarDateRangeDataModel implements
ValueChangeListener, Serializable {
private static Logger logger = LoggerFactory.getLogger(CalendarDateRangeTestBean.class);
private Pair<LocalDate> dateRange = Pair.of(LocalDate.now(), LocalDate.now().plusDays(7));
private UIComponent component1;
public UIComponent getComponent1() {
return component1;
}
public LocalDateRange getDateRange() {
return dateRange;
}
public void processBehaviorEvent(final javax.faces.event.AjaxBehaviorEvent event) {
logger.info("processing event " + event + ": " + event.getBehavior());
final FacesContext context = FacesContext.getCurrentInstance();
logger.info("Setting render to " + component1.getClientId(context));
// This seems to cause a rerender of the first component
context.getPartialViewContext().getRenderIds().add(component1.getClientId(context));
}
#Override
public void processValueChange(final ValueChangeEvent event) throws AbortProcessingException {
logger.info(this.toString() + ": processing value change event " + event + ": ["
+ event.getOldValue() + ":" + event.getNewValue() + "]");
}
public void setComponent1(final UIComponent component1) {
this.component1 = component1;
}
public void setDateRange(final Pair<LocalDate> dateRange) {
logger.info("Setting date range to " + dateRange);
this.dateRange = dateRange;
}
}
My composite 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:ui="http://java.sun.com/jsf/facelets"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
xmlns:composite="http://java.sun.com/jsf/composite">
<!-- Methods exposed on rich:component are available in the __proto__ object. -->
<composite:interface componentType="com.yieldex.platform.ui.CalendarDateRange">
<composite:attribute name="value" required="true" type="demo.Pair"/>
<composite:attribute name="dataModel" required="false" type="demo.Pair" />
<composite:clientBehavior name="change" event="change" targets="startCalendar endCalendar" default="true"/>
</composite:interface>
<composite:implementation>
<h:outputStylesheet library="yieldex/platform" name="css/yieldex-platform.css" target="head" />
<div id="#{cc.clientId}" class="yxp-calendar-date-range">
<rich:calendar id="startCalendar"
binding="#{cc.startCalendar}"
styleClass="yxp-start-date-range"
converter="localDateConverter" mode="ajax"
dataModel="#{not empty cc.attrs.dataModel ? cc.attrs.dataModel.startCalendarDataModel : standardCalendarDateRangeDataModel.startCalendarDataModel}"
monthLabels="#{dateRangeMessages.monthNames}"
weekDayLabelsShort="#{dateRangeMessages.weeksShort}"
monthLabelsShort="#{dateRangeMessages.monthNames}" popup="false"
showInput="false" showFooter="false" showWeeksBar="false"
showWeekDaysBar="true" showApplyButton="false"
buttonIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}"
buttonDisabledIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}">
<f:facet name="weekDays"></f:facet>
<f:ajax immediate="true" execute="#all" render="#this endCalendar"/>
</rich:calendar>
<rich:calendar id="endCalendar"
binding="#{cc.endCalendar}"
styleClass="yxp-end-date-range"
converter="localDateConverter" mode="ajax"
dataModel="#{not empty cc.attrs.dataModel ? cc.attrs.dataModel.endCalendarDataModel : standardCalendarDateRangeDataModel.endCalendarDataModel}"
monthLabels="#{dateRangeMessages.monthNames}"
weekDayLabelsShort="#{dateRangeMessages.weeksShort}"
monthLabelsShort="#{dateRangeMessages.monthNames}" popup="false"
showInput="false" showFooter="false" showWeeksBar="false"
showWeekDaysBar="true" showApplyButton="false"
buttonIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}"
buttonDisabledIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}">
<f:facet name="weekDays"></f:facet>
<f:ajax immediate="true" execute="#all" render="startCalendar #this"/>
</rich:calendar>
</div>
</composite:implementation>
</ui:composition>
My backing component:
#FacesComponent("com.yieldex.platform.ui.CalendarDateRange")
public class YXCalendarDateRange extends UIInput implements NamingContainer {
private UICalendar startCalendarComponent;
private UICalendar endCalendarComponent;
#Override
public void encodeBegin(final FacesContext context) throws IOException {
final Pair<LocalDate> value = (Pair<LocalDate>) this.getValue();
if (value == null) {
startCalendarComponent.setValue(null);
endCalendarComponent.setValue(null);
} else {
startCalendarComponent.setValue(value.getStart());
endCalendarComponent.setValue(value.getEnd());
}
super.encodeBegin(context);
}
#Override
protected Object getConvertedValue(final FacesContext context, final Object submittedValue) {
final LocalDate startDate = (LocalDate) startCalendarComponent.getConverter().getAsObject(context,
startCalendarComponent, (String) this.startCalendarComponent.getSubmittedValue());
final LocalDate endDate = (LocalDate) endCalendarComponent.getConverter().getAsObject(context,
endCalendarComponent, (String) this.endCalendarComponent.getSubmittedValue());
if (startDate == null || endDate == null) {
return null;
} else {
if (startDate.isAfter(endDate)) {
final FacesMessage message = new FacesMessage();
message.setSeverity(FacesMessage.SEVERITY_ERROR);
message.setSummary("start date cannot be after end date");
message.setDetail("start date cannot be after end date");
throw new ConverterException(message);
}
return Pair.of(startDate, endDate);
}
}
public UICalendar getEndCalendar() {
return this.endCalendarComponent;
}
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
public UICalendar getStartCalendar() {
return this.startCalendarComponent;
}
#Override
public Object getSubmittedValue() {
return this;
}
public void setEndCalendar(final UICalendar endCalendarComponent) {
this.endCalendarComponent = endCalendarComponent;
}
public void setStartCalendar(final UICalendar startCalendarComponent) {
this.startCalendarComponent = startCalendarComponent;
}
}
What I see is that the valueChangedEvent is coming though. I also see my processBehaviorEvent being called, and the first outputText being rerendered as I'm calling that programmatically. But the second one doesn't seem to get rerendered. I am trying to figure out if this is a bug in Mojarra 2.1.25 or is there something fundamentally wrong with my approach. Any suggestions would be greatly appreciated.
Any client ID in <f:ajax render> is evaluated relative to the parent naming container of the component it has been attached to. In this construct, the <f:ajax> ends up being attached inside the composite component, which is by itself a naming container. However, there's no component with ID out2 inside the composite, which is the problem.
To solve it, specify the absolute client ID. For example, when it's inside a <h:form id="formId"> element:
<f:ajax execute="#all" render=":formId:out2" />
If it's more complicated, binding the component to the view and refer to its client ID dynamically:
<h:outputText id="out2" binding="#{out2}" ... />
...
<f:ajax execute="#all" render=":#{out2.clientId}" />
See also:
How to find out client ID of component for ajax update/render? Cannot find component with expression "foo" referenced from "bar"
In my composite component, I iterate a list<list<javaDetailClass>>. I get all my <h:commandButon> attribute's values through value expression like #{iterator.value}. But the problem comes with attribute action, where action accepts only method expression. whereas I can assign only value expression there, resulting in MethodNotFoundException
<cc:interface>
<cc:attribute name="formElements" />
</cc:interface>
<cc:implementation>
<c:forEach items="#{cc.attrs.formElements}" var="element">
<c:forEach items="#{element}" var="iterator">
<h:commandButton id="#{iterator.id}"
value="#{iterator.value}"
action="#{iterator.action}">
</h:commandButton>
</c:forEach>
</c:forEach>
</cc:implementation>
Can anyone help me in fixing this?
Thanks in advance.
UPDATE
this will be the detail class in my situation,
package com.stackoverflow.test;
public class TestData {
/*Properties based on the implementation of your composite.
Change type where it is needed*/
private String id;
private String value;
private String attributeName;
private String action;
public TestData() {
}
/*Getters and setters omitted*/
}
Bean.java simply holds an ArrayList of ArrayList. The constructor simply created five TestData objects and assigns some default value to its attributes.
package com.stackoverflow.test;
import java.util.ArrayList;
import javax.faces.bean.*;
#ManagedBean
#RequestScoped
public class Bean {
private ArrayList<ArrayList<TestData>> list = new ArrayList<ArrayList<TestData>>();
public Bean() {
ArrayList<TestData> testDataList = new ArrayList<TestData>();
TestData data;
for(int i = 0; i < 5; i++) {
data = new TestData();
data.setId("ID" + i);
data.setValue("VALUE" + i);
data.setAttributeName("ATTRIBUTE" + i);
/**this sets the action attribute of TestData with a API from some other managed bean**/
data.setAction("someOtherManagedbean.someactionAPI");
testDataList.add(data);
}
list.add(testDataList);
}
public ArrayList<ArrayList<TestData>> getList() {
return list;
}
public void setList(ArrayList<ArrayList<TestData>> list) {
this.list = list;
}
}
index.html simply calls the composite by assinging the value of "#{bean.list} to the name attribute
I'm assuming that your TestData.java has the following method public String getAction() (since I'm seeing a setAction(String)) and not
public String action(). Therefore, the reason why you are getting a MethodNotFoundException is because you are supplying the wrong method name to the action attribute. In your case it should be iterator.getAction and not iterator.action. You only supply the abbreviated names when an attribute is expecting a value expression. The interface below has been modifed.
<cc:interface>
<cc:attribute name="formElements" />
</cc:interface>
<cc:implementation>
<c:forEach items="#{cc.attrs.formElements}" var="element">
<c:forEach items="#{element}" var="iterator">
<h:commandButton id="#{iterator.id}"
value="#{iterator.value}"
action="#{iterator.getAction}">
</h:commandButton>
</c:forEach>
</c:forEach>
</cc:implementation>