Tuesday, July 24, 2012

Metawidget On Mojarra

I'm thrilled to announce the next release of Metawidget will be fully compatible with Oracle Mojarra, the Java Server Faces Reference Implementation.

The Metawidget and Mojarra teams have been working hard to resolve long-standing issues (1313, 1383, 1402, 1414, 1717, 1826, 1972, 2231 and 2283) and ensure dynamic component manipulation is reliable and robust.

Here's a screenshot of Metawidget 2.4 running with Mojarra 2.1.7 and RichFaces 4:


Of course, these fixes will also benefit other component developers looking to use dynamic component manipulation in Mojarra. I blogged about the 'best practice' way to implement this here.

Huge thanks to everybody involved over the years, especially Ed Burns, Ryan Lubke, Roger Kitain and Manfred Riem!

Wednesday, July 4, 2012

Forge and Metawidget: Now With Added Prettiness

I've just refreshed the JBoss Forge Scaffolding Look & Feel (which uses Metawidget) based on work done by Cheyenne Weaver. We're now using Bootstrap to get lots of visual goodies with minimal additional code.

Thanks Cheyenne!

You can download a WAR for JBoss AS 7 here, or full source code here. You can also compare to the previous Look & Feel here.

Tuesday, July 3, 2012

Metawidget: Collections Support (Nested Metawidgets)

A recent post on the forum started:

"I've written a WidgetBuilder to handle collections... [by using] Metawidget to produce a [nested Metawidget] component for each object in the collection..."

We've covered something like this before, but this time things were complicated by the need to support BeansBindingProcessor with both save and rebind.

Here's what we came up with (needs Metawidget 2.3):

package com.myapp;

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

import java.awt.*;
import java.awt.event.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.swing.*;

import org.metawidget.inspector.annotation.UiComesAfter;
import org.metawidget.swing.SwingMetawidget;
import org.metawidget.swing.widgetbuilder.SwingWidgetBuilder;
import org.metawidget.swing.widgetprocessor.binding.beansbinding.BeansBindingProcessor;
import org.metawidget.util.*;
import org.metawidget.widgetbuilder.composite.*;
import org.metawidget.widgetbuilder.iface.WidgetBuilder;

public class Application {

   public static void main( final String[] args ) {

      // Model

      final Person homerSimpson = new Person( "Homer Simpson", "Bart", "Lisa",
            "Maggie" );
      final Person nedFlanders = new Person( "Ned Flanders", "Rod", "Todd" );

      // UI

      final JFrame frame = new JFrame();
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

      // Metawidget

      final SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );
      CompositeWidgetBuilderConfig<JComponent, SwingMetawidget> config = new CompositeWidgetBuilderConfig<JComponent, SwingMetawidget>();
      config.setWidgetBuilders( new CollectionWidgetBuilder(),
            new SwingWidgetBuilder() );
      metawidget
            .setWidgetBuilder( new CompositeWidgetBuilder<JComponent, SwingMetawidget>(
                  config ) );
      metawidget.addWidgetProcessor( new CollectionBeansBindingProcessor() );
      metawidget.setToInspect( homerSimpson );
      frame.add( metawidget, BorderLayout.CENTER );

      // Buttons

      JButton rebindButton = new JButton( "Rebind" );
      rebindButton.addActionListener( new ActionListener() {

         @Override
         public void actionPerformed( ActionEvent e ) {

            try {
               BeansBindingProcessor beansBindingProcessor = metawidget
                     .getWidgetProcessor( BeansBindingProcessor.class );
               beansBindingProcessor.save( metawidget );
               JOptionPane.showMessageDialog(
                     frame,
                     "Saved children as "
                           + ( (Person) metawidget.getToInspect() )
                                 .getChildren() );

               beansBindingProcessor.rebind( nedFlanders, metawidget );
            } catch ( Exception ex ) {
               JOptionPane.showMessageDialog( frame, ex.getMessage(),
                     "Error", JOptionPane.ERROR_MESSAGE );
               return;
            }
         }
      } );

      JPanel buttonPanel = new JPanel();
      buttonPanel.add( rebindButton );
      frame.add( buttonPanel, BorderLayout.SOUTH );

