I wanted to try out the primefaces library for jsf and see if it fits my needs in regards to dashboarding and chart creation. I tried setting up a dashboard with some fake data in a linechart to evaluate how it behaves:
<div style="height:500px">
<p:growl id="msgs" showDetail="true" />
<p:dashboard id="board" model="#{dashboardManagedBean.model}">
<p:ajax event="reorder" listener="#{dashboardManagedBean.handleReorder}" update="msgs" />
<p:panel width="100%" id="Finance" header="Finance" closable="true" toggleable="true">
<p:chart type="line" model="#{dashboardManagedBean.chartModel}" style="height:500px;"/>
</p:panel>
</p:dashboard>
</div>
This is the dashboard bean:
package com.journaldev.primefaces.beans;
import java.util.Date;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import org.primefaces.event.DashboardReorderEvent;
import org.primefaces.model.DashboardColumn;
import org.primefaces.model.DashboardModel;
import org.primefaces.model.DefaultDashboardColumn;
import org.primefaces.model.DefaultDashboardModel;
import org.primefaces.model.chart.*;
#ManagedBean
#SessionScoped
public class DashboardManagedBean {
private DashboardModel model;
private LineChartModel chartModel;
public DashboardManagedBean() {
// Initialize the dashboard model
this.model = new DefaultDashboardModel();
DashboardColumn column2 = new DefaultDashboardColumn();
column2.addWidget("Finance");
this.model.addColumn(column2);
chartModel = new LineChartModel();
chartModel.setTitle("Linear Chart");
chartModel.setLegendPosition("e");
chartModel.setZoom(true);
LineChartSeries series1 = new LineChartSeries();
series1.setLabel("Series 1");
chartModel.addSeries(series1);
Date date = new Date();
double y = 0;
for (int i=0;i<1000;i++){
series1.set(date.getTime() + 1000*i,y);
y+=Math.random();
}
DateAxis axis = new DateAxis("Dates");
axis.setTickAngle(-50);
axis.setTickFormat("%d.%m.%y %H:%#M:%S");
axis.setMin("09.09.18 17:38:00");
axis.setMax("09.09.18 18:00:00");
axis.setTickCount(12);
chartModel.getAxes().put(AxisType.X, axis);
}
public DashboardModel getModel() {
return model;
}
public LineChartModel getChartModel() {
return chartModel;
}
public void setModel(DashboardModel model) {
this.model = model;
}
}
And the result I got looks mostly good. The issues start coming in when I try zooming in the chart, then it gets really scuffed:
Zoomed Chart
So i have multiple concerns regarding primefaces charts:
Why is the theme I picked not applied? I installed the maven dependencies and added to the web.xml. For the rest of the dashboard it seems to work out.
Why does the zooming behave this weirdly?
Is there really no autoscaling on the linechart available for xmin and xmax?
Thanks for helping an absolute primefaces noob.
Related
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 :)
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.
This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 6 years ago.
I know this seems to be a common one, but I'm lost with it. Occurs on clicking the Add button in assessment.jsf. Anyway, I've attached what I think are the relevant sections.
FWIW, AssessmentType.equals() isn't triggered when I debug.
Thanks in advance.
j_idt38:j_idt47:j_idt48: Validation Error: Value is not valid
assessment.xhtml:
<h:form>
<h:selectOneMenu value="#{assessmentBean.assessmentField}">
<f:selectItems value="#{assessmentBean.assessment.type.fields}" />
</h:selectOneMenu>
<h:commandButton value="Add" action="#{assessmentBean.doAddField}">
<f:param name="assessmentId"
value="#{assessmentBean.assessment.id}" />
</h:commandButton>
</h:form>
assessment.jsf:
<form id="j_idt38:j_idt47" name="j_idt38:j_idt47" method="post" action="/jsf-web/edit/assessment.jsf" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="j_idt38:j_idt47" value="j_idt38:j_idt47" />
<select name="j_idt38:j_idt47:j_idt48" size="1"> <option value="1">Presenting Condition</option>
<option value="2">Problem Duration</option>
</select>
<script type="text/javascript" src="/jsf-web/javax.faces.resource/jsf.js.jsf?ln=javax.faces"></script>
<input type="submit" name="j_idt38:j_idt47:j_idt50" value="Add" onclick="mojarra.jsfcljs(document.getElementById('j_idt38:j_idt47'),{'j_idt38:j_idt47:j_idt50':'j_idt38:j_idt47:j_idt50','assessmentId':'1'},'');return false" /><input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="3431661972220941645:6952134510589038883" autocomplete="off" />
</form>
AssessmentType.java:
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.envers.Audited;
#Audited
#Data
#Entity
public class AssessmentType implements Comparable<AssessmentType> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private String name;
#OneToMany( fetch=FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=AssessmentField.class )
private List<AssessmentField> fields;
#Override
public int compareTo(final AssessmentType o) {
return getId().compareTo(o.getId());
}
#Override
public String toString() {
return getName();
}
}
AssessmentFieldConverter.java
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import com.htu.fizio.api.AssessmentFieldManager;
import com.htu.fizio.domain.AssessmentField;
#FacesConverter(forClass = AssessmentField.class)
public class AssessmentFieldConverter implements Converter {
AssessmentFieldManager<AssessmentField> assessmentFieldManager;
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
public Object getAsObject(FacesContext ctx, UIComponent component, String value) {
try {
final InitialContext ic = new InitialContext();
assessmentFieldManager = (AssessmentFieldManager) ic.lookup("fizio/AssessmentFieldManagerImpl/local");
return assessmentFieldManager.find(Long.valueOf(value));
} catch (NamingException e) {
e.printStackTrace();
}
return null;
}
#Override
public String getAsString(FacesContext ctx, UIComponent component, Object value) {
return String.valueOf(((AssessmentField) value).getId());
}
}
AssessmentBean.java
import java.util.List;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import lombok.Getter;
import lombok.Setter;
import com.htu.fizio.api.AssessmentManager;
import com.htu.fizio.domain.Assessment;
import com.htu.fizio.domain.AssessmentField;
import com.htu.fizio.domain.AssessmentFieldValue;
import com.htu.fizio.jsf.faces.FacesUtil;
...
#PostConstruct
public void init() {
if (FacesUtil.containsKey("assessmentId")) {
final Long id = Long.parseLong(FacesUtil.get("assessmentId"));
assessment = assessmentManager.find(id);
} else {
assessment = new Assessment();
}
}
public String doAddField() {
final AssessmentFieldValue value = new AssessmentFieldValue();
value.setField(assessmentField);
value.setValue("");
assessment.getFieldValues().add(value);
assessmentManager.save(assessment);
return "/edit/assessment";
}
Edit:
Just noticed this when debugging, is it a likely suspect?:
Daemon Thread [HandshakeCompletedNotify-Thread] (Suspended (exception ConcurrentModificationException))
HashMap$EntryIterator(HashMap$HashIterator<E>).nextEntry() line: 793
HashMap$EntryIterator.next() line: 834
HashMap$EntryIterator.next() line: 832
SSLSocketImpl$NotifyHandshakeThread.run() line: 2214
Validation Error: Value is not valid
To the point, this error means that the selected item does not match any of the items available in the list. I.e. the object represented by the selected item value has never returned true on its equals() call with any of the available select items.
There are only two causes for this problem:
The equals() method of the object type in question is broken.
The contents of the list of items is different during the validations phase of the form submit request than as it was during the render response phase of the initial request to display the form.
Since the first seems to be properly implemented -as per the comments-, the only cause left is the second. Assuming that you're nowhere doing business logic in a getter method, an easy test is to put the #{assessmentBean} in the session scope. If it works, then the data (pre)loading logic of the list of select items is definitely wrong.
The validation is failing because after your converter converts the String representation of AssessmentType back to an object, JSF iterates over the existing values (assessmentBean.assessment.type.fields) and compares this recently converted object with all those existing ones.
Since you did not implement Object#equals for AssessmentType, it will default to an object identity comparison (roughly spoken, the memory address of your object) , which will of course fail.
The solution is thus to either implement Object#equals, or let the converter get the object from assessmentBean.assessment.type.fields instead of from AssessmentTypeManager.
I think I've resolved this, at least, I've moved on to the next error!
Despite posting reams of code I'd not posted the full xhtml in which there were multiple and nested form tags. Just the one form seems to allow passing the assessmentId parameter which in turn allows the AssessmentBean to then populate the List of AssessmentFields for the assessment type correctly.
Thanks for all the help.
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
I'm trying to get a composite component working with it's own backing bean,
using the example on p375 from the Core JSF 3 book, but just get an NPE. The problem seems to be at the start of encodeBegin(), Date date = (Date) getValue() returns null.
If I'm honest I don't really understand where the value of the component is supposed to
be getting stored, I specify it as a java.util.Date using cc:attribute type=, but I
don't really understand how this: public Object getSubmittedValue() { return this; } -
which is going to return an instance of an InputDateBean class - results in a Date. I am generally good and confused by how this is supposed to work.
Unlike the book example I am trying to the use backing component for temporary storage,
so when the day is input I try to store it in #{cc.day}, in the book they use an application scoped bean for some reason.
Thanks for any help. I am using Mojarra 2.1.
inputDate.xhtml
<cc:interface componentType="uk.co.myco.jsfbeans.sqcc.InputDateBean">
<cc:attribute name="value" type="java.util.Date"/>
</cc:interface>
<cc:implementation>
<h:panelGrid columns="3">
<h:inputText id="day" value="#{cc.day}"
converter="javax.faces.Integer"/>
<h:inputText id="month" value="#{cc.month}"
converter="javax.faces.Integer"/>
<h:inputText id="year" value="#{cc.year}"
converter="javax.faces.Integer"/>
</h:panelGrid>
</cc:implementation>
InputDateBean.java
package uk.co.myco.jsfbeans.sqcc;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import javax.faces.component.FacesComponent;
import java.util.GregorianCalendar;
import javax.faces.application.FacesMessage;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import uk.co.myco.general.SQLog;
import uk.co.myco.jsfbeans.helper.Messages;
#FacesComponent(value = "uk.co.myco.jsfbeans.sqcc.InputDateBean")
public class InputDateBean extends UIInput implements NamingContainer {
private int day = 0, month = 0, year = 0;
public InputDateBean() {
}
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
Date date = (Date) getValue();
Calendar cal = new GregorianCalendar();
cal.setTime(date);
UIInput dayComponent = (UIInput) findComponent("day");
UIInput monthComponent = (UIInput) findComponent("month");
UIInput yearComponent = (UIInput) findComponent("year");
dayComponent.setValue(cal.get(Calendar.DATE));
monthComponent.setValue(cal.get(Calendar.MONTH) + 1);
yearComponent.setValue(cal.get(Calendar.YEAR));
super.encodeBegin(context);
}
#Override
public Object getSubmittedValue() {
return this;
}
#Override
protected Object getConvertedValue(FacesContext context, Object newSubmittedValue)
throws ConverterException {
UIInput dayComponent = (UIInput) findComponent("day");
UIInput monthComponent = (UIInput) findComponent("month");
UIInput yearComponent = (UIInput) findComponent("year");
int lday = (Integer) dayComponent.getValue();
int lmonth = (Integer) monthComponent.getValue();
int lyear = (Integer) yearComponent.getValue();
if (isValidDate(lday, lmonth, lyear)) {
return new GregorianCalendar(lyear, lmonth - 1, lday).getTime();
} else {
FacesMessage message = Messages.getMessage("util.messages", "invalidDate", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ConverterException(message);
}
}
// getters & setters & isValidDate() removed
}
I now see my mistake. The problem was that the composite component has to be
called with a Date object, i.e. <cclib:inputDate value="#{bean.date}"/>. As the
code stands the date needs to be instantiated, but it wasn't. The more robust
way of doing this is to do a new Date() in encodeBegin() in the event that
getValue() is null. This then works the same a h:inputText/f:convertDateTime
which does not require that the value is instantiated.