Thursday, February 28, 2013

Metawidget meets FreeMarker

A recent conversation with the JBoss AeroGear team inspired me to add FreeMarker support to Metawidget. This should be useful for those wanting to declare static layouts using FreeMarker templates rather than Java code.

To use the new FreeMarkerLayout, simply configure it:

StaticHtmlMetawidget metawidget = new StaticHtmlMetawidget();

BaseObjectInspectorConfig config = new BaseObjectInspectorConfig()
   .setPropertyStyle( new StaticPropertyStyle() );

metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig()
   .setInspectors(
      new PropertyTypeInspector( config ),
      new MetawidgetAnnotationInspector( config ) ) ) );

metawidget.setLayout( new FreemarkerLayout( new FreemarkerLayoutConfig()
   .setDirectoryForTemplateLoading( "./src/main/resources" )
   .setTemplate( "template.ftl" ) )
);

Or you can use metawidget.xml if your prefer...

<staticHtmlMetawidget xmlns="java:org.metawidget.statically.html">
   ...
   <layout>
      <freemarkerLayout xmlns="java:org.metawidget.statically.freemarker.layout"
         config="FreemarkerLayoutConfig">
         <directoryForTemplateLoading>
            <string>./src/main/resources</string>
         </directoryForTemplateLoading>
         <template>
            <string>template.ftl</string>
         </template>
      </freemarkerLayout>

   </layout>
</staticHtmlMetawidget>

...define your FreeMarker template...

<table>
   <tbody>
      <#list widgets as widget><tr>
         <th>${widget.label}:</th>
         <td>${widget.xml}</td>
         <#if (widget.attributes.required!"false") == "true">
         <td>*</td>
         <#else>
         <td></td>
         </#if>
      </tr></#list>
   </tbody>
</table>

...and point Metawidget at your business class...

metawidget.setPath( Person.class.getName() );

StringWriter writer = new StringWriter();
metawidget.write( writer );
System.out.println( writer );

Metawidget will use its existing Inspectors, InspectionResultProcessors, WidgetBuilders and WidgetProcessors to prepare the widgets - but will lay them out using your FreeMarker template.

I've put together a complete example you can download here. Feedback welcome!

Sunday, February 10, 2013

AngularJS Improvements: Metawidget v3.1

Version 3.1 of Metawidget, the dynamic form generator is now available!

This release was focused on:
  • Improvements to pure JavaScript, JQuery UI and Angular JS Metawidgets
  • Bug fixes, documentation and unit tests
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.

Friday, February 1, 2013

Metawidget and REST

When using one of the new, pure JavaScript Metawidgets you can use an InspectionResultProcessor to retrieve metadata using Representational State Transfer (REST). Note this is distinct from simply retrieving data using REST. Retreiving data is usually much simpler, as it is done outside of Metawidget.

REST is best performed asychronously. Because of this, we need a way to temporarily suspend Metawidget's inspection process then resume it once the REST call has returned. Inspectors are not suitable for this, because they are designed to operate without any dependency on the UI. This is so they can be deployed on remote tiers of an application (such as the database tier). InspectionResultProcessors, on the other hand, are for those boundary cases where you need both an inspection and access to the UI. REST is one such boundary case.

To create a RestInspectionResultProcessor for your pure JavaScript application, use the following approach:

var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {

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

      var xhr = new XMLHttpRequest();
      xhr.open( "GET", "rest/metadata/get", 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
   } ]
} );

For JQuery UI, the code is slightly easier:

$( "#metawidget" ).metawidget( {

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

      $.ajax( 'rest/metadata/get' ).done( function ( data ) {

         metawidget.util.combineInspectionResults( inspectionResult, JSON.parse( data ));

         // Resume Metawidget operation

         mw._refresh( inspectionResult );
      } );

      // Return nothing to suspend Metawidget operation
   } ]
} );

For Angular JS, the code is very similar:

$scope.metawidgetConfig = {

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

      $http.get( 'rest/metadata/get' ).then( function( result ) {

         metawidget.util.combineInspectionResults( inspectionResult, result.data );

         // Resume Metawidget operation

         mw.buildWidgets( inspectionResult );
      } );

      // Return nothing to suspend Metawidget operation
   } ]
};

Working examples of all three approaches can be found in the Metawidget integration tests.