Drilldown Edit with JSF
Whilst building a simple test application for use with Shale, I came across
a relatively common use case that I couldn't actually find a pre-built implementation
of. A day or so later I saw the same question being asked on the Yahoo JSF list,
so evidently I'm not the only one.
The use case in question consists of two screens, the first with a tabular
summary containing a few of the attributes of the bean in the collection, and
the second providing an input or edit screen for a single instance of the bean.
The idea being that you can review the list of records on the first screen and
then elect to click on a commandLink or button to edit a particular record,
or create a new record for the collection. The important thing here is that
I want to use the same screen to edit or create a new record and I want to be
able to choose to save my changes or cancel on navigation back from the edit/drilldown
screen.
Setup
To illustrate all of this I'll use a simple collection of Contact beans:
The Contact Bean
package com.groundside.jsf;
public class Contact {
String _firstName;
String _lastName;
int _age;
int _contactId = 0;
public Contact(int contactId,
String
firstName,
String
lastName,
int
age)
{
_contactId = contactId;
_firstName = firstName;
_lastName = lastName;
_age = age;
}
/* Getters and setters for the fields */
...
(Click
here for the complete class)
Next then there is a collection of those contact objects, which for convenience also creates some sample data
The Neighbors Bean
package com.groundside.jsf;
import java.util.ArrayList;
public class Neighbors {
ArrayList _contacts = new ArrayList();
int _maxContactId;
public Neighbors()
{
_contacts.add(new Contact(1,"Fred","Flintstone",35));
_contacts.add(new Contact(2,"Wilma","Flintstone",32));
_contacts.add(new Contact(3,"Barney","Rubble",33));
_contacts.add(new Contact(4,"Betty","Rubble",33));
_maxContactId = 4;
}
public void setContacts(ArrayList contacts){...}
public ArrayList getContacts() {...}
public void updateContact(Contact updatedContact) {...}
public void addContact(Contact newContact) {...}
public void deleteContact(int contactId){...}
}
(Click
here for the complete class)
The PageFlow
I just need two pages for this example, the tabular.jsp to present the summary
table and the edit.jsp to edit a particular row:
Managed Beans
Each of the pages has a request scope backing bean, additionally the Neighbors
object is exposed as a session scoped bean and there is a request scope editContact
managed bean of type Contact used to transfer the current row from the
tabular screen to the edit screen. The complete faces-config is available here.
Calling the Edit screen
Getting to the edit screen with a new record is trivial, we just have to navigate
there and let faces take care of creating a new empty Contact bean for that
page:
public String newButtonAction()
{
return "drilldown";
}
Navigating to the edit page with an existing record involves getting the current
row from the dataTable control. Faces will have handled setting the row currency
correct based on the row you clicked on. Once we have the contact object we
pre-seed the editContact managed bean with it's content:
public String editLinkAction()
{
/* Pull out the currently selected contact */
Contact editContact = (Contact)this.getDataTable().getRowData();
/* Pre-seed the managed bean */
FacesContext ctx
= FacesContext.getCurrentInstance();
ValueBinding binding
= ctx.getApplication().createValueBinding("#{editContact}");
binding.setValue(ctx,editContact);
return "drilldown";
}
Returning from the Edit Screen
On the edit screen I have two buttons:
- A Cancel button which has a hardcoded navigation action of return, with
immediate set to true. - A Save button which looks up the Neighbors collection and sends the changed
Contact record back to it. In this implementation, the updateContact
method on the Neighbors object has the smarts to work out if this is indeed
an update or if this is a new record being added.
Here's the code for that Save action:
public String saveAction()
{
FacesContext ctx = FacesContext.getCurrentInstance();
Application app = ctx.getApplication();
ValueBinding editedContactBind
= app.createValueBinding("#{editContact}");
ValueBinding contactListBind
= app.createValueBinding("#{neighbors}");
Contact contact
= (Contact)editedContactBind.getValue(ctx);
Neighbors neighbors
= (Neighbors)contactListBind.getValue(ctx);
neighbors.updateContact(contact);
return "return";
}
Summary
So that's one way to handle this scenario - is the the best way?, I
don't know, but at least it works, if you know of a better way just let me know.