Dynamically adding fields to JSF form - jsf

I have the following situation that I would like to handle in JSF. My form consists of family information (name/address/phone) and then children information. Since a family can have more then one child, I need to be able to allow the person to click "add more children" and another child "section" will appear.
Here is a simple test case I've thrown together.
Backing Bean. A family has a list of children.
#ViewScoped
#ManagedBean
public class TestBackingBean implements Serializable {
private Family f = new Family();
private Child childToRemove;
public TestBackingBean() {
f.addChild(new Child());
}
public Family getFamily() {
return f;
}
public void setChildToRemove(Child childToRemove) {
this.childToRemove = childToRemove;
}
public TimeZone getTimezone() {
return TimeZone.getDefault();
}
public List<Child> getChildren() {
return f.getChildrenAsList();
}
public Child getChildToRemove() {
return childToRemove;
}
public void addChild() {
f.addChild(new Child());
}
public void removeChild() {
f.removeChild(childToRemove);
}
}
Here is the JSF page:
<!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: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:h="http://java.sun.com/jsf/html">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title><ui:insert name="title" />
</title>
<link rel="stylesheet" type="text/css" href="/hcbb/css/main.css" />
</h:head>
<h:body>
<h:form>
<h:panelGroup id="parentSection">
<h:outputLabel value="Parent Name" for="parent_firstName" />
<h:inputText id="parent_firstName" requiredMessage="Required"
immediate="true" label="Parent First Name"
value="#{testBackingBean.family.firstName}">
<f:validateRequired />
</h:inputText>
<rich:message id="parent_firstNameMessage" for="parent_firstName" />
</h:panelGroup>
<h:panelGroup id="childrenSection">
<h:dataTable value="#{testBackingBean.children}" var="child">
<h:column>
<h:panelGrid id="childPanel" columns="3"
style="border:1px solid brown; padding: 5px; margin-bottom:5px; width: 600px;">
<h:outputText id="childTitle" value="Child"
style="font-weight: bold;" />
<h:outputText id="spacer" />
<a4j:commandButton id="removeBtn"
action="#{testBackingBean.removeChild}" immediate="true"
value="Remove Child" render="childrenSection"
style="float:right;" title="Remove">
<f:setPropertyActionListener
target="#{testBackingBean.childToRemove}" value="#{child}" />
</a4j:commandButton>
<h:outputLabel id="child_firstNameLbl" value="First Name" />
<h:inputText id="child_firstName" requiredMessage="Required"
immediate="true" label="Child First Name"
value="#{child.firstName}">
<f:validateRequired />
</h:inputText>
<rich:message id="child_firstNameMessage" for="child_firstName" />
<h:outputLabel id="child_lastNameLbl" value="Last Name" />
<h:inputText id="child_lastName" requiredMessage="Required"
immediate="true" label="Child Last Name"
value="#{child.lastName}">
<f:validateRequired />
</h:inputText>
<rich:message id="child_lastNameMessage" for="child_lastName" />
<h:outputLabel id="child_dobLbl" value="Birth Date" />
<h:inputText id="child_dob" label="Child Birth Date"
immediate="true" requiredMessage="Required"
value="#{child.dateOfBirth}">
<f:convertDateTime id="dobConverter" pattern="MM/dd/yyyy"
timeZone="#{testBackingBean.timezone}" />
<f:validateRequired />
</h:inputText>
<rich:message id="child_dobNameMessage" for="child_dob" />
</h:panelGrid>
</h:column>
</h:dataTable>
<a4j:commandLink id="addChildBtn" immediate="true"
render="childrenSection" action="#{testBackingBean.addChild}"
value="Add Another Child">
</a4j:commandLink>
</h:panelGroup>
</h:form>
</h:body>
</html>
The issue is the saving of the values when adding/removing child sections. If you enter parent name and then child name and date of birth and then click add the fields for the child you just added will disappear? I would have thought that immediate=true on the button and the fields will get them through.
The problem is add a parent first name, enter child info and click the add another child button and the child information you just entered will be erased.
Any suggestions on how I might be able to make this all work. Seems like a fairly simple and somewhat standard use case.
Thanks!