      frame.setSize( 400, 200 );
      frame.setVisible( true );
   }

   //
   // Model
   //

   public static class Person {

      private String         mName;
      private List<Person>   mChildren   = CollectionUtils.newArrayList();

      public Person( String name, String... children ) {

         mName = name;
         for ( String child : children ) {
            mChildren.add( new Person( child ) );
         }
      }

      public String getName() {

         return mName;
      }

      public void setName( String name ) {

         mName = name;
      }

      @UiComesAfter( "name" )
      public List<Person> getChildren() {

         return mChildren;
      }

      @Override
      public String toString() {

         return mName;
      }
   }

   //
   // Widget Builder
   //

   public static class CollectionWidgetBuilder
      implements WidgetBuilder<JComponent, SwingMetawidget> {

      @Override
      public JComponent buildWidget( String elementName,
            Map<String, String> attributes, SwingMetawidget metawidget ) {

         // Not for us?

         Class<?> clazz = WidgetBuilderUtils.getActualClassOrType(
               attributes, null );
         if ( clazz == null ) {
            return null;
         }
         if ( !Collection.class.isAssignableFrom( clazz ) ) {
            return null;
         }

         // Create nested Metawidgets

         List<?> list = (List<?>) ClassUtils.getProperty(
               metawidget.getToInspect(), attributes.get( NAME ) );

         if ( list == null || list.isEmpty() ) {
            return null;
         }

         JPanel panel = new JPanel();
         panel.setLayout( new BoxLayout( panel, javax.swing.BoxLayout.Y_AXIS ) );

         for ( Object child : list ) {
            SwingMetawidget nestedMetawidget = new SwingMetawidget();
            Map<String, String> emptyAttributes = Collections.emptyMap();
            metawidget.initNestedMetawidget( nestedMetawidget,
                  emptyAttributes );
            nestedMetawidget.setToInspect( child );
            nestedMetawidget.setPath( child.getClass().getName() );
            panel.add( nestedMetawidget );
         }
         return panel;
      }
   }

   //
   // Widget Processor
   //

   public static class CollectionBeansBindingProcessor
      extends BeansBindingProcessor {

      @Override
      public JComponent processWidget( JComponent component,
            String elementName, Map<String, String> attributes,
            SwingMetawidget metawidget ) {

         // Support nested widgets from the CollectionWidgetBuilder

         if ( component instanceof JPanel ) {
            getNestedPanels( metawidget ).put( attributes.get( NAME ),
                  (JPanel) component );
            return component;
         }

         return super.processWidget( component, elementName, attributes,
               metawidget );
      }

      @Override
      public void save( SwingMetawidget metawidget ) {

         super.save( metawidget );

         // Nested save

         for ( JPanel nestedPanel : getNestedPanels( metawidget ).values() ) {
            for ( Component nestedComponent : nestedPanel.getComponents() ) {
               if ( nestedComponent instanceof SwingMetawidget ) {
                  SwingMetawidget nestedMetawidget = (SwingMetawidget) nestedComponent;
                  save( nestedMetawidget );
               }
            }
         }
      }

      @Override
      public void rebind( Object toRebind, SwingMetawidget metawidget ) {

         super.rebind( toRebind, metawidget );

         // Cannot expect to rebind nestedPanels, so rebuild them instead

         for ( Map.Entry<String, JPanel> entry : getNestedPanels( metawidget )
               .entrySet() ) {

            // Clear the panel

            JPanel panel = entry.getValue();
            panel.removeAll();

            // Use a dummy Metawidget to create just the child Metawidgets
            // of the Collection

            SwingMetawidget dummyMetawidget = new SwingMetawidget();
            Map<String, String> attributes = CollectionUtils.newHashMap();
            attributes.put( NAME, entry.getKey() );
            metawidget.initNestedMetawidget( dummyMetawidget, attributes );

            // Copy them into our real Metawidget

            for ( Component child : ( (JPanel) dummyMetawidget
                  .getComponent( 1 ) ).getComponents() ) {
               panel.add( child );
            }
         }

         metawidget.validate();
      }

      private Map<String, JPanel> getNestedPanels( SwingMetawidget metawidget ) {

         Map<String, JPanel> nestedPanels = (Map<String, JPanel>) metawidget
               .getClientProperty( CollectionBeansBindingProcessor.class );

         if ( nestedPanels == null ) {
            nestedPanels = CollectionUtils.newHashMap();
            metawidget.putClientProperty(
                  CollectionBeansBindingProcessor.class, nestedPanels );
         }

         return nestedPanels;
      }
   }
}