f:ajax and inputs inside ui:repeat - why aren't getters called? - ajax

I'm having trouble understanding why my getters aren't being called when I expect them inside a ui:repeat. I'm using Glassfish 3.1.1 / Mojarra 2.1.3.
The code below will render a table like
Alice [empty input] [empty output] [link: update value] [link: cancel]
Bob [empty input] [empty output] [link: update value] [link: cancel]
If I click "update value" on the Alice row, then "update value" on the "Bob" row, I end up with this:
Alice [Alice] Alice
Bob [Alice] Bob
I don't understand why the output for the "Bob" row is picking up "Alice" instead. It's like the getter isn't being called during the render-response phase, and instead the old value from the managed bean is stuck to the UIInput in the update-model-values phase.
What's weird is that if I hit "update value" on the Alice row, then "cancel", then "update value" on the Bob row, I get the expected result.
Also, if I add "#form" to the render=... on the "update value" link, I will see the right values (although they will be duplicated on each row). I don't like this as much, primarily because I don't want to update the whole table to process a single row.
What could be causing this? What am I missing about the JSF lifecycle?
Also - the same pattern works just fine outside of a ui:repeat. In that case, the h:inputText seems to always refresh with the right value from the managed bean, calling the getter in the "render response" phase as I expect.
This was originally using PrimeFaces p:commandLink but I get exactly the same behavior with standard JSF h:commandLink and f:ajax.
Also I'm aware of PrimeFaces row editor and that would possibly be a better solution to the general overall problem - I still want to understand why this doesn't work though.
Thanks!
The relevant XHTML is as follows
<h:form id="testForm">
<table style="width:400px; ">
<ui:repeat value="#{testBean.customers}" var="customer" varStatus="status">
<tr>
<td><h:outputText id="output" value="#{customer.name}"/></td>
<td><h:inputText id="input" value="#{testBean.customerEdit.name}"/></td>
<td><h:outputText id="otherOutput" value="#{testBean.customerEdit.name}"/></td>
<td>
<h:commandLink actionListener="#{testBean.edit(status.index)}">
<f:ajax execute="#this" render="input otherOutput"/>
Update value
</h:commandLink>
<h:commandLink actionListener="#{testBean.cancel}">
<f:ajax execute="#this" render="input otherOutput"/>
Cancel
</h:commandLink>
</td>
</tr>
</ui:repeat>
</table>
</h:form>
The "testBean" managed bean is view-scoped:
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ViewScoped
#ManagedBean
public class TestBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public static class Customer {
private String name;
public Customer(String name) {
this.name = name;
}
public String getName() {
System.out.println("returning name: " + name);
return name;
}
public void setName(String name) {
this.name = name;
}
}
private List<Customer> customers;
private Customer customerEdit = new Customer(null);
#PostConstruct
public void init() {
customers = Arrays.asList(new Customer("Alice"),
new Customer("Bob"), new Customer("Carol"), new Customer("David"), new Customer("Eve"));
}
public Customer getCustomerEdit() {
return customerEdit;
}
public void setCustomerEdit(Customer customerEdit) {
this.customerEdit = customerEdit;
}
public void edit(int index) {
System.out.println("Called edit with index: " + index);
customerEdit = new Customer(customers.get(index).getName());
}
public void save(int index) {
System.out.println("Called save with index: " + index + " new name = " + customerEdit.getName());
customers.set(index, customerEdit);
customerEdit = null;
}
public void cancel() {
System.out.println("Called cancel");
customerEdit = null;
}
public List<Customer> getCustomers() {
return customers;
}
public void setCustomers(List<Customer> customers) {
this.customers = customers;
}
}

Your problem lies in this row:
<h:commandLink actionListener="#{testBean.edit(status.index)}">
You can't send arguments to actionlisteners this way, that's not how it works. You need to change that row to something like:
<h:commandLink actionListener="#{testBean.edit}" customerIndex="#{status.index}>
And then change the edit method to something like this.
public void edit(ActionEvent ae) {
int index = ae.getComponent().getAttributes().get("customerIndex");
System.out.println("Called edit with index: " + index);
customerEdit = new Customer(customers.get(index).getName());
}
Also I'm not sure how your "save" method relates to anything else, but that's probably just because you skipped some irrelevant code.
EDIT: You can send arguments this way if it's a javascript method, but not to managed beans or anything else inside the #{} tags.

Related

ClassCastException for <h:selectManyCheckbox> with validation on ajax update when INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL is active

I have a <h:selectManyCheckbox> that has a required-validation on. If I submit the form, I get a validation error when nothing is selected. So far, this ist expected. However, if I do an ajax update on the checkbox then, I get a ClassCastException. But only if empty values are treated as null.
So, I have the following setup. In the web.xml I set
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
Then I have an xhtml-page like this:
<h:form id="main">
<h:selectManyCheckbox id="value" value="#{testcb.selected}" required="true" requiredMessage="Select at least one entry">
<f:selectItems value="#{testcb.available}"/>
</h:selectManyCheckbox>
<div><h:message for="value" style="color:red;"/></div>
<h:outputLabel for="checkit" value="Enter some text: "/>
<h:inputText id="checkit" value="#{testcb.text}">
<f:ajax event="change" execute="#this" render=":main:value"/>
</h:inputText>
<div><h:commandButton type="submit" value="Submit" action="#{testcb.action}"/></div>
</h:form>
And this backing bean:
#Named("testcb")
#SessionScoped
public class TestCBBean implements Serializable {
private final Set<TestValue> available = EnumSet.allOf(TestValue.class);
private final Set<TestValue> selected = EnumSet.noneOf(TestValue.class);
private String text;
public void action() {}
public Set<TestValue> getAvailable() { return available; }
public void setAvailable(Set<TestValue> available) {
this.available.clear();
this.available.addAll(available);
}
public Set<TestValue> getSelected() { return selected; }
public void setSelected(Set<TestValue> selected) {
this.selected.clear();
this.selected.addAll(selected);
}
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
And this enum:
public enum TestValue { ONE, TWO, THREE }
I am running this in Wildfly 26.0.1-Final (JavaEE 8). But this also happens in older versions (like Wildfly 15). What I am doing:
enter some text and leave the box: an ajax update runs setting the value successfully in the model
I press submit: the validation error for the empty checkboxes pops up
I modify the text in the input and leave the box: the ajax update results in the following Exception:
java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.renderkit.html_basic.MenuRenderer.getSubmittedSelectedValues(MenuRenderer.java:508)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.renderkit.html_basic.SelectManyCheckboxListRenderer.encodeEnd(SelectManyCheckboxListRenderer.java:89)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:600)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1655)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:628)
com.sun.jsf-impl#2.3.17.SP01//com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:159)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
javax.faces.api#3.1.0.SP01//javax.faces.component.UIForm.visitTree(UIForm.java:355)
On the ajax update the checkboxes are not submitted. But they seem to contain an empty string as submitted value from the validation step before.
When setting the context parameter to false this works. But I want to keep it on true. Any ideas how I could work around this problem?
Reproduced. This is indeed a bug in Mojarra.
It boils down to that the following method in UIInput superclass ...
#Override
public Object getSubmittedValue() {
if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) {
return "";
} else {
return submittedValue;
}
}
... is not overridden in UISelectMany superclass in such way that it returns new String[0] instead of "". This was an oversight during implementing Faces issue 671.
I have fixed it in Mojarra issue 5081.
In the meanwhile, until you can upgrade to the Mojarra version containing the fix, you can temporarily work around it by copy pasting the entire source code file of UISelectMany into your project while maintaining the package and adding the following method to it:
#Override
public Object getSubmittedValue() {
Object submittedValue = super.getSubmittedValue();
return "".equals(submittedValue) ? new String[0] : submittedValue;
}

JSF Primefaces: Update a dataTable open in another tab?

I have a simple webapp which allows the user to create transactions, and on another page the user can see all past transactions. I would like:
To have the dataTable be up-to-date when I open the past transaction page
To update the dataTable when I create a transaction on the creation page
I am not sure if this can be covered by one or two functionalities. Also, I am not sure how to decouple the functionalities. Should the creation of a transaction trigger the refresh of the dataTable, or should the dataTable itself find new entries in the DB ?
The past transactions page:
My TransactionListModel
package model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import entity.Transaction;
#Named
#SessionScoped
public class TransactionListModel implements Serializable {
private static final long serialVersionUID = 1L;
private List<Transaction> txList;
#PostConstruct
public void init() {
txList = new ArrayList<Transaction>();
}
public List<Transaction> getTxList() {
return txList;
}
public void clearList(){
txList = new ArrayList<Transaction>();
}
}
My Transaction view
<!-- Fill the table before rendering -->
<f:event type="preRenderView" listener="# {transactionListController.findAllTx()}" />
<h:form id="alltxform">
<p:dataTable id="tableAllTransactions" var="transaction"
value="#{transactionListModel.txList}">
<f:facet name="header">Transactions</f:facet>
<p:column headerText="Id">
<h:outputText value="#{transaction.id}" />
</p:column>
</p:dataTable>
</h:form>
My TransactionList Controller
#Named
#RequestScoped
public class TransactionListController implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
private Logger log;
#Inject
private TransactionService txService;
#Inject
private TransactionListModel txListModel;
public void findAllTx() {
txListModel.clearList();
txListModel.getTxList().addAll(txService.findAllTx());
}
public void reset() {
if (txListModel.getTxList() != null) {
txListModel.clearList();
}
}
}
The creation page
There is a simple textInputField (bound to a model) with a button:
<h:commandButton id="sendtx" value="Send" styleClass="ui-priority-primary">
<f:actionListener binding="#{transactionXmlController.sendTx()}" />
</h:commandButton>
The called method:
public void sendTx() {
FacesMessage message;
if (!transactionService.sendTx(transactionXmlEditableModel.getXml()).equals("OK"))
message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "KO");
else {
message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Success", "OK");
}
fc.addMessage(null, message);
}
So far, this works.
My Questions
How do I reload the dataTable with AJAX ?
I would like the dataTable to update even if it has been opened in another tab before the creation of a new transaction. How to do this ?
How to replace my "f:event type="preRenderView" with a viewAction in order to fill the dataTable before rendering the page ?
I'm not sure how you would update the table in another tab, without something like <p:poll> but to update your table with Ajax you can do something like:
<h:commandButton id="sendtx" value="Send" styleClass="ui-priority-primary">
<f:ajax listener = '#{transactionXmlController.sendTx()}' render="alltxform:tableAllTransactions" />
</h:commandButton>

How to add multiple results to an JSF form

Hi there i currently have a web page that uses ajax on submit to display the message the user entered on the same page below the input box, what i am wondering, if it is possible to keep a record of all messages inputted such as message 1, message 2 etc all displayed under them?
what is the best method for this ? also is there a way to do this with out the user having to press the submit button each time ?
this is my code so far :
<h:body>
<h3>JSF 2.0 + Ajax Hello World Example</h3>
<h:form>
<h:inputText id="name" value="#{helloBean.name}"></h:inputText>
<h:commandButton value="Welcome Me">
<f:ajax execute="name" render="output" />
</h:commandButton>
<h2><h:outputText id="output" value="#{helloBean.sayWelcome}" /></h2>
</h:form>
</h:body>
my bean
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import java.io.Serializable;
#ManagedBean
#SessionScoped
public class HelloBean implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSayWelcome(){
//check if null?
if("".equals(name) || name ==null){
return "";
}else{
return "Ajax message : Welcome " + name;
}
}
}
First you will need to change your bean. Make the attribute a List<String> to store all of the messages.
After that, to show the list you need to change output to a component that allows for showing all elements of the List; v.g. dataTable.
Also, you will need to invoke an action method in you ajax request, because your application will need to execute some logic (add name to the list).

how to use value change listener

