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!

Tuesday, October 12, 2010

Metawidget: Adjusting the Style and Layout of Fields

I was recently asked:

"The one thing I'm still missing is information about how to adjust the style and layout of the fields. In addition to field ordering, I think this is the one major concern people always have. Early on, they will likely just throw up the fields on the page however they fall, but over time, they will want to tweak it. What level of control do they have here? Explaining this customization closes a very important gap in people's minds"

There are 3 parts to the answer:

#1: Metawidget doesn't try to 'own' your entire UI
Metawidget, as its name suggests, is just a widget. It's not trying to be your whole UI. It just focuses on creating native subcomponents for slotting into existing UIs. And it's very lightweight, so there's nothing stopping you combining 4 or 5 or more Metawidgets on the same screen:

Here you can see a simple UI with its Metawidgets outlined with red squares. There are 5 Metawidgets used here. You can scatter them around in an arbitrary fashion, embed them inside other parts of your UI (like dialog boxes and status bars), and surround them however you want (like images next to them, or JTables under them).

This approach alone gives you a huge amount of flexibility in how you design the look of your UI, and ensures you're not locked in to any particular appearance and your app doesn't 'feel' like it is using UI generation.

#2: Metawidget doesn't hide your existing UI toolkit
Most UI frameworks have extensive support for tweaking the look of your UI. For example Swing has Look & Feels; JSF has CSS and skinnable component libraries; Android has XML style files.

Metawidget doesn't hide any of these. It just creates the native components for you, and you can style them like you always do. It helps a little where it can, for example by putting CSS style classes where you want them and generating sensible ids for table rows. Here's some actual output from the JSF Metawidget:

<table id="form:j_id_jsp_628835842_3" class="table-form">
   <tfoot>
      <tr>
         <td colspan="3" class="buttons">
            <input id="form:contactSearchSearch" name="form:contactSearchSearch" type="submit" value="Search" />
            <input id="form:contactSearchAddPersonal" name="form:contactSearchAddPersonal" type="submit" value="Add Personal Contact" />
            <input id="form:contactSearchAddBusiness" name="form:contactSearchAddBusiness" type="submit" value="Add Business Contact" />
         </td>
      </tr>
   </tfoot>
   <tbody>
      <tr id="table-contactSearchCurrentFirstname-row">
         <th id="table-contactSearchCurrentFirstname-label-cell" class="table-label-column">Firstname:</th>
         <td id="table-contactSearchCurrentFirstname-cell" class="table-component-column"><input id="form:contactSearchCurrentFirstname" name="form:contactSearchCurrentFirstname" type="text" value="" /></td>
         <td class="table-required-column"><div></div></td>
      </tr><tr id="table-contactSearchCurrentSurname-row">
         <th id="table-contactSearchCurrentSurname-label-cell" class="table-label-column">Surname:</th>
         <td id="table-contactSearchCurrentSurname-cell" class="table-component-column"><input id="form:contactSearchCurrentSurname" name="form:contactSearchCurrentSurname" type="text" value="" /></td>
         <td class="table-required-column"><div></div></td>
      </tr><tr id="table-contactSearchCurrentType-row">
         <th id="table-contactSearchCurrentType-label-cell" class="table-label-column">Type:</th>
         <td id="table-contactSearchCurrentType-cell" class="table-component-column">
            <select id="form:contactSearchCurrentType" name="form:contactSearchCurrentType" size="1">
               <option value="" selected="selected"></option>
               <option value="PERSONAL">Personal</option>
               <option value="BUSINESS">Business</option>
            </select>
         </td>
         <td class="table-required-column"><div></div></td>
      </tr>
   </tbody>
</table>

Not very shocking, and that's kind of the point. This is familiar territory. The same approach your UI toolkit already uses. Oh, and it's fully configurable of course.

#3: Metawidget has pluggable layouts
The one part of your UI Metawidget does control is how the subcomponents inside each Metawidget are laid out. But this is completely pluggable. Metawidget comes with pre-built layouts for, say, arranging the widgets in a table (with one column for the label and another for the widget) or arranging the widgets horizontally all in a row (handy for button bars).

It also comes with a bunch of 'layout decorators', so you can decorate one layout with another. For example, you can decorate a HtmlTableLayout with a RichFaces TabPanelLayoutDecorator to put parts of your UI inside tabs:

And finally Metawidget makes it straightforward to plug in your own, custom Layouts as needed. See this section of the Reference Documentation.

Thursday, September 30, 2010

Runtime Generation of User Interface: Metwidget v1.0

Metawidget v1.0 is now available! Representing the culmination of two and a half years of careful incremental releases, we're proud to announce this final 1.0 release of the industry's most practical User Interface generator.

