Sunday, July 18, 2010

Customizing Which Form Fields Are Displayed: Part 3

Following on from Part 1 and Part 2, I thought I'd blog some of the other field ordering preferences I've encountered.

Out of the box, Metawidget supplies UiComesAfter and ComesAfterInspectionResultProcessor to let you order your fields on a 'per domain' basis. But of course there are other approaches.

This one is from the Naked Objects .NET guys. In this presentation you can see how they use a .NET MemberOrder attribute to specify field order as 1, 2, 3 etc. Example implementation below. Some points to note:
  • It's in Swing so you can just cut and paste and run it

  • It defines a custom annotation (UiOrder) and custom Inspector (OrderInspector) to detect it

  • It defines an OrderInspectionResultProcessor that sorts the properties/actions
package com.myapp;

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

import java.lang.annotation.*;
import java.util.*;

import javax.swing.*;

import org.metawidget.inspectionresultprocessor.iface.*;
import org.metawidget.inspector.annotation.*;
import org.metawidget.inspector.composite.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.*;
import org.metawidget.util.*;
import org.w3c.dom.*;

public class Main {

   public static void main( String[] args ) {

      Person person = new Person();

      SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig().setInspectors(
            new PropertyTypeInspector(),
            new MetawidgetAnnotationInspector(),
            new OrderInspector() ) ) );
      metawidget.addInspectionResultProcessor( new OrderInspectionResultProcessor() );
      metawidget.setToInspect( person );

      JFrame frame = new JFrame( "Metawidget Tutorial" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 250 );
      frame.setVisible( true );
   }

   static class Person {

      @UiOrder( 1 )
      public String name;

      @UiOrder( 2 )
      public int age;

      @UiOrder( 3 )
      public boolean retired;

      @UiOrder( 4 )
      @UiLarge
      public String notes;
   }

   @Retention( RetentionPolicy.RUNTIME )
   @Target( { ElementType.FIELD, ElementType.METHOD } )
   static @interface UiOrder {

      int value();
   }


   static class OrderInspector
      extends BaseObjectInspector {

      @Override
      protected Map<String, String> inspectTrait( Trait trait )
         throws Exception {

         Map<String, String> attributes = CollectionUtils.newHashMap();
         UiOrder order = trait.getAnnotation( UiOrder.class );

         if ( order != null ) {
            attributes.put( "order", String.valueOf( order.value() ) );
         }

         return attributes;
      }
   }

   static class OrderInspectionResultProcessor
      implements InspectionResultProcessor<SwingMetawidget> {

      public String processInspectionResult( String inspectionResult, SwingMetawidget metawidget, Object toInspect, String type, String... names ) {

         try {
            // Start a new document
            //
            // (Android 1.1 did not cope well with shuffling the nodes of an existing document)

            Document newDocument = XmlUtils.newDocument();
            Element newInspectionResultRoot = newDocument.createElementNS( NAMESPACE, ROOT );

            Document document = XmlUtils.documentFromString( inspectionResult );
            Element inspectionResultRoot = document.getDocumentElement();
            XmlUtils.setMapAsAttributes( newInspectionResultRoot, XmlUtils.getAttributesAsMap( inspectionResultRoot ) );
            newDocument.appendChild( newInspectionResultRoot );

            Element entity = (Element) inspectionResultRoot.getChildNodes().item( 0 );
            Element newEntity = newDocument.createElementNS( NAMESPACE, ENTITY );
            XmlUtils.setMapAsAttributes( newEntity, XmlUtils.getAttributesAsMap( entity ) );
            newInspectionResultRoot.appendChild( newEntity );

            // Record all traits (ie. properties/actions) that have an order

            Map<Integer, Element> traitsWithOrder = CollectionUtils.newTreeMap();
            NodeList traits = entity.getChildNodes();

            for ( int loop = 0, length = traits.getLength(); loop < length; loop++ ) {
               Node node = traits.item( loop );

               if ( !( node instanceof Element ) ) {
                  continue;
               }

               Element trait = (Element) node;

               // (if no order, move them across to the new document)

               if ( !trait.hasAttribute( "order" ) ) {
                  newEntity.appendChild( XmlUtils.importElement( newDocument, trait ) );
                  continue;
               }

               traitsWithOrder.put( Integer.valueOf( trait.getAttribute( "order" ) ), trait );
            }

            // Output the traits in TreeMap order

            for ( Element trait : traitsWithOrder.values() ) {
               newEntity.appendChild( XmlUtils.importElement( newDocument, trait ) );
            }

            // Return the new document

            return XmlUtils.documentToString( newInspectionResultRoot.getOwnerDocument(), false );

         } catch ( Exception e ) {
            throw InspectionResultProcessorException.newException( e );
         }
      }
   }
}

