Monday, July 19, 2010

Customizing Which Form Fields Are Displayed: Part 4

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

This one is from Dan Haywood, who suggests using a Dewey Decimal approach and ordering fields as 1.1, 1.2, 2.1 etc. This has advantages over a strictly numerical approach in that new fields can be inserted without reordering all the others, and also it lets superclasses interleave their fields amongst subclasses. 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 (UiDeweyOrder) and custom Inspector (DeweyOrderInspector) to detect it

  • It defines a DeweyOrderInspectionResultProcessor that sorts the properties/actions

  • It defines a DeweyDecimalComparator that compares by Dewey Decimal (how come I couldn't find one of these on the Web already?)

  • It's very similar to the example in Part 3
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 DeweyOrderInspector() ) ) );
      metawidget.addInspectionResultProcessor( new DeweyOrderInspectionResultProcessor() );
      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 {

      @UiDeweyOrder( "1.1" )
      public String   name;

      @UiDeweyOrder( "1.1.1" )
      public int      age;

      @UiDeweyOrder( "1.2.1" )
      public boolean   retired;

      @UiDeweyOrder( "2.1" )
      @UiLarge
      public String   notes;
   }

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

      String value();
   }


   static class DeweyOrderInspector
      extends BaseObjectInspector {

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

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

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

         return attributes;
      }
   }

   static class DeweyOrderInspectionResultProcessor
      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 a dewey-order

            Map<String, Element> traitsWithOrder = new TreeMap<String, Element>( new DeweyDecimalComparator() );
            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 dewey-order, move them across to the new document)

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

               traitsWithOrder.put( trait.getAttribute( "dewey-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 );
         }
      }
   }

   static class DeweyDecimalComparator
      implements Comparator<String> {

      @Override
      public int compare( String order1, String order2 ) {

         // Split the Dewey-Decimal...

         List<Integer> parts1 = CollectionUtils.newArrayList();
         List<Integer> parts2 = CollectionUtils.newArrayList();

         for( StringTokenizer tokenizer = new StringTokenizer( order1, "." ); tokenizer.hasMoreTokens(); )
         {
            parts1.add( Integer.valueOf( tokenizer.nextToken() ));
         }

         for( StringTokenizer tokenizer = new StringTokenizer( order2, "." ); tokenizer.hasMoreTokens(); )
         {
            parts2.add( Integer.valueOf( tokenizer.nextToken() ));
         }

         // ...and compare it

         int loop = 0;

         for( int length = parts1.size(); loop < length; loop++ )
         {
            if ( loop >= parts2.size() ) {
               return 1;
            }

            int compare = parts1.get( loop ) - parts2.get( loop );

            if ( compare != 0 ) {
               return compare;
            }
         }

         if ( loop < parts2.size() ) {
            return -1;
         }

         return 0;
      }
   }
}

0 comments: