Monday, April 22, 2013

Metawidget and Backbone Forms

I recently read a Google+ comment comparing AngularJS Metawidget to the excellent Backbone Forms library. The two approaches look surprisingly similar, so I thought it might be beneficial to start a dialogue between our respective teams.

So, here's the Backbone Forms Usage Example mapped (roughly) into AngularJS Metawidget:

<html>
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular.min.js"></script>
      <script src="http://metawidget.org/js/3.3/metawidget-core.min.js"></script>
      <script src="http://metawidget.org/js/3.3/metawidget-angular.min.js"></script>
      <script>
         angular.module( 'myApp', [ 'metawidget' ] );

         function UserController( $scope ) {

            $scope.user = {
               title: "Mr",
               name: "John Doe",
               email: "",
               birthday: new Date( 1, 1, 1970 ),
               password: "",
               address: {
                  street: "123 Sample Street",
                  city: "Sampleville",
                  postcode: "1234"
               },
               notes: ""
            };

         }
      </script>
   </head>
   <body ng-app="myApp" ng-controller="UserController">
      <metawidget ng-model="user"/>
   </body>
</html>

Backbone Forms further supports schemas to refine your UI. Metawidget does something similar using the JSON Schema specification:

<html>
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular.min.js"></script>
      <script src="http://metawidget.org/js/3.3/metawidget-core.min.js"></script>
      <script src="http://metawidget.org/js/3.3/metawidget-angular.min.js"></script>
      <script>
         angular.module( 'myApp', [ 'metawidget' ] );

         function UserController( $scope ) {

            $scope.user = {};

            $scope.config = {
               inspector: function( toInspect, type, names ) {

                  if ( names === undefined ) {
                     return {
                        properties: {
                           title: { type: 'string', enum: ['Mr', 'Mrs', 'Ms'] },
                           name: { type: 'string' },
                           email: { type: 'string', required: true },
                           birthday: { type: 'date' },
                           password: { type: 'string', masked: true },
                           address:   {},
                           notes: { type: 'string', large: true }
                        }
                     }
                  }

                  if ( names[0] === 'address' ) {
                     return {
                        properties: {
                           street: { type: 'string' },
                           city: { type: 'string' },
                           postcode: { type: 'string' }
                        }
                     }
                  };
               }

            }
         }
      </script>
   </head>
   <body ng-app="myApp" ng-controller="UserController">
      <metawidget ng-model="user" config="config"/>
   </body>
</html>

I think our two approaches have a lot of common. But it'd be interesting to explore our differences and see if we can converge further. For example, I'd be interested in whether Backbone Forms has (either implicit or explicit) equivalents to the five stages of Metawidget's pipeline. Or whether Metawidget's API can be simplified for JavaScript users.

Monday, April 15, 2013

JSON Schema UI Generator: Metawidget v3.3

Version 3.3 of Metawidget, the JSON Schema UI generator is now available!

This release was focused on:
This release contains breaking changes. We apologise for the disruption and provide this Metawidget 3.2 to 3.3 Migration Guide.

As always, the best place to start is the Reference Documentation:


http://metawidget.org/doc/reference/en/pdf/metawidget.pdf


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

Tuesday, April 2, 2013

Metawidget 3.2 to 3.3 Migration Guide

The next release of Metawidget (v3.3) contains some breaking changes. We apologise for the disruption and provide this Migration Guide to help in moving from v3.2 to v3.3. All of the existing documentation and examples have already been migrated, as a reference point.

Migrate JavaScript Metawidgets to JSON Schema

The three existing JavaScript Metawidgets (AngularJS, JQueryUI and pure JavaScript) have migrated to using JSON Schema internally. This change was made because:

  • JSON Schema is a lightweight, extensible standard that fits nicely with Metawidget
  • Metawidget's proprietary, internal format was already close to JSON Schema
  • adopting a standard has knock-on benefits - not least interoperability and documentation

Although beneficial, this migration has affected some code. First, the format returned by the existing Inspector and InspectionResultProcessor interfaces used to be an array of objects. For example:

[ { "name": "root", "_root": "true" }, { "name": "prop1", "required": "true" }, { "name": "prop2", "large": "true" } ]

Under JSON Schema this becomes an object with a properties sub-object:

{ "name": "root", properties: { "prop1": { "required": "true" }, "prop2": { "large": "true" } } }

Second, an additional parameter elementName has been added to the WidgetBuilder, WidgetProcessor and Layout interfaces:

function buildWidget( elementName, attributes, mw ) { ... }
function processWidget( widget, elementName, attributes, mw ) { ... }
function layout( widget, elementName, attributes, mw ) { ... }

This parameter can take the values entity, property or action and helps distinguish 'root' properties from child properties. It is also more consistent with the Java-based Metawidget. Third, some metadata names have been migrated to JSON Schema:

label -> title
lookup -> enum
lookupLabels -> enumTitles
minimumValue -> minimum
maximumValue -> maximum
minimumLength -> minLength
maximumLength -> maxLength

Finally, metawidget.util.combineInspectionResults no longer returns anything (it just modifies the parameters you give it).

Migrate to Vaadin 7

The Vaadin 7 API breaks compatibilty with previous versions. We have moved the existing VaadinMetawidget to a different artifactId metawidget-vaadin6 and will be starting work on a Vaadin 7-compatible Metawidget for future releases.

Feedback welcome!

Monday, April 1, 2013

Java-based JSON and JSON Schema User Interface (UI) generator

In a previous post I talked about using Metawidget on the client-side to generate UIs from JavaScript objects. I was asked if it could do the same thing in Java environments.

The answer is yes! The next release of Metawidget (3.3) will be able to inspect both JSON and JSON Schema files for useful metadata. Here's a sample using SWT:

public static void main( String[] args ) {

   // Metawidget

   Display display = new Display();
   Shell shell = new Shell( display );
   shell.setText( "JSON Viewer" );
   shell.setLayout( new FillLayout() );

   String json = "{ \"firstname\": \"Richard\", \"surname\": \"Kennard\", \"notes\": \"Software developer\" }";
   String jsonSchema = "{ properties: { \"firstname\": { \"required\": true }, \"notes\": { \"large\": true }}}";


   final SwtMetawidget metawidget = new SwtMetawidget( shell, SWT.None );
   metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig().setInspectors(
         new JsonInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( json.getBytes() ) ) ),
         new JsonSchemaInspector( new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( jsonSchema.getBytes() ) ) ) )
) );
   metawidget.setInspectionPath( "personObject" );

   // Shell

   shell.setVisible( true );
   shell.open();

   while ( !shell.isDisposed() ) {
      if ( !display.readAndDispatch() ) {
         display.sleep();
      }
   }

   display.dispose();
}

This will produce the screen below:

Binding the JSON values into the UI will depend on your particular requirements. Here's an example that binds into a Google GSON object:

public class JsonBindingProcessor
   implements AdvancedWidgetProcessor<Control, SwtMetawidget> {

   //
   // Public methods
   //

   public void onStartBuild( SwtMetawidget metawidget ) {

      getWrittenComponents( metawidget ).clear();
   }

   /**
    * Retrieve the values from the JSON and put them in the Controls.
    */

   public Control processWidget( Control control, String elementName, Map<String, String> attributes, SwtMetawidget metawidget ) {

      String attributeName = attributes.get( NAME );
      getWrittenComponents( metawidget ).put( attributeName, control );

      // Fetch the value...

      JsonObject jsonObject = metawidget.getToInspect();
      String value = jsonObject.get( attributeName ).getAsString();

      // ...and apply it to the component. For simplicity, we won't worry about converters

      String controlProperty = metawidget.getValueProperty( control );
      ClassUtils.setProperty( control, controlProperty, value );

      return control;
   }

   public void onEndBuild( SwtMetawidget metawidget ) {

      // Do nothing
   }

   /**
    * Store the values from the Controls back into the JSON.
    */

   public void save( SwtMetawidget metawidget ) {

      JsonObject jsonObject = metawidget.getToInspect();

      for ( Map.Entry<String, Control> entry : getWrittenComponents( metawidget ).entrySet() ) {

         Control control = entry.getValue();
         String controlProperty = metawidget.getValueProperty( control );
         Object value = ClassUtils.getProperty( control, controlProperty );

         jsonObject.addProperty( entry.getKey(), (String) value );
      }
   }

   //
   // Private methods
   //

   /**
    * During load-time we keep track of all the controls. At save-time we write them all back
    * again.
    */

   private Map<String, Control> getWrittenComponents( SwtMetawidget metawidget ) {

      @SuppressWarnings( "unchecked" )
      Map<String, Control> writtenComponents = (Map<String, Control>) metawidget.getData( JsonBindingProcessor.class.getName() );

      if ( writtenComponents == null ) {
         writtenComponents = CollectionUtils.newHashMap();
         metawidget.setData( JsonBindingProcessor.class.getName(), writtenComponents );
      }

      return writtenComponents;
   }
}

To add it into the example above:

metawidget.setInspectionPath( "personObject" );

metawidget.addWidgetProcessor( new JsonBindingProcessor() );
metawidget.setToInspect( new JsonParser().parse( json ) );

Button button = new Button( metawidget, SWT.NONE );
button.setText( "Save" );

button.addSelectionListener( new SelectionAdapter() {

   @Override
   public void widgetSelected( SelectionEvent e ) {

      metawidget.getWidgetProcessor( JsonBindingProcessor.class ).save( metawidget );
      System.out.println( metawidget.getToInspect().toString() );
   }
} );


// Shell

shell.setVisible( true );

Feedback welcome!

Friday, March 15, 2013

XML Schema, WSDL and SOAP UI Generator

Following a recent request, the next release of Metawidget will add support for XML Schema (XSD) files. It should be useful for anybody wanting to automatically generate User Interfaces from XSD schemas, Web Services Description Language (WSDL) files, or SOAP.

To use, simply take an existing XML Schema or WSDL file:

<?xml version="1.0"?>
<wsdl:definitions ...>

   ...
   <wsdl:types>
      <xsd:schema>
         <xsd:element name="GetEndorsingBoarder">
            <xsd:complexType>
               <xsd:sequence>
                  <xsd:element name="name" type="string" minOccurs="1" />
                  <xsd:element name="manufacturer" type="string" />
                  <xsd:element name="model" type="xs:integer" />
                  <xsd:element name="quantity">
                     <xsd:simpleType>
                        <xsd:restriction base="xs:integer">
                           <xsd:minInclusive value="0" />
                           <xsd:maxInclusive value="8" />
                        </xsd:restriction>
                     </xsd:simpleType>
                  </xsd:element>
                  <xsd:element name="active" type="xs:boolean" />
               </xsd:sequence>
            </xsd:complexType>
         </xsd:element>

   ...

</wsdl:definitions>

...point Metawidget at it...

public class Main {

   public static void main( String[] args ) {

      final SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setInspector( new WsdlInspector(
         new XmlSchemaInspectorConfig().setInputStream( new SimpleResourceResolver()
            .openResource( "endorsementSearch.wsdl" ) ) ) );
      metawidget.addInspectionResultProcessor( new XmlSchemaToJavaTypeMappingProcessor() );
      metawidget.setPath( "GetEndorsingBoarder" );


      JFrame frame = new JFrame( "Metawidget WSDL" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 210 );
      metawidget.setBorder( BorderFactory.createEmptyBorder( 5,5,5,5 ) );
      frame.setVisible( true );
   }
}

...and this will create the screenshot seen below:

Getting data into the UI components and back out again will vary depending on your requirements (choice of UI toolkit, choice of data storage etc). Here's a simple implementation that reads/writes from a DOM document to Swing JComponents:

