Wednesday, February 12, 2014

JavaScript Form Generator: from Java EE to Bootstrap 3

I've been asked to provide a complete example of using Metawidget to bridge the gap between an annotation-based, Java EE back-end and a JavaScript, Bootstrap 3 front-end.

This requires metadata to be fed from the 'Java world' into the 'JavaScript world'. Metawidget excels at this, using a process it calls 'remote inspection'. Metawidget's five-stage pipeline is designed to be split across multiple tiers and disparate architectures.

First, create the annotated Java object. We'll use Metawidget's built-in annotations here, but you can of course use JPA annotations, Hibernate Validator annotations etc.

public class Person {

   private String   mFirstname;

   private String   mSurname;

   private int      mAge;

   private boolean   mRetired;

   @UiRequired
   public String getFirstname() {...}

   @UiComesAfter( "firstname" )
   public String getSurname() {...}

   @UiComesAfter( "surname" )
   public int getAge() {...}


   ...other getters and setters...
}

Then, create a REST service that uses Metawidget's inspectors to inspect that object, and returns the inspection result in JSON format:

mInspector = new CompositeInspector( new CompositeInspectorConfig().setInspectors(
      new PropertyTypeInspector(),
      new MetawidgetAnnotationInspector()
      ) );

mInspectionResultProcessors = new DomInspectionResultProcessor[] {
      new ComesAfterInspectionResultProcessor<Object>(),
      new JsonSchemaMappingProcessor<Object>(),
      new JsonTypeMappingProcessor<Object>()
};

...

// Inspect

Element inspectionResult = mInspector.inspectAsDom( null, entityClass.getName() );
for ( DomInspectionResultProcessor<Element, Object> inspectionResultProcessor : mInspectionResultProcessors ) {
   inspectionResult = inspectionResultProcessor.processInspectionResultAsDom(
      inspectionResult, null, null, entityClass.getName(), (String[]) null );
}

// Convert to JSON Schema

return XmlUtils.elementToJsonSchema( inspectionResult );

Finally, create a JavaScript Metawidget that uses Bootstrap 3 and calls the REST service asynchronously:

<!DOCTYPE HTML>
<html>
   <head>
      <link href="lib/bootstrap/css/bootstrap.min.css" type="text/css" rel="stylesheet"/>
      <script src="lib/jquery/jquery.min.js"></script>
      <script src="lib/bootstrap/js/bootstrap.min.js"></script>
      <script src="js/metawidget-core.min.js"></script>
      <script src="js/metawidget-bootstrap.min.js"></script>
   </head>
<body>
   <div style="width: 500px; margin: 50px auto">
      <form class="form-horizontal">
         <div id="metawidget">
            <button class="btn btn-primary" onclick="save(); return false">Save</button>
         </div>
      </form>
   </div>
   <script type="text/javascript">
      var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {

         inspectionResultProcessors: [ function( inspectionResult, mw, toInspect, type, names ) {

            var xhr = new XMLHttpRequest();
            xhr.open( "GET", "rest/schema/person", true );

            xhr.onreadystatechange = function() {

               if ( xhr.readyState == 4 && xhr.status == 200 ) {

                  metawidget.util.combineInspectionResults( inspectionResult,
                     JSON.parse( xhr.responseText ) );

                  // Resume Metawidget operation

                  mw.buildWidgets( inspectionResult );
               }
            }

            xhr.send();

            // Return nothing to suspend Metawidget operation
         } ]
,
         
         addWidgetProcessors: [ new metawidget.bootstrap.widgetprocessor.BootstrapWidgetProcessor() ],
         layout: new metawidget.bootstrap.layout.BootstrapDivLayout()
      } );
      mw.toInspect = {};
      mw.buildWidgets();
      function save() {

         mw.getWidgetProcessor( function( widgetProcessor ) {

            return widgetProcessor instanceof metawidget.widgetprocessor.SimpleBindingProcessor;
         } ).save( mw );
         console.log( mw.toInspect );
      }
   </script>
</body>
</html>

This will produce:

Here's a link to a complete Eclipse project. Feedback welcome!

Sunday, January 26, 2014

Easy Forms For JavaScript: Dynamically Generating A Map Widget

I was recently asked if a JavaScript Metawidget could be used to display a map when given a location.

Metawidget is designed around a 5 stage pluggable pipeline. To incorporate a map widget, the best plugin point is a WidgetBuilder.

WidgetBuilders are passed all metadata for a given property, and try to return the most suitable widget. Or they can return nothing, and let another WidgetBuilder further down the chain have a try. In our case, we could inspect the property name (say, zip) and return either a map widget or nothing (in which case the standard HtmlWidgetBuilder can have a try).

Here's a complete example:

