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 );
         }
      }
   }
}

0 comments: