Primefaces: Form fields not synced with Managed Bean automatically - jsf

Have a nice day. I don't know if i'm wrong about this. I have a form in my xhtml like this:
<p:outputLabel value="Número de pasajeros" />:
<p:inputText value="#{vueloMB.instancia.numPasajeros}" maxlength="3" >
</p:inputText>
<br />
<p:outputLabel value="Hora de salida" />:
<p:calendar value="#{vueloMB.instancia.fechaHoraSalida}" navigator="true"
mode="popup" pattern="dd/MM/yyyy HH:mm" />
<br />
<p:outputLabel value="Avión" />:
<p:selectOneMenu value="#{vueloMB.instancia.avion}" >
<f:selectItems value="#{vueloMB.aviones}" var="avi"
itemLabel="#{avi.modelo}" itemValue="#{avi}" />
</p:selectOneMenu>
<br />
<p:outputLabel value="Pais de salida" />:
<p:selectOneMenu value="#{vueloMB.instancia.paisSalida}" converter="omnifaces.SelectItemsConverter" >
<f:selectItems value="#{vueloMB.paises}" var="pai"
itemLabel="#{pai.nombre}" itemValue="#{pai}" />
<f:param name="tipoPais" value="S"></f:param>
<p:ajax update="ciusal" listener="#{vueloMB.cargarListaCiudades}" process="#this" >
</p:ajax>
</p:selectOneMenu>
<br />
<p:outputLabel value="Ciudad de salida" />:
<p:selectOneMenu value="#{vueloMB.instancia.ciudadSalida}" converter="omnifaces.SelectItemsConverter"
id="ciusal" disabled="#{vueloMB.instancia.paisSalida==null}" >
<f:selectItems value="#{vueloMB.ciudadesSalida}" var="ciu"
itemLabel="#{ciu.nombre}" itemValue="#{ciu}" />
</p:selectOneMenu>
<br />
<p:commandButton value="Guardar" rendered="#{vueloMB.instancia.id == null}" action="#{vueloMB.guardar()}" process="#form" ajax="true" />
</h:form>
The dropdown labeled "Ciudad de salida" refreshes another dropdown after i choose a country here, updates the list that feeds the second dropdown and it works fine. The problem is when i press the "Guardar" button to save the entity (vueloMB.instancia is my entity) with JPA, because it doesn't do anything.
So, i added the attribute immediate="true" to the button, it calls the ManagedBean method, but when i see the entity, only the field vueloMB.instancia.paisSalida isn't null, even if i fill all the fields. Because of that, i assumed that, because the dropdown calls an MB method because it refresh the second dropdown, it's value is refreshed on the MB. Based on that, i modified the first field like this:
<p:inputText value="#{vueloMB.instancia.numPasajeros}" maxlength="3" >
<p:ajax />
</p:inputText>
I added the ajax tag to my inputText. After doing that, i press the "Guardar" button and the field that i've modified (Número de pasajeros) now it carries the value on vueloMB.instancia.numPasajeros.
So, if i add to all my fields, when i press the submit button it will work, it will save the entity without problems and all the fields will travel to the managed bean, but is necessary to do that with every field? There's no automatic way JSF does this? Or i have something wrong with my code?
EDIT: Here is the code of the managed bean. A CDI Managed Bean with #ConversationScoped:
package com.saplic.fut.beans;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import com.saplic.fut.daos.VueloDAO;
import com.saplic.fut.entity.Avion;
import com.saplic.fut.entity.Ciudad;
import com.saplic.fut.entity.Pais;
import com.saplic.fut.entity.Vuelo;
#Named("vueloMB")
#ConversationScoped
public class VueloManagedBean implements Serializable {
private static final long serialVersionUID = -203436251219946811L;
#Inject
private VueloDAO vueloDAO;
#Inject
private Conversation conversation;
#PostConstruct
public void iniciarConversacion() {
if(conversation.isTransient())
conversation.begin();
}
public void finalizarConversacion() {
if(!conversation.isTransient())
conversation.end();
}
private Vuelo instancia;
private List<Vuelo> vuelos;
private List<Avion> aviones = new ArrayList<Avion>();
private List<Pais> paises = new ArrayList<Pais>();
private List<Ciudad> ciudadesSalida = new ArrayList<Ciudad>();
private List<Ciudad> ciudadesAterrizaje = new ArrayList<Ciudad>();
private Integer idVuelo;
public String cargarLista() {
iniciarConversacion();
vuelos = vueloDAO.cargarVuelos();
return "/vuelos/lista";
}
public void cargarListaCiudades() {
String tipoLista = FacesContext.getCurrentInstance().
getExternalContext().getRequestParameterMap().get("tipoPais");
if(tipoLista.equalsIgnoreCase("S"))
setCiudadesSalida(vueloDAO.cargarCiudades(getInstancia().getPaisSalida()));
if(tipoLista.equalsIgnoreCase("A"))
setCiudadesAterrizaje(vueloDAO.cargarCiudades(getInstancia().getPaisAterrizaje()));
}
public String cargarDetalle() {
Vuelo fltVuelo = new Vuelo();
fltVuelo.setId(getIdVuelo());
instancia = vueloDAO.cargarDetalle(fltVuelo);
if(instancia == null)
setInstancia(new Vuelo());
//Cargamos lista de aviones para combo
setAviones(vueloDAO.cargarAviones());
setPaises(vueloDAO.cargarPaises());
return "/vuelos/detalle";
}
public String guardar() {
vueloDAO.guardar(instancia);
finalizarConversacion();
return cargarLista();
}
public String actualizar() {
vueloDAO.actualizar(instancia);
finalizarConversacion();
return cargarLista();
}
public String eliminar() {
vueloDAO.eliminar(instancia);
finalizarConversacion();
return cargarLista();
}
public Vuelo getInstancia() {
return instancia;
}
public void setInstancia(Vuelo instancia) {
this.instancia = instancia;
}
public List<Vuelo> getVuelos() {
return vuelos;
}
public void setVuelos(List<Vuelo> vuelos) {
this.vuelos = vuelos;
}
public Integer getIdVuelo() {
return idVuelo;
}
public void setIdVuelo(Integer idVuelo) {
this.idVuelo = idVuelo;
}
public List<Avion> getAviones() {
return aviones;
}
public void setAviones(List<Avion> aviones) {
this.aviones = aviones;
}
public List<Pais> getPaises() {
return paises;
}
public void setPaises(List<Pais> paises) {
this.paises = paises;
}
public List<Ciudad> getCiudadesSalida() {
return ciudadesSalida;
}
public void setCiudadesSalida(List<Ciudad> ciudadesSalida) {
this.ciudadesSalida = ciudadesSalida;
}
public List<Ciudad> getCiudadesAterrizaje() {
return ciudadesAterrizaje;
}
public void setCiudadesAterrizaje(List<Ciudad> ciudadesAterrizaje) {
this.ciudadesAterrizaje = ciudadesAterrizaje;
}
}
Regards.

Your entities must implements the method equals() hashCode() and toString as specified in the omnifaces showcase. I can't help you much more than that since I'm not familiar with omnifaces and the ConversationScope. I think it's because the two objects are not at the same place in memory so when you use equals the result is false. In the case of omnifaces I read it uses toString() to see if two objects are equal so if the method is not reimplemented you will have different results.
In other words you have null values because when the value as string comes back from the form it cannot be converted back to the original object. I'd appreciate if someone could attest this as I'm not 100% positive that's what is happening.

Related

How to Compare the values JSF selectonemenu

I am developing a JSF application
I have 2 selectOnemenu controls and submit button.
I need to disable the button if the values of 2 fields are equal
<h:selectOneMenu id="from" value="#{currencyBean.history.fromCurrency}" >
<f:selectItems value="#{currencyBean.currency}" var="c" itemValue="#{c}" itemLabel="#{c.name}"/>
</h:selectOneMenu>
<p:outputLabel for="to" value="to:" />
<h:selectOneMenu id="to" value="#{currencyBean.history.toCurrency}" >
<f:selectItems value="#{currencyBean.currency}" var="c" itemValue="#{c}" itemLabel="#{c.name}"/>
</h:selectOneMenu>
<p:commandButton id="calculateButton" value="Convert" update=":convert :historyPanel" onstart="PF('statusDialog').show()" onsuccess="PF('statusDialog').hide()" validateClient="true" ajax="true" action="#{currencyBean.Calculate()}" />
I tried to use onchange with ajax but everytime I change one dropdown the value of the second drowpdown became null in the backbean so I cannot read it.
Here is my backbean
#Named(value = "currencyBean")
#RequestScoped
public class CurrencyBean implements Serializable {
/**
* Creates a new instance of CurrencyBean
*/
private History history;
private Currency currency;
private Date currentDate;
#Inject
private Loginbean loginBean;
private List<History> historyList;
public List<History> getHistoryList() {
int userId = loginBean.getUserId();
if (userId != -1) {
return new HistoryHelper().GetHistoryListByUser(userId);
}
return null;
}
public CurrencyBean() {
history = new History();
}
public History getHistory() {
return history;
}
public void setHistory(History history) {
this.history = history;
}
public Currency[] getCurrency() {
return Currency.values();
}
public Date getCurrentDate() {
return new Date();
}
public void Calculate() {
int userId = loginBean.getUserId();
if (userId != -1) {
new CurrencyClient().Convert(userId, this.history);
}
}
}
any clue ?
My assumption is that all of your problems come from your managed bean scope. You have #Request scope so every request your managed bean will be removed from container, thus when you define onchange="submit()" (this is only my assumption because you haven't define how you implement onchange attribute) and you select value from one selectBox component values for this component is updated but the first one is still null. When you select second selectBox value updated from first selectBox doesn't exists anymore as managed bean has been removed after first request. You should try with wider scope for instance #ViewScope. If it doesn't help then further informations like implementation onchange attribute will be needed

