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!

6 comments:

Anonymous said...

Awesome! I've just known Metawidget and fell in love :).

Richard Kennard said...

Thanks for your kind words!

Khuong Truong Giang said...

Hi Richard!

I would like to use metawidget inside a Liferay's portlet (Spring MVC). Is it a good idea?

Regards,

Giang.

Richard Kennard said...

Metawidget is designed to be a lightweight addition to your existing architecture. So yes, it should work well within a Liferay portlet.

However, I haven't tried this directly. Please let me know how you go!

Regards,

Richard.

Gavin Munro said...

The .elementToJsonSchema is not found in XmlUtils
Use inspectionResultToJsonSchema maybe?

Richard Kennard said...

Yeah, sorry API changed a little in version 4. See breaking changes here: http://blog.kennardconsulting.com/2014/11/domain-driven-forms-metawidget-40.html