I have a data table that lists Parent items. One of the columns nests another table to list the Children. Also in this column next to the nested children datatable I have Primeface commandButton to insert a child on that item. I also have another another column with another Primeface commandButton on each row to toggle a confirmed boolean on the parent.
If I first do an add child operation, the table gets ajaxically refreshed and I see the new child record. If I then press the other button which updates the confirmed boolean on the same parent item that I just added the child record to, the table ajaxically refreshes fine, the boolean flag gets updated correctly and I still see the added child. But in reality, the second jpa update added the child record again. I don't see the duplicate child record in the table until a do a refresh on my browser.
Here is some code
public class Parent implements Serializable {
private boolean confirmed;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parent", cascade = CascadeType.ALL)
#OrderBy("date")
private Set<Child> children;
public class Child implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
public class Bean
public void applyAddChild() {
newChild.setDate(new Date());
newChild.setParent(selectedParent);
selectedParent.getChildren().add(newChild);
try {
service.update(selectedParent);
Messages.addFlashMessage("Insert Successful");
} catch (Exception e) {
Messages.addFlashErrorMessage(e.getMessage());
}
newChild = new Child();
}
public void confirmSelected() {
selectedParent.setConfirmed(true);
try {
service.update(selectedParent);
Messages.addFlashMessage("Confirmed For Parent");
} catch (Exception e) {
Messages.addFlashErrorMessage(e.getMessage());
}
}
When I put a breakpoint and debug the confirmedSelected method, the children are listed as a PersistentSets
Here is the confirmed Button
<p:commandButton action="#{bean.confirmSelected()}"
rendered="#{not parent.confirmed}" value="Confirm?" update="#form">
<f:setPropertyActionListener value="#{parent}"
target="#{bean.selectedParent}" />
</p:commandButton>
And the add Child Button
<p:commandButton oncomplete="PF('addChildDialogWv').show()"
update="#form" value="Add Child">
<f:setPropertyActionListener value="#{parent}"
target="#{bean.selectedParent}" />
</p:commandButton>
If I hit refresh on the browser in between all the ajax refreshes things work just fine. I suppose I can not use ajax and do full redirect/refresh but I'd like to know why it's not working with Ajax.
EDIT
I was able to resolve my issue but I'm not sure if this is a Band-Aid or proper way. I'm explicitly swapping out the updated object in the datatable list.
New code --->
public class Bean
public void applyAddChild() {
newChild.setDate(new Date());
newChild.setParent(selectedParent);
selectedParent.getChildren().add(newChild);
---> Parent updatedParent = new Parent();
try {
---> updatedParent = service.update(selectedParent);
Messages.addFlashMessage("Insert Successful");
} catch (Exception e) {
Messages.addFlashErrorMessage(e.getMessage());
}
---> int index = parents.indexOf(selectedParent);
---> parents.set(index, updatedParent);
newChild = new Child();
}
Related
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;
}
I have a selectOneListBox and a button
<p:selectOneListbox id="somelistBox" value="#{bean.selectedItem}" styleClass="listBox">
<f:selectItems value="#{bean.list}" var="item" itemValue="#{item}" itemLabel="#{preparer.prepare(item)}"/>
</p:selectOneListbox>
<p:commandButton id="somebutton" process="#(.listBox)" update="#(.listBox)"/>
and a bean
#Component
public void class Bean
{
private List<Item> list;
private Item selectedItem;
public List<Item> getList()
{
return list;
}
public void getSelectedItem()
{
return selectedItem;
}
public void setSelectedItem(Item selectedItem)
{
this.selectedItem = selectedItem;
}
}
However, when I press the button the validation fails on selectOneListBox during the ajax post. From what I've read this can happen when you are trying to set the bound object in the bean with the wrong type of object, however my selectOneListBox contains a list of "Items" and the value is selectedItem which is also an "Item" so I can't see where I'm going wrong.
I think my problem was borne out of a misunderstanding of how browers work. They have no knowledge of java objects, just strings, so attempting to pass the objects back and forwards between browser and server was impossible. Instead I have changed the item value to the id of the item and then had to retrieve this object on the back end with a database call, ie
<p:selectOneListbox id="somelistBox" value="#{bean.selectedItem}" styleClass="listBox">
<f:selectItems value="#{bean.list}" var="item" itemValue="#{item.id}" itemLabel="#{preparer.prepare(item)}"/>
</p:selectOneListbox>
and then some call on the back end to get the object back from the id that is passed by the ajax post (I chose to do this in a Converter).
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.
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.
I am currently trying to dynamically add a new component to the JSF component tree during an ajax request.
In fact I add a child to the UIViewRoot component in my AjaxBehaviorListener which is fired on server side during the ajax request process.
The issue is that the new component is not rendered. It seems that this component is not taken into account in the render response phase.
Could you help me on this issue ?
Regards,
Guillaume
This solution works in case of you know before the ajax request the component to add.
But if you are not able to know which component to add, it does not work.
I maybe found a solution :
My solution is to implement my custom PartialViewContext and use the method startInsertAfter or startInsertBefore of the PartialResponseWriter.
It is working, but you have to put the component added as transient. (uiComponent.setTransient(Boolean.TRUE);)
Regards,
Guillaume
This works for me:
Bean holding binding to UIComponent under which you want to add other UIComponents dynamically should be request scoped otherwise it can throw some nasty exceptions (don't ask me why):
#ManagedBean
#RequestScoped
public class AddressEditorBean {
// session bean reference for holding id number line and model
#ManagedProperty(value = "#{addressValueBean}")
private AddressValueBean address;
public String addOutputText() {
HtmlOutputText text = new HtmlOutputText();
int c = address.getC();
text.setValue("new text! " + c);
text.setId("id" + c++);
address.setC(c); // hold id number line in sessionbean
address.getComps().add(text); // hold new uicomponent in session bean to be able to rebuild it
panel.getChildren().clear(); // need to clear children and add them all again,
panel.getChildren().addAll(address.getComps()); // otherwise there are problems with duplicate children (bug?)
return "success";
}
public HtmlPanelGroup getPanel() {
return panel;
}
public void setPanel(HtmlPanelGroup pane) {
if (panel == null) {
this.panel = pane;
if (panel != null) {
panel.getChildren().addAll(address.getComps());
}
} else {
this.panel = pane;
}
}
}
code snippet from page. I dynnamically add components to <h:panelGroup>
<h:form>
<h:panelGroup id="section" binding="#{addressEditorBean.panel}">
</h:panelGroup>
<h:commandButton value="add new text" action="#{addressEditorBean.addOutputText}">
<f:ajax execute="#this" render="section" event="action"/>
</h:commandButton>
</h:form>
In Session bean I hold actual dynamic model so that I can rebuild it after page reload:
#ManagedBean
#SessionScoped
public class AddressValueBean extends ValueBean<Address> {
private int c = 0;
private List<UIComponent> comps = new ArrayList<UIComponent>();
public AddressValueBean() {
setValue(new Address());
}
public int getC() {
return c;
}
public void setC(int cn) {
this.c = cn;
}
public List<UIComponent> getComps() {
return comps;
}
public void setComps(List<UIComponent> comps) {
this.comps = comps;
}
}
Instead of trying to add the component dynamically during the ajax request, try defining the component upfront, and setting it's rendered tag to false. The contents of the component can then be populated with the ajax request, and the rendered attribute flipped to display the attribute.