I'm a bit stuck in PrimeFaces dataTable part. I created a table with rowEditor and sortBy built-in methods, but when I modify a row in the table order of entries are not updated.
In theory, it should work: update=":form:fruits". But it doesn't update the modified row. So I tried to update the whole form: update=":form". But in this case I loose all data from the table except the updated one without any desing.
ScreenShot01
ScreenShot02
Here is a very short sample code to reproduce the problem.
Fruit.java:
public class Fruit {
private Integer id;
private String name;
public Fruit(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
#Override
public boolean equals(Object o) {
if (!(o instanceof Fruit)) {
return false;
}
return id == ((Fruit) o).getId();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Fruits.java:
#ManagedBean(name = "fruits")
#ViewScoped
public class Fruits implements Serializable {
private static final long serialVersionUID = 1L;
private ArrayList<Fruit> list = new ArrayList<Fruit>() {
{
add(new Fruit(1, "apple"));
add(new Fruit(2, "orange"));
add(new Fruit(3, "banana"));
add(new Fruit(4, "pineapple"));
add(new Fruit(5, "cocoa"));
}
};
public void onRowEdit(RowEditEvent event) {
Fruit fruit = (Fruit) event.getObject();
list.set(list.indexOf(fruit), fruit);
}
public ArrayList<Fruit> getList() {
return list;
}
public void setList(ArrayList<Fruit> list) {
this.list = list;
}
}
fruits.xhtml:
<h:head />
<h:body>
<h:form id="form">
<p:dataTable id="fruits" value="#{fruits.list}" var="fruit"
editable="true" sortBy="#{fruit.name}">
<p:ajax event="rowEdit" listener="#{fruits.onRowEdit}" update=":form"></p:ajax>
<p:column headerText="Name" sortBy="#{fruit.name}">
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{fruit.name}" />
</f:facet>
<f:facet name="input">
<h:inputText value="#{fruit.name}" />
</f:facet>
</p:cellEditor>
</p:column>
<p:column>
<p:rowEditor />
</p:column>
</p:dataTable>
</h:form>
</h:body>
</html>
Any ideas how I could fix the problem?
Today I had the same problem and found a workaround:
Create a remoteCommand which updates the table and call this remoteCommand as oncomplete action after the cellEdit ajax event.
Snippet:
<h:form id="form">
<p:remoteCommand name="remoteCmd" update="table" />
<p:dataTable id="table" ...>
<p:ajax event="cellEdit" listener="#{anyClass.onCellEdit}"
oncomplete="remoteCmd();" />
<p:column headerText="header">
values
</p:column>
</p:dataTable>
</h:form>
try to use #form instead of :form
using :form:fruits is the correct form I don't know why it's not working with you.
I already tried all of possible keywords (#this, #form, #all), but none of these helped.
Today I added a Refresh button to the end of form. Using this button I can refresh the table as expected. But I don't really undrestand why update=":form"doesn't works with event "rowEdit".
<p:commandButton value="Refresh">
<p:ajax update=":form"></p:ajax>
</p:commandButton>
I have the same or a similar problem in PF 6.1. If I don't sort the datatable by clicking a sortable header, I can edit a cell just fine (I'm using cell edit, not row edit) and all is good. If I sort the table on any of the sortable headers and then edit a cell, the model data is not corrupted but the edited cell displays the value of a seemingly random cell above or below it in the table, as if it is displaying the value the cell would have had if the table not sorted. The other cells in the row are fine. If I click in the cell to re-edit it, the displayed value in the input element is correct; if I then move focus out of the cell, the incorrect value reappears. If I then refresh the page, or do another sort, the values displayed in the table are correct.
I have encountered a scenario where the model data is corrupted but I cannot reproduce it now.
I tried Gregor's workaround with RemoteCommand, no change.
I also have a "Refresh" button which does clear the problem but it's not intuitive to the user do use it for this purpose.
I know this isn't an answer but it may help someone troubleshooting the same problem. Just adding to the knowledgebase.
Related
Please have a look at the following JSF page and at the definition of the managed bean class which it references.
When I run my app and load the page I get the results shown in the "Good Results" screen shot. Note that the page is displaying data and that the "Hello World" string printed by the bean's constructor appears in the console.
If I alter the bean's definition by commenting out the definition of the "junk" integer (near the top) then I get the results shown in the "Bad Results" screen shot. Now there is no data and, most tellingly, the "Hello World" string does not appear in the console. No errors are reported to the console. The page is being rendered but it appears as though the JSF engine has decided that it does not like the bean's definition and is therefore not going to use it (the constructor is not called).
I have tried mightily to produce a Minimal, Complete, and Verifiable example. I eliminated several forms from the JSF page (you can observe that much of the bean's code is no longer being referenced by the JSF page). I eliminated JPA from the picture by creating the DummyDataAccessService class. I tried to eliminate the use of one or more of my own custom classes (Order, Patient, Product, DataAccessService, and DummyDataAccessService) but I could not: almost any alteration of the bean's definition produces the same strange behavior that results from removing the definition of the "junk" member variable.
I created a custom logging.properties file which bumped the level to ALL. The logging produced by the Good vs. Bad cases were close to the same. The two "Logging Differences" screen shots below reveal the primary differences.
I do not know how to explore this further. I don't know enough to even conjecture what might be going on. Any clue or recommendation on a course of action will be greatly appreciated.
JSF Page
<!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://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
<h:outputStylesheet library="default" name="css/style.css" />
<title>Managed Bean Problem</title>
</h:head>
<h:body>
<h3 class="title">Managed Bean Problem</h3>
<h:outputText value=" " />
<table align="center" width="600">
<tr><td><h:form>
<h:dataTable value="#{orderController.table}" var="order" styleClass="demo" columnClasses="columns, columns">
<h:column>
<f:facet name="header">
<h:column>
<h:outputText value="Patient"></h:outputText>
</h:column>
</f:facet>
<h:outputText value="#{order.patient.fullName}"></h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:column>
<h:outputText value="Product"></h:outputText>
</h:column>
</f:facet>
<h:outputText value="#{order.product.name}"></h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:column>
<h:outputText value="Actions"></h:outputText>
</h:column>
</f:facet>
<h:panelGrid columns="1">
<h:commandLink value="delete" action="#{orderController.delete}">
<f:setPropertyActionListener target="#{orderController.target}" value="#{order}" />
</h:commandLink>
</h:panelGrid>
</h:column>
</h:dataTable>
</h:form></td></tr>
</table>
</h:body>
Order Controller Managed Bean
package com.rvaessen.dmescripts.controllers;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;
import com.rvaessen.dmescripts.model.*;
#ManagedBean
public class OrderController {
private int junk; // Leave it in; Good. Comment it out; Bad.
private DataAccessService dataService = new DummyDataAccessService();
public OrderController() {
System.out.println("Hello, World");
}
#PostConstruct
public void init() {
initPatients();
initProducts();
initOrders();
}
// ********************************* The Orders Table ***************************************
private Order target;
private List<Order> table;
private void initOrders() { table = dataService.getOrders(); }
public List<Order> getTable() { return table; }
public void setTarget(Order order) { target = order; }
public String delete() {
dataService.removeOrder(target);
table.remove(target);
return null;
}
// ********************************* Add New Order ***************************************
// NOTE: The Add New Order methods are no longer referenced by the JSF page
private Order newOrder;
public String addNew() {
newOrder = new Order();
return null;
}
public String save() {
dataService.addOrder(newOrder, patient, product);
table.add(newOrder);
cancel();
return null;
}
public String cancel() {
newOrder = null;
return null;
}
public boolean getAddingNew() { return newOrder != null; }
/************************ The Patients Menu **********************************/
// NOTE: The Patients Menu methods are no longer referenced by the JSF page
private Patient patient;
private List<Patient> patients;
private void initPatients() {
patients = dataService.getPatients();
if (patients.size() > 0) patient = patients.get(0);
}
public List<SelectItem> getPatients() {
List<SelectItem> list = new ArrayList<SelectItem>();
patients.forEach(patient -> list.add(new SelectItem(patient.getId(), patient.getFullName())));
return list;
}
public Long getPatientId() {
return patient == null ? 0 : patient.getId();
}
public void setPatientId(Long id) {
patients.forEach(patient -> {
if (patient.getId() == id) {
this.patient = patient;
}
});
}
/************************ The Products Menu **********************************/
// NOTE: The Products Menu methods are no longer referenced by JSF page
private Product product;
private List<Product> products;
private void initProducts() {
products = dataService.getProducts();
if (products.size() > 0) product = products.get(0);
}
public List<SelectItem> getProducts() {
List<SelectItem> list = new ArrayList<SelectItem>();
if (patient != null) {
products.forEach(product -> {
if (product.getInsurance().equals(patient.getInsurance())) {
list.add(new SelectItem(product.getId(), product.getName()));
}
});
}
return list;
}
public Long getProductId() {
return product == null ? 0 : product.getId();
}
public void setProductId(Long id) {
products.forEach(product -> {
if (product.getId() == id) {
this.product = product;
}
});
}
}
Good Results
Bad Results
Log Differences 1
Log Differences 2
This issue was never solved. It was made to go away by recreating the Eclipse project from scratch; simultaneously upgrading to JSF 2.3 / CDI 1.2.
I have a problem with validation of input fields in a dataTable, which you can add / delete lines. I wanted to validate key params unique in whole table. I get it by incorrect way I think.
Here's a simplified version:
In xhtml:
<p:dataTable value="#{bean.parameters}" var="parameter">
<p:column>
<p:inputText id="name" value="#{parameter.name}">
<f:validator validatorId="duplicateInputValidator" />
</p:inputText>
</p:column>
<p:column>
<p:inputText id="value" value="#{parameter.value}"/>
</p:column>
</p:dataTable>
And my validator:
#FacesValidator("duplicateInputValidator")
public class DuplicateInputValidator implements Validator {
private final List<String> values = new ArrayList<>();
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (values.contains(value.toString())) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "error validation", ""));
}
values.add(value.toString());
}
}
In this simple case, it works, but if validation is also related to different columns (in this example: value) it will not work.
Is there any better way to do this? I was thinking about the possibility to retrieve all the data from datatable and validate them. 'findComponent' not working if I have repeteable rows.
I have a problem and i simplfied the code to show you.
<h:form enctype="multipart/form-data">
<h:panelGroup id="images">
<h:inputText id="auctionImage" value="#{testBean.nowy}"/>
<h:commandButton value="Add">
<f:ajax execute="auctionImage" render="images"/>
</h:commandButton>
<ui:repeat value="#{testBean.elements}" var="oneImage">
<h:outputText value="#{oneImage.title}" />
</ui:repeat>
</h:panelGroup>
</h:form>
This is my main Bean
#SessionScoped
#ManagedBean
public class TestBean {
private List<Element> elements;
private String nowy;
public String getNowy() {
return nowy;
}
public void setNowy(String nowy) {
Element el = new Element();
el.setTitle(nowy);
if(elements==null) elements = new ArrayList<>();
elements.add(el);
this.nowy = nowy;
}
public List<Element> getElements() {
return elements;
}
public void setElements(List<Element> elements) {
this.elements = elements;
}
}
This is the element class
#ManagedBean
public class Element {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
I want to populate the list everytime I click Add button and print it using ajax. The list is populated but ajax refresh the panelGroup only for the first time. To make it work again I have to refresh website. What i do wrong?
Ok i was managed to resolve the issue. Well, to be honest it wasn't me but jsf programmers. I found out that it happens when you use
<h:form enctype="multipart/form-data">
with ajax. This is the bug and it has been repaired some time ago. You just have to update your jsf version.
http://javaserverfaces.java.net/ - i got 2.2.3 version which is the newest for now.
After restarting glassfish and redeploying application everything works just fine.
I hope someone can help me. This is a "Category-Type-Item" situation where the user has to select from three different drop-down lists. In JSF I made it with three h:selectoneMenu. The first two h:selectOneMenu work OK. I read a solution here, about a "malformedXML" error when I tried the first time. That's the reason each selectOneMenu is surrounded by a h:panelGorup. I implemented it and work partially and only after reload (F5) the page, but this is not an acceptable solution.
Here is the code that I'm implementing:
<p:panel rendered="#{temporadaControllerExt.noHayGrupos}"
style="width: 650px">
<h:panelGrid columns="3">
<h:panelGroup id="fases">
<h:outputLabel value="Fase: "/>
<h:selectOneMenu value="#{temporadaControllerExt.faseSelectedFinal}">
<f:selectItems value="#{temporadaControllerExt.fases}"/>
<f:ajax render="grupos"/>
</h:selectOneMenu>
</h:panelGroup>
<h:panelGroup id="grupos">
<h:outputLabel value="Grupo: "/>
<h:selectOneMenu value="#{temporadaControllerExt.grupoSelected}"
disabled="#{temporadaControllerExt.grupoListDisable}">
<f:selectItems value="#{temporadaControllerExt.gruposDefase}"/>
<f:ajax render="jornadas"/>
</h:selectOneMenu>
</h:panelGroup>
<h:panelGroup id="jornadas">
<h:outputLabel value="Jornada: "/>
<h:selectOneMenu value="#{temporadaControllerExt.jornadaSelected}"
disabled="#{temporadaControllerExt.jornadaListDiseable}">
<f:selectItems value="#{temporadaControllerExt.jornadasEnGrupo}"/>
</h:selectOneMenu>
</h:panelGroup>
Of course is not complete, for space reasons. And here is part of the bean controller that I'm using:
#ManagedBean(name = "temporadaControllerExt")
#SessionScoped
public class TemporadaControllerExt extends TemporadaController {
private Fase fase;
private Grupo grupo;
private Jornada jornada;
private String faseSelectedFinal;
private String grupoSelected;
private String jornadaSelected;
private boolean grupoListDiseable = true;
private boolean jornadaListDiseable = true;
// a lot more declarations, and methods incluiding getters & setters not important for the answer...
//Entities Cache
private Map<String, Fase> mapFases = new HashMap<>();
private Map<String, Grupo> mapGrupos = new HashMap<>();
// use to enable the second selectOneMenu
public void setFaseSelectedFinal(String faseSelectedFinal) {
this.faseSelectedFinal = faseSelectedFinal;
this.grupoListDiseable = false;
}
public void setGrupoSelected(String grupoSelected) {
this.grupoSelected = grupoSelected;
this.jornadaListDiseable = false;
}
// methods used to populate the selecOneMenu's
public List<SelectItem> getFases(){
List<SelectItem> fasesList = new ArrayList<>();
fasesList.add(new SelectItem("-- Seleccione Fase --"));
for(Map.Entry<String, Fase> item : mapFases.entrySet()){
fasesList.add(new SelectItem(item.getKey()));
}
return fasesList;
}
public List<SelectItem> getGruposDefase(){
List<SelectItem> grupoList = new ArrayList<>();
grupoList.add(new SelectItem("-- Seleccione Grupo --"));
Fase faseSel = mapFases.get(faseSelectedFinal);
List<Grupo> grupoInt = faseSel.getGrupoList();
for(Grupo grp : grupoInt){
grupoList.add(new SelectItem(grp.getNombre()));
}
return grupoList;
}
public List<SelectItem> getJornadasEnGrupo(){
List<SelectItem> jornadaList = new ArrayList<>();
jornadaList.add(new SelectItem("-- Seleccione Jornada --"));
Grupo grupoSel = mapGrupos.get(grupoSelected);
for(Jornada jor : grupoSel.getJornadaList()){
jornadaList.add(new SelectItem(jor.getNumero().toString()));
}
return jornadaList;
}
}
As you can appreciate I'm using primefaces (3.4), and this panel is rendered in accordance to the value of the "noHayGrupos" boolean variable. This works OK, my problem is the selectOneMenu chain.
After some investigation on my problem, I found a solution. I read a tutorial in this place, the section relative to "Integrated Ajax Support in JSF 2.0" by Marty Hall. The solution was quite simple:
Declare a boolean variable to determinate if a comboBox is selected or not, and ask if a previous combobox has a data valid selected, and that's it, worked every thing. Here the code that I use in the method getGruposDefase():
public List<SelectItem> getGruposDefase() {
List<SelectItem> grupoList = new ArrayList<>();
grupoList.add(new SelectItem("-- Seleccione Grupo --"));
if (!grupoListDiseable && (faseSelectedFinal != null)) {
Fase faseSel = mapFases.get(faseSelectedFinal);
List<Grupo> grupoInt = faseSel.getGrupoList();
for (Grupo grp : grupoInt) {
grupoList.add(new SelectItem(grp.getNombre()));
}
}
return grupoList;
}
The variable grupoListDisable is used to control access to the second comboBox, and faseSelectedFinal, say if you have selected a valid item in the first comboBox. This simple solution makes the code work smoothly. Thanks Marty Hall
I have this form:
<h:form>
<h:outputText value="Tag:" />
<h:inputText value="#{entryRecorder.tag}">
<f:ajax render="category" />
</h:inputText>
<h:outputText value="Category:" />
<h:inputText value="#{entryRecorder.category}" id="category" />
</h:form>
What I'm trying to achieve: When you type in the "tag" field, the entryRecorder.tag field is updated with what was typed. By some logic upon this action the bean also updates its category field. This change should be reflected in the form.
Questions:
What scope shall I use for EntryRecorder? Request may not be satisfactory for multiple AJAX requests, while session will not work with multiple browser windows per one session.
How can I register my updateCategory() action in EntryRecorder so that it is triggered when the bean is updated?
Answering point 2:
<h:inputText styleClass="id_tag" value="#{entryRecorder.tag}"
valueChangeListener="#{entryRecorder.tagUpdated}">
<f:ajax render="category" event="blur" />
</h:inputText>
Bean:
#ManagedBean
#ViewScoped
public class EntryRecorder {
private String tag;
private String category;
#EJB
private ExpenseService expenseService;
public void tagUpdated(ValueChangeEvent e) {
String value = (String) e.getNewValue();
setCategory(expenseService.getCategory(value));
}
}
Number 1, anybody?
To point 1, I'll use Request since there is no need to use View and Session is, as you well pointed, completely unnecessary.
For point 2, since you are using <f:ajax/> I suggest making full use of it. Here is my proposal:
xhtml:
<h:form>
<h:outputText value="Tag:" />
<h:inputText value="#{entryRecorder.tag}">
<f:ajax render="category" event="valueChange"/>
</h:inputText>
<h:outputText value="Category:" />
<h:inputText value="#{entryRecorder.category}" id="category" />
</h:form>
Note the use of valueChange event instead of blur (not that blur doesn't work but I find valueChange more 'proper' for a value holder component).
bean:
#ManagedBean
#RequestScoped
public class EntryRecorder {
private String tag;
private String category;
public String getCategory() {
return category;
}
public String getTag() {
return tag;
}
public void setCategory(String category) {
this.category = category;
}
public void setTag(String tag) {
this.tag = tag;
tagUpdated();
}
private void tagUpdated() {
category = tag;
}
}
Unless you really want the tagUpdated method executed only when tag is updated through the view, my proposal looks more clear. You don't have to deal with the events (nor casting) and the tagUpdated method can be private hiding it's functionality from possible misuses.