The past 30 months have been an exciting ride. We've paid close attention to your feedback, conducted detailed interviews and case studies, and assisted over a dozen production deployments. We've seen Metawidget deployed at energy companies, by governments, in pharmaceutical labs, for ERP, for light and sound engineering, for telecommunications, and more.

We've worked closely with key industry players and developed strong ties with the academic community - publishing conference papers and journal articles, and fostering relationships with other research teams.

We'd like to thank everyone for their support in getting us this far. And now that we're 1.0, there's never been a better time to dive in and see what Metawidget can do for you! It's mature, it's Open Source, and it's ready to save you from all that error-prone, laborious, boilerplate UI code!

This release was focused on:
Special thanks to Gérard Collin and Ian Darwin for their help with this release!

Your continued feedback is invaluable to us. Please download it and let us know what you think.

Thursday, September 23, 2010

Domain Driven Design versus Anemic Domain Model: FIGHT!

I've been reading a bit lately about Domain Driven Design (DDD) versus the Anemic Domain Model (ADM).

On the one hand you've got folks who argue DDD is closer to 'true' Object Oriented Programming because you keep the business logic and the data together in the same class. On the other hand most of the popular Java EE frameworks 'encourage' ADM: your POJOs are just a bunch of getters and setters, then you wrap technologies like JPA, Bean Validation, Session Beans, Drools etc around them to provide business logic.

So who's right? Hmmmm? Well, I'm certainly not going to go there!

All I wanted to blog was that Metawidget doesn't mind, and works great with both approaches. This is because Metawidgets are lightweight: there's no problem combining several of them in the one screen, each pointing to a different part of your domain model.

Here are two examples you can paste into the Metawidget Live Demo editor (so you can try it right now). The first is for DDD:

import javax.swing.*; import org.metawidget.inspector.annotation.*;
import org.metawidget.inspector.composite.*; import org.metawidget.inspector.iface.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.impl.propertystyle.*; import org.metawidget.inspector.impl.propertystyle.groovy.*;
import org.metawidget.inspector.java5.*; import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.*;

enum Title { Mr, Mrs, Miss }

class Person {
   Title title;

   @UiComesAfter("title")
   String firstname;

   @UiComesAfter("firstname")
   String surname;

   @UiAction
   @UiComesAfter("surname")
   void save() {
      JOptionPane.showMessageDialog(SwingUtilities.getSharedOwnerFrame(),"Save button clicked!","Save",JOptionPane.INFORMATION_MESSAGE);
   }
}

class PersonDialog extends JDialog {

   PersonDialog() {
      super(SwingUtilities.getSharedOwnerFrame(),"Domain Driven Design",true);

      PropertyStyle propertyStyle = new GroovyPropertyStyle();
      BaseObjectInspectorConfig groovyConfig = new BaseObjectInspectorConfig().setPropertyStyle(propertyStyle);
      Inspector inspector = new CompositeInspector( new CompositeInspectorConfig().setInspectors(
         new PropertyTypeInspector(groovyConfig),
         new MetawidgetAnnotationInspector(groovyConfig),
         new Java5Inspector(groovyConfig)));

      SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setInspector(inspector);
      metawidget.setToInspect(new Person());
      metawidget.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
      add(metawidget,java.awt.BorderLayout.CENTER);
      
      setSize( 400, 150 );
   }   
}

new PersonDialog().show();


And the second is for ADM:

import javax.swing.*; import org.metawidget.inspector.annotation.*;
import org.metawidget.inspector.composite.*; import org.metawidget.inspector.iface.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.impl.propertystyle.*; import org.metawidget.inspector.impl.propertystyle.groovy.*;
import org.metawidget.inspector.java5.*; import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.*; import org.metawidget.swing.layout.*;

enum Title { Mr, Mrs, Miss }

class Person {
   Title title;

   @UiComesAfter("title")
   String firstname;

   @UiComesAfter("firstname")
   String surname;
}

class PersonDialog extends JDialog {

   PersonDialog() {
      super(SwingUtilities.getSharedOwnerFrame(),"Anemic Domain Model",true);

      PropertyStyle propertyStyle = new GroovyPropertyStyle();
      BaseObjectInspectorConfig groovyConfig = new BaseObjectInspectorConfig().setPropertyStyle(propertyStyle);
      Inspector inspector = new CompositeInspector( new CompositeInspectorConfig().setInspectors(
         new PropertyTypeInspector(groovyConfig),
         new MetawidgetAnnotationInspector(groovyConfig),
         new Java5Inspector(groovyConfig)));

      SwingMetawidget metawidget1 = new SwingMetawidget();
      metawidget1.setInspector(inspector);
      metawidget1.setToInspect(new Person());
      metawidget1.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
      add(metawidget1,java.awt.BorderLayout.CENTER);

      SwingMetawidget metawidget2 = new SwingMetawidget();
      metawidget2.setInspector(inspector);
      metawidget2.setToInspect(this);
      metawidget2.setMetawidgetLayout(new FlowLayout());
      add(metawidget2,java.awt.BorderLayout.SOUTH);

      setSize( 400, 150 );
   }
   
   @UiAction
   void save() {
      JOptionPane.showMessageDialog(this,"Save button clicked!",getTitle(),JOptionPane.INFORMATION_MESSAGE);
   }   
}

new PersonDialog().show();

Metawidget: it's like the Switzerland of the DDD vs ADM wars :)

Wednesday, September 22, 2010

Metawidget Neat Trick: Concurrent Inspectors

Here's another neat trick I've discovered while using Metawidget in my own work.

Metawidget comes with multiple Inspectors to inspect different aspects of your back-end architecture. It then merges the result. This merging is done by CompositeInspector which is itself just another Inspector and therefore pluggable. This means you can plug in different ways of running and merging multiple Inspectors.

What sorts of different ways? Well, since each Inspector is immutable, it's easy to isolate each one in its own Thread. And if you have some fancy processor with a bunch of cores (12, anyone?) you may as well put them to use!

Here's the code:
package com.myapp;

import java.util.concurrent.CyclicBarrier;

import org.metawidget.inspector.composite.*;
import org.metawidget.inspector.iface.*;
import org.w3c.dom.Document;

public class ConcurrentCompositeInspector
   extends CompositeInspector {

   private final Inspector[]   mConcurrentInspectors;

   public ConcurrentCompositeInspector( ConcurrentCompositeInspectorConfig config ) {

      super( config );

      Inspector[] concurrentInspectors = config.getConcurrentInspectors();

      // Must have at least one concurrentInspector (else we may as well use CompositeInspector)

      if ( concurrentInspectors == null || concurrentInspectors.length == 0 ) {
         throw InspectorException.newException( "ConcurrentCompositeInspector needs at least one concurrentInspector" );
      }

      // Defensive copy

      mConcurrentInspectors = new Inspector[concurrentInspectors.length];

      for ( int loop = 0, length = concurrentInspectors.length; loop < length; loop++ ) {
         Inspector inspector = concurrentInspectors[loop];

         for ( int checkDuplicates = 0; checkDuplicates < loop; checkDuplicates++ ) {
            if ( mConcurrentInspectors[checkDuplicates].equals( inspector ) ) {
               throw InspectorException.newException( "ConcurrentCompositeInspector's list of Concurrent Inspectors contains two of the same " + inspector.getClass().getName() );
            }
         }

         mConcurrentInspectors[loop] = inspector;
      }
   }

   /**
    * Overriden to use a CyclicBarrier to run inspectors concurrently.
    */

   @Override
   protected Document runInspectors( Document masterDocument, final Object toInspect, final String type, final String... names )
      throws Exception {

      // Run concurrent Inspectors...

      int length = mConcurrentInspectors.length;
      final Document[] concurrentDocuments = new Document[length];

      // Prepare a CyclicBarrier of 'n concurrent inspectors + 1 primary thread'

      final CyclicBarrier barrier = new CyclicBarrier( length + 1 );

      for ( int loop = 0; loop < length; loop++ ) {
         final int concurrentInspectorIndex = loop;

         new Thread( "ConcurrentCompositeInspector Thread" ) {

            @Override
            public void run() {

               try {
                  concurrentDocuments[concurrentInspectorIndex] = ConcurrentCompositeInspector.this.runInspector( mConcurrentInspectors[concurrentInspectorIndex], toInspect, type, names );
               } catch ( Exception e ) {
                  throw InspectorException.newException( e );
               } finally {
                  try {
                     barrier.await();
                  } catch ( Exception e ) {
                     throw InspectorException.newException( e );
                  }
               }
            }
         }.start();
      }

      // ...run regular Inspectors at the same time (if any)...

      Document masterDocumentToUse;

      try {
         masterDocumentToUse = super.runInspectors( masterDocument, toInspect, type, names );
      } finally {
         barrier.await();
      }

      // ...merge the result...

      for ( Document concurrentDocument : concurrentDocuments ) {
         masterDocumentToUse = combineInspectionResult( masterDocumentToUse, concurrentDocument );
      }

      // ...and return

      return masterDocumentToUse;
   }
}

You'll also need a Config class:
package com.myapp;

import org.metawidget.inspector.composite.CompositeInspectorConfig;
import org.metawidget.inspector.iface.Inspector;
import org.metawidget.util.simple.ObjectUtils;

public class ConcurrentCompositeInspectorConfig
   extends CompositeInspectorConfig {

   private Inspector[]   mConcurrentInspectors;

   public Inspector[] getConcurrentInspectors() {

      return mConcurrentInspectors;
   }

   public ConcurrentCompositeInspectorConfig setConcurrentInspectors( Inspector... concurrentInspectors ) {

      mConcurrentInspectors = concurrentInspectors;

      return this;
   }

   @Override
   public boolean equals( Object that ) {

      if ( this == that ) {
         return true;
      }

      if ( that == null ) {
         return false;
      }

      if ( getClass() != that.getClass() ) {
         return false;
      }

      if ( !ObjectUtils.nullSafeEquals( mConcurrentInspectors, ( (ConcurrentCompositeInspectorConfig) that ).mConcurrentInspectors ) ) {
         return false;
      }

      return super.equals( that );
   }

   @Override
   public int hashCode() {

      int hashCode = super.hashCode();
      hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode( mConcurrentInspectors );

      return hashCode;
   }
}

And then you can deploy it like this:
<inspector>
   <concurrentCompositeInspector xmlns="java:com.myapp"
      config="ConcurrentCompositeInspectorConfig">
      <inspectors>
         <array>
            ...
         </array>
      </inspectors>
      <concurrentInspectors>
         <array>
            ...
         </array>
      </concurrentInspectors>
   </concurrentCompositeInspector>
</inspector>

This should spread the load across your cores. It's not a no-brainer: some Inspectors may rely on ThreadLocal variables (like FacesContext) and so will need to be kept on the primary Thread. But where it can be used, it works well.

Hope that helps!

Tuesday, September 21, 2010

Metawidget Neat Trick: Enhancing the RichFaces Color Picker

I thought I'd post a few blog entries about 'neat tricks' I've discovered while using Metawidget for my own clients (eating my own dog food, as it were).

The first one concerns a bugbear I've had with the RichFaces color picker widget. The widget looks like this:
It's a cool component, but for me it lacks an important feature: once you've chosen a color there's no way to unchoose it - to clear the box. This is annoying because typically getColor is a nullable field, so we need a way to null it. One approach would be to extend HtmlColorPicker (or its Renderer) and try to add the extra functionality - but that can be a bit scary.

Metawidget affords us a different approach. We can write a small WidgetProcessor and it will process every ColorPicker (across our entire application) and add the extra button. Here's the code:

package com.myapp;

import java.util.Map;

import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.component.html.HtmlGraphicImage;
import javax.faces.context.FacesContext;

import org.metawidget.faces.component.UIMetawidget;
import org.metawidget.widgetprocessor.iface.WidgetProcessor;
import org.richfaces.component.html.HtmlColorPicker;

public class ColorPickerWidgetProcessor
   implements WidgetProcessor<UIComponent, UIMetawidget> {

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

      if ( !( component instanceof HtmlColorPicker ) ) {
         return component;
      }

      FacesContext context = FacesContext.getCurrentInstance();
      Application application = context.getApplication();

      UIComponent stubComponent = application.createComponent( "org.metawidget.Stub" );
      stubComponent.getChildren().add( component );

      HtmlGraphicImage graphicImage = (HtmlGraphicImage) application.createComponent( "javax.faces.HtmlGraphicImage" );
      graphicImage.setStyle( "vertical-align: middle; margin-left: 2px; cursor: pointer" );
      graphicImage.setValue( "/media/core.delete.gif" );
      graphicImage.setTitle( "Remove colour" );
      graphicImage.setOnclick( "if ( confirm( 'Okay to remove this colour?' )) { var picker = document.getElementById( '" + component.getClientId( context ) + "' ); picker.childNodes[0].value = ''; picker.childNodes[1].style.backgroundColor = '#ffffff'; }" );
      stubComponent.getChildren().add( graphicImage );

      return stubComponent;
   }
}


And here's what it produces:
Now you can click the red 'X' next to each ColorPicker, and it will use JavaScript to clear the box ready for POST back.

Feedback welcome!

Monday, September 20, 2010

UML Wallchart

Check out this awesome Metawidget wallchart created by Steffen Luypaert.

It's a large UML class diagram showing which technologies Metawidget can mix and match, and what configuration options are available. The wallchart includes all supported back-end technologies (shown in red), desktop frameworks (shown in green), web frameworks (shown in blue) and mobile frameworks (shown in yellow).

It's a great tool for both familiarising yourself with the overall architecture of Metawidget, and for exploring what plugins are available. It's downloadable as a PNG image designed to be printed across two pieces of landscape A3 or A4 paper. Click here to download it.

Thanks Steffen!

Click here to download the UML wallchart