I don't know how to deal with f:valueChangeListener , I want to select country and the capital appear so this is my code but it doen't work what is miss or what is the wrong?
Country:
<h:selectOneMenu value="#{event.country}" onchange="submit()">
<f:valueChangeListener type="org.jsf.model.ValueListener"/>
<f:selectItems value="#{event.countries}"/>
</h:selectOneMenu>
Capital: #{event.capital}
My Managed bean
public class EventsBean{
private String capital;
private String country;
String countryCapital;
private String [] countries = {"Select","Egypt","United States","Kuwait"};
public String[] getCountries() {
return countries;
}
// getters and setters
}
The class that implements ValueChangeListener
package org.jsf.model;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ValueChangeEvent;
import javax.faces.event.ValueChangeListener;
public class ValueChangeClass implements ValueChangeListener {
String capital;
#Override
public void processValueChange(ValueChangeEvent event)throws AbortProcessingException {
if ("Egypt".equals(event.getNewValue()) capital = "Cairo";
else if ("Kuwait".equals(event.getNewValue())) capital = "Kuwait";
else if ("United States".equals(event.getNewValue())) capital = "Washington";
else capital = "";
new EventsBean().setCapital(capital);
}
}
It doesn't work !
Is this new EventsBean().setCapital(capital);right ?
Is this new EventsBean().setCapital(capital);right ?
No, it is not right. You're manually creating a brand new instance instead of using the one which is managed by JSF. Your instance would totally disappear once the method finishes and returns. You should instead be setting the capital in the instance which is managed by JSF. There are several ways to achieve this. If you really intend to use the ValueChangeListener this way (which is rather strange for this particular purpose by the way), then you need to fix it as follows:
FacesContext context = FacesContext.getCurrentInstance();
EventsBean eventsBean = context.getApplication().evaluateExpressionGet(context, "#{event}", EventsBean.class);
eventsBean.setCapital(capital);
Easier would be to do the job just in the EventsBean itself.
<h:selectOneMenu value="#{event.country}" valueChangeListener="#{event.changeCountry}" onchange="submit()">
<f:selectItems value="#{event.countries}"/>
</h:selectOneMenu>
Capital: #{event.capital}
private String country;
private String capital;
private Map<String, String> capitals;
#PostConstruct
public void init() {
capitals = new HashMap<>();
capitals.put("Egypt", "Cairo");
capitals.put("Kuwait", "Kuwait");
capitals.put("United States", "Washington D.C.");
}
public void changeCountry(ValueChangeEvent event) {
capital = capitals.get(event.getNewValue());
}
Or, since you're already using JSF 2.0, much better is to use <f:ajax>. It does the right job at the right moment. (Ab)using the valueChangeListener the way as in your original code is actually a leftover of the JSF 1.x era.
<h:selectOneMenu value="#{event.country}">
<f:selectItems value="#{event.countries}"/>
<f:ajax listener="#{event.changeCountry}" render="capital" />
</h:selectOneMenu>
Capital: <h:outputText id="capital" value="#{event.capital}" />
// ...
public void changeCountry() {
capital = capitals.get(country);
}
See also:
When to use valueChangeListener or f:ajax listener?

Strange behavior with <f:ajax> inside <h:form> inside <ui:repeat>

I have an table of data presented by an ui:repeat. Because I want the user to be able to change the data on a per row basis, each row is enclosed in an h:form. Finally each h:form has a button with the f:ajax tag. I am getting wildly inconsistent behavior.
<ui:repeat value="#{importManager.items}" var="item" varStatus="status">
<h:form>
<tr>
<td>
<h:outputText value="#{status.index}"/>
</td>
<td>
<h:inputText id="title" value="#{item.title}" styleClass="#{item.validTitle ? 'valid' : 'invalid'}"/>
</td>
<td>
<h:inputText id="artist" value="#{item.artist}" styleClass="#{item.validArtist ? 'valid' : 'invalid'}"/>
</td>
<td>
<h:commandButton value="#{importMessages.submit}">
<f:ajax execute="#form" render="#all" listener="#{importManager.publish(item)}"/>
</h:commandButton>
</td>
</tr>
</h:form>
</ui:repeat>
The above works but obviously is not cheap on bandwidth.
If I change the render="#all" to render="#form", Firebug shows the partial response being sent ok, but my browser (Firefox) mysteriously does not display it. So I am guessing it (the browser) does not find the element to update?
If I change execute="#form" to execute="#all" I get very strange behavior, namely the data gets lost, and the affected fields go blank.
The backing bean is quite simple:
public void publish(final Item item)
{
Set<ConstraintViolation<Item>> violations = item.validate();
if (violations.isEmpty())
{
temporaryRegistry.deleteItem(item);
registry.storeItem(item);
}
else
{
// Display error messages
}
}
And the model:
#Entity
public class Item implements Cloneable
{
#Id #GeneratedValue
private long identifier;
#NotNull(groups={Warning.class})
#Length(min=1, max=80, groups={Warning.class})
private String title;
#NotNull(groups={Warning.class})
#Length(min=1, max=80, groups={Warning.class})
private String artist;
#NotNull(groups={Warning.class})
#Length(min=1, max=10, groups={Warning.class})
private String media;
#NotNull(groups={Warning.class})
#Length(min=1, max=5, groups={Warning.class})
#Column(name = "_condition")
private String condition;
// Setters and Getters
public boolean isValidTitle()
{
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
final Validator validator = factory.getValidator();
final Set<ConstraintViolation<Item>> violations = validator.validateProperty(this, "title", Warning.class);
return violations.isEmpty();
}
public boolean isValidCondition()
{
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
final Validator validator = factory.getValidator();
final Set<ConstraintViolation<Item>> violations = validator.validateProperty(this, "condition", Warning.class);
return violations.isEmpty();
}
public boolean isValidArtist()
{
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
final Validator validator = factory.getValidator();
final Set<ConstraintViolation<Item>> violations = validator.validateProperty(this, "artist", Warning.class);
return violations.isEmpty();
}
#Override
public boolean equals(final Object object)
{
return (object instanceof Item) && (object != null) && (((Item) object).getIdentifier() == identifier);
}
#Override
public int hashCode()
{
return Long.valueOf(identifier).hashCode();
}
public Set<ConstraintViolation<Item>> validate()
{
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
final Validator validator = factory.getValidator();
return validator.validate(this, Warning.class);
}
}
Can anyone explain this, and does anyone have a way to submit the form and the form only by ajax and display the result?
Your HTML ends up having a <form> around each <tr>. This is illegal HTML syntax and the browser behaviour is therefore unspecified.
You need to put the <h:form> around the <table>. If you need a form around a single "row", then you might want to redesign the single <table> to be multiple <table>s with fixed column widths or a group of <div>s.

Resources