<html>
   <head>
      <script src="http://metawidget.org/js/3.7/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 385px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var config = {
            widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder([
               function(elementName, attributes, mw) {

                     // If metadata indicates this property needs a map...

                     if (attributes.name === 'zip') {

                        // ...read the value...

                        var typeAndNames = metawidget.util.splitPath(mw.path);
                        var toInspect = metawidget.util.traversePath(mw.toInspect, typeAndNames.names);
                        var value = toInspect[attributes.name];

                        // ...and build a map widget

                        var img = document.createElement('img');
                        img.setAttribute('src', 'http://maps.googleapis.com/maps/api/staticmap?center=' + value + '&size=300x300&sensor=false');
                        return img;
                     }
               }
,
               new metawidget.widgetbuilder.HtmlWidgetBuilder()
            ])
         }
         var mw = new metawidget.Metawidget(document.getElementById('metawidget'), config);
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            zip: 90212
         };
         mw.buildWidgets();
      </script>
   </body>
</html>

This will display:

Of course in a real-world scenario inspecting a property's name may not be a good approach. Better to have some orthogonal metadata that determines whether to return a map widget. Metawidget supports many ways to supply such orthogonal metadata. Here's an example using JSON Schema:

<html>
   <head>
      <script src="http://metawidget.org/js/3.7/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 385px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var config = {
            inspector: new metawidget.inspector.CompositeInspector( [
               new metawidget.inspector.PropertyTypeInspector(),
               function() {
                  return { properties: { zip: { type: 'location' } } }
               }

            ] ),
            widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder([
               function(elementName, attributes, mw) {

                     // If metadata indicates this property needs a map...

                     if (attributes.type === 'location') {

                        // ...read the value...

                        var typeAndNames = metawidget.util.splitPath(mw.path);
                        var toInspect = metawidget.util.traversePath(mw.toInspect, typeAndNames.names);
                        var value = toInspect[attributes.name];

                        // ...and build a map widget

                        var img = document.createElement('img');
                        img.setAttribute('src', 'http://maps.googleapis.com/maps/api/staticmap?center=' + value + '&size=300x300&sensor=false');
                        return img;
                     }
               },
               new metawidget.widgetbuilder.HtmlWidgetBuilder()
            ])
         }
         var mw = new metawidget.Metawidget(document.getElementById('metawidget'), config);
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            zip: 90212
         };
         mw.buildWidgets();
      </script>
   </body>
</html>

Hope that helps. Feedback welcome!

Saturday, January 4, 2014

Dynamic UI Generation: JQuery Mobile 1.4 Support

I ported one of my client's apps over to JQuery Mobile 1.4 today:
I was delighted to find Metawidget worked out-of-the-box, dynamically generating my UI screens for free. Plus everything received that new JQM modern look-and-feel goodness!

Sunday, December 8, 2013

Metadata Driven User Interface: Metawidget v3.7

Version 3.7 of Metawidget, the Metadata Driven UI 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.

Saturday, December 7, 2013

JQuery Mobile UI Generator

The next release of Metawidget (3.7) adds support for JQuery Mobile (JQM). This allows you to easily create dynamic User Interfaces for JQM applications. It supports the full Metawidget pipeline of pluggable Inspectors, WidgetBuilders, Layouts and more - so you can scale from simple UIs to complex, enterprise apps:

Here's a simple (but complete) example. It creates a JavaScript object, renders a UI, and saves it again (the result is printed in the console):

<!DOCTYPE html>
<html>
   <head>
      <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
      <link rel="stylesheet" href="lib/jquery.mobile/jquery.mobile-1.3.2.min.css" />
      <script src="lib/jquery/jquery-1.8.3.min.js"></script>
      <script src="lib/jquery.mobile/jquery.mobile-1.3.2.min.js"></script>
      <script src="lib/metawidget/core/metawidget-core.min.js"></script>
      <script src="lib/metawidget/jquery.mobile/metawidget-jquerymobile.min.js"></script>
      <script type="text/javascript">

         var person = {
            name: 'Homer Simpson',
            age: 36,
            retired: true
         };

         $( document ).on( 'pageinit', '#page', function( event ) {
            var page = $( event.target );
            var mw = $( "#metawidget" );
            mw.metawidget();
            mw.metawidget( "buildWidgets", person );

         } );

         save = function( event ) {

            var page = $( event ).parents( 'article' );
            var mw = page.find( '#metawidget' );

            var processor = mw.metawidget( "getWidgetProcessor", function( widgetProcessor ) {
               return widgetProcessor instanceof metawidget.widgetprocessor.SimpleBindingProcessor;
            } );
            processor.save( mw.data( 'metawidget' ) );

            console.log( person );
         }
      </script>
   </head>
   <body>
      <article id="page" data-role="page">
         <section data-role="content">
            <div id="metawidget" data-role="metawidget"></div>
         </section>
         <footer data-role="footer" data-position="fixed" data-tap-toggle="false" data-transition="none">
            <div id="footer-navbar" data-role="navbar">
               <ul>
                  <li><a data-icon="check" data-iconpos="top" onclick="save( this )">Save</a></li>
               </ul>
            </div>
         </footer>
      </article>
   </body>
