I've got multiple pages that allow the download of the same resource (retrieved from my DB).
The problem is that the download works just on some of them, even with the SAME code and calling the SAME bean.
This thing is getting quite annoying because, on the non working pages, clicking on the download link will just reload the page without any message/exception, so I can't find out what's happening.
Here's my BEAN code :
package ManagedBeans;
import ejb.DispensaManagerLocal;
import entity.Dispensa;
import entity.Utente;
import java.io.ByteArrayInputStream;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import org.primefaces.event.RateEvent;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;
/**
*
* #author stefano
*/
#ManagedBean
#RequestScoped
public class DispensaBean {
#EJB
private DispensaManagerLocal dispensaManager;
#ManagedProperty(value = "#{loginBean.utente}")
private Utente utente;
public Utente getUtente() {
return utente;
}
public void setUtente(Utente utente) {
this.utente = utente;
}
/**
* Creates a new instance of DispensaBean
*/
public DispensaBean() {
}
public StreamedContent getDownload() {
String id = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("dispensaId");
System.out.println("________" + id);
Dispensa d = dispensaManager.findById(Integer.parseInt(id));
String type = getMimeFromByte(d.getDatiFile());
String estensione = "";
if(type.equals("application/pdf")){
estensione = ".pdf";
} else if(type.equals("application/zip")) {
estensione = ".zip";
} else if(type.equals("application/vnd.ms-powerpoint")) {
estensione = ".ppt";
}
return new DefaultStreamedContent(new ByteArrayInputStream(d.getDatiFile()), type, d.getTitolo() + estensione);
}
private String getMimeFromByte(byte[] src) {
if (src[0] == 0x25 && src[1] == 0x50 && src[2] == 0x44 && src[3] == 0x46) {
return "application/pdf";
}
if (src[0] == 0x50 && src[1] == 0x4b) {
return "application/zip";
}
if (src[0] == 0xd0 && src[1] == 0xcf && src[2] == 0x11 && src[3] == 0xe0 && src[4] == 0xa1 && src[5] == 0xb1 && src[6] == 0x1a && src[7] == 0xe1) {
return "application/vnd.ms-powerpoint";
}
return "application/octet-stream";
}
}
Now, on the NON working pages, the getDownload() method is NOT called, as it doesn't print anything.
Here's the download button code
<h:form style="float: right">
<pou:commandLink id="downloadDispensa" ajax="false" disabled="#{!loginBean.logged}">
<pou:graphicImage value="./resources/images/download.png" height="30"/>
<pou:fileDownload value="#{dispensaBean.getDownload()}"/>
<f:param name="dispensaId" value="#{dispensa.id}"/>
</pou:commandLink>
</h:form>
What I've noticed is that the download link just RELOADS the page instead of calling the method, and this happens only in the pages in which #{dispensa.id} depends on a GET parameter.
For example, I've got a page called dispensa.xhtml that displays all my files in the DB if no GET parameters are passed.
Indeed, dispensa.xhtml?id=5 will display just the file with id=5.
Clicking on the download link, in the first case, works without problems.
Doing it in the second case will reload the page and will lose the GET parameter, so the it will load dispensa.xhtml instead of dispensa.xhtml?id=5.
I'd think that there's some problem in using the GET parameter, but..yesterday it WORKED and I did NOT change this code!
The other NON working page is ricerca.xhtml which shows the (multiple) results of a query given by ricerca.xhtml?key=query.
Finally, to mess things up, the download in profile.xhtml?user=username WORKS.
This destroys my whole theory about GET parameters.
To avoid having a null byte[] datiFile, I'ved edited my Dispensa entity this way :
#Basic(optional = true, fetch=FetchType.EAGER)
#Lob
#Column(name = "datiFile")
private byte[] datiFile;
I don't know what to do because it doesn't say what's going wrong, it just reloads the page, bypassing my download!
EDIT :
I've tried changing my getDownload() method to return a File which is on my HD, to understand if the problem is caused by a null data on the db but it still doesn't work as I said!
Seems that I solved this by using an alternative solution.
I've changed all
<h:form style="float: right">
<pou:commandLink id="downloadDispensa" ajax="false" disabled="#{!loginBean.logged}">
<pou:graphicImage value="./resources/images/download.png" height="30"/>
<pou:fileDownload value="#{dispensaBean.getDownload()}"/>
<f:param name="dispensaId" value="#{dispensa.id}"/>
</pou:commandLink>
</h:form>
to
<h:form style="float: right">
<h:outputLink id="downloadDispensa" disabled="#{!loginBean.logged}" target="_blank" value="./download.xhtml?id=#{dispensa.id}">
<pou:graphicImage value="./resources/images/download.png" height="30"/>
</h:outputLink>
</h:form>
where download.xhtml has this code :
<script type="text/javascript">
if(document.referrer == "" || document.referrer == "download.xhtml"){
self.location='./index.xhtml';
}
document.onblur = new Function('self.close()');
</script>
<h:body onload="document.getElementsByClassName('downloadDispensa')[0].click();" rendered="#{loginBean.logged}">
<h:form>
<h:commandLink class="downloadDispensa" id="downloadDispensa" style="display: none">
<pou:graphicImage value="./resources/images/download.png" height="30"/>
<pou:fileDownload value="#{dispensaBean.download}"/>
<f:param name="dispensaId" value="#{request.getParameter('id')}"/>
</h:commandLink>
</h:form>
</h:body>
<h:body onload="self.location='./index.xhtml';" rendered="#{!loginBean.logged}">
</h:body>
So it loads the download page, autoclicks on the download link and it autocloses the page when the download dialog is shown.
I have faced the same issue. I have debugged it and came to know that there is form inside form as I have included template inside another template as it is a summary screen.So I have removed all h:form tags in the inner templates except the root xhtml page which has all these templates and it worked.
Related
My goal is to create a panel where I can drag and drop elements of two different types: button and textfield. All of the elements are stored in a TreeMap and are iterated over with JSTL <c:forEach> tag. I can add extra components and remove the most recently selected ones thorough dedicated p:commandXxx fields outside of the viewPanel. Once draggable components are in the viewPanel, I can drag and drop them. Through javascript, I'm appending top and left coordinates of a particular component before the drop action to update component's location is invoked. The delete commandButton should remove the selected element from the list and trigger the update of the viewPanel.
Problem: Deleting any component except the most recently added fails to update the viewPanel resulting in the following exception:
Severe: Error Rendering View[/developer/testDr.xhtml]
org.primefaces.expression.ComponentNotFoundException: Cannot find >component for expression "button-" referenced from "defStep-10:j_idt13"..
It appears that in render time draggable's for attribute evaluates #{child.idstepNode} to null. Does anyone know why this happens, or how to circumvent this?
Interestingly, when Delete button is clicked again or the whole page is refreshed, viewPanel renders without a hitch.
<h:form id="viewPanel" class="default-step droppable" style="width:500px;height:500px;background:green;">
<p:droppable for="viewPanel" tolerance="touch" activeStyleClass="ui-state-highlight" >
<p:ajax listener="#{stepUtility.onDrop}" update="viewPanel" />
</p:droppable>
<c:forEach items="${stepUtility.stepNodesMap}" var="child">
<c:if test="${child.value.elementType == 'BUTTON'}">
<p:outputLabel id="button-${child.value.idstepNode}" style="top:${child.value.top};left:${child.value.left};
background-color: beige;position:absolute;" value="button ${child.value.elementValue}"/>
<p:draggable for="button-${child.value.idstepNode}"/>
</c:if>
<c:if test="${child.value.elementType == 'TEXT'}">
<p:outputLabel id="text-${child.value.idstepNode}" style="top:${child.value.top};left:${child.value.left};
background-color: beige;position:absolute;" value="text ${child.value.elementValue}"/>
<p:draggable for="text-${child.value.idstepNode}" />
</c:if>
</c:forEach>
</h:form>
//...
<p:commandButton value="Delete" actionListener="#{stepUtility.removeStepNode()}"
update=":viewPanel"/>
stepUtility bean
#Named(value = "stepUtility")
#SessionScoped
public class StepUtility implements Serializable {
private Integer selecteNode;
public void setSelectedElement(Integer idstepNode) //'StepNode')
{
selecteNode=idstepNode;
}
public void addNewComp(String typeNode)
{
if(stepNodesMap == null)
stepNodesMap = new TreeMap<>();
StepNodeSimple snode = new StepNodeSimple();
Integer id = 0;
if(stepNodesMap.size()>0)
{
Integer lastNodeId = ((StepNodeSimple) stepNodesMap.lastEntry().getValue()).idstepNode;
id = lastNodeId+1;
}
snode.idstepNode = id;
snode.elementType = typeNode;
snode.elementValue = typeNode +"value";
snode.left = "2px;";
snode.top = "2px;";
stepNodesMap.put(id, snode);
}
NavigableMap stepNodesMap;
public NavigableMap getStepNodesMap() {
return stepNodesMap;
}
public void setStepNodesMap(NavigableMap stepNodesMap) {
this.stepNodesMap = stepNodesMap;
}
public void removeStepNode()
{
if(selecteNode!=null)
{
stepNodesMap.remove(selecteNode);
}
}
public void onDrop(DragDropEvent dragDropEvent)
{
String dragId = dragDropEvent.getDragId();
Map<String, String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
String left = params.get(dragId + "_left");
String top = params.get(dragId + "_top");
Integer idstepNode = Integer.parseInt(dragId.substring(dragId.lastIndexOf("-")+1));
setSelectedElement(idstepNode);
StepNodeSimple element = (StepNodeSimple) stepNodesMap.get(selecteNode);
element.left = left+"px;";
element.top = top+"px;";
}
}
I'm really in trouble!
I need some brilliant idea to get a jsf page in which 7 charts slide one after another every 20 seconds and I need these charts are
refreshed every 30 seconds.
I tried some solutions with bad results:
1.Slideshow + Poll
<h:form>
<div align="center">
<p:panel id="chartcontainer" style="border: none;">
<p:imageSwitch style="width: 100%"
id="slider"
widgetVar="chartSlideShow"
effect="turnDown"
slideshowSpeed="5000">
<ui:include src="/charts/chart1.xhtml"/>
<ui:include src="/charts/chart2.xhtml"/>
<ui:include src="/charts/chart3.xhtml"/>
<ui:include src="/charts/chart4.xhtml"/>
<ui:include src="/charts/chart5.xhtml"/>
<ui:include src="/charts/chart6.xhtml"/>
<ui:include src="/charts/chart7.xhtml"/>
</p:imageSwitch>
</p:panel>
<p:poll widgetVar="pollWidget"
update="#form"
interval="30"
oncomplete="PF('btnPlayWidget').disable();"/>
</div>
</h:form>
This solution's problem is that the slideshow and poll are not synchronized so when I'm watching the chart3, for example, and poll is executed I don't expect the change and the slideshow restart from the first chart. This is really annoying!
2.Poll by itself
<h:form>
<div align='center'>
<p:panel id="chartcontainer" style="border: none;">
<ui:include src="#{slideView.slide}"/>
</p:panel>
<p:poll widgetVar="pollWidget"
update="#form"
interval="20"
listener="#{slideView.next()}"
oncomplete="PF('btnPlayWidget').disable();"/>
</div>
</h:form>
Here's my slideView's bean:
package com.tvop.beans;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ManagedBean
#ViewScoped
public class SlideView implements Serializable {
private int index = 0;
String slide = "/charts/chart1.xhtml";
private final String[] slides = new String[] {
"/charts/chart1.xhtml",
"/charts/chart2.xhtml",
"/charts/chart3.xhtml",
"/charts/chart4.xhtml",
"/charts/chart5.xhtml",
"/charts/chart6.xhtml",
"/charts/chart7.xhtml"
};
public String getSlide() {
return slide;
}
public void setSlide(String slide) {
this.slide = slide;
}
public String next() {
index %= slides.length;
this.slide = slides[index];
index++;
return slide;
}
}
This solution runs more or less but the poll's interval is not exact, especially at the beginning, as soon as the page is loaded.
The first change between the first and the second chart happens after 30/35 seconds and not 20, as setted in the poll's interval.
I really need some good idea, I don't want to be fired.
Thank you all my friends!
Solved!
The solution is to use the poll component by its own; updating the poll every 20 seconds it emulates the imageSwitch behavior! It's important to modify the java bean too to make it work.
Following the solution:
xhtml poll:
<div align='center'>
<p:panel id="chartcontainer" style="border: none;">
<p:panel style="border: 0px">
<ui:include src="#{slideView.slide}" />
</p:panel>
</p:panel>
<p:poll widgetVar="pollWidget"
update="chartcontainer"
interval="20"
listener="#{slideView.next()}"
oncomplete="PF('btnPlayWidget').disable();"/>
</div>
slideview bean:
package com.tvop.beans;
import com.tvop.exceptions.DMLException;
import com.tvop.persistence.TkResourceJPA;
import com.tvop.persistence.dbentities.TkResource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
#ManagedBean
#ViewScoped
public class SlideView implements Serializable {
private SessionManager sessionManager = null;
private List<TkResource> resources;
private int index;
private String slide;
private final List<String> slides = new ArrayList<>();
public SlideView() {
// Ottengo i privilegi sui chart tramite il bean sessionManager
FacesContext ctx = FacesContext.getCurrentInstance();
ExternalContext extCtx = ctx.getExternalContext();
Map<String, Object> sessionMap = extCtx.getSessionMap();
sessionManager = (SessionManager) sessionMap.get("sessionManager");
try {
resources = TkResourceJPA.getCharts();
} catch (DMLException ex) {
ex.printStackTrace();
}
SessionPrivileges privileges = sessionManager.getSessionPrivileges();
for(TkResource resource : resources) {
// Se è definito un privilegio controllo che privilegio è
if (privileges.contains(resource.getResourceid())) {
boolean currentResRendered = privileges.getRendered(resource.getResourceid());
if(currentResRendered){
slides.add(resource.getUrl());
}
}
}
index = 0;
slide = slides.get(index);
}
public String getSlide() {
return slide;
}
public void setSlide(String slide) {
this.slide = slide;
}
public String next() {
index %= slides.size();
if (index == slides.size() - 1) {
index = -1;
}
index++;
this.slide = slides.get(index);
return slide;
}
public String previous() {
if (index == 0) {
index = slides.size();
}
--index;
this.slide = slides.get(index);
return slide;
}
}
Enjoy :)
I have stumbled upon an, at least for me, unexpected behaviour. When using an ui:repeat, it seems I can access the var from outside.
Code - Page:
<f:metadata>
<f:event type="preRenderView" listener="#{xTest.init()}" />
</f:metadata>
<h:form id="xTestForm">
<h:panelGroup layout="block">
Track: #{trk.name}
</h:panelGroup>
<table>
<ui:repeat
value="#{xTest.trackList}"
var="trk">
<tr>
<td>#{trk.name}</td>
<td>
<p:commandLink
actionListener="#{xTest.setTrack(track)}"
value="test"
update=":xTestForm" />
</td>
</tr>
</ui:repeat>
</table>
</h:form>
Code - Bean
package beans;
import dao.DAOFactory;
import dao.track.TrackDAO;
import dto.Track;
import exceptions.DAOException;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
import util.MessageUtil;
#ManagedBean
#ViewScoped
public class xTest implements Serializable {
private DAOFactory daoFactory = Config.getInstance().getDAOFactory();
private TrackDAO trackDAO;
private Track track = new Track();
private MessageUtil msg = new MessageUtil();
private List<Track> trackList = new ArrayList();
public xTest() {
trackDAO = daoFactory.getTrackDAO(true);
}
public void init() {
if (!FacesContext.getCurrentInstance().isPostback()) {
try {
trackList = trackDAO.listByAlbumid(241);
} catch (SQLException | DAOException ex) {
msg.setErrorMessage(ex);
}
}
}
public List<Track> getTrackList() {
return trackList;
}
public void setTrack(Track track) {
this.track = track;
}
}
If I click a link in the list of tracks, the track name will be displayed in the panelGroup. How is this possible?
This is a bug in Mojarra. Its UIRepeat component forgets to remove the iteration variable from the request scope by end of iteration during restore view phase. It doesn't work that way in for example MyFaces.
You shouldn't rely your business code on it. Note that <h:dataTable> doesn't have this problem, it properly removes the iteration variable from the request scope by end of iteration by ((UIData) component).setRowIndex(-1) in encodeEnd() method.
8 years later someone reported this to PrimeFaces so I opened a Mojarra issue and a PR to fix the issue.
Mojarra Issue: https://github.com/eclipse-ee4j/mojarra/issues/4830
Mojarra PR: https://github.com/eclipse-ee4j/mojarra/pull/4831
Well currently I have this:
<rich:fileUpload addLabel="Agregar" clearAllLabel="Quitar todos"
clearLabel="Quitar" deleteLabel="Quitar"
doneLabel="Completado" uploadLabel="Subir archivos"
fileUploadListener="#{uploadBean.doUpload}"
acceptedTypes="txt, csv"
noDuplicate="true">
<a4j:ajax event="uploadcomplete" render="validationButton"/>
<a4j:ajax event="clear" listener="#{uploadBean.doClearFilesList}"
render="validationButton"/>
</rich:fileUpload>
On the backing bean I have a list of the files uploaded. When I click on Clear/Clear all button the event clear is fired and the method doClearFilesList (which just clears the list of files uploaded) is perfectly when the user hits the Clear All button, but If the user clicks on Clear button It should just delete the item on the list corresponding to the file cleared.
What can I do on my UploadBean.doClearFilesList method to delete a single file from the list? Should be something like:
public void doClearFilesList(){
files.clear(); //when CLEAR ALL is clicked
files.remove(oneFile); //when CLEAR is clicked
validationButtonRendered = false;
}
Any idea?
Cheers
UPDATE
RichFaces 4.1.0 Final
JSF Mojarra 2.1.6
Tomcat 7
I am not clear at which point you failed to run the sample described at https://community.jboss.org/message/727544#727544
However I hope following would work for you which is very similar to above sample.
Page:
<h:head>
<script>
function clear(event) {
var files = new Array();
var data = event.rf.data;
for (var i in data) {
files[i] = data[i].name;
}
clearFunc(files);
}
</script>
</h:head>
<body>
<h:form>
<rich:fileUpload onclear="clear(event);"/>
<a4j:jsFunction name="clearFunc" action="#{del.clearFile}" ajaxSingle="true">
<a4j:param name="fName" assignTo="#{del.fileNames}" />
</a4j:jsFunction>
</h:form>
</body>
Class:
public class Del {
String[] fileNames;
public void clearFile() {
for(String name : fileNames) {
System.out.println(">>" + name);
//Do file removing part here
}
}
public String[] getFileNames() {
return fileNames;
}
public void setFileNames(String[] fileNames) {
this.fileNames = fileNames;
}
}
Add "onclear" attribute to your <rich:fileUpload/> component and call a <a4j:jsFunction/> and pass the file name to it as below.
<rich:fileUpload onclear="clearFunc(event.memo.entry.fileName);" ..../>
Your <a4j:jsFunction/> should be as below.
<a4j:jsFunction name="clearFunc" actionListener="#{uploadBean.clearFile}" ajaxSingle="true">
<a4j:actionparam name="fName" />
</a4j:jsFunction>
Inside the listener method you can access the file name as below.
public void clearFile(ActionEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
String fileName = context.getExternalContext().getRequestParameterMap().get("fName").toString();
System.out.println("fileName = " + fileName);}
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".