It looks fine at the first glance. The problem symptoms boils down that the f.getChildrenAsList() doesn't return a list with the new child while JSF is about to apply request values. Perhaps that method is re-fetching the list from DB again upon submit? Add a breakpoint to investigate its retun value. Or perhaps the view scope failed and caused the bean to be reconstructed? Add a breakpoint to the bean's constructor. It should not be reconstructed when you're submitting the form against the same view.
As to using the immediate attribute, all your usage of them is entirely superfluous. Just remove them. To understand its use better, get yourself through the Debug JSF lifecycle article (true, it's JSF 1.2 targeted, but the principles are the same for JSF2) and then read particularly this summary:
Okay, when should I use the immediate attribute?
If it isn't entirely clear yet, here's a summary, complete with real world use examples when they may be beneficial:
If set in UIInput(s) only, the process validations phase will be taken place in apply request values phase instead. Use this to prioritize validation for the UIInput component(s) in question. When validation/conversion fails for any of them, the non-immediate components won't be validated/converted.
If set in UICommand only, the apply request values phase until with update model values phases will be skipped for any of the UIInput component(s). Use this to skip the entire processing of the form. E.g. "Cancel" or "Back" button.
If set in both UIInput and UICommand components, the apply request values phase until with update model values phases will be skipped for any of the UIInput component(s) which does not have this attribute set. Use this to skip the processing of the entire form expect for certain fields (with immediate). E.g. "Password forgotten" button in a login form with a required but non-immediate password field.

Related

Primefaces: conditionally rendered component doesn't get submitted

I have a field that is rendered conditionally, based on a dropdown. All of that goes without problem, but when I submit the form of which the newly rendered component should be part of, it doesn't get submitted.
The code is fairly straightforward:
<h:form id="form">
<p:layout id="layout">
...
<p:layoutUnit id="layoutUnit">
...
<p:panel id="panel">
<p:outputPanel id="container1">
<p:selectOneMenu id="value1" value="#{bean.value1}">
<f:selectItem itemValue="1"/>
<f:selectItem itemValue="2"/>
<f:selectItem itemValue="3"/>
<f:selectItem itemValue="18"/>
<p:ajax event="change" update="container2"/>
</p:selectOneMenu>
</p:outputPanel>
<p:outputPanel id="container2">
<p:inputText id="value2"
value="#{bean.value2}"
rendered="#{bean.value1 eq 18}"
>
</p:inputText>
</p:outputPanel>
</panel>
<div id="buttons">
<p:commandButton id="commandButton" action="#{bean.save}" value="save" />
</div>
</layoutUnit>
</layout>
</form>
Tried possible solutions:
#ViewScoped of JSF is not usable, because it clashes with CDI
JSF Dynamic Rendered Component's Value becomes null when the form is submitted
conditionally rendered input component does not update value
commandButton/commandLink/ajax action/listener method not invoked or input value not updated
I can think of a few scenarios causing this behaviour:
'Rendered' seems to re-evaluate based on values in the backing bean,
rather than the new values given in the UI (if it defaults to 1, it is 1 again on submit, not 18). The component therefore
doesn't get submitted.
The added component isn't correctly added to
the form, and therefore not submitted.
?
Option 1 seems to be the most likely one, but could anyone point me in the right direction?
I work with WAS8.5 (so JSF2.0), Primefaces and CDI.
Using CDI's #ConversationScoped in addition to #Named is a solution. That scope works comparable to the #ViewScoped from JSF.
To get it working I've simply added code from the example here. So in short:
Bean.java
#Named
#ConversationScoped
public class Bean implements Serializable{
#Inject
private Conversation conversation;
Bean values, getters & setters
public void initConversation(){
if (!FacesContext.getCurrentInstance().isPostback() && conversation.isTransient()) {
conversation.begin();
}
}
public String endConversation(){
if(!conversation.isTransient()){
conversation.end();
}
}
public String navigateAwayFromScreen(){
endConversation();
}
}
beanOutput.xhtml
<?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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:event listener="#{bean.initConversation}" type="preRenderView"/>
<h:form id="form">
<p:layout id="layout">
...
<p:layoutUnit id="layoutUnit">
...
<p:panel id="panel">
<p:outputPanel id="container1">
<p:selectOneMenu id="value1" value="#{bean.value1}">
<f:selectItem itemValue="1"/>
<f:selectItem itemValue="2"/>
<f:selectItem itemValue="3"/>
<f:selectItem itemValue="18"/>
<p:ajax event="change" update="container2"/>
</p:selectOneMenu>
</p:outputPanel>
<p:outputPanel id="container2">
<p:inputText id="value2"
value="#{bean.value2}"
rendered="#{bean.value1 eq 18}"
>
</p:inputText>
</p:outputPanel>
</panel>
<div id="buttons">
<p:commandButton id="commandButton" action="#{bean.navigateAwayFromScreen}" value="Go away!" />
</div>
</layoutUnit>
</layout>
</form>
</html>
Now, a conversation gets started when you open the page (due to the initConversation being called from the top of beanOutput.xhtml), and ended when you click the button navigateAwayFromScreen.
(However, if you are able to work with JSF2.2, it should be possible to use the #ViewScoped in combination with CDI. (I say 'should': I am not in the situation to upgrade. But it may be useful to others to examine)).

Processing of JSTL in JSF [duplicate]

I have the below Test.xhtml where i can select mode as Sea/Air. I don't want to load the all the 4 pages(Page1.xhtml, Page2.xhtml, Page3.xhtml, Page4.xhtml) into the jsf view tree. In my scenario, mode once selected and saved that can not be changed to other mode. The saved mode will be shown as view mode. Since at any point of time i need only 2 files (Page1.xhtml, Page2.xhtml (or) Page3.xhtml, Page4.xhtml)... I am using the JSTL tag handler for dynamic including the pages. the below thing is working fine. But i am not able to understand how the rendering working.
Test.xhtml
<!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: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:c="http://java.sun.com/jsp/jstl/core">
<h:head><title>JSF And JSTL</title>
<link href="../css/styles.css"
rel="stylesheet" type="text/css"/>
</h:head>
<h:body>
<div align="center">
<h:form id="form1">
<h:panelGrid columns="2">
<h:outputLabel value="Select Mode"/>
<h:selectOneMenu id="selectPageId" value="#{testBean.selectedMode}">
<f:selectItem itemLabel="Select" />
<f:selectItem itemLabel="Sea" itemValue="sea" />
<f:selectItem itemLabel="Air" itemValue="air" />
<f:ajax execute="#this" render="pageGroup"/>
</h:selectOneMenu>
</h:panelGrid>
<h:panelGroup id="pageGroup">
<c:if test="#{'sea' eq testBean.selectedMode}">
<ui:include src="Page1.xhtml" />
<ui:include src="Page2.xhtml" />
</c:if>
<c:if test="#{'air' eq testBean.selectedMode}">
<ui:include src="Page3.xhtml" />
<ui:include src="Page4.xhtml" />
</c:if>
</h:panelGroup>
</h:form>
</div>
</h:body>
</html>
TestBean.java
package com.test;
#ManagedBean(name = "testBean")
#ViewScoped
public class TestBean {
private String selectedMode;
public String getSelectedMode() {
return selectedMode;
}
public void setSelectedMode(String selectedMode) {
this.selectedMode = selectedMode;
}
}
Updated Again
Please help to clarify the below.
When i send a request first time to access the Test.xhtml, the jsf view (UIViewRoot) gets created. Since testBean.selectedMode by default is null.. this created view would not contain any component details of <c:if> and <ui:include> which are inside of <h:panelGroup id="pageGroup">.
After that when i send a second request(ajax postback) by selecting the mode as sea, the UIViewRoot gets created again for the request in the Restore View Phase. While UIViewRoot creation for this request in Restore View Phase, the tag handlers(<c:xxx>, <f:xxx>, <ui:include>) will get executed.
Since the selected value 'sea' will be updated to model(TestBean.java) only in Update Model Values Phase, in the Restore View Phase the EL #{testBean.selectedMode} will be evaluated to null.
So in my example Test.xhtml page the both tag handlers (<c:if test="#{'sea' eq testBean.selectedMode}"> and <c:if test="#{'air' eq testBean.selectedMode}">) will be evaluated to false. So the UIViewRoot does not contain any information related to the components inside <h:panelGroup id="pageGroup">.
Even though the UIViewRoot does not contain the components information inside the <h:panelGroup id="pageGroup">, How JSF able to render the included pages(<ui:include src="Page1.xhtml" />,<ui:include src="Page2.xhtml" />) successfully?
I am not sure how JSF rendering happening here. While Rendering, Is JSF re-creating the component tree for the particular rendering part(<h:panelGroup id="pageGroup">).?
Note: The example is working fine when i select mode as sea/air. The corresponding parts are rendering correctly.

Omit validation for p:selectOneMenu, for Ajax requests

I have a Jsf page with a fragment with <p:selectOneMenu/> (Prime faces) and an Ajax associated with it. It have the default select item "--- Select One ---" and others items that were created dynamically.
This field is required, so if users submit the form without select, the page show a error message.
The problem occurs when I select one of this others options and go back selecting "--- Select One ---" again. Then the page shows the required field message, even before I submit the form. I try different ways of immediate in <p:ajax> and <p:selectOneMenu> tags but none of them has the expected behavior.
The <p:messages> is declared as below:
-- list.xhtml
<p:messages id="messages" autoUpdate="true" closable="true" escape="false" />
<.... include fragment.xhtml ....>
And the fragment:
-- fragment.xhtml
<p:selectOneMenu id="selectTipoCarro"
value="#{carroBean.carro.tipoCarroEnum}"
required="true">
<f:selectItems value="#{carroBean.listaSelectItemTipoCarroInclusao}" />
<p:ajax update="outputInicioVigenciaWrapper outputLabelCalendarInicioVigenciaWrapper"
listener="#{carroBean.aoMudarTipoCarro}" />
</p:selectOneMenu>
<h:panelGroup id="outputLabelCalendarInicioVigenciaWrapper">
<h:outputLabel id="outputLabelCalendarInicioVigencia"
rendered="#{carroBean.edicaoDataInicioVigenciaDisponivel}"
for="calendarInicioVigencia">
<span>*
#{labels['carro.inicio.vigencia']}: </span>
<p:calendar id="calendarInicioVigencia"
value="#{carroBean.carro.dataInicioVigencia}"
showOn="button"
pattern="dd/MM/yyyy" mask="true"
required="true"/>
</h:outputLabel>
</h:panelGroup>
<h:panelGroup id="outputInicioVigenciaWrapper">
<h:outputLabel for="outputInicioVigencia"
rendered="#{not carroBean.edicaoDataInicioVigenciaDisponivel}">
<span aria-live="polite">
<h:outputText id="outputInicioVigencia"
value="#{carroBean.carro.dataInicioVigencia}"
styleClass="dataFormat"
</h:outputText>
</span>
</h:outputLabel>
</h:panelGroup>
private SelectItem obterSelectItemSelecione() {
SelectItem selectItem = new SelectItem("", "-- Select One --");
return selectItem;
}
private void preencherListaSelectItemTipoCarro(List<SelectItem> select, TipoCarroEnum[] tiposCarrosConsiderados) {
select.clear();
select.add(obterSelectItemSelecione());
for (TipoCarroEnum tipoCarro : tiposCarrosConsiderados) {
select.add(new SelectItem(tipoCarro, tipoCarro.getNome()));
}
}
public void aoMudarTipoCarro() {
getCarro().setDataInicioVigencia(carroService.obterProximaDataInicioVigenciaDisponivel(getCarro().getTipoCarroEnum()));
}
That's the expected behaviour. When adding a p:ajax tag to your p:selectOneMenu you make the value be processed everytime the user changes the input, so it will be validated and rejected if you mark it as required. My favourite workaround for this cases is to include a request param in the button to submit the whole form and check for it in the required attribute. That's it:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:comp="http://java.sun.com/jsf/composite/comp"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head />
<h:body>
<h:form>
<p:messages autoUpdate="true" />
<p:selectOneMenu value="#{val}"
required="#{param['validate']}">
<p:ajax event="change" />
<f:selectItem itemLabel="None" noSelectionOption="true" />
<f:selectItem itemLabel="val1" itemValue="val1" />
</p:selectOneMenu>
<p:commandButton value="Submit" ajax="false">
<f:param name="validate" value="true" />
</p:commandButton>
</h:form>
</h:body>
</html>
See also:
Conditionally skip JSF validation but perform model updates
Explanation
This happens because you have a p:messages component with autoUpdate set to true and you have attached p:ajax to your selectOneMenu so anytime your value changes, p:messages is updated. Therefore, due to the use of required="true" an error messages is shown as soon as you have selected "--- Select One ---".
Solution
Add ignoreAutoUpdate="true" to your p:ajax or
Remove autoUpdate="true" if not necessary

Updating components inside a custom ui:component when it is used multiple times on same page

I am building a simple component in JSF (Mojarra 2.1.x) where I have to access parent ui components to update them, currently I'm using binding to achieve this, but it only works as long as I don't use the component more than once on the same page.
So I need a solution that would allow me to use the component multiple times on same page.
In the following code I'm updating commandButton with binding="#{msButton}" and panel with binding="#{msPanel}":
<?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">
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:layout="http://sterz.stlrg.gv.at/jsf/layout"
xmlns:p="http://primefaces.org/ui">
<cc:interface>
<cc:attribute name="controller" required="true" />
<cc:attribute name="converter" required="true" />
</cc:interface>
<cc:implementation>
<p:commandButton id="msButton#{cc.attrs.controller.class.getSimpleName()}" binding="#{msButton}" value="#{msg.mehr} (#{cc.attrs.controller.itemList.size()})" type="button" />
<p:overlayPanel id="msOverlayPanel" for=":#{msButton.clientId}" hideEffect="fade" my="right top" at="right bottom">
<p:panel id="msPanel#{cc.attrs.controller.class.getSimpleName()}" binding="#{msPanel}" styleClass="ui-panel-fit">
<ui:repeat id="repeat" value="#{cc.attrs.controller.itemList}"
var="item">
<p:commandButton id="removeButton"
actionListener="#{cc.attrs.controller.removeItem(item)}"
icon="ui-icon-trash" update=":#{msPanel.clientId} :#{msButton.clientId}" ajax="true"
process="#this" disabled="#{cc.attrs.controller.itemList.size() == 1}"/>
<p:selectBooleanButton id="value1" value="#{item.exclude}"
offLabel="und" onLabel="und nicht" style="width:80px;">
<p:ajax event="change" process="#this" />
</p:selectBooleanButton>
<p:autoComplete converter="#{cc.attrs.converter}"
readonly="#{cc.attrs.readonly}" value="#{item.from}"
dropdown="true"
completeMethod="#{cc.attrs.controller.autocomplete}" var="gp"
itemLabel="#{gp.displayName}" itemValue="#{gp}">
<p:ajax event="itemSelect" process="#this" />
</p:autoComplete>
<h:outputText value=" #{msg.bis} " />
<p:autoComplete converter="#{cc.attrs.converter}"
readonly="#{cc.attrs.readonly}" value="#{item.to}" dropdown="true"
completeMethod="#{cc.attrs.controller.autocomplete}" var="gp"
itemLabel="#{gp.displayName}" itemValue="#{gp}">
<p:ajax event="itemSelect" process="#this" />
</p:autoComplete>
<br />
</ui:repeat>
<hr />
<p:commandButton id="addButton" actionListener="#{cc.attrs.controller.addItem}"
icon="ui-icon-plus" value="#{msg.zufuegen}" update="#parent :#{msButton.clientId}"
ajax="true" process="#this"/>
</p:panel>
</p:overlayPanel>
</cc:implementation>
</ui:component>
Any help is much apprecieted.
The solution is to use the faces component bean
#FacesComponent("com.xxx.MultiselectorIdComponent")
public class MultiselectorIdComponent extends UINamingContainer
{
UIComponent msPanel;
// getters/settter and other ui compunents
}
tell the component interface what faces component bean to use
<cc:interface componentType="com.xxx.MultiselectorIdComponent">
bind the JSF component to the one in the faces component bean
<p:panel binding="#{cc.msPanel}"/>
and to access the components, for example to update the component, we use the binding
<p:commandButton value="My Button" update=":#{cc.msPanel.clientId}"/>
ALSO:
A good practice is to use a parent container (like <div>) with the following ID
<div id="#{cc.clientId}">
Hope this helps,
Regards
You could target the components for update by their style class
<p:commandButton styleClass="msButton" ... />
<p:panel styleClass="msPanel" ... />
And I guess you update them from addButton, which would look like this
<p:commandButton id="addButton" update="#(.msButton, .msPanel)" ... />
It should have no problems working with many cc instances on the same page.
UPDATE
You can try with update="#composite", which should refresh the whole custom component. Or, the cumbersome update="#parent #parent:#parent:#parent:#child(1)" which should target panel and button respectively. Or update="#parent #parent:#parent:#previous", which should do the same. Take a look at chapter 4.3 in Primefaces User Guide for more examples and supported keywords.
I solve similar problem by common bean "bindingBean" stored in ViewContext. BindingBean holds all binded component in internal MAP of component records. The key of hash map is an EL expression - it is factory of the component. EL is used for components creating. Components are hold in records stored in MAP in bind bean.
Example:
<h:panel binding="#{bindBean.get('msPanelFactory.getPanel()').component}"/>
Record:
public class BindingRecord {
private Object component;
private String compExpr;
public BindingRecord(String compExpr) {
this.compExpr = compExpr;
}
public Object getComponent() {
if (component == null) {
component = getValueExprValue("#{" + compExpr + "}");
}
return component;
}
public void setComponent(Object component) {
this.component = component;
}
public Object getStoredComponent() {
return component;
}
}
Binding bean:
public class BindingBean {
private final Map<String, BindingRecord> bindingMap = new HashMap<>();
public BindingRecord get(String key) {
BindingRecord result = bindingMap.get(key);
if (result == null) {
result = new BindingRecord(key);
bindingMap.put(key, result);
}
return result;
}
}
In you case you can use:
<h:panel binding="#{bindingBean.get('panelFactory.create()').component}"/>
Inside composite component you can use trick - Pass component name by composite parameter:
<h:panel binding="#{bindingBean.get('panelFactory.create('.concate(cc.attrs.componentName).concate(')')).component}"/>

Dynamic Id's in JSF/Seam

Got a little problem with a Seam application I'm working on and I was wondering if anyone knows a way round it. I've got a form in my application that uses AJAX to show certain input boxes depending on an item in a dropdown box. The code works fine except for setting the ID's in my input boxes. It looks like JSF doesn't let me set an ID via a variable. Other attributes like "for" in labels are fine. Here's some code explaining what I mean:
<ui:repeat value="#{serviceHome.instance.serviceSettings}" var="currSetting" >
<li>
<!-- Imagine the below works out as "settingABC" -->
<c:set var="labelKey" value="setting#{jsfUtils.removeWhitespace(currSetting.key.name)}" />
<!-- Labelkey is correctly added into this input so for = "settingABC" -->
<h:outputLabel for="#{labelKey}" styleClass="required generated" value="#{currSetting.key.name}:"/>
<s:decorate styleClass="errorwrapper">
<!-- Labelkey ISN'T correctly added into this input. Instead we just get "setting" -->
<h:inputText id="#{labelKey}" value="#{currSetting.value}"/>
<a4j:outputPanel ajaxRendered="true">
<h:message for="#{labelKey}" styleClass="errormessage" />
</a4j:outputPanel>
</s:decorate>
</li>
</ui:repeat>
Does anyone have any idea how I can get past this?
You see why they don't let you set the ID, right? JSF takes over id creation because you're in a repeated loop of components and, if they let you just set the id, you'd end up with duplicate IDs, which wouldn't help you anyway.
Without knowing WHY you want to set the ID explicitly, it's hard to give you a workaround. If it's for JavaScript, you can do what Grant Wagner suggests, and let JSF give you what it put as the id. You can also take a peek at the generated HTML and see what format the id is in. JSF usually uses
"form_id:loop_id:loop_index:component_id"
as the id it generates for components in a form/repeat. You'd have to be sure and give id's to your form and ui:repeat tags to know what they were.
Ok, you answered that you want to have an h:message tag for a specific inputText inside the loop, that's easy.
<h:inputText id="myInput" .... />
<h:message for="myInput" ... />
Now, messages generated for the input will be displayed in the message, and JSF will mangle the "for" attribute (though that isn't generated to HTML) just like it will the "id" attribute in the inputText so they match.
You can even make your OWN messages in your handler code to go to the specific h:message, but you'll need to use a call to clientId to get the target of the message, given the backing bean (not the value backing bean) of the component in question.
I'm assuming you want to control the ID of your input component so you can reference it later in Javascript?
Since you can't set the ID via an expression, I do this:
<h:inputText id="whatever" value="..." />
Then later in the code:
<script type="text/javascript">
var theElement = document.getElementById('<h:outputText value="#{pagecode.whateverClientId}"/ >');
...
</script>
In the pagecode:
protected HtmlInputText getWhatever() {
if (whatever == null) {
whatever = (HtmlInputText) findComponentInRoot("whatever");
}
}
public String getWhateverClientId() {
return getWhatever().getClientId(getFacesContext());
}
Hope that helps.
Have you tried use facelets?
That will allow you to assing your own ids ie:
me:labelKeyThingo can then use the id=#{labelKey} to make a unique label. Here is an example facelet called m:textPassword from my bad code:
<!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" xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core" xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich">
<ui:composition>
<c:set var="styleClass" value="formPrompt" />
<c:set var="requiredLabel" value="" />
<c:choose>
<c:when test="${required=='true'}">
<c:set var="required" value="true" />
<c:set var="styleClass" value="formRequiredPrompt" />
<c:set var="requiredLabel" value="*" />
</c:when>
</c:choose>
<h:panelGroup id="#{id}_formRowTemplateLabel_panelGroup">
<h:outputLabel for="#{id}" styleClass="#{styleClass}" id="#{id}_formRowTemplate_outPut"
value="#{label}" />
<c:if test="${required == 'true'}">
<h:outputText value="#{requiredLabel}" styleClass="formRequiredPromptAsterix"></h:outputText>
</c:if>
</h:panelGroup>
<h:panelGroup id="#{id}_textPasswordTemplate_panelGroup">
<h:inputSecret required="${required}" id="#{id}" value="#{property}"
styleClass="formText">
<f:validator validatorId="Maserati.Password" />
<f:validateLength maximum="16" minimum="8" />
<ui:insert name="additionalTags"></ui:insert>
</h:inputSecret>
<h:message styleClass="formErrorMsg" id="#{id}_textPasswordTemplate_msg" for="#{id}" />
</h:panelGroup>
</ui:composition>
</html>
It is used thusly:
<m:textPassword id="password" label="#{msgs.passwordPrompt}"
property="#{individualApplicationMBean.password}"
required="true" maxlength="16" />

Resources