Wednesday, July 14, 2010

Customizing Which Form Fields Are Displayed: Part 2

Following on from Part 1, Dan asked whether the screen could decide based on some kind of 'view groups', rather like Bean Validation's validation groups.

I actually like this idea a lot: it combines the flexibility of local field ordering with the safety of not hard-coding field names into the screens. Having said that, this is the first time it's been suggested. So I'll wait and see if it becomes popular before deciding whether to provide it 'out of the box' (this blog series will explore a lot of alternate preferences).

In the meantime, example implementation below. Some points to note:
  • It's in Swing so you can just cut and paste and run it

  • It defines a custom annotation (UiViewGroup) and custom Inspector (ViewGroupInspector) to detect it

  • It defines a ViewGroupInspectionResultProcessor that screens out properties/actions

  • It uses this in a chain with the usual ComesAfterInspectonResultProcessor
To see it in action, try running the code and changing the 'putClientProperty' line to use different view groups (ie. 'summary' or 'detail').

package com.myapp;

import java.lang.annotation.*;
import java.util.*;

import javax.swing.*;

import org.metawidget.inspectionresultprocessor.iface.*;
import org.metawidget.inspectionresultprocessor.sort.*;
import org.metawidget.inspector.annotation.*;
import org.metawidget.inspector.composite.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.*;
import org.metawidget.util.*;
import org.w3c.dom.*;

public class Main {

   public static void main( String[] args ) {

      Person person = new Person();

      SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig().setInspectors(
         new PropertyTypeInspector(),
         new MetawidgetAnnotationInspector(),
         new ViewGroupInspector() )));
      metawidget.addInspectionResultProcessor( new ViewGroupInspectionResultProcessor() );
      metawidget.addInspectionResultProcessor( new ComesAfterInspectionResultProcessor<SwingMetawidget>() );
      metawidget.putClientProperty( "view-group", "summary" );
      metawidget.setToInspect( person );

      JFrame frame = new JFrame( "Metawidget Tutorial" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 250 );
      frame.setVisible( true );
   }

   static class Person {

      @UiViewGroup( { "summary", "detail" } )
      public String name;

      @UiComesAfter( "name" )
      @UiViewGroup( "summary" )
      public int age;

      @UiComesAfter( "name" )
      @UiViewGroup( "detail" )
      public boolean retired;

      @UiComesAfter
      @UiLarge
      public String notes;
   }

   @Retention( RetentionPolicy.RUNTIME )
   @Target( { ElementType.FIELD, ElementType.METHOD } )
   static @interface UiViewGroup {

      String[] value();
   }


   static class ViewGroupInspector
      extends BaseObjectInspector {

      @Override
      protected Map<String, String> inspectTrait( Trait trait )
         throws Exception {

         Map<String, String> attributes = CollectionUtils.newHashMap();
         UiViewGroup viewGroup = trait.getAnnotation( UiViewGroup.class );

         if ( viewGroup != null ) {
            attributes.put( "view-group", ArrayUtils.toString( viewGroup.value() ) );
         }

         return attributes;
      }
   }

   static class ViewGroupInspectionResultProcessor
      implements InspectionResultProcessor<SwingMetawidget> {

      public String processInspectionResult( String inspectionResult, SwingMetawidget metawidget, Object toInspect, String type, String... names ) {

         String viewGroup = (String) metawidget.getClientProperty( "view-group" );
         Document document = XmlUtils.documentFromString( inspectionResult );
         Element entity = (Element) document.getDocumentElement().getFirstChild();

         for ( int loop = 0; loop < entity.getChildNodes().getLength(); ) {

            Element trait = (Element) entity.getChildNodes().item( loop );

            if ( !trait.hasAttribute( "view-group" ))
            {
               loop++;
               continue;
            }

            String[] viewGroups = ArrayUtils.fromString( trait.getAttribute( "view-group" ));

            if ( ArrayUtils.contains( viewGroups, viewGroup )) {
               loop++;
               continue;
            }

            entity.removeChild( trait );
         }

         return XmlUtils.documentToString( document, false );
      }
   }
}