public class DocumentBindingProcessor
   implements AdvancedWidgetProcessor<JComponent, SwingMetawidget> {

   //
   // Public methods
   //

   @Override
   public void onStartBuild( SwingMetawidget metawidget ) {

      getWrittenComponents( metawidget ).clear();
   }

   /**
    * Retrieve the values from the Document and put them in the Components.
    */

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

      String attributeName = attributes.get( NAME );
      getWrittenComponents( metawidget ).put( attributeName, component );

      // Fetch the value...

      Document toInspect = metawidget.getToInspect();
      Element root = toInspect.getDocumentElement();
      Element valueNode = XmlUtils.getChildNamed( root, attributeName );

      if ( valueNode == null ) {
         return component;
      }

      // ...and apply it to the component. For simplicity, we won't worry about converters

      String componentProperty = metawidget.getValueProperty( component );
      ClassUtils.setProperty( component, componentProperty, valueNode.getTextContent() );

      return component;
   }

   @Override
   public void onEndBuild( SwingMetawidget metawidget ) {

      // Do nothing
   }

   /**
    * Store the values from the Components back into the Document.
    */

   public void save( SwingMetawidget metawidget ) {

      Document toInspect = metawidget.getToInspect();
      Element root = toInspect.getDocumentElement();

      for ( Map.Entry<String, JComponent> entry : getWrittenComponents( metawidget ).entrySet() ) {

         JComponent component = entry.getValue();
         String componentProperty = metawidget.getValueProperty( component );
         Object value = ClassUtils.getProperty( component, componentProperty );

         Element valueNode = XmlUtils.getChildNamed( root, entry.getKey() );

         if ( valueNode == null ) {
            continue;
         }

         valueNode.setTextContent( (String) value );
      }
   }

   //
   // Private methods
   //

   /**
    * During load-time we keep track of all the components. At save-time we write them all back
    * again.
    */

   private Map<String, JComponent> getWrittenComponents( SwingMetawidget metawidget ) {

      @SuppressWarnings( "unchecked" )
      Map<String, JComponent> writtenComponents = (Map<String, JComponent>) metawidget.getClientProperty( DocumentBindingProcessor.class );

      if ( writtenComponents == null ) {
         writtenComponents = CollectionUtils.newHashMap();
         metawidget.putClientProperty( DocumentBindingProcessor.class, writtenComponents );
      }

      return writtenComponents;
   }
}

To use it, simply add it into your Metawidget's pipeline:

metawidget.addInspectionResultProcessor( ... );
metawidget.addWidgetProcessor( new DocumentBindingProcessor() );
String xml = "<endorsingBoarder><name>Richard</name><manufacturer>Kennard Consulting</manufacturer></endorsingBoarder>";

final Document document = XmlUtils.documentFromString( xml );
metawidget.setToInspect( document );

metawidget.setPath( "GetEndorsingBoarder" );

...

metawidget.add( new JButton( new AbstractAction( "Save" ) {

   public void actionPerformed( ActionEvent e ) {
      metawidget.getWidgetProcessor( DocumentBindingProcessor.class ).save( metawidget );
      System.out.println( XmlUtils.documentToString( document, true ));

   }
} ));

And you'll get this:

Feedback welcome!

Monday, March 11, 2013

Lightweight JSON to User Interface (UI) generator

I write a lot on this blog about customizing Metawidget for different scenarios. But it's worth stressing Metawidget also works as a lightweight, no fuss solution out-of-the-box.

For example, say you just wanted to quickly render a JSON object on the client. Here's the entire code:

<!DOCTYPE HTML>
<html>
   <head>
      <script src="js/lib/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ));
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            age: 36
         };
         mw.buildWidgets();

      </script>
   </body>
</html>

This will handily render:

Of course you can go much (much) deeper, but for once I'll resist the temptation to show that. If you're interested, start with the tutorial here.

Wednesday, March 6, 2013

JavaScript Form Generator: Metawidget v3.2

Version 3.2 of Metawidget, the JavaScript form generator is now available!

This release was focused on:
As always, the best place to start is the Reference Documentation:


http://metawidget.org/doc/reference/en/pdf/metawidget.pdf


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