Wednesday, November 24, 2010

Metawidget: Collections Support Part 2 (Advanced)

In my previous post, I showed how to render a Collection as one item per tab. This is pretty easy in most frameworks, but ICEfaces presents a challenge because:
  1. The ICEfaces PanelTabSet doesn't have an option to switch tabs on the client-side, using just JavaScript and CSS to show/hide the tabs (RichFaces has switchType="client")

  2. The ICEfaces PanelTabSet stores its tab state inside the component, so when doing server-side switching you cannot destroy and recreate the component or the tabs won't switch
In my last post I resolved this by setting COMPONENT_ATTRIBUTE_NOT_RECREATABLE on the PanelTabSet. I noted this would allow the user to switch tabs, at the expense of making the tabs themselves less dynamic. But a couple people wanted to know how to get the best of both worlds: how to switch tabs and have them be dynamic.

There are a few answers to this question:
  1. There is still quite a lot of dynamicism, even with COMPONENT_ATTRIBUTE_NOT_RECREATABLE set. Metawidget is smart enough to work around the COMPONENT_ATTRIBUTE_NOT_RECREATABLE components, and still recreates their siblings and even their children. As the project from the previous post shows, you can have an 'Edit' button that makes the components (both inside and outside the tab) turn from read-only labels into input boxes

  2. The COMPONENT_ATTRIBUTE_NOT_RECREATABLE only applies during POST requests. Re-rendering the page afresh as a GET request works fine. So if your user clicks a link to navigate into the page with the TabPanelSet on it, no problem
But there is a further case: what if I want to be able to add tabs with a POST request? Here we need something new. We need to be able to tell Metawidget: don't recreate the tabs normally (else we won't be able to switch between them), but do recreate them when I click 'add tab'.

I have put together a complete project you can download from here. It'll produce something that looks like this:

The trick is to use JSF's binding attribute. As I blogged previously, this is a little-used but very useful feature of JSF that allows programmatic control over your components. So in your page you do:

<m:metawidget value="#{headerDemo}" binding="#{headerDemo.metawidget}"/>

And in your managed bean you do:

public UIMetawidget getMetawidget() {

   return mMetawidget;

public void setMetawidget( UIMetawidget metawidget ) {

   mMetawidget = metawidget;

@UiComesAfter( "details" )
public void addTab() {

   mDetails.add( new Detail( mDetails.size(), "Detail #" + mDetails.size() ) );

Hope that helps!