</html>

Of course Metawidget can take this much, much further. To see how, the best place to start is the JavaScript tutorial. There's also a complete Address Book sample application included in the examples pack.

Wednesday, October 16, 2013

Angular Form Generator: third-party widgets

I've been asked about supporting date pickers in IE8 through Metawidget. There are two answers:

Answer #1: Metawidget only automates what you'd do manually

Metawidget tries hard not to introduce any 'magic' into your UI development. It only automates what you'd normally have to do manually. So by default if you have a property of type date it will generate:

<input type="date">

This is an HTML5-specific input type. It will only render as a date picker in HTML5-compliant browsers. And even then, only in some HTML5-compliant browsers (Chrome works, IE 10 and Firefox do not). IE8 will fall back to a text box.

Answer #2: Metawidget supports third-party widget libraries

Of course, there's no shortage of third-party widget libraries that can supply JavaScript-based date pickers. And these will work great in IE8. For this example, we'll pick Angular-UI Date. To use ui-date manually (outside of Metawidget) you have to do:

<input type="date" ui-date>

That's only a minor tweak over what Metawidget generates anyway, so we can plug-in a WidgetProcessor to process the generated widget and add ui-date. If the required HTML was radically different from what Metawidget usually generates (say some nested divs) we could plug-in a whole new WidgetBuilder.

Here's a complete example, tested in IE8:

<html ng-app="myApp">
   <head>
      <!--[if lte IE 8]>
      <script>
         document.createElement( 'metawidget' );
      </script>
      <![endif]-->
      <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.0/themes/base/jquery-ui.css" type="text/css" media="all" />
      <script src="jquery-1.8.3.js" type="text/javascript"></script>
<script src="jquery-ui-1.9.0.js" type="text/javascript"></script>
      <script src="angular-1.0.7.min.js" type="text/javascript"></script>
      <script src="ui-date.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.6/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.6/metawidget-angular.min.js" type="text/javascript"></script>
      <script type="text/javascript">
         angular.module( 'myApp', [ 'metawidget', 'ui.date' ] )
            .controller( 'myController', function( $scope ) {
               $scope.metawidgetConfig = {
                  addWidgetProcessors: [ function( widget, elementName, attributes, mw ) {

                     if ( attributes.type === 'date' ) {
                        widget.setAttribute( 'ui-date', '' );
                     }

                     return widget;
                  } ]

               };
               $scope.person = {
                  firstname: 'Homer',
                  surname: 'Simpson',
                  date: new Date()
               };
            } );
      </script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
            display: block;
         }
      </style>
   </head>
   <body ng-controller="myController">
      <metawidget id="metawidget" ng-model="person" config="metawidgetConfig"/>
   </body>
</html>

Feedback welcome!

Thursday, September 5, 2013

AngularJS UI Generator: custom widgets

I've been asked to blog about "select boxes versus radio buttons versus checkboxes" in Metawidget. There are two answers:

Answer #1: WidgetBuilders

UIs often have exacting requirements about which widgets to use. Metawidget is designed for this. It has a pluggable WidgetBuilder architecture allowing you to plug-in (and chain together) custom WidgetBuilders to handle just your custom case, and fall back to built-in WidgetBuilders for more general cases. So if you find yourself needing a particular custom widget: write a little WidgetBuilder.

Here's an example:

<html ng-app="myApp">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.5/metawidget-angular.min.js" type="text/javascript"></script>
      <script type="text/javascript">
         angular.module( 'myApp', [ 'metawidget' ] )
            .controller( 'myController', function( $scope ) {
               $scope.metawidgetConfig = {
                  widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder( [
                     function( elementName, attributes, mw ) {
                        if ( attributes.name === 'hobby' ) {
                           var select = document.createElement( 'select' );
                           select.innerHTML = '<option/><option>Beer</option><option>TV</option><option>Eating</option>';
                           return select;
                        }
                     },

                     new metawidget.widgetbuilder.HtmlWidgetBuilder()
                  ] )
               };
               $scope.person = {
                  firstname: 'Homer',
                  surname: 'Simpson',
                  age: 36,
                  hobby: 'Beer'
               };
               $scope.save = function() {
                  console.log( $scope.person );
               }
            } );
      </script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
            display: block;
         }
         #metawidget button {
            display: block;
            margin: 10px auto 0px;
         }
      </style>
   </head>
   <body ng-controller="myController">
      <metawidget id="metawidget" ng-model="person" config="metawidgetConfig">
         <button ng-click="save()">Save</button>
      </metawidget>
   </body>