Tuesday, July 13, 2010

Customizing Which Form Fields Are Displayed: Part 1

Talking to Dan Allen about Metawidget recently, he commented:

"One thing that I think would really help people along is to have an example of how to customize form fields displayed on a JSF view. This seems to be one of the first thing any JSF developer wonders about. I know that customization is possible, both at the global and field level, but just having a simple how-to would go a long way"

This is a great point, worthy of a little blog series.

Where To Start: InspectionResultProcessors

Out of the box, Metawidget has a few different options for ordering fields. I'll just mention the most simple ones here.

You can exclude fields on a 'per screen' basis using stub tags:

<m:metawidget value="#{person}">
   <m:stub value="#{person.age}"/>
</m:metawidget>

You can order fields at the 'domain' level using annotations:

package com.myapp;

import org.metawidget.inspector.annotation.*;

public class Person {
   public String name;

   @UiComesAfter( "name" )
   public int age;

   @UiComesAfter( "age" )
   public boolean retired;
}

And you can use XML (which is implicitly ordered):

<entity type="com.myapp.Person">
   <property name="name"/>
   <property name="age"/>
   <property name="retired"/>
</entity>

But after lots of feedback from interviews, adoption studies and forum posts, I realized the issue of 'what fields appear, and what order they appear in' covered a lot of different preferences and requirements. To satisfy these, I introduced the InspectionResultProcessor interface (click to enlarge):


InspectionResultProcessors sit after the Inspectors and before the WidgetBuilders. Out of the box, UiComesAfter is implemented using ComesAfterInspectionResultProcessor. But InspectionResultProcessors have access both to the inspection result and the Metawidget that is about to render it, and this vantage point gives them a number of capabilites.

Swing: Letting The Screen Decide

One capability is to allow the screen to choose which fields it should render. Now, I don't particularly recommend this approach: it means your screen contains hard-coded field names. These won't refactor well, nor will they evolve well as your business objects evolve. But, hey, Metawidget is all about working the way you want to!

So let's do a Swing example first as it's easier to cut and paste and try yourself. Here's a custom InspectionResultProcessor that chooses, and sorts, business object fields based on a JComponent client property. It extends the code from the Metawidget Tutorial:

package com.myapp;
         
import static org.metawidget.inspector.InspectionResultConstants.*;

import javax.swing.*;
import org.metawidget.swing.*;
import org.metawidget.inspectionresultprocessor.iface.*;
import org.metawidget.util.*;
import org.w3c.dom.*;


public class Main {

   public static void main( String[] args ) {
      Person person = new Person();

      SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.addInspectionResultProcessor( new IncludingInspectionResultProcessor() );
      metawidget.putClientProperty( "include", new String[]{ "retired", "age" } );

      metawidget.setToInspect( person );

      JFrame frame = new JFrame( "Metawidget Tutorial" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 250 );
      frame.setVisible( true );
   }
   
   static class Person {
      public String name;
      public int age;
      public boolean retired;
   }
   
   static class IncludingInspectionResultProcessor
      implements InspectionResultProcessor<SwingMetawidget> {
   
      public String processInspectionResult( String inspectionResult, SwingMetawidget metawidget, Object toInspect, String type, String... names ) {
   
         String[] includes = (String[]) metawidget.getClientProperty( "include" );
         Document document = XmlUtils.documentFromString( inspectionResult );
         Element entity = (Element) document.getDocumentElement().getFirstChild();      
         int propertiesToCleanup = entity.getChildNodes().getLength();

         // Pull out the names in order

         for( String include : includes ) {
         
            Element property = XmlUtils.getChildWithAttributeValue( entity, NAME, include );

            if ( property == null )
               continue;

            entity.appendChild( property );
            propertiesToCleanup--;
         }

         // Remove the rest

         for( int loop = 0; loop < propertiesToCleanup; loop++ ) {
            entity.removeChild( entity.getFirstChild() );
         }

         return XmlUtils.documentToString( document, false );
      }
   }

}

If this approach happens to be your preference, you may be surprised you have to code an InspectionResultProcessor for it yourself - why doesn't Metawidget support it out of the box? However, you may also be surprised at how many other preferences there are, as we shall see later in this blog series. Metawidget isn't about providing flags for every possible variation: UI requirements are too diverse for that. Instead, Metawidget tries to be pluggable enough, in enough places, that you can always tweak it to suit.

JSF: Letting The Screen Decide

For completeness (and because it's what Dan actually asked for!) let's do a JSF version of the above:

package com.myapp;
         
import static org.metawidget.inspector.InspectionResultConstants.*;

import javax.faces.component.*;
import org.metawidget.faces.*;
import org.metawidget.faces.component.*;
import org.metawidget.inspectionresultprocessor.iface.*;
import org.metawidget.util.*;
import org.w3c.dom.*;

public class IncludingInspectionResultProcessor
   implements InspectionResultProcessor<UIMetawidget> {
   
   public String processInspectionResult( String inspectionResult, UIMetawidget metawidget, Object toInspect, String type, String... names ) {

      UIParameter includeParameter = FacesUtils.findParameterWithName( metawidget, "include" );
   
      if ( includeParameter == null )
         return null;

      String[] includes = ArrayUtils.fromString( (String) includeParameter.getValue() );
      Document document = XmlUtils.documentFromString( inspectionResult );
      Element entity = (Element) document.getDocumentElement().getFirstChild();      
      int propertiesToCleanup = entity.getChildNodes().getLength();

      // Pull out the names in order

      for( String include : includes ) {
      
         Element property = XmlUtils.getChildWithAttributeValue( entity, NAME, include );
      
         if ( property == null )
            continue;

         entity.appendChild( property );
         propertiesToCleanup--;
      }

      // Remove the rest

      for( int loop = 0; loop < propertiesToCleanup; loop++ ) {
         entity.removeChild( entity.getFirstChild() );
      }

      return XmlUtils.documentToString( document, false );
   }
}

You'd then add this into your metawidget.xml:

<metawidget xmlns="http://metawidget.org"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://metawidget.org http://metawidget.org/xsd/metawidget-1.0.xsd" version="1.0">

   <htmlMetawidget xmlns="java:org.metawidget.faces.component.html">
      .
      .
      .
      <inspectionResultProcessors>
         <array>
            <includingInspectionResultProcessor xmlns="java:com.myapp"/>
         </array>
      </inspectionResultProcessors>
      .
      .
      .
   </htmlMetawidget>
</metawidget>

And use it in your page:

<m:metawidget value="#{contact.current}">
   <f:param name="include" value="title,firstname,surname,edit,save,delete"/>
</m:metawidget>

Note this can include actions (like 'edit' and 'save') as well as properties. More InspectionResultProcessor examples to come!

Friday, June 25, 2010

Metawidget Present-It-Yourself

I've been asked for presentation material to help those wanting to promote Metawidget within their organisation, say at a Brown Bag Lunch. The material includes speaker notes and is available in two forms, including an OpenOffice version you can edit to your needs:

Also available through the Documentation page. Feedback welcome!

Wednesday, June 23, 2010

The Peril of the Passionate Programmer

For me, as I approach the end of any given day's programming, I am usually in one of two frames of mind:

  1. "Man, I've been chasing this bug for hours. I've been down so many dead-ends. I'm really frustrated. But by now my head is really deep in the code, so I'm in the best mindset to figure it out if I just keep going. I don't want to end the day like this."

  2. "Wow, today has gone really great. I'm on a roll. I've got lots done and I'm excited. I'd love to just add this, and this, and this before I go. I don't want to end the day like this."
So on a bad day, you don't want to stop. And on a good day, you don't want to stop. Rare is the average day, when you can just say:

  • "Today everything went about as well as expected. Nothing went really well, but nothing really badly either. I got done what I wanted to get done. Now's a good time to stop."
And that, as far as work/life balance goes, seems the peril of the passionate programmer!

Sunday, June 6, 2010

Pleasing Joe Nuxoll

Joe NuxollOn the latest episode of the Java Posse podcast Roundup '10 - Design vs Engineering, Joe Nuxoll - co-host and passionate UI designer who's worked for both Sun and Apple (and nobody can argue Apple know a thing or two about great UI design) - opens the podcast by saying:

"At least in my opinion, it's one of the core tenets of good UI design is that your internal data structure is not directly reflected in your UI. So you have to consider the use cases in your design flow, and you consider the best way, most efficient way to store the data and organize it in your back-end design - but the two of those should not be one-to-one mappings at all. Usually they're not"

Metawidget couldn't agree more.

Indeed, it's one of the main reasons we feel the Naked Objects approach to UI generation is impractical: really great UIs have a user-oriented model of the application space that is tailored to the user's way of seeing the world, and which does not necessarily match any internal domain model.

But let's be clear: this user-oriented model, although an abstraction of the underlying code, will have some kind of programmed representation. And you will need to map your UI widgets to that representation - be it a collection of lightweight POJOs, or some kind of XML definition, or something as simple as a Map. This representation may even be a mixture of UI-only classes and actual domain classes. For example, you may use an actual domain class 'Employee' but introduce a UI-only class 'EmployeeSearch' for an employee search screen.

Tor NorbyeSo while Metawidget does advocate removing the boilerplate code and error-prone tedium of using visual tools (like Matisse - sorry Tor) or UI languages (like Facelets) to duplicate UI definitions between back-end code and widgets, it does not advocate mapping directly to your domain model. It leaves that decision completely in your hands, by providing a rich array of plugins for different back-end architectures (POJOs, Scala objects, Struts XML files, etc) and a straightforward mechanism to add your own.

Dick WallOf course Joe, being passionate about his pixels, would also be pleased to hear Metawidget does this without hiding or restricting your existing UI framework. So you still use your preferred tools to get the exact look your users require, only now you can automate a lot of the drudgery. Building great UIs is both art and science. Metawidget does not attempt to address the art, it only automates the science. That is to say, it only tries to help the creation of those areas of UI design that are already rigidly defined - it does not overlap with those areas involving subjectivity, creativity or aesthetics. This may come as a blow to Dick Wall!

Finally I could mention that since Metawidget does all this at runtime, without any static code generation, your UIs automatically update in sync with changes to your back-end classes - saving you time and removing many categories of bugs. But since Joe long ago stopped wanting to do any coding, that discussion would probably put him to sleep :)



P.S. In case it's not obvious from some of the above jibes, I am a huge fan and long-time-listener-but-first-time-blogger of the podcast. Thanks for all the hard work guys!

Wednesday, June 2, 2010

Metawidget featured at JBoss World 2010


JBoss have announced that Metawidget will be showcased at a featured campground session at this year's JBoss World.

Pete Muir and myself will present a session where we'll discuss the pain of form development and what we can do about it:

  • Why current approaches to form development (visual tools like Matisse; UI languages like Facelets; code generators like Naked Objects) are unsatisfactory

  • How Metawidget enables you to use any combination of back-end technologies

  • How Metawidget enables you to use any combination of front-end technologies, including different UI frameworks and mixing third-party widget libraries

  • How Metawidget fits in with your existing UI design process, leveraging the full flexibility of your existing toolkit to create highly usable UIs
Hope to see you there!