I am designing a site using standard Java EE components like JSF 2.0, CDI, EJB and JPA. It is in its early stages so the navigation is quite simple. The problem is that I have difficulties to retain some data when navigating.
Here is the seminarController backing bean:
SeminarController.java
@Named
@RequestScoped
public class SeminarController {
@Inject
private SeminarDao seminarDao;
private int seminarID;
private Seminar seminar;
...
// Getters and setters
...
public Collection<Seminar> getSeminars() {...}
public Collection<Seminar> getUpcomingSeminars() {...}
public void initSeminar() {
seminar = seminarDao.findSeminar(seminarID);
}
public String save() {
seminarDao.save(seminar);
return "details";
}
}
The first page is currently just listing some seminars that are upcoming and might be of interrest.
index.xhtml
<ul>
<ui:repeat value="#{seminarController.upcomingSeminars}" var="seminar">
<li>
<h:link value="#{seminar.title}" outcome="details">
<f:param name="id" value="#{seminar.seminarID}"/>
</h:link>
on
<h:outputText value="#{seminar.eventDate}"/>
</li>
</ui:repeat>
</ul>
As you can see I have a list with links that will take me to the details page if I click the link. I am using f:param to add the id of the seminar to the HTTP Request parameters.
The details page show some more details (of course) about the upcoming seminar. The seminar can also be edited by clicking the link.
details.xhtml
<f:metadata>
<f:viewParam name="id" value="#{seminarController.seminarID}"/>
<f:event type="preRenderView" listener="#{seminarController.initSeminar}"/>
</f:metadata>
<ui:define name="body">
<h2>#{seminarController.seminar.title}</h2>
<p>
<strong>When:</strong>
#{seminarController.seminar.eventDate}
</p>
<p>
<strong>Where:</strong>
#{seminarController.seminar.address},
#{seminarController.seminar.country}
</p>
...
<p>
<h:link value="Edit" outcome="edit">
<f:param name="id" value="#{seminarController.seminarID}"/>
</h:link> |
<h:link value="Back to List" outcome="index"/>
</p>
So far so good, everything works fine.
Now we follow the link to edit the seminar and we go to the edit page. We are still using f:param for the id of the current seminar.
edit.xhtml
<f:metadata>
<f:viewParam name="id" value="#{seminarController.seminarID}"/>
<f:event type="preRenderView" listener="#{seminarController.initSeminar}"/>
</f:metadata>
<ui:define name="body">
<h:form>
<h:messages title="Please correct the errors and try again."/>
<fieldset>
<h:panelGroup class="editor-label" layout="block">
<h:outputLabel value="Seminar Title:" for="title"/>
</h:panelGroup>
<h:panelGroup class="editor-field" layout="block">
<h:inputText id="title" value="#{seminarController.seminar.title}"/>
<h:message for="title" title="*"/>
</h:panelGroup>
...
<p>
<h:commandButton value="Save" action="#{seminarController.save}">
<f:param id="id" value="#{seminarController.seminar.seminarID}"/>
</h:commandButton>
</p>
</fieldset>
</h:form>
This is where I am starting to have problems. I want to go back to the details page when the Save button is pressed. The details page should show the current seminar with the newly updated details. The problem is that when the Save button is pressed the seminar object in the seminarController is null.
When the edit view is rendered the seminar object is initialized and used to display the current values in the form. But when the Save button is pressed the seminar object no longer exists in the seminarController. And the initSeminar method is not called either.
So the question is how should I design the design-edit-design page navigation?
ViewScope would not work since I am navigating away to a new view when pressing Save. And ViewScope does not exist when using CDI anyhow.
Any help would be grateful!
Edit
The Seminar class has some fields that will not be seen in the form.
Seminar.java
@Entity
public class Seminar {
@Id
private int seminarID;
@Column
private String title;
@Column
private Date eventDate; // Date when the seminar occurs
@Column
private Date createdDate; // Not visible in forms
...
// Some more fields and Getters/Setters
}
Edit2:
I ended up doing this in the SeminarController:
SeminarController.java
@PostConstruct
public void init() {
if (FacesContext.getCurrentInstance().isPostback()) {
seminar = new Seminar();
} else {
seminar = seminarDao.findSeminar(seminarID);
}
}
public void setSeminarID(int seminarID) {
if (this.seminarID != seminarID) {
seminar = seminarDao.findSeminar(seminarID);
this.seminarID = seminarID;
}
}
public String save() {
seminarDao.save(seminar);
return "details?faces-redirect=true&includeViewParams=true";
}
And then I added the <h:inputHidden value="#{seminarController.seminarID}"/> to the edit.xhtml.
The <f:event/> has been changed accordingly in details.xhtml and edit.xhtml to call the init() method instead of the old initSeminar().
This seems to work although I do not really like that the seminar object is created using new in the post-back and then it is overwritten when the seminarID is being set.
You need to prepare the
seminaryourself before the update model values phase takes place. The pre render view event is too late.One of the ways is to create a
@PostConstructwherein you check if the current request is a postback and if so then precrate theSeminarso that JSF has the chance to invoke its setters during update model values phase.I’m not sure what
initSeminar()does, but I think you also need to change it accordingly that it does its job onlyif (!FacesContext.getCurrentInstance().isPostback()).