My JSF application is behaving strangely, and I ask colleagues to help me identify a solution.
The application fetches data from the database through Facade+DAO classes, and through debug and println I can state that the object collection is correct (in the example below, the collection contains the 5 objects and their attributes), however, when passing this collection to the Primefaces page, dataTable does not display the attributes, it becomes clear that the the amount of rows is correct but the attributes are not displayed as shown in the figure.
I researched other posts, but the errors described do not resemble mine:
after filtering Empty rows blank rows displayed while paging in the datatable using Primefaces
primefaces datatable is showing blank rows. Showing the same number of rows as the records in backed list
Since the managed bean is reposting the collection correctly, I figured the issue should be on display (ie on the JSF page), and to try to find where the fault could be, I created a page without using Primefaces or Facelets, just pure JSF components, but the failure persisted. The basic code looks like this:
Here are the code snippets:
simple page
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<link href="scripts/teste.css" rel="stylesheet" type="text/css" media="all" />
</h:head>
<h:body>
<h:form>
<h:dataTable value="#{coletaMB.coletas}" var="coleta"
styleClass="order-table"
headerClass="order-table-header"
rowClasses="order-table-odd-row,order-table-even-row">
<h:column>
<f:facet name="header">Nr. Setor</f:facet>
<h:outputText value="#{coleta.setor.numero}"/>
---- #{coleta.setor.numero} ----
</h:column>
</h:dataTable>
</h:form>
With this simple code, the page looks like this:
managed bean
#ManagedBean(name="coletaMB")
#SessionScoped
public class ColetaMB{
#ManagedProperty(name="coleta", value="#{coleta}")
private Coleta coleta;
#ManagedProperty(name="coletaFacade", value="#{coletaFacade}")
private ColetaFacade coletaFacade;
private List<Coleta> coletas;
public List<Coleta> getColetas(){
if(coletas == null){
coletas = getListColetas();
}
return coletas;
}
private List<Coleta> getListColetas(){
coletas = new ArrayList<Coleta>();
try {
coletas = coletaFacade.getColetas();
return coletas;
} catch (DAOException e) {
(...)
}
}
(...)
}
Coleta.java
public class Coleta {
private int ano;
private Setor setor;
private int mes;
private int semana;
private int numeroEntrevista;
(*)getters and setter
}
Setor.java
public class Setor {
private Agencia agencia;
private String numero;
private String upa;
(*)getters and setters
}
Agencia.java
public class Agencia {
private int idAgencia;
private String nome;
(*)getters and setters
}
Facade
public List<Coleta> getColetas() throws DAOException {
return dao.getColetas();
}
DAO
#Value("#{queries.sql01}")
private String sql01;
public List<Coleta> getColetas() throws DAOException {
try{
RowMapper<Coleta> mapper = getRowMapper();
return getJdbcTemplate().query(sql01, mapper);
} catch (DataAccessException de) {
de.printStackTrace();
throw new DAOException(de.getMessage());
}
}
private RowMapper<Coleta> getRowMapper() {
return new RowMapper<Coleta>() {
public Coleta mapRow(ResultSet rs, int rowNum) throws SQLException {
Agencia ag = new Agencia();
ag.setIdAgencia(rs.getInt(1));
ag.setNome(rs.getString(2));
Setor s = new Setor();
s.setAgencia(ag);
s.setUpa(rs.getString(3));
s.setNumero(rs.getString(4));
Coleta c = new Coleta();
c.setSetor(s);
c.setAno(rs.getInt(5));
c.setMes(rs.getInt(6));
c.setSemana(rs.getInt(7));
c.setNumeroEntrevista(rs.getInt(8));
return c;
}
};
}
In getListColetas, I inserted a println to verify the collection and it is complete, that is, each object 'coleta' has the object 'setor' and each 'setor' has the object 'agencia'. But, following the suggestion of using 'empty' on the JSF page,
<h:outputText value="#{empty coleta} - #{empty coleta.setor} - #{empty coleta.setor.numero}"/>
the return was false - true - true, and I don't know why.
My complete application is using the following libraries and dependencies (Spring is only used for DI and DAO classes):
Resolved: in dataTable tag, I changed the attribute var="coleta" to var="c", like this:
<h:dataTable value="#{coletaMB.coletas}" var="c"
styleClass="order-table"
headerClass="order-table-header"
rowClasses="order-table-odd-row,order-table-even-row">
<h:column>
<f:facet name="header">Nr. Setor</f:facet>
<h:outputText value="#{c.setor.numero}"/>
---- #{c.setor.numero} ----
</h:column>
</h:dataTable>
I imagine JSF was conflicting with the #ManagedProperty 'coleta' in ColetaMB, although I understand that the var attribute is specific to varying collection objects delivered to dataTable.
Related
This question already has answers here:
Creating master-detail pages for entities, how to link them and which bean scope to choose
(2 answers)
Closed 3 years ago.
I'm working on one school project, where i need to use JSF to make CRUD app.
I'm using MySQL database, and managed to make list of all objects, delete button, and i have trouble with edit button.
When i click on edit it redirect me to edit.xhtml page, get id and fill all fields based on that id.
When i click update button on edit page, it always change customer with id=0.
I have one java doc with getter, setter and methods
have two views index.xhtml and edit.xhtml
and one page with method for connection with database.
All other methods work fine, except update.
Customer.java
#ManagedBean
#RequestScoped
public class Customer {
private int id;
private String username;
private String adress;
private int quantity;
private double price;
private Map<String, Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
public String edit() {
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, String> params = fc.getExternalContext().getRequestParameterMap();
String primarId = params.get("action");
System.out.println(primarId);
try {
DatabaseConnection dbc = new DatabaseConnection();
Connection connection = dbc.getConnection();
Statement st = connection.createStatement();
ResultSet rs = st.executeQuery("select * from customer where customer_id=" + primarId);
Customer customer = new Customer();
rs.next();
customer.setUsername(rs.getString("username"));
customer.setAdress(rs.getString("adress"));
customer.setQuantity(rs.getInt("quantity"));
customer.setPrice(rs.getDouble("price"));
sessionMap.put("editcustomer", customer);
} catch (SQLException ex) {
System.out.println(ex);
}
return "/edit.xhtml?faces-redirect=true";
}
public String updateCustomer() {
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, String> params = fc.getExternalContext().getRequestParameterMap();
String fieldId = params.get("action");
System.out.println(fieldId);
try {
DatabaseConnection dbc = new DatabaseConnection();
Connection connection = dbc.getConnection();
PreparedStatement ps = connection.prepareStatement("update customer set username=?,adress=?,quantity=?,price=? where customer_id=?");
ps.setString(1, username);
ps.setString(2, adress);
ps.setInt(3, quantity);
ps.setDouble(4, price);
ps.setInt(5, id);
System.out.println(id);
ps.executeUpdate();
} catch (SQLException ex) {
System.out.println(ex);
}
return "/index.xhtml?faces-redirect=true";
}
edit.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<center>
<h:form>
Username: <h:inputText value="#{editcustomer.username}"></h:inputText> <br/>
Adress: <h:inputText value="#{editcustomer.adress}"></h:inputText> <br/>
Quantity: <h:inputText value="#{editcustomer.quantity}"></h:inputText> <br/>
Price: <h:inputText value="#{editcustomer.price}"></h:inputText> <br/><br/>
<h:commandButton value="Update" action="#{editcustomer.updateCustomer()}">
<f:param name="action" value="#{editcustomer.id}" />
</h:commandButton>
</h:form>
</center>
</h:body>
</html>
when I run this code the ID stays 0
I think the problem is the #RequestScoped annotation in the bean. With this annotation data only "lives" in the current page. As you are redirecting to another URL (altough it is the same one), you lose your changes (more taking into account that you are putting the customer in the session map. Try using #SessionScoped instead.
Hope it works.
I'm using JSF2 and Tomcat server. I programmed a simple example in which:
User Selects a faculty from "h:selectOneMenu"
Upon selection, the value of "h:inputText" is changed to "odd" or "even" based on facultyNo
Also, upon selection, the value of "h:selectBooleanCheckBox" is changed to "checked" if facultyNo is even and "not checked" if facultyNo is odd
Everything works fine for "h:inputText". On the other hand, the value of "h:selectBooleanCheckBox" does not change. Why is this happening?
By the way, the use of boolean value inside a HashMap is intentional because the project I'm working on has lots of boolean values in a HashMap. So, replacing the Hashmap with a simple boolean property and using a getter and a setter for it is definitely not a solution for my case.
The code for the xhtml page is below:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>
<title>Dummy Title</title>
</head>
<body>
<h:form>
<label for="faculty">Faculty</label>
<h:selectOneMenu id="faculty" value="#{test.selectedFaculty}" converter="faccon" valueChangeListener="#{test.facultyChange}" onchange="submit()">
<f:selectItems value="#{start.app.faculties}"/>
</h:selectOneMenu>
<h:selectBooleanCheckbox id="mycheck" value="#{test.x.get(0)}"></h:selectBooleanCheckbox>
<h:outputText value="#{test.res}"></h:outputText>
<h:commandButton value="Save" action="#{test.saveChoices}" />
</h:form>
</body>
</html>
The code for the backing bean is below
import java.io.Serializable;
import java.util.HashMap;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.event.ValueChangeEvent;
import com.myWork.Application;
import com.myWork.Faculty;
#ManagedBean(name="test")
#RequestScoped
public class TestBean implements Serializable
{
private HashMap<Integer,Boolean> x;
private String res;
private Faculty selectedFaculty;
#PostConstruct
public void init(){
Application app = Application.getInstance();
selectedFaculty = app.getFaculties()[0];
x = new HashMap<Integer, Boolean>();
if (selectedFaculty.getFacultyNo()%2==0)
{
x.put(0, true);
res = "even";
}
else
{
x.put(0, false);
res = "odd";
}
}
public HashMap<Integer,Boolean> getX() {
return x;
}
public void setX(HashMap<Integer,Boolean> x) {
this.x = x;
}
public Faculty getSelectedFaculty() {
return selectedFaculty;
}
public void setSelectedFaculty(Faculty selectedFaculty) {
this.selectedFaculty = selectedFaculty;
}
public String getRes() {
return res;
}
public void setRes(String res) {
this.res = res;
}
public void facultyChange(ValueChangeEvent e){
Faculty fac = (Faculty) e.getNewValue();
if (fac.getFacultyNo()%2==0)
{
x.put(0, true);
res = "even";
}
else
{
x.put(0, false);
res = "odd";
}
}
public String saveChoices(){
return "test";
}
}
Any help is greatly appreciated.
Actually your problem is not related to JSF, it is related to EL. in your case, EL treat you map keys as long not int, so you should change your map to be:
HashMap<Long, Boolean> map = new HashMap<>();
and set the key values as follows :
map.put(0L, false);
map.put(1L, true);
to force it to be auto-boxed to long.
Note: the problem described in detailed in the following question :
EL access a map value by Integer key
In order to send updates to a different component, you can make use of partial page rendering by making Ajax calls to send parameters to Managed bean.
Within <h:selectBooleanCheckBox>
<h:selectOneMenu id="faculty" value="#{test.selectedFaculty}" converter="faccon" valueChangeListener="#{test.facultyChange}">
<f:selectItems value="#{start.app.faculties}"/>
</h:selectOneMenu>
<h:selectBooleanCheckbox id="mycheck" valueChangeListener="#{test.facultyChange}" value="#{test.x.get(0)}">
<f:ajax event="change" execute="#form" render="faculty"/>
</h:selectBooleanCheckbox>
Edit: Removed call to onSubmit() in selectOneMenu. introduced an ajax call in selectBooleanCheckbox for partial page refresh.
Refer full solution at this post.
I'm having an issue with lazy loading a Primefaces Datascroller component.
I have a jsf page that should display 10 events on page load. If the user wants to see more he/she can click the more button to load and display the 10 next events. For each of the event rows, there is a link that can be used to display the event's details.
<h:form id="mainForm" >
<p:dataScroller value="#{backing.lazyModel}" var="event" lazy="true" chunkSize="10" rowIndexVar="index">
#{event.name}
<p:commandLink class="view-trigger"
value="View Event Details"
actionListener="#{backing.initViewEventDetails(index, event)}"/>
<f:facet name="loader">
<p:outputPanel
visible="#{backing.lazyModel.rowCount gt 10}"
rendered="#{backing.lazyModel.rowCount gt 10}">
<p:commandLink value="More" />
</p:outputPanel>
</f:facet>
</p:dataScroller>
</h:form>
The initial search works fine, that is, when I click the view event details link, my backing bean is invoked and I see that the index and event received correspond to the row I clicked on.
However, once I load the next chunk, which consists of 1 extra event, the page displays 11 events but clicking a view event details link sends the proper index but does not send the proper event. For example, if I click on event at index 0, I get the event at index 10, if I click on event at index 1 my backing bean is not invoked.
It looks like the datascroller forgets about the last 10 events when I click on the more button but my lazy data model still remembers.
The backing bean:
#ManagedBean(name="backing")
#ViewScoped
public class DataScrollerBacking implements Serializable {
private static final long serialVersionUID = 4012320411042043677L;
private static final Logger LOGGER = Logger.getLogger(DataScrollerBacking.class);
#ManagedProperty("#{settings.dataSource}")
private String dataSource;
private WebEventDAO webEventDAO;
private LazyDataModel<Event> lazyModel;
#PostConstruct
public void init() {
webEventDAO = CommonDAOFactory.getInstance(dataSource).getWebEventDAO();
search();
}
public void search() {
DateTime start = new DateTime(2014, 1, 1, 0, 0 ,0);
final Date startDate = start.toDate();
final Date endDate = start.plus(Years.ONE.toPeriod()).minus(Seconds.ONE.toPeriod()).toDate();
lazyModel = new LazyDataModel<Event>() {
private static final long serialVersionUID = 1231902031619933635L;
private LinkedHashSet<Event> eventCache; // Ordered set of all retrieved events so far.
// I'm using a set because the load method is called twice on page load (any idea why???) and I don't want duplicates in my cache.
#Override
public List<Event> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
List<Event> events = new ArrayList<Event>(10);
try {
if(eventCache == null){
int count = webEventDAO.getSearchByPeriodRaceTypeAndRaceStatusForCompanyCount(Collections.singletonList(1), startDate, endDate, null, null);
this.setRowCount(count);
eventCache = new LinkedHashSet<Event>(count);
}
events = webEventDAO.searchByPeriodRaceTypeAndRaceStatusForCompany(Collections.singletonList(1), startDate, endDate, null, null, true, first, pageSize);
eventCache.addAll(events);
} catch (DAOException e) {
LOGGER.error("An error occurred while retrieving events.", e);
}
return events;
}
};
}
public void initViewEventDetails(Integer index, Event event){
LOGGER.info("index=" + index + " eventname=" + event.getName());
}
public String getDataSource() {
return dataSource;
}
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
public LazyDataModel<Event> getLazyModel() {
return lazyModel;
}
public void setLazyModel(LazyDataModel<Event> lazyModel) {
this.lazyModel = lazyModel;
}}
Since the page displays the proper information and the index received is always valid, my current workaround is to go fetch the Event in the lazy data model by index.
However, I would like to understand why the received event is not the one I clicked on.
Am I doing something wrong or this is just how the scroller is implemented?
Running on Mojarra 2.2, Tomcat 7, Primefaces 5, Omnifaces 1.8
I found a good explanation about the behavior of request scope in this link http://www.theserverside.com/news/thread.tss?thread_id=44186
If you are using ManagedBeans in request scope, you get problems with
CommandLinks inside DataTables. DataTables are one thing I really like
about JSF, and CommandLinks often come in handy as well. But when you
put a CommandLink inside a DataTable, e. g., to select the entry of
the row in which the CommandLink is, you get bitten. That is, if you
want ManagedBeans with request scope. The action which should be
triggered by the CommandLink is never triggered, the page is simply
rendered again. The reason for this behaviour is that the DataTable
modifies the id of the CommandLink during renderering, but the
CommandLink does not know that it was rendered with a different id.
During the decoding of the request which was triggered by clicking the
CommandLink, the ComandLinkRenderer looks at a hidden form parameter.
If the value of that form parameter equals the id of the CommandLink,
an action is queued. If not, nothing is done. Since the DataTable
changes the ids, the value of the hidden form parameter does not match
the id of the CommandLink.
Based on above context, you need to change the scope annotations from #ViewScoped to
#SessionScope, and your problem will be solved automatically. It seems to be a better solution than write additional code, unless you need to keep the #ViewScopped
A workaround would be to use PrimeFaces remote command, passing arguments with rc([name: 'paramName', value: someParamValue]). These arguments should be available using #{param['paramName']} EL expression
Example:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:body>
<ui:composition>
<p:dataTable id="#{id}" widgetVar="#{id}"
value="#{requestCache.getLazy(id, () -> dataSource)}" var="rpo"
selectionMode="single" selection="#{selection}"
lazy="true" paginator="true" rows="#{pageSizeController.pageSize}"
pageLinks="10" paginatorPosition="top"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
currentPageReportTemplate="(#{label.Page} {currentPage} #{label.of} {totalPages}, #{label.Row} {startRecord} - {endRecord} #{label.of} {totalRecords})"
rowsPerPageTemplate="5 10 20 30 40 50 100"
scrollable="true" scrollHeight="#{empty scrollHeight ? 300 : scrollHeight}"
resizableColumns="true" emptyMessage="#{label.Table_is_empty}">
<p:column>
<h:outputText value="#{rpo.decreeSequence.code}" />
<p:commandLink id="displayAdditionalInfoCommandLink" type="link" style="float: right; text-decoration: none"
onclick="displayAdditionalInfo([{name: 'refundPaymentOrderId', value: #{rpo.id}}])"
title="#{label.Additional_information}">
<h:outputLabel for="displayAdditionalInfoCommandLink" styleClass="fa fa-info-circle"
onmouseover="jQuery(this).addClass('fa-lg').css('cursor', 'pointer')"
onmouseout="jQuery(this).removeClass('fa-lg')"/>
</p:commandLink>
</p:column>
</p:dataTable>
<p:remoteCommand name="displayAdditionalInfo" process="#this" update="#parent">
<f:setPropertyActionListener target="#{refundPaymentOrderCache.refundPaymentOrder}"
value="#{refundPaymentOrderRepo.find(requestCache.toLong(param['refundPaymentOrderId']))}" />
<f:actionListener binding="#{dialog.displayInputForm('RPO_ADDITIONAL_INFO')}" />
</p:remoteCommand>
</ui:composition>
</h:body>
</html>
I finally had time to spend on this issue and I found a workaround. It's a hack so maybe the proper solution would be to use a different component or create my own.
It seems like Primefaces DataScroller limitation that occurs when using the DataScroller with a LazyDataModel. It would seem that the component was not designed to do this.
To avoid this issue, I implemented my own lazy loading where the same list instance is returned in addition to the newly added elements.
Here is my previous example modified to implement this new lazy loading pattern:
The html page:
<h:form id="mainForm" >
<p:dataScroller value="#{backing.events}" var="event" rowIndexVar="index">
#{event.name}
<p:commandLink class="view-trigger"
value="View Event Details"
action="#{backing.initViewEventDetails(index, event)}"/>
<f:facet name="loader"><h:outputText value=""/></f:facet>
</p:dataScroller>
<p:commandLink value="More" process="#form" update="#form"
action="#{backing.loadMore()}"
visible="#{backing.totalCount gt backing.events.size()}"
rendered="#{backing.totalCount gt backing.events.size()}"/>
</h:form>
The DataScroller no longer has lazy="true", chunkSize="10", uses a list called events as the value and declares an empty loader facet (to avoid auto-load more when the bottom of the list is reached). I used a commandLink that calls backing.loadMore() and updates the form to replace the loader facet.
The backing bean:
#Named("backing")
#ViewScoped
public class DataScrollerBacking implements Serializable {
private static final long serialVersionUID = 4012320411042043677L;
private static final Logger LOGGER = Logger.getLogger(DataScrollerBacking.class);
private static final Integer CHUNK_SIZE = 10;
#DataSource
#Inject
private String dataSource;
private WebEventDAO webEventDAO;
private List<Event> events;
private Integer totalCount;
private Date startDate;
private Date endDate;
#PostConstruct
public void init() {
webEventDAO = CommonDAOFactory.getInstance(dataSource).getWebEventDAO();
search();
}
public void search() {
DateTime start = new DateTime(2014, 1, 1, 0, 0 ,0);
startDate = start.toDate();
endDate = start.plus(Years.ONE.toPeriod()).minus(Seconds.ONE.toPeriod()).toDate();
try {
totalCount = webEventDAO.getSearchByPeriodRaceTypeAndRaceStatusForCompanyCount(Collections.singletonList(1), startDate, endDate, null, null);
events = new ArrayList<Event>(totalCount);
loadMore();
} catch (DAOException e) {
LOGGER.error("An error occurred while retrieving events.", e);
}
}
public void loadMore() {
List<Event> newEvents = new ArrayList<Event>(CHUNK_SIZE);
try {
newEvents = webEventDAO.searchByPeriodRaceTypeAndRaceStatusForCompany(Collections.singletonList(1), startDate, endDate, null, null, true, events.size(), CHUNK_SIZE);
events.addAll(newEvents);
} catch (DAOException e) {
LOGGER.error("An error occurred while retrieving events.", e);
}
}
public void initViewEventDetails(Integer index, Event event){
LOGGER.info("index=" + index + " eventname=" + event.getName());
}
public String getDataSource() {
return dataSource;
}
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
public List<Event> getEvents() {
return events;
}
public void setEvents(List<Event> events) {
this.events = events;
}
public Integer getTotalCount() {
return totalCount;
}
public void setTotalCount(Integer totalCount) {
this.totalCount = totalCount;
}}
In the backing bean, the search method counts the total number of events, saves that information and calls loadMore() to load the first 10 events in the events list.
When the more button is clicked, loadMore() is called again and the next 10 events are appended at the end of events list.
Now when I click on newly loaded elements, the commandLink invokes the backing bean with the correct value.
I'm writing my custom table composite component with Mojarra JSF. I'm also trying to bind that composite to a backing component. The aim is to be able to specify the number of elements the table has in a composite attribute, later on the bound backing component will autogenerate the elements itself before view gets rendered. I've this sample code:
Main page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:comp="http://java.sun.com/jsf/composite/comp">
<h:head />
<body>
<h:form>
<comp:myTable itemNumber="2" />
</h:form>
</body>
</html>
myTable.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html">
<h:body>
<composite:interface componentType="components.myTable">
<composite:attribute name="itemNumber"
type="java.lang.Integer" required="true" />
</composite:interface>
<composite:implementation>
<h:dataTable value="#{cc.values}" var="value">
<h:column headerText="column">
#{value}
<h:commandButton value="Action" action="#{cc.action}" />
</h:column>
</h:dataTable>
</composite:implementation>
</h:body>
</html>
MyTable.java:
#FacesComponent("components.myTable")
public class MyTable extends UINamingContainer {
private List<String> values = new ArrayList<String>();
public void action() {
System.out.println("Called");
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
// Initialize the list according to the element number
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
super.encodeBegin(context);
}
public List<String> getValues() {
return values;
}
}
The issue is table gets rendered properly (in this case with two items), but action method doesn't get called when pressing the button on the lines.
If I follow the wiki page for composite components, I can get it work in that way, but having to initialize the List each time getValues() is called, introducing logic into the getter method :-(.
Any idea about that? It seems to be a trouble related with overriding encodeBegin method. I also tried initializing it on markInitialState, but attributes are not yet available there...
Tested with Mojarra 2.1.27 + Tomcat 6-7 & Mojarra 2.2.5 + Tomcat 7
As to the cause, UIComponent instances are inherently request scoped. The postback effectively creates a brand new instance with properties like values reinitialized to default. In your implementation, it is only filled during encodeXxx(), which is invoked long after decode() wherein the action event needs to be queued and thus too late.
You'd better fill it during the initialization of the component. If you want a #PostConstruct-like hook for UIComponent instances, then the postAddToView event is a good candidate. This is invoked directly after the component instance is added to the component tree.
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
...
</cc:implementation>
with
private List<String> values;
public void init() {
values = new ArrayList<String>();
Integer num = (Integer) getAttributes().get("value");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
}
(and remove the encodeBegin() method if it isn't doing anything useful anymore)
An alternative would be lazy initialization in getValues() method.
A simpler solution would be to store and retrieve values as part of the components state. Storing can happen during encodeBegin, and retrieving could directly happen within the getter:
#FacesComponent("components.myTable")
public class TestTable extends UINamingContainer {
public void action() {
System.out.println("Called");
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
// Initialize the list according to the element number
List<String> values = new ArrayList<>();
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
getStateHelper().put("values",values);
super.encodeBegin(context);
}
public List<String> getValues() {
return (List<String>)getStateHelper().get("values");
}
}
To avoid repeating the logic in getValues(), there could be additional parsing required in more complex cases, there should be a way to process and cache the attributes right after they become available, although I am not sure when and how at this point.
Either way - this seemed to be the simplest way to solve this problem.
I came up with a strange problem. I tried to isolate the problem so following is my simplified code.
public class MyBean {
private List<Data> dataList;
Data selectedData;
public MyBean() {
dataList = new ArrayList<Data>();
dataList.add(new Data("John", 16));
dataList.add(new Data("William", 25));
}
public List<Data> getDataList() {
return dataList;
}
public void edit(Data data) {
selectedData = data;
}
public void newData() {
selectedData = new Data(null, null);
}
public Data getSelectedData() {
return selectedData;
}
public class Data {
String name;
Integer age;
Data(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
}
xhtml:
<rich:modalPanel id="pop">
<h:form>
Name: <h:inputText value="#{myBean.selectedData.name}" required="true" id="txtName"/><br/>
Age : <h:inputText value="#{myBean.selectedData.age}" required="true" id="txtAge"/>
<a4j:commandButton value="Save"/>
<a4j:commandButton value="Close" onclick="Richfaces.hideModalPanel('pop');return false;"/>
<br/>
<rich:message for="txtName"/><br/>
<rich:message for="txtAge"/>
</h:form>
</rich:modalPanel>
<h:form>
<rich:dataTable value="#{myBean.dataList}" var="data">
<rich:column>#{data.name}</rich:column>
<rich:column>
<a4j:commandLink value="Edit" action="#{myBean.edit(data)}" reRender="pop" oncomplete="Richfaces.showModalPanel('pop')"/>
</rich:column>
</rich:dataTable>
<a4j:commandButton value="New" action="#{myBean.newData()}" reRender="pop" oncomplete="Richfaces.showModalPanel('pop')"/>
</h:form>
This is the path to error:
Load the page
Click the "Edit" link in first row(popup displays)
In popup, clear the "Age" field and click "Save".(Required message shown)
Click cancel(without filling "Age" field)
Click second link.
Now it shows irrelevant data(previous data). - This is the problem
Even when I click "New" button it shows incorrect data.
This happens only if a validation is failed in the popup.
Is there a solution for this?
This problem is in JSF 2 also recognized and explained in detail in the following answer: How can I populate a text field using PrimeFaces AJAX after validation errors occur? If you were using JSF 2, you could have used OmniFaces' ResetInputAjaxActionListener or PrimeFaces' <p:resetInput> or resetValues="true" for this.
To the point, you need to clear the state of the EditableValueHolder component when it's about to be ajax-rendered, but which isn't included in the ajax-execute. To clear the state, in JSF 2 you would have used the convenience method resetValue() for this, but this isn't available in JSF 1.2 and you need to invoke the 4 individual methods setValue(null), setSubmittedValue(null), setLocalValueSet(false), setValid(true) to clear the state.
To figure out which components are to be ajax-rendered, but aren't been ajax-executed, in JSF 2 you would have used PartialViewContext methods for this, but this is not available in JSF 1.2 which hasn't standardized ajax yet. You'd need to fiddle with RichFaces specific ajax API in order to figure that. I can't tell that from top of head, so here's a kickoff example assuming that you already know the components which needs to be cleared. Imagine that the form in your popup has id="popForm" and the name input field has id="nameInput", here's how you could clear it inside the newData() method:
UIInput nameInput = (UIInput) context.getViewRoot().findComponent("popForm:nameInput");
nameInput.setValue(null);
nameInput.setSubmittedValue(null);
nameInput.setLocalValueSet(false);
nameInput.setValid(true);
do one thing on cancel action set all popup values null. now in your next click all values set to be default.
or on click set all previous values null. and set all respective values after that.
I had the same problem. if you are using Primefaces, the solution is as simple as putting resetValues="true" on your p:commandLink or p:commandButton that loads the selected item.
After validation failed if you want to remain same as input data which you have pass as submission parameter, then set value attribute as your form bean name as mention below i.e.
<input type="text" id="fname" path="fname" value="${myFormBean.fname}"/>