</html>

Note that Metawidget's WidgetProcessors (in this case AngularWidgetProcessor) are still able to automatically data-bind your custom widget. And your widget choice can key off any piece of metadata, not just attributes.name.

Answer #2: The Default

Having said that, the built-in WidgetBuilders do a decent job. You may find them sufficient for your use case. Here's an example of using JSON Schema metadata to create the same dropdown:

<html ng-app="myApp">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.5/metawidget-angular.min.js" type="text/javascript"></script>
      <script type="text/javascript">
         angular.module( 'myApp', [ 'metawidget' ] )
            .controller( 'myController', function( $scope ) {
               $scope.metawidgetConfig = {
                  inspector: new metawidget.inspector.CompositeInspector( [
                     new metawidget.inspector.PropertyTypeInspector(),
                     new metawidget.inspector.JsonSchemaInspector( {
                        properties: {
                           hobby: {
                              enum: [ 'Beer', 'TV', 'Eating' ]
                           }
                        }

                     } )
                  ] )
               };
               $scope.person = {
                  firstname: 'Homer',
                  surname: 'Simpson',
                  age: 36,
                  hobby: 'Beer'
               };
               $scope.save = function() {
                  console.log( $scope.person );
               }
            } );
      </script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
            display: block;
         }
         #metawidget button {
            display: block;
            margin: 10px auto 0px;
         }
      </style>
   </head>
   <body ng-controller="myController">
      <metawidget id="metawidget" ng-model="person" config="metawidgetConfig">
         <button ng-click="save()">Save</button>
      </metawidget>
   </body>
</html>

Widget choice is often dictated by data type. If you want checkboxes instead of a dropdown, then your data type needs to be an array rather than a string, so that the user can select multiple values:

<html ng-app="myApp">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.5/metawidget-angular.min.js" type="text/javascript"></script>
      <script type="text/javascript">
         angular.module( 'myApp', [ 'metawidget' ] )
            .controller( 'myController', function( $scope ) {
               $scope.metawidgetConfig = {
                  inspector: new metawidget.inspector.CompositeInspector( [
                     new metawidget.inspector.PropertyTypeInspector(),
                     new metawidget.inspector.JsonSchemaInspector( {
                        properties: {
                           hobby: {
                              enum: [ 'Beer', 'TV', 'Eating' ]
                           }
                     }
                  } )
                  ] )
               };
               $scope.person = {
                  firstname: 'Homer',
                  surname: 'Simpson',
                  age: 36,
                  hobby: [ 'Beer' ]
               };
               $scope.save = function() {
                  console.log( $scope.person );
               }
            } );
      </script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
            display: block;
         }
         #metawidget button {
            display: block;
            margin: 10px auto 0px;
         }
      </style>
   </head>
   <body ng-controller="myController">
      <metawidget id="metawidget" ng-model="person" config="metawidgetConfig">
         <button ng-click="save()">Save</button>
      </metawidget>
   </body>
</html>

Sometimes widget choice purely is aesthetic, though. If you want radio buttons instead of a select box, the built-in WidgetBuilder recognises a componentType metadata:

<html ng-app="myApp">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.5/metawidget-angular.min.js" type="text/javascript"></script>
      <script type="text/javascript">
         angular.module( 'myApp', [ 'metawidget' ] )
            .controller( 'myController', function( $scope ) {
               $scope.metawidgetConfig = {
                  inspector: new metawidget.inspector.CompositeInspector( [
                     new metawidget.inspector.PropertyTypeInspector(),
                     new metawidget.inspector.JsonSchemaInspector( {
                     properties: {
                        hobby: {
                              enum: [ 'Beer', 'TV', 'Eating' ],
                              componentType: 'radio'
                           }
                        }
                     } )
                  ] )
               };
               $scope.person = {
                  firstname: 'Homer',
                  surname: 'Simpson',
                  age: 36,
                  hobby: 'Beer'
               };
               $scope.save = function() {
                  console.log( $scope.person );
               }
            } );
      </script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
            display: block;
         }
         #metawidget button {
            display: block;
            margin: 10px auto 0px;
         }
      </style>
   </head>
   <body ng-controller="myController">
      <metawidget id="metawidget" ng-model="person" config="metawidgetConfig">
         <button ng-click="save()">Save</button>
      </metawidget>
   </body>
</html>

Feedback welcome!