JSF 2 convertNumber permille - jsf

We have a #{object.amount} equal to 0.003 and we would like to display it as 3‰ (With the permille operator)
We also would like to convert the value of an inputText to permille. (e.g: a user input of 3 will store 0.003)
The f:convertNumber allow us to convert to the type % but not ‰ :
<h:outputText value="#{object.amount}" >
<f:convertNumber type="percent" />
</h:outputText>
How can we display a value permille in an outputText and convert a value to a permille in an inputText ?

Like Kukeltje suggested, you can implement a custom converter, for example:
#FacesConverter("permilleConverter")
public class PermilleConverter implements Converter {
private static final String PERMILLE_SYMBOL = "‰";
private static final int PERMILLE_FACTOR = 1000;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null) {
return null;
}
return getDoubleValue(value) / PERMILLE_FACTOR;
}
private Double getDoubleValue(String value) {
NumberFormat nf = new DecimalFormat();
Double doubleValue;
try {
Number number = nf.parse(value);
doubleValue = number.doubleValue();
} catch (ParseException e) {
throw new ConverterException("no valid format");
}
return doubleValue;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return null;
}
double amount = (double) value;
NumberFormat nf = new DecimalFormat();
nf.setMaximumFractionDigits(0);
return nf.format(amount * PERMILLE_FACTOR) + PERMILLE_SYMBOL;
}
}
And use it like this:
<h:form>
<h:outputText value="#{userBean.amount}">
<f:converter converterId="permilleConverter" />
</h:outputText>
<h:inputText value="#{userBean.amount}">
<f:converter converterId="permilleConverter" />
</h:inputText>
<h:commandButton value="test">
<f:ajax execute="#form" render="#form" />
</h:commandButton>
</h:form>

Related

JSF - "Value is not a valid option" for unknown reason >> To re-evaluate 'duplicate' [duplicate]

This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 6 years ago.
In my JSF application using RichFaces, I have a screen with rich:dataTable
that displays my objects, and a column that refers to another page, with the details of the selected record so that it can be used.
In this second page, after completing the requested data, when running submit, the validation returns the message:
:Value is not a valid option.
Look that, unlike the problem cited in this post, the name of the problem field is not being displayed.
During debugging, I noticed that in the selecionarEmitente() method, the object localidade is null. If I made the set on the dataTable page and the name of the selected object appears on the secondPage, what is missing?
I already researched other posts about this problem. For example, according to this post, my javabeans involved have the equals() and hashCode() methods. Unlike this other post, I'm using custom converters.
Where is my mistake?
I'm using RichFaces 4.5.1.Final, MyFaces 2.2.10, Spring 3.1.1 .
dataTable.xhtml
<h:selectOneMenu id="estado"
immediate="true" validator="#{validadorMB.validarEstado}"
converter="estadoConverter">
<f:selectItem itemLabel="---" />
<f:selectItems value="#{localidadeMB.estados}"
var="est" itemValue="#{est}" itemLabel="#{est.uf}" />
<a4j:ajax event="change" actionListener="#{localidadeMB.filtrarUF}" render="table" />
</h:selectOneMenu>
<a4j:region id="region" immediate="true">
<rich:dataTable id="tabela"
value="#{localidadeMB.getLocalidadesPorUF()}" var="loc"
rowKeyVar="row" rows="20" width="800px" render="scroller">
<rich:column id="col_localidade" sortBy="#{loc.nome}" filterBy="#{loc.nome}">
<h:outputText id="nomeLocalidade" value="#{loc.nome}" />
</rich:column>
...
<rich:column>
<h:commandLink id="declaracaolink" action="#{localidadeMB.carregarLocalidade}">
<h:graphicImage url="/img/declaracao.png" />
<f:setPropertyActionListener value="#{loc}" target="#{localidadeMB.localidade}" />
</h:commandLink>
</rich:column>
</rich:dataTable>
</a4j:region>
secondPage.xhtml
<h:body>
<h:form id="formDeclaracao" focus="estado">
<h:panelGrid columns="2" columnClasses="labelFormulario">
<h:outputLabel value="UF" for="estado" />
<h:selectOneMenu id="estado"
value="#{localidadeMB.localidade.estado}"
label="#{localidadeMB.localidade.estado.uf}"
valueChangeListener="#{localidadeMB.selecionarEstado}"
validator="#{validadorMB.validarEstado}" immediate="true"
converter="estadoConverter"
style="width: 45px;" styleClass="listbox">
<f:selectItems value="#{localidadeMB.estados}"
var="est" itemValue="#{est}" itemLabel="#{est.uf}" />
<a4j:ajax event="change" render="nomeLocalidade" />
</h:selectOneMenu>
<h:outputLabel value="Nome da localidade:" for="nomeLocalidade" />
<h:selectOneMenu id="nomeLocalidade"
value="#{localidadeMB.localidade}"
label="#{localidadeMB.localidade.nome}" immediate="true"
valueChangeListener="#{localidadeMB.selecionarLocalidade}"
validator="#{validadorMB.validarLocalidade}"
converter="localidadeConverter"
style="width: 220px;" styleClass="listbox">
<f:selectItems value="#{localidadeMB.localidades}"
var="loc" itemValue="#{loc}" itemLabel="#{loc.nome}" />
</h:selectOneMenu>
<h:outputLabel value="Interessado:" for="interessado" />
<h:inputText id="interessado" value="" required="true"
requiredMessage="xxxxxxxxx" immediate="true"
styleClass="listbox" style="width: 215px;">
</h:inputText>
<h:outputLabel value="Solicitante:" for="solicitante" />
<h:inputText id="solicitante" value="" required="true"
requiredMessage="xxxxxxxxxxxxx" immediate="true">
</h:inputText>
<h:outputLabel value="Documento apresentado:" for="documento" />
<h:inputText id="documento" value="" required="true"
requiredMessage="xxxxxxxxxxxxx" immediate="true">
</h:inputText>
<h:outputLabel value="Emitente:" for="emitente" />
<h:selectOneMenu id="emitente"
value="#{localidadeMB.emitente}"
label="#{localidadeMB.emitente.descricaoReduzida}"
valueChangeListener="#{localidadeMB.selecionarEmitente}"
validator="#{validadorMB.validarEmitente}"
converter="emitenteConverter">
<f:selectItem itemLabel="---" />
<f:selectItems value="#{localidadeMB.emitentes}" var="em" itemValue="#{em}" itemLabel="#{em.descricaoReduzida}" />
</h:selectOneMenu>
<h:commandButton id="btnGerarDeclaracao" value="Get PDF"
action="#{localidadeMB.getPDF()}" style="width: 84px;" />
</h:panelGrid>
</h:form>
</h:body>
ManagedBean
#ManagedBean
public class LocalidadeMB{
private Emitente emitente;
private Estado estado;
private Localidade localidade;
public String carregarLocalidade() {
estado = localidade.getEstado();
localidade = getLocalidade();
getLocalidades();
carregarLocalidadePorUF(estado);
return "secondPage.xhtml";
}
public void filtrarUF(ActionEvent action) {
try {
String uf = JSFHelper.getRequestParameter("formConsulta"
+ UINamingContainer.getSeparatorChar(JSFHelper
.getFacesContext()) + "estado");
estado = estadoFacade.getEstado(Integer.parseInt(uf));
} catch (Exception e) {
...
}
}
public List<Emitente> getEmitentes() {
try {
return emitenteFacade.getEmitentes();
} catch (Exception e) {
...
}
}
public List<Emitente> getEstados() {
try {
return estadoFacade.getEstados();
} catch (...) {
...
}
}
public List<Localidade> getLocalidades() {
try {
return localidadeFacade.getLocalidades();
} catch (Exception e) {
...
}
}
public List<Localidade> getLocalidadesPorUF() {
try {
String uf = JSFHelper.getRequestParameter("formConsulta"
+ UINamingContainer.getSeparatorChar(JSFHelper
.getFacesContext()) + "estado");
if ((uf != null) && (!uf.equals("---"))) {
estado = estadoFacade.getEstado(Integer.parseInt(uf));
return localidadeFacade.getLocalidadesPorEstado(estado);
} else {
return null;
}
} catch (Exception e) {
...
}
}
public void selecionarEmitente(ValueChangeEvent evento) {
emitente = (Emitente)evento.getNewValue();
}
public void selecionarEstado(ValueChangeEvent evento) {
estado = (Estado)evento.getNewValue();
}
public void selecionarLocalidade(ValueChangeEvent evento) {
localidade = (Localidade)evento.getNewValue();
}
}
Validator
#ManagedBean(name = "validadorMB")
public class ValidadorLocalidadeMB {
public void validarEmitente(FacesContext context, UIComponent componentToValidate, Object value) throws ValidatorException {
if (((Emitente) value).getEmitenteId() == 0) {
FacesMessage msg = new FacesMessage(null, "Selecione o emitente");
throw new ValidatorException(msg);
}
}
public void validarEstado(FacesContext context, UIComponent componentToValidate, Object value) throws ValidatorException {
if (((Estado) value).getEstadoId() == 0) {
FacesMessage msg = new FacesMessage(null, "Selecione uma UF");
throw new ValidatorException(msg);
}
}
public void validarLocalidade(FacesContext context, UIComponent componentToValidate, Object value) throws ValidatorException {
if (((Localidade) value).getLocalidadeId() == 0) {
FacesMessage msg = new FacesMessage(null, "Selecione uma localidade");
throw new ValidatorException(msg);
}
}
}
Converters
#FacesConverter(value = "emitenteConverter")
public class EmitenteConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent ui, String value) throws ConverterException {
ValueExpression vex = context
.getApplication()
.getExpressionFactory()
.createValueExpression(context.getELContext(),
"#{emitenteFacade}", EmitenteFacadeImpl.class);
EmitenteFacadeImpl fac = (EmitenteFacadeImpl) vex.getValue(context
.getELContext());
try {
return fac.getEmitentePorId(Integer.valueOf(value));
} catch (NumberFormatException | DAOException e) {
....
}
}
#Override
public String getAsString(FacesContext context, UIComponent ui, Object value) throws ConverterException {
if (value == null) {
return "";
}
if (value instanceof Emitente) {
return String.valueOf(((Emitente) value).getEmitenteId());
}
}
}
#FacesConverter(value = "estadoConverter")
public class EstadoConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent ui, String value) throws ConverterException {
ValueExpression vex = context
.getApplication()
.getExpressionFactory()
.createValueExpression(context.getELContext(),
"#{estadoFacade}", EstadoFacadeImpl.class);
EstadoFacadeImpl fac = (EstadoFacadeImpl) vex.getValue(context
.getELContext());
try {
return fac.getEstadoPorId(Integer.valueOf(value));
} catch (NumberFormatException | DAOException e) {
....
}
}
#Override
public String getAsString(FacesContext context, UIComponent ui, Object value) throws ConverterException {
if (value == null) {
return "";
}
if (value instanceof Estado) {
return String.valueOf(((Estado) value).getEstadoId());
}
}
}
#FacesConverter(value = "localidadeConverter")
public class LocalidadeConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent ui, String value) throws ConverterException {
ValueExpression vex = context
.getApplication()
.getExpressionFactory()
.createValueExpression(context.getELContext(),
"#{localidadeFacade}", LocalidadeFacadeImpl.class);
LocalidadeFacadeImpl fac = (LocalidadeFacadeImpl) vex.getValue(context.getELContext());
try {
return fac.getLocalidadePorId(Integer.valueOf(value));
} catch (NumberFormatException | DAOException e) {
....
}
}
#Override
public String getAsString(FacesContext context, UIComponent ui, Object value) throws ConverterException {
if (value == null) {
return "";
}
if (value instanceof Localidade) {
return String.valueOf(((Localidade) value).getLocalidadeId());
}
}
}
Your problem might be with this line:
value="#{(localidadeMB.localidade.localidadeId != null) ? localidadeMB.localidade.localidadeId :localidadeMB.localidade}"
The value of a <h:selectOneMenu> should be a simple property, not an expression, as it has to set.
Edit:
There could be another problem with your code:
You construct those SelectItems with the entity's ID as the value property, and then use converter to convert String to/from the entity itself.
Thus there are a few possibilities for you:
Use the converter, and the "whole" entity as the value of SelectItem.
Or don't use Selectitems, but a List of the entities themselves, as your first link does.
The other possibility is to use a dummy entity, set its id property, and in your valueChangeListener retrieve the entity by the ID.

#Inject PersonDao is null in #FacesConverter class [duplicate]

This question already has answers here:
How to inject #EJB, #PersistenceContext, #Inject, #Autowired, etc in #FacesConverter?
(5 answers)
Closed 7 years ago.
I am trying to get from selectOneMenu an object that has fields such as first name, last name and so on. This is my form:
<h:form>
<p:outputLabel value="Persons: " />
<p:selectOneMenu value="#{personBean.person}">
<f:selectItem itemLabel="Select Person" itemValue="" noSelectionOption="true"/>
<f:selectItems itemLabel="#{person.firstName}" itemValue="#{person}" var="person" value="#{personBean.persons}" />
</p:selectOneMenu>
<br /><br />
<p:commandButton value="Submit"
action="#{personBean.showSomething()}" icon="ui-icon-check" />
I don't understand.. where am i going wrong? How can i get that object?
I've been trying for a few days but i haven't managed to fix this problem...
I tried using a Converter but i kept getting NPEs.
EDIT
This is my converter:(I am trying to get the object from my postgres db with the help of my DAO)
#Named
#FacesConverter(forClass=Person.class)
public class PersonConverter implements Converter {
#Inject
private PersonDao personDao;
#Override
public Object getAsObject(FacesContext arg0, UIComponent component, String submittedValue) {
if (submittedValue == null || submittedValue.isEmpty()) {
return null;
}
try {
Person p = personDao.findById(Long.valueOf(submittedValue));
return p;
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(submittedValue + " is not a valid Person ID"), e);
}
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) {
if (arg2 == null) {
return "";
}
if (arg2 instanceof Person) {
return String.valueOf(((Person) arg2).getId());
} else {
throw new ConverterException(new FacesMessage(arg2 + " is not a valid Person"));
}
}
}
This is where i get a NPE Person p = personDao.findById(Long.valueOf(submittedValue));
The find() method works anywhere else...
When i used the debugger i noticed that personDao is null. How can i fix this?
In the page:
<p:selectOneMenu value="#{personBean.person}">
<f:selectItem itemLabel="Select Person" itemValue="" noSelectionOption="true"/>
<f:selectItems itemLabel="#{person.firstName}" itemValue="#{person}" var="person" value="#{personBean.persons}" />
<f:converter binding="#{personConverter}" />
<f:attribute name="attrpersons" value="#{personBean.persons}" />
</p:selectOneMenu>
In the Person bean:
You must implement toString and equals methods.
And finally you have to add the converter class:
#Named
public class PersonConverter implements Converter{
public PersonConverter(){}
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String submittedValue) {
List<Person> persons = (List<Person>)component.getAttributes().get("attrpersons");
if (submittedValue.trim().equals("")) {
return null;
} else {
Iterator<Person> it = persons.iterator();
while(it.hasNext()){
Person p = it.next();
if(p.toString().equals(submittedValue))
return p;
}
}
return null;
}
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object value) {
if (value == null) {
return "";
} else {
return value.toString();
}
}
}

