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.4/metawidget-core.min.js"></script>
      <script src="http://metawidget.org/js/3.4/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.4/metawidget-core.min.js"></script>
      <script src="http://metawidget.org/js/3.4/metawidget-angular.min.js"></script>
      <script>
         angular.module( 'myApp', [ 'metawidget' ] );

         function UserController( $scope ) {

            $scope.user = {};

            $scope.config = {
               inspector: new metawidget.inspector.JsonSchemaInspector( {
                        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: {
                              properties: {
                                 street: { type: 'string' },
                                 city: { type: 'string' },
                                 postcode: { type: 'string' }
                              }
                           },
                           notes: { type: 'string', large: true }
                        }
                     } )

            }
         }
      </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!