Reputation: 4093
I've got a pretty typical CRUD situation that I'm struggling with so I assume I must be misunderstanding something. I've put together a little demo app to better explain my problem. The two files of interest are shown below:
PersonView - CDI Managed Bean backing the JSF page
package example
import java.io.Serializable;
import java.util.List;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;
@ConversationScoped @Named
public class PersonView implements Serializable {
private Person selectedPerson;
@Inject private PersonService personService;
@Inject private Conversation conversation;
public PersonView() {}
public List<Person> getPeople() { return personService.findAll(); }
public void beginConversation() { if( conversation.isTransient() ) {conversation.begin();} }
public void endConversation() { if( !conversation.isTransient() ) { conversation.end();} }
public void createPerson() {
beginConversation();
setSelectedPerson( new Person() );
}
public void addPerson() {
personService.addPerson( getSelectedPerson() );
endConversation();
}
public void updatePerson() { personService.updatePerson( getSelectedPerson() ); }
public Person getSelectedPerson() { return selectedPerson; }
public void setSelectedPerson(Person selectedPerson) { this.selectedPerson = selectedPerson; }
}
index.xhtml - JSF page for manipulating people
<?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://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>CRUD Example</title>
</h:head>
<h:body>
<h:form prependId="false">
<p:dataTable var="p" value="#{personView.people}" id="person_table" rowKey="#{p.id}" selection="#{personView.selectedPerson}">
<p:column selectionMode="single"/>
<p:column><f:facet name="header">ID</f:facet>#{p.id}<p:column>
<p:column><f:facet name="header">Name</f:facet>#{p.name}</p:column>
<f:facet name="footer">
<p:commandButton value="Create Person" onclick="create_dialog.show();" actionListener="#{personView.createPerson}"/>
<p:commandButton value="Edit Person" onclick="edit_dialog.show();" update="edit_panel"/>
</f:facet>
</p:dataTable>
</h:form>
<p:dialog header="Create Person" id="create_dialog" widgetVar="create_dialog" modal="true" width="750" height="300">
<h:form prependId="false">
<p:panel id="create_panel">
<p>Name: <p:inputText value="#{personView.selectedPerson.name}" required="true"/></p>
<p><p:commandButton value="Add" actionListener="#{personView.addPerson}" oncomplete="create_dialog.hide();" update="person_table" /></p>
</p:panel>
</h:form>
</p:dialog>
</h:body>
On the index page the user is shown a data table containing all the people the system knows about. They then press the Create Person button at the bottom of the table. I have checked that this correctly calls the createPerson method and the conversation apparently starts. The create_dialog is then displayed where the user can enter a name. The problem comes when the user clicks the Add button. JSF attempts to store the persons name but the selectedPerson variable is now null so it fails with a NullPointerException.
I realize this isn't a common way of creating objects but in my current application is makes sense as I can guess some of the values of a new Person. It also fits quite well with how I'd like to do editing.
So my question(s): why doesn't the conversation propagate? The PersonView bean appears to be in request scope all the time. I've read about @ViewScoped in JSF2 but I'd rather stick with CDI if possible. From what I've read I think the problem is a failure to pass the CID name with the request but I'm at a loss to see how I could do that with AJAX.
The only solution that I've come up with is to move PersonView into the session but that feels like a huge kludge. e
Upvotes: 4
Views: 4899
Reputation: 4868
Working with @ConversationScoped in combination with <f:ajax>
tags makes it necessary to pas the conversation id to commandButton and commandLink elements.
See this Example:
<h:commandLink value="update"
actionListener="#{myController.updateSomething(myData)}">
<f:ajax render="...."/>
<f:param name="cid" value="#{myController.getCID()}" />
</h:commandLink>
Upvotes: 0
Reputation: 449
Seems like you are using PrimeFaces, Im not sure how to make an ajax call with Primefaces, so I will show you how its done with standard JSF 2.0
First of all when using AJAX, you MUST pass the cid
with every AJAX request.
Try adding the following to your ConversationScope Managed Bean (You need to somehow pass the cid
to the view - index.xhtml, then every subsequent AJAX call will pass the same cid
back to the server):
@Named
@ConversationScoped
public class PersonView implements Serializable {
public List<Person> getPeople() {
return personService.findAll();
}
@Inject
private Conversation conversation;
//Start the conversation once the
//bean is created and all injection is done on the bean
//I typically use this in the case of AJAX
@PostContruct
public void beginConversation() {
if( conversation.isTransient() ) {
conversation.begin();
}
}
public void endConversation() {
if( !conversation.isTransient() ) {
conversation.end();
}
}
// This will be used in the view (index.xhtml)
public String getConversationId() {
return conversation.getId();
}
public void createPerson() {
setSelectedPerson( new Person() );
}
public void addPerson() {
personService.addPerson( getSelectedPerson() );
endConversation();
// beginConversation(); //might need to start a new conversation once old one is done
}
}
now in your view you will typically do the following:
<h:commandButton action="#{personView.createPerson}" value="Create Person">
<!-- passing the cid -->
<f:param name="cid" value="#{personView.conversationId}" />
<f:ajax execute="@form" />
</h:commandButton>
<h:commandButton action="#{personView.addPerson}" value="Add">
<!-- passing the cid -->
<f:param name="cid" value="#{personView.conversationId}" />
<f:ajax execute="@form" render=":person_table" />
</h:commandButton>
This works as long as all subsequent calls are AJAX. If you start mixing it with the normal calls the conversation gets lost. On the other hand, it this was a non-AJAX list of calls you are making, the cid
would be passed in automatically for you
Upvotes: 0
Reputation: 9983
The only way that I got this to work is to use @ViewAccessScoped in MyFaces CODI. CDI allows extensibility so all you have to do is include the CODI jar files in with your application. This works even if you are using Mojarra and not MyFaces.
So if you want to use CDI annotations, that's my recommendation. I tried using ConversationScoped annotation for a while but I just couldn't get it to work conveniently. Once I started using CODI all my problems went away.
Upvotes: 4