Categories

Duncan Mills

Syndicate this blog

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.