Friday, November 19, 2010

Metawidget: Collections Support Part 2

I was recently asked:

"I would like to create a widget that displays each item contained in a List using an ICEfaces PanelTabSet. I would like the widget inside each panelTab to be generated through Metawidget. How can I accomplish this?"

This represents (yet) another aesthetic preference for rendering Collections. Namely, rendering one item per tab. Some other preferences, such as rendering one item per row of a table, have been covered in a previous blog entry). As I said in that entry:

To support this, Metawidget provides WidgetBuilders.

So it's a case of writing a WidgetBuilder that will return a PanelTabSet for the List. I have put together a complete project you can download here. It'll produce something that looks like this:

Code for the WidgetBuilder part below:

package com.myapp;

import static org.metawidget.inspector.InspectionResultConstants.*;

import java.util.List;
import java.util.Map;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import org.metawidget.faces.FacesUtils;
import org.metawidget.faces.component.UIMetawidget;
import org.metawidget.faces.component.UIStub;
import org.metawidget.faces.component.html.HtmlMetawidget;
import org.metawidget.util.ClassUtils;
import org.metawidget.util.simple.StringUtils;
import org.metawidget.widgetbuilder.iface.WidgetBuilder;

import com.icesoft.faces.component.paneltabset.PanelTab;
import com.icesoft.faces.component.paneltabset.PanelTabSet;

public class IceFacesTabsWidgetBuilder
   implements WidgetBuilder<UIComponent, UIMetawidget> {

   //
   // Public methods
   //

   @Override
   public UIComponent buildWidget( String elementName, Map<String, String> attributes, UIMetawidget metawidget ) {

      // Not for us?

      String type = attributes.get( TYPE );

      if ( type == null ) {
         return null;
      }

      Class<?> clazz = ClassUtils.niceForName( type );

      if ( !List.class.isAssignableFrom( clazz ) ) {
         return null;
      }

      // Create a Stub to wrap around the PanelTabSet.
      //
      // This is because:
      //
      // 1. PanelTabSet will not switch tabs if it is destroyed/recreated. We must set
      // COMPONENT_ATTRIBUTE_NOT_RECREATABLE on it (see
      // http://metawidget.sourceforge.net/doc/reference/en/html/ch02s04.html#section-architecture-widgetbuilders-implementing-your-own-faces).
      // This limits some dynamicism, such as being able to dynamically add tabs, but there are
      // further ways around that
      //
      // 2. COMPONENT_ATTRIBUTE_NOT_RECREATABLE only works if there is a 'value' expression that
      // OverriddenWidgetBuilder can match against
      //
      // 3. PanelTabSet is a little unusual in how it treats its 'value' expression (binding it to
      // a UI mechanism, rather than a model mechanism) so we need to avoid using it. We use the
      // Stub's 'value' expression instead

      UIStub stub = new UIStub();

      FacesContext context = FacesContext.getCurrentInstance();
      ELContext elContext = context.getELContext();
      ValueExpression metawidgetValueExpression = metawidget.getValueExpression( "value" );
      String metawidgetValueExpressionString = FacesUtils.unwrapExpression( metawidgetValueExpression.getExpressionString() );
      ExpressionFactory expressionFactory = context.getApplication().getExpressionFactory();
      String tabSetValueExpressionString = metawidgetValueExpressionString + StringUtils.SEPARATOR_DOT_CHAR + attributes.get( NAME );
      ValueExpression tabSetValueExpression = expressionFactory.createValueExpression( elContext, FacesUtils.wrapExpression( tabSetValueExpressionString ), Object.class );

      stub.setValueExpression( "value", tabSetValueExpression );

      // Create tab set

      PanelTabSet panelTabSet = new PanelTabSet();
      panelTabSet.getAttributes().put( UIMetawidget.COMPONENT_ATTRIBUTE_NOT_RECREATABLE, true );
      stub.getChildren().add( panelTabSet );
      List<UIComponent> tabs = panelTabSet.getChildren();

      // Lookup the List from the parent Metawidget to see how many items it has

      List<?> list = (List<?>) tabSetValueExpression.getValue( elContext );

      // Create one tab per item...

      for ( int loop = 0, length = list.size(); loop < length; loop++ ) {

         PanelTab tab = new PanelTab();
         tabs.add( tab );

         // ...with each tab itself containing a Metawidget.
         //
         // The 'value' expression of each Metawidget uses the EL #{foo.bar[0]} notation for
         // accessing arrays

         HtmlMetawidget tabMetawidget = new HtmlMetawidget();
         ValueExpression tabValueExpression = expressionFactory.createValueExpression( elContext, FacesUtils.wrapExpression( tabSetValueExpressionString + '[' + loop + ']' ), Object.class );
         tabMetawidget.setValueExpression( "value", tabValueExpression );
         tabMetawidget.setValueExpression( "readOnly", metawidget.getValueExpression( "readOnly" ) );
         tab.getChildren().add( tabMetawidget );
      }

      // Return tab set

      return stub;
   }
}

8 comments:

Simone said...

Hi Richard,
I'm doing progress!

image1

image1


I've resolved the first problem and now JAXB correctly create getter Methods for Boolean object ( I needed to upgrade to JAXB version 2.2 and use the -enableIntrospection flag).

I've also added an xmlAnnotationInspector to extract XmlAttribute information from JAXB java beans ( I needed also to implement an XmlJavaBeanPropertyStyle to inspect protected members variables).

Now I'm facing another problem: if I set the COMPONENT_ATTRIBUTE_NOT_RECREATABLE attribute to panelTabSet, when I switch to another bean from the combo I get an exception :
[code]javax.servlet.ServletException: java.io.IOException: javax.el.PropertyNotFoundException: Property 'ldmDetails' not found on type it.trs.atc.ahs.gcm.sita.decoder.BTMMsg
com.icesoft.faces.webapp.http.servlet.MainServlet.service(MainServlet.java:158)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
com.icesoft.faces.webapp.xmlhttp.BlockingServlet.service(BlockingServlet.java:56)
[/code]

If I've understood right, it is because the panelTabSet is not destroyed and so it try to set the ldmDetails property also for the new selected BTMMsg bean. Right?

On the other hand, if I don't set the attibute COMPONENT_ATTRIBUTE_NOT_RECREATABLE I cannot switch between tabs (as you said to me) because the panelTabSet lost its state.
Have you any suggestion ?!?

Thanks a lot again !

Simone

Leon said...

Hi Richard,

It creates some new metawidget instances in this case. Is it possible to control their display dynamically in the same view? If I render the same view again, how can I remove or hide the existed metawidgets?

Thanks,

Richard said...

Hi guys,

I have put up a new blog post that I hope answers your question: http://kennardconsulting.blogspot.com/2010/11/metawidget-collections-support-part-2_24.html

Regards,

Richard.

Leon said...

Thanks Richard, it works. I can handle the existing metawidgets in the same view.

Simone said...

Hi Richard,
Thanks a lot!It works also for me!

Tomorrow I'll post my progress.

Thanks again!

Richard said...

Just to let you know I have come up with an improved solution (no COMPONENT_ATTRIBUTE_NOT_RECREATABLE, no binding attribute):

http://kennardconsulting.blogspot.com/2010/11/metawidget-collections-support-part-2_25.html

Hope that helps,

Richard.

Anonymous said...

Hi Richard,

Not sure it is related to this particular post, but I did not find any other.
Basically, I would like to be able to do what you describe in this post, only in Swing.

And so far I can not even get to display my collections.
As an example, imagine I have a main class (Parent) and another class (Child). Parent has a simple String member ("name") and so does Child. But Parent also contains a "List children". I hope it makes sense so far...

But then, while displaying, I will only get to see the "name" of the Parent, nothing about the children appears (even if the data actually exists of course).

Do you know what I could be doing wrong? I could give you a test case if that's needed. But there might be a very simple answer...

Thanks,
Yoann

Richard Kennard said...

Hi Yoann,

Did you try this blog post? http://blog.kennardconsulting.com/2010/10/metawidget-collections-support.html