Wednesday, October 13, 2010

Metawidget: JRadioButton instead of JComboBox

I've had a couple requests from people wanting to use JRadioButtons in their generated UI (by default, Metawidget generates JComboBox components for selection lists).

Devil in the Details

There are a few aesthetic issues with using JRadioButtons: should they be arranged horizontally or vertically; should they be horizontal up to a certain number of items, then switch to vertical; should they have a choice at the top for 'null'; should you be able to 'unselect all' after you've made an initial choice; should they only be used for not-nullable fields; etc. These are the kind of things keeping me from putting JRadioButtons in the core SwingWidgetBuilder.

But it's straightforward to add your own WidgetBuilder - where you can decide all the above little UI aesthetics according to your personal taste.

In a Bind

While you're at it, you probably want to add binding support. Again this depends on your personal preference, but let's say you want to use BeansBindingProcessor. It turns out BeansBinding isn't very good at binding to groups of JRadioButtons (a known problem), so one approach is to dummy up a little ButtonGroupPanel with its own getter and setter.

Example Code

Here's an example WidgetBuilder. You'll need to add it as part of a CompositeWidgetBuilder, as shown here in the Reference Documentation.

public class JRadioButtonWidgetBuilder
   implements WidgetBuilder<JComponent, SwingMetawidget>, SwingValuePropertyProvider {

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

      String lookupAttribute = attributes.get( LOOKUP );

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

      String[] lookups = ArrayUtils.fromString( lookupAttribute );
      String[] lookupLabels = ArrayUtils.fromString( attributes.get( LOOKUP_LABELS ) );

      ButtonGroupPanel panel = new ButtonGroupPanel();
      panel.setLayout( new GridLayout( 1, lookups.length ) );

      for ( int loop = 0; loop < lookups.length; loop++ ) {

         JRadioButton radioButton = new JRadioButton();

         if ( lookupLabels.length == 0 ) {
            radioButton.setText( lookups[loop] );
         } else {
            radioButton.setText( lookupLabels[loop] );
         }

         radioButton.setActionCommand( lookups[loop] );
         panel.add( radioButton );
      }

      return panel;
   }

   public String getValueProperty( Component component ) {

      if ( component instanceof ButtonGroupPanel ) {
         return "selected";
      }

      return null;
   }

   public static class ButtonGroupPanel
      extends JPanel {

      private ButtonGroup   mButtonGroup   = new ButtonGroup();

      public String getSelected() {

         ButtonModel buttonModel = mButtonGroup.getSelection();

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

         return buttonModel.getActionCommand();
      }

      public void setSelected( String selected ) {

         for ( Enumeration<AbstractButton> e = mButtonGroup.getElements(); e.hasMoreElements(); ) {

            AbstractButton button = e.nextElement();

            if ( !selected.equals( button.getActionCommand() ) ) {
               continue;
            }

            String oldValue = getSelected();
            mButtonGroup.setSelected( button.getModel(), true );
            firePropertyChange( "selected", oldValue, selected );
            break;
         }
      }

      @Override
      protected void addImpl( Component component, Object constraints, int index ) {

         super.addImpl( component, constraints, index );

         if ( component instanceof AbstractButton ) {
            mButtonGroup.add( (AbstractButton) component );
         }
      }
   }
}

Feedback welcome!

2 comments:

Anonymous said...

Hi Richard,

Could you provide some guidance on how using a radiobutton group might be achieved via JSF2 ?

Thanks
Andrew

Richard Kennard said...

Andrew,

JSF already has a component (HtmlSelectOneRadio) that manages radiobutton groups for you.

To tell Metawidget to use it, you just need to add a little custom WidgetBuilder to suit your needs. Or, as a quick hack, you can do...

@UiFacesComponent( "javax.faces.HtmlSelectManyCheckbox" )

...on your getter.

Regards,

Richard.