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:

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

    ReplyDelete
  2. Hi Richard!

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

    Regards,

    Giang.

    ReplyDelete
  3. 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.

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

    ReplyDelete
  5. 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

    ReplyDelete