#postConstruct method with parameter

I've got a list of orders on a database, and I want to show two separate datatables in two JSF pages: one table regarding all orders, and one table regarding the current logged user.
Problem is, only the first one is actually showed on the page.
JSF page with the datatable links
<h:commandLink action="#{ordineController.listaOrdini}"
value="Consulta gli ordini esistenti" rendered="#{not empty loginAdmin.admin.email}"/>
<div>
<h:commandLink
action="#{ordineController.listaOrdiniCliente}"
value="Controlla i tuoi ordini"
rendered="#{not empty loginCliente.clienteLoggato.email}">
<f:setPropertyActionListener target="#{ordineController.clienteCorrente}"
value="#{loginCliente.clienteLoggato}" />
</h:commandLink>
</div>
ViewScoped bean
#ManagedBean(name="ordineController")
#ViewScoped
public class OrdineController implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#PostConstruct
public void init() {
ordini = oFacade.getListaOrdini();
listaOrdiniCliente();
}
public String listaOrdini() {
this.ordini = oFacade.getListaOrdini();
return "showOrdini";
}
public String listaOrdiniCliente() {
this.ordiniCliente = oFacade.getOrdiniCliente(clienteCorrente);
return "showOrdiniCliente";
}
/*Getters and setters*
The JSF page that doesn't work (showOrdiniCliente.xhtml)
<h:outputText value="Non c'è nessun ordine."
rendered="#{empty ordineController.ordiniCliente}" />
<h:dataTable id="lista" value="#{ordineController.ordiniCliente}"
var="ordine" rendered="#{not empty ordineController.ordiniCliente}">
The JSF page that does work (showOrdini.xhtml)
<h:outputText value="Non c'è nessun ordine."
rendered="#{empty ordineController.ordini}" />
<h:form rendered="#{not empty ordineController.ordini}">
<h:dataTable id="lista" value="#{ordineController.ordini}"
var="ordine">
Why is the #{ordineController.ordiniCliente} empty?
Shouldn't it be builded along with the "ordini" variable in the #PostConstruct?
edit
Facade method (it retrieves all the orders of a customer)
public List<Ordine> getOrdiniCliente (Cliente cliente) {
try {
TypedQuery<Ordine> q = em.createQuery("SELECT ord FROM Ordine ord WHERE ord.cliente = :cliente", Ordine.class);
q.setParameter("cliente", cliente);
return q.getResultList();
}
catch (Exception e) {
String q = "Il cliente " +cliente.getNickname()+ " non ha creato degli ordini";
System.out.println(q);
return null;
}
}

How to pass a row object to the backing bean using JSF 2 and RichFaces?

I am using RichFaces's ordering list to display a table custom Command objects to the user. The user uses a form to create new commands which are then added to the list. Here is the orderingList implementation:
app.xhtml
<rich:orderingList id="oList" value="#{commandBean.newBatch}" var="com"
listHeight="300" listWidth="350" converter="commandConverter">
<f:facet name="header">
<h:outputText value="New Batch Details" />
</f:facet>
<rich:column width="180">
<f:facet name="header">
<h:outputText value="Command Type" />
</f:facet>
<h:outputText value="#{com.commandType}"></h:outputText>
</rich:column>
<rich:column>
<f:facet name="header">
<h:outputText value="Parameters" />
</f:facet>
<h:outputText value="#{com.parameters}"></h:outputText>
</rich:column>
<rich:column>
<h:commandButton value="Remove #{com.id} : #{com.seqNo}"
action="#{commandBean.remove(com.id,com.seqNo)}"
onclick="alert('id:#{com.id} seqNo:#{com.seqNo}');"/>
</rich:column>
My troubles began when I tried to implement a remove button which would send a command's ID and seqNo to the backing bean (cb) to be removed from the list. Here is the backing bean:
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
#ManagedBean
#SessionScoped
public class CommandBean implements Serializable{
private static final long serialVersionUID = 1L;
private CommandType type;
private String parameters;
private List<Command> newBatch = new ArrayList<Command>();
private Set<Command> commandSet = new HashSet<Command>();
private String msg = "not removed";
public CommandType[] getCommandTypes() {
return CommandType.values();
}
public void addCommand(CommandType type, String parameters) {
newBatch.add(new Command(type, parameters));
}
CommandType getType() {
return type;
}
void setType(CommandType type) {
this.type = type;
}
String getParameters() {
return parameters;
}
void setParameters(String parameters) {
this.parameters = parameters;
}
public List<Command> getNewBatch() {
return newBatch;
}
public void setNewBatch(List<Command> newBatch) {
this.newBatch = newBatch;
}
public Set<Command> getCommandSet() {
return commandSet;
}
public void setCommandSet(Set<Command> commandSet) {
this.commandSet = commandSet;
}
String getMsg() {
return msg;
}
public void remove(Integer id, Integer seqNo) {
for(Command c : newBatch) {
if(c.getId() == id && c.getSeqNo() == seqNo) {
newBatch.remove(c);
msg = "removed " + c;
return;
}
}
msg = String.format("%d : %d", id,seqNo);
}
}
When the Command (com)'s id and seqNo are passed via #{cb.remove(com.id,com.seqNo)} they are both 0. I also read somewhere that null values are transformed to 0's, so that would explain it. I also tried to pass the Command object directly via #{cb.remove(com)} but the Command was null when bean tried to process it.
I'm betting there is something off with the scoping, but I am too new to JSF to figure it out...
UPDATE
I have eliminated the conflicting #Named tag and have updated the html to reflect the new name of the bean, namely commandBean. Still having issues though.
you can pass the two values as request parameters:
<h:commandButton ... >
<f:param name="id" value="#{com.id}"/>
<f:param name="seqNo" value="#{com.seqNo}"/>
</h:commandButton>
and get retrieve them in managed bean like this:
HttpServletRequest request = ((HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest());
System.out.println(request.getParameter("id"));
System.out.println(request.getParameter("seqNo"));
You're trying to get a value from a variable that is used in a for-cycle after the cycle is over. The #action is being resolved on the server side, by the time the #var is null.
You can do this:
<a4j:commandButton action="#{commandBean.remove()}" … >
<a4j:param assignTo="#{commandBean.idToRemove}" value="#{com.id}"/>
</a4j:commandButton>
The a4j:param resolves the value on client side, when the button is clicked it sends it to the server.

SelectOneMenu control not returning selected value

I am having a problem with the SelectOneMenu control. I want the the selected item to be be displayed via the valueChange Ajax event listen. But this is not happening.
However, when I change the value in the SelectOneMenu and then click on the Submit button, then selected value is getting displayed via the 'save' bean function
Cannot figure out why this is not working. Would appreciate any help on this.
Thanks.
The relevant xhtml code is as follows:
<h:form>
<h:dataTable value="#{dynamicList.myData}" var="item" >
<h:column>
<h:outputText value="#{item.oracleType}"></h:outputText>
</h:column>
<h:column>
<h:selectOneMenu value="#{item.coffeeFlavour}" rendered="#{item.showLov}" >
<f:selectItems value="#{item.coffeeList}"></f:selectItems>
<f:ajax event="valueChange" listener="#{dynamicList.listen}" ></f:ajax>
</h:selectOneMenu>
<h:inputText value="#{item.coffeeFlavour}" rendered="#{item.showText}">
</h:inputText>
</h:column>
</h:dataTable>
<p:commandButton value="Submit" action="#{dynamicList.save}" ></p:commandButton>
</h:form>
The relevant bean code is as follows:
#ManagedBean
#ViewScoped
public class DynamicList implements Serializable{
private List<OraclePrfl> oracleList=new ArrayList<OraclePrfl>();
private String coffee;
private Map<String,String> coffeeList=new LinkedHashMap<String,String>();
public List<OraclePrfl> getOracleList() {
return oracleList;
}
public List<OraclePrfl> getMyData()
{
oracleList.clear();
oracleList.add(new OraclePrfl("Oracle Lot Number",new HashMap<String,String>(){
{
put("Coffee2 - Cream Latte", "Cream Latte");
put("Coffee2 - Extreme Mocha", "Extreme Mocha");
put("Coffee2 - Buena Vista", "Buena Vista");
}
},true,false));
oracleList.add(new OraclePrfl("Oracle Product Number",new HashMap<String,String>(){
{
put("ABC", "abc");put("PQR", "pqr");put("XYZ", "xyz");
}
},true,false));
oracleList.add(new OraclePrfl("Oracle Specification",new HashMap<String,String>(){
{
put("MNP", "mnp");put("WXY", "wxy");put("XYZ", "xyz");
}
},true,false));
oracleList.add(new OraclePrfl("Address",false,true));
return oracleList;
}
public void setOracleList(List<OraclePrfl> oracleList) {
this.oracleList = oracleList;
}
public String getCoffee() {
return coffee;
}
public void setCoffee(String coffee) {
this.coffee = coffee;
}
public Map<String,String> getCoffeeList() {
coffeeList.clear();
coffeeList.put("Coffee2 - Cream Latte", "Cream Latte"); //label, value
coffeeList.put("Coffee2 - Extreme Mocha", "Extreme Mocha");
coffeeList.put("Coffee2 - Buena Vista", "Buena Vista");
return coffeeList;
}
public void setCoffeeList(Map<String,String> coffeeList) {
this.coffeeList = coffeeList;
}
public void save(){
for(OraclePrfl oracle:oracleList){
System.out.println("oracle type------"+oracle.getOracleType()+"------coffee----
"+oracle.getCoffeeFlavour());
}
}
public void listen(AjaxBehaviorEvent event){
System.out.println("calling listener "+event.getSource().toString());
for(OraclePrfl oracle:oracleList){
System.out.println("type....."+oracle.getOracleType()+"----value-----
"+oracle.getCoffeeFlavour());
}
}
}
Try removing the event:
<f:ajax listener="#{dynamicList.listen}" ></f:ajax>
It should default to event="change".

JSF 2.0: Validate equality of 2 InputSecret Fields (confirm password) without writing Code?

I'm developing a pure JavaEE6 application with JSF 2.0 and Glassfish.
My JSF implementation is Primefaces (beside Mojarra provided by Glassfish).
I want to verify if the values of 2 password fields in a JSF form are equal.
With Seam, there is the neat component <s:validateEquality for="pw1"/>.
I want do to the same without Seam, just using JSF (or maybe a component of a JSF library). Until now i only saw examples which validate the form with a custom validator. But i would like to compare the fields without writing Java code or Javascript code.
Is that possible?
This what it looks like with Seam:
...
<h:inputSecret id="passwort" value="#{personHome.instance.password}"
redisplay="true" required="true">
<f:validateLength minimum="8"/>
<a:support event="onblur" reRender="passwortField" bypassUpdates="true" ajaxSingle="true" />
</h:inputSecret>
...
<h:inputSecret id="passwort2" required="true" redisplay="true">
<!-- find the JSF2.0-equivalent to this tag: -->
<s:validateEquality for="passwort"/>
<a:support event="onblur" reRender="passwort2Field" bypassUpdates="true" ajaxSingle="true" />
</h:inputSecret>
...
You may use Primefaces tag in this very simple way:
<p:password id="password" value="#{bean.password}" match="repeated_password" />
<p:password id="repeated_password" value="#{bean.password}" />
The Seam3 Faces module will support "Cross-field form validation" in it's imminent Alpha3 release. This is your best bet for a minimal code solution, see this blog for a howto.
Alternatively I've done this programmatically by using the f:attribute tag to pass the clientId of another form field to a custom validator, then using the UIComponent passed into the custom validator to access the other filed by id.
Here's the facelet file:
<h:outputLabel value="Enter your email address" rendered="#{!cc.attrs.registration.subRegistration}" />
<h:inputText label="Email" id="textEmail1" value="#{cc.attrs.registration.email}" rendered="#{!cc.attrs.registration.subRegistration}" required="true" maxlength="128" size="35"></h:inputText>
<h:message for="textEmail1" rendered="#{!cc.attrs.registration.subRegistration}"></h:message>
<h:outputLabel value="Re-enter your email address confirmation:" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}" />
<h:inputText label="Email repeat" id="textEmail2" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}" maxlength="64" size="35">
<f:validator validatorId="duplicateFieldValidator" />
<f:attribute name="field1Id" value="#{component.parent.parent.clientId}:textEmail1" />
</h:inputText>
<h:message for="textEmail2" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}"></h:message>
Here's the validator class:
package ca.triumf.mis.trevents.jsf.validator;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
#FacesValidator(value="duplicateFieldValidator")
public class DuplicateFieldValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException {
// Obtain the client ID of the first field from f:attribute.
System.out.println(component.getFamily());
String field1Id = (String) component.getAttributes().get("field1Id");
// Find the actual JSF component for the client ID.
UIInput textInput = (UIInput) context.getViewRoot().findComponent(field1Id);
if (textInput == null)
throw new IllegalArgumentException(String.format("Unable to find component with id %s",field1Id));
// Get its value, the entered text of the first field.
String field1 = (String) textInput.getValue();
// Cast the value of the entered text of the second field back to String.
String confirm = (String) value;
// Check if the first text is actually entered and compare it with second text.
if (field1 != null && field1.length() != 0 && !field1.equals(confirm)) {
throw new ValidatorException(new FacesMessage("E-mail addresses are not equal."));
}
}
}
I had to use a mixture of both answers to succeed.
I used ifischers short solution but my bean password field was null.
So I used the lines from Brian Leathem to get the UIInput from the context:
public void passwordValidator(FacesContext context, UIComponent toValidate, Object value) {
UIInput passwordField = (UIInput) context.getViewRoot().findComponent("registerForm:password");
if (passwordField == null)
throw new IllegalArgumentException(String.format("Unable to find component."));
String password = (String) passwordField.getValue();
String confirmPassword = (String) value;
if (!confirmPassword.equals(password)) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Passwords do not match!", "Passwords do not match!");
throw new ValidatorException(message);
}
}
If you're using JSF utility library OmniFaces, then you could use <o:validateEqual>. It also allows setting a custom message. The showcase has a live example demonstrating the common usecase of validating the password confirmation. You don't even need ajax to update the model before invoking the validator (as your own approach does).
Here's the minimum necessary code:
<h:inputSecret id="password" value="#{personHome.person.password}" />
<h:message for="password" />
<h:inputSecret id="password2" />
<h:message for="password2" />
<o:validateEqual components="password password2"
message="Passwords do not match!" showMessageFor="password2" />
No Java code needed.
This is the way i finally did it, which i like cause it's short and easy. The only problem is that it's not really re-usable, but as i only need this in one case, i rather save some LOCs and do it this way.
Snippet from my view:
<h:inputSecret id="password" value="#{personHome.person.password}">
<f:ajax event="blur" render="passwordError" />
</h:inputSecret>
<h:message for="password" errorClass="invalid" id="passwordError" />
<h:inputSecret id="password2" validator="#{personHome.validateSamePassword}">
<f:ajax event="blur" render="password2Error" />
</h:inputSecret>
<h:message for="password2" errorClass="invalid" id="password2Error" />
My Backing Bean (just the important part):
#Named #ConversationScoped
public class PersonHome {
private Person person;
public Person getPerson() {
if (person == null) return new Person();
else return person;
}
public void validateSamePassword(context:FacesContext, toValidate:UIComponent, value:Object) {
String confirmPassword = (String)value;
if (!confirmPassword.equals(person.getPassword()) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Passwords do not match!", "Passwords do not match!")
throw new Validatorexception(message);
}
}
You can do it easily with Apache MyFaces ExtVal.
Without solution, I was forced to do the validation in a ugly way (not recommended). At least it works till I found better solution.
In the method that returns the action, I check both values, in case of different values, I add error messages on context and return null to the navigation handler.
package com.jsf.beans.user;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.component.html.HtmlInputSecret;
import org.apache.commons.lang.StringUtils;
import com.pichler.jsf.beans.base.JsfViewBean;
#ManagedBean(name = "changePassword")
#RequestScoped
public class ChangePassword extends JsfViewBean {
private HtmlInputSecret inputSecret1, inputSecret2;
/**
* #return the inputSecret1
*/
public HtmlInputSecret getInputSecret1() {
return inputSecret1;
}
/**
* #param inputSecret1
* the inputSecret1 to set
*/
public void setInputSecret1(HtmlInputSecret inputSecret1) {
this.inputSecret1 = inputSecret1;
}
/**
* #return the inputSecret2
*/
public HtmlInputSecret getInputSecret2() {
return inputSecret2;
}
/**
* #param inputSecret2
* the inputSecret2 to set
*/
public void setInputSecret2(HtmlInputSecret inputSecret2) {
this.inputSecret2 = inputSecret2;
}
private String password1, password2;
public String alterar() {
if (!StringUtils.equals(password1, password2)) {
addErrorMessage(inputSecret1.getClientId(),
"As senhas não coincidem");
addErrorMessage(inputSecret2.getClientId(),
"As senhas não coincidem");
return null;
}
return null;
}
/**
* #return the password1
*/
public String getPassword1() {
return password1;
}
/**
* #param password1
* the password1 to set
*/
public void setPassword1(String password1) {
this.password1 = password1;
}
/**
* #return the password2
*/
public String getPassword2() {
return password2;
}
/**
* #param password2
* the password2 to set
*/
public void setPassword2(String password2) {
this.password2 = password2;
}
}
*JsfViewBean is just a class that has some common methods, as "addMessages".

Resources