How to render graphicImage in dataTable based on a column value?

I have a dataTable, containing Package objects with id and phaseList attributes. I would like this dataTable to display package id and its five phases status with some graphicImages like colored circles.
For example: If a package's phase one is completed, the first circle rendered in a progress column is a green one, other four are red ones.
So basically I need to render a circle based on a value from phaseList element attribute. I already accomplished all of this, but it seems to me that there should be some other way. The problem is that I must have five phase color attributes in a backing bean and five getters and setters for those attributes (see code below). In those getters there is very similar code for all five attributes, so I thought I should have just one color attribute and one getter and there should somehow get an information which circle is currently rendered.
Is there any other way (with less duplicated code) to render those circles?
public class Package implements Serializable {
private static final long serialVersionUID = 1L;
private BigDecimal id;
private List<PhaseStatus> phaseList;
public BigDecimal getId() {
return id;
}
public void setId(BigDecimal id) {
this.id = id;
}
public void setPhaseList(List<PhaseStatus> phaseList) {
this.phaseList= phaseList;
}
public List<PhaseStatus> getPhaseList() {
return phaseList;
}
}
PhaseStatus class:
public class PhaseStatus implements Serializable {
private static final long serialVersionUID = 1L;
private BigDecimal phaseId;
private BigDecimal phaseStatus;
private String hint;
public void setPhaseId(BigDecimal phaseId) {
this.phaseId = phaseId;
}
public BigDecimal getPhaseId() {
return phaseId;
}
public void setPhaseStatus(BigDecimal phaseStatus) {
this.phaseStatus = phaseStatus;
}
public BigDecimal getPhaseStatus() {
return phaseStatus;
}
public void setHint(String hint) {
this.hint = hint;
}
public String getHint() {
return hint;
}
}
Xhtml code:
<p:column headerText="Id">
<p:outputLabel value="#{item.id}" />
</p:column>
<p:column headerText="Progress">
<p:commandLink id="lnkPhase1">
<p:graphicImage library="icons" name="#{bean.phaseOneColor}" width="24px" height="24px" />
</p:commandLink>
<p:tooltip id="hint1" for="lnkPhase1" value="#{bean.phaseOneHint}" />
<p:commandLink id="lnkPhase2">
<p:graphicImage library="icons" name="#{bean.phaseTwoColor}" width="24px" height="24px" />
</p:commandLink>
<p:tooltip id="hint2" for="lnkPhase2" value="#{bean.phaseTwoHint}" />
<p:commandLink id="lnkPhase3">
<p:graphicImage library="icons" name="#{bean.phaseThreeColor}" width="24px" height="24px" />
</p:commandLink>
<p:tooltip id="hint3" for="lnkPhase3" value="#{bean.phaseThreeHint}" />
<p:commandLink id="lnkPhase4">
<p:graphicImage library="icons" name="#{bean.phaseFourColor}" width="24px" height="24px" />
</p:commandLink>
<p:tooltip id="hint4" for="lnkPhase4" value="#{bean.phaseFourHint}" />
<p:commandLink id="lnkPhase5">
<p:graphicImage library="icons" name="#{bean.phaseFiveColor}" width="24px" height="24px" />
</p:commandLink>
<p:tooltip id="hint5" for="lnkPhase5" value="#{bean.phaseFiveHint}" />
</p:column>
Part of my backing bean:
private String phaseOneColor;
private String phaseTwoColor;
private String phaseThreeColor;
private String phaseFourColor;
private String phaseFiveColor;
private DataTable dtPackages;
one of the getter's:
public String getPhaseOneColor() {
Package myPackage = (Package) getDtPackages().getRowData();
List<PhaseStatus> list = myPackage.getPhaseList();
BigDecimal status = list.get(0).getPhaseStatus();
if (status != null) {
switch (status.intValue()) {
case 1:
phaseOneColor = "green.png";
break;
default:
phaseOneColor = "red.png";
break;
}
} else {
phaseOneColor = "red.png";
}
return phaseOneColor;
}
I'm using PrimeFaces 5.1.
What about something like this
public String getPhaseColor(int phase) {
Package package = (Package) getDtPackages().getRowData();
List<PhaseStatus> list = package.getPhaseList();
BigDecimal status = list.get(phase).getPhaseStatus();
if (status != null) {
switch (status.intValue()) {
case 1:
phaseColor = "green.png";
break;
default:
phaseColor = "red.png";
break;
}
} else {
phaseColor = "red.png";
}
return phaseColor;
}
Similar for tooltips
public String getPhaseTooltip(int phase) {
Package package = (Package) getDtPackages().getRowData();
List<PhaseStatus> list = package.getPhaseList();
BigDecimal status = list.get(phase).getPhaseStatus();
String tooltip = null;
if (status != null) {
switch (status.intValue()) {
case n:
tooltip = "Phase " + phase + " tooltip";
break;
default:
}
} else {
tooltip = "probably error";
}
return tooltip;
}
And you use this in page like this
<p:column headerText="Progress">
<ui:repeat var="phase" value="#{item.phaseList}" varStatus="status">
<p:commandLink id="lnkPhase#{status.index + 1}">
<p:graphicImage library="icons" name="#{bean.getPhaseColor(status.index + 1)}" width="24px" height="24px" />
</p:commandLink>
<p:tooltip id="hint#{status.index + 1}" for="lnkPhase#{status.index + 1}" value="#{bean.getPhaseTooltip(status.index + 1)}" />
</ui:repeat>
</p:column>
There might be some errors in the code since I'm typing it in here directly, but you'll get the idea.
UPDATE
For Java 5 (method invocations with parameters doesn't work), you could do something like this
public Map<Integer, String> getPhaseColors() {
Package package = (Package) getDtPackages().getRowData();
Map<Integer, String> phaseColors = new HashMap<Integer, String>();
if (package != null) {
List<PhaseStatus> list = package.getPhaseList();
for (int i = 0; i < list.size(); i++;) {
BigDecimal ps = list.get(i).getPhaseStatus();
if (ps != null) {
phaseColors.put(i, ps.intValue() == 1 ? "green.png" : "red.png");
} else {
phaseColors.put(i, "red.png");
}
}
}
return phaseColor;
}
<p:graphicImage library="icons" name="#{bean.phaseColors[status.index + 1]}" width="24px" height="24px" />
Similar for tooltips.

ClassCastException in selectManyMenu - Integer cannot be cast to String

The list passed to the select's value is of Integer type.
<p:selectManyMenu id="estabelecimentos" value="#{questionarioMB.estabelecimentosIds}" var="e" converter="#{estabelecimentoConverter}" style="width:100%" filter="true" filterMatchMode="contains" showCheckbox="true">
<f:selectItems value="#{questionarioMB.estabelecimentos}" var="estabelecimento" itemValue="#{estabelecimento}" itemLabel="#{estabelecimento.nomefantasia}" />
<p:column>
<h:outputText value="#{estabelecimentoMB.getIdentificadorByEstabelecimentoId(e.id)}" />
</p:column>
<p:column>
<h:outputText value="#{e.nomefantasia}" />
</p:column>
</p:selectManyMenu>
Netbeans cannot find the attributes in the outputTexts ("unknown property") and the line throwing the exception is the following:
this.estabelecimentosIds.parallelStream().forEach((Integer id) -> {
this.questionarioBean.insertQuestionarioHasEstabelecimento(this.questionarioBean.getLastId() + 1, id);
});
The converter:
#Named
public class EstabelecimentoConverter implements Converter {
#Override
public Object getAsObject(FacesContext fc, UIComponent uic, String value) {
if (value != null && value.trim().length() > 0) {
try {
EstabelecimentoMB estabelecimentoMB = (EstabelecimentoMB) fc.getExternalContext().getApplicationMap().get("estabelecimentoMB");
return estabelecimentoMB.getEstabelecimentos().get(Integer.parseInt(value));
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erro de Conversão", "Estabelecimento inválido."));
}
} else {
return null;
}
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object o) {
if (o != null) {
return String.valueOf(((Estabelecimento) o).getId());
} else {
return null;
}
}
}
P.S.: I can't use the field tradingName because it can be repeated in table establishment, so I must use the "id" to differentiate them. The first column has the identifier for that establishment (in another table, "client_has_establishment", and can be repeated as well - but not for the same client_id).
Based on BalusC's answer here: https://stackoverflow.com/a/13866179/1639141
Changed the converter to JSF builtin IntegerConverter:
<p:selectManyMenu ... converter="javax.faces.Integer">

Invoke ActionListener of Backing Component in Composite Component

try to write a composite component that allows mutltiple text inputs. I read that it is possible to define a backing component for a composite component, so I don't have to write a renderer nor a handler. What I couldn't figure out is how to delegate actions declared in composite's xhtml to the backing component. I guess i did not yet quite understand the concept of this. Does anybody has an Idea?
I am using Tomcat 7, EL 2.2, Spring 3, Mojarra 2.1.7
This is the way i'd like to use the component:
<custom:multiInput value="#{backingBean.inputList}"/>
Where the BackingBean.java holds a list of objects:
#Component
#Scope(value="view")
public class BackingBean {
...
private List<Foo> inputList;
....
}
The composite component multiInput.xhtml looks like this:
<cc:interface componentType="MultiInput">
<cc:attribute name="value" required="true" type="java.util.List" />
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
<h:dataTable value="#{cc.attrs.rows}" var="row">
<h:column>
<!-- here will be a selector component in order to select a foo object -->
</h:column>
<h:column>
<h:commandButton value="Remove Row">
<f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.removeRow(row)}" />
</h:commandButton>
</h:column>
<h:column>
<h:commandButton value="Add Row" rendered="#{cc.lastRow}">
<f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.addEmptyRow()}" />
</h:commandButton>
</h:column>
</h:dataTable>
</div>
</cc:implementation>
And here the backing component MultiInput.java:
#FacesComponent(value="MultiInput")
public class MultiInput extends UIInput implements NamingContainer, Serializable{
...
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
initRowsFromValueAttribute();
super.encodeBegin(context);
}
public void removeRow(MultiInputRow row) {
// why is this method is never reached when clicking remove button?
}
public void addEmptyRow() {
// why is this method is never reached when clicking add button?
}
public ListDataModel<MultiSelectRow> getRows() {
return (ListDataModel<MultiSelectRow>) getStateHelper().eval(PropertyKeys.rows, null);
}
private void setRows(ListDataModel<MultiSelectRow> rows) {
getStateHelper().put(PropertyKeys.rows, rows);
}
...
}
Now - removeRow and addEmptyRow is never called on MultiInput. An ajax request is triggered but it gets lost somewhere. Why?
I think the method signature for ajax listener methods should include the AjaxBehaviorEvent (unverified):
public void addEmptyRow(AjaxBehaviorEvent event) { ... }
and the f:ajax tag should just look like (without parentheses):
<f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.addEmptyRow}" />
I'm struggling with the same problem here: using <f:ajax>, action listener methods in the composite component backing component are not executed.
It works partially when using Primefaces <p:commandButton>: the action listener method is correctly called in this case. However, the value of the 'process' attribute seems to be ignored in this case: All form fields are submitted, which causes validation failure in my case. If this is not a problem for you, you could try this.
I have created some test classes that reproduce the problem:
The composite component file testComponent.xhtml:
<html xmlns="http://www.w3c.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface componentType="testComponent">
</composite:interface>
<composite:implementation>
<div id="#{cc.clientId}">
<h:panelGroup id="addPanel">
<h:inputText id="operand1" value="#{cc.operand1}"/>
<h:outputText value=" + " />
<h:inputText id="operand2" value="#{cc.operand2}"/>
<h:outputText value=" = " />
<h:outputText id="result" value="#{cc.result}" />
<br />
<p:commandButton id="testButton1" value="Primefaces CommandButton"
actionListener="#{cc.add()}" process="addPanel" update="addPanel"/>
<h:commandButton id="testButton2" value="f:ajax CommandButton">
<f:ajax execute="addPanel" render="addPanel" listener="#{cc.add()}" />
</h:commandButton>
</h:panelGroup>
</div>
</composite:implementation>
</html>
The backing component class:
package be.solidfrog.pngwin;
import javax.faces.component.FacesComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.event.ActionEvent;
#FacesComponent("testComponent")
public class TestComponent extends UINamingContainer {
private Integer operand1, operand2, result;
public void add() {
System.err.println("Adding " + operand1 + " and " + operand2);
result = operand1 + operand2;
}
public Integer getOperand1() { return operand1; }
public void setOperand1(Integer operand1) { this.operand1 = operand1; }
public Integer getOperand2() { return operand2; }
public void setOperand2(Integer operand2) { this.operand2 = operand2; }
public Integer getResult() { return result; }
public void setResult(Integer result) { this.result = result; }
}
And the using page test.xhtml:
<!DOCTYPE html>
<html xmlns="http://www.w3c.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"
xmlns:sf="http://java.sun.com/jsf/composite/solidfrog">
<h:body>
<h:messages />
<h:form id="testForm">
<h:outputLabel for="field1" value="Integer field: "/>
<h:inputText id="field1" value="#{testBean.field1}" />
<hr/>
<sf:testComponent id="testComponent" />
</h:form>
</h:body>
</html>
When clicking the first button and filling in the two operand fields, the result is correctly calculated. However, when a non-numeric value is entered in field1, there is a failed verification.
When using the second button, the action listener method is never calculated. However, the complete form is always submitted, so entering a non-numeric value in field1 triggers the error too.
I also tried p:ajax, which behaved the same as f:ajax.
I really have no idea what is happening here. Hopefully someone with more JSF wisdom can help out.
Although I don't understand everything in detail, I found a way to make it work. Since on each request a new instance of the backing component MultiInput is created, I had to save the state by overwriting saveState and restoreState. This way I could keep the property rows as a simple property. I also removed the encodeBegin method and overwrote getSubmittedValue.
At least this way it is working in Mojarra. When using MyFaces with default settings, I got some serialization exceptions, but I did not get deepter into that since we will stick on Mojarra. Also MyFaces seemed to be more stricked with ajax event listeners. It required "AjaxBehaviorEvent" parameters in listener methods.
Here the complete backing component MultInput:
#FacesComponent(value = "MultiInput")
public class MultiInput extends UIInput implements NamingContainer, Serializable {
ListDataModel<MultiInputRow> rows;
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
#Override
public Object getSubmittedValue() {
List<Object> values = new ArrayList<Object>();
List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
for (MultiInputRow row : wrappedData) {
if (row.getValue() != null) { // only if a valid value was selected
values.add(row.getValue());
}
}
return values;
}
public boolean isLastRow() {
int row = getRows().getRowIndex();
int count = getRows().getRowCount();
return (row + 1) == count;
}
public boolean isFirstRow() {
int row = getRows().getRowIndex();
return 0 == row;
}
public void removeRow(AjaxBehaviorEvent e) {
List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
wrappedData.remove(rows.getRowIndex());
addRowIfEmptyList();
}
public void addEmptyRow(AjaxBehaviorEvent e) {
List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
wrappedData.add(new MultiInputRow(null));
}
public ListDataModel<MultiInputRow> getRows() {
if (rows == null) {
rows = createRows();
addRowIfEmptyList();
}
return rows;
}
public List<Object> getValues() {
return (List<Object>) super.getValue();
}
private ListDataModel<MultiInputRow> createRows() {
List<MultiInputRow> wrappedData = new ArrayList<MultiInputRow>();
List<Object> values = getValues();
if (values != null) {
for (Object value : values) {
wrappedData.add(new MultiInputRow(value));
}
}
return new ListDataModel<MultiInputRow>(wrappedData);
}
private void addRowIfEmptyList() {
List<MultiInputRow> wrappedData = (List<MultiInputRow>) rows.getWrappedData();
if (wrappedData.size() == 0) {
wrappedData.add(new MultiInputRow(null));
}
}
#Override
public Object saveState(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
Object[] values = new Object[2];
values[0] = super.saveState(context);
values[1] = rows != null ? rows.getWrappedData() : null;
return (values);
}
#Override
public void restoreState(FacesContext context, Object state) {
if (context == null) {
throw new NullPointerException();
}
if (state == null) {
return;
}
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
rows = values[1] != null ? new ListDataModel<MultiInputRow>((List<MultiInputRow>) values[1]) : null;
}
/**
* Represents an editable row that holds a value that can be edited.
*/
public class MultiInputRow {
private Object value;
MultiInputRow(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
}

Resources