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!

Tuesday, August 13, 2013

How to generate a UI from JSON Objects using AngularJS

My previous minimal post has been quite popular, so I thought I'd do an AngularJS version. As expected, all that Angular goodness makes it even cleaner:

<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.person = {
                  firstname: 'Homer',
                  surname: 'Simpson',
                  age: 36
               };

               $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">
         <button ng-click="save()">Save</button>
      </metawidget>

   </body>
</html>

This will handily render:

Clicking the Save button will print the results to the console.

Of course Metawidget can extend this further to support form validation, alternate layouts, third-party components, and much more. To see how, the best place to start is the tutorial.

Thursday, August 8, 2013

NoSQL UI Generator

I did an experiment combining MongoDB, JAX-RS, AngularJS and Metawidget to show how Metawidget lets you render NoSQL objects straight to the client. The advantage of this is that your client can dynamically adapt to whatever data is in your NoSQL database, without having to duplicate definitions between client and server. It's a MongoDB UI Generator!

Of course, there's nothing MongoDB-specific (or NoSQL-specific) about Metawidget. But it was great to see how easily these technologies played together. The only real gotcha was this bug, which I worked around by 'unwrapping' the $oid field:

// MongoDB's built-in ObjectId uses an $oid field, which AngularJS doesn't like to serialize

contact.put( "_id", new ObjectId().toString() );

You can download complete sample source code here. You'll need to deploy it on a Java EE compatible server (I used JBoss 7.1.1.Final) for JAX-RS, and MongoDB should be installed.

Feedback welcome!

Sunday, July 28, 2013

Troubleshooting JSON UI Generation: nested properties

I was asked why the following piece of Metawidget code returns an unusual result:

<!DOCTYPE html>
<html>
   <head>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
      <script type="text/javascript">
      var person = {
         name: "Homer Simpson",
         age: 40,
         retired: false
      };
      </script>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
            inspector: new metawidget.inspector.CompositeInspector( [
               new metawidget.inspector.PropertyTypeInspector(),
               function( toInspect, type, names ) {
                  return {
                     properties: {
                        last_name: {
                           type:"object",
                           required:false,
                           properties: {
                              prefix: {
                                 type:"string",
                                 required:false
                              },
                              suffix: {
                                 type:"number",
                                 required:false
                              }
                           }
                        }
                     }
                  };
               }

            ] )
         } );
         mw.toInspect = person;
         mw.buildWidgets();
      </script>
   </body>
</html>

The developer was trying to add a custom Inspector to declare a new property last_name. This should be rendered along with his normal JSON object (which contains name, age and retired). But the result looks strange:

Let's explain what Metawidget is doing. Metawidget uses its Inspectors to retrieve metadata about which properties to render. Then it iterates over those properties and chooses appropriate widgets for them. For simple types, like string and number, Metawidget chooses native widgets like input type="text" and input type="number". But for complex types, such as object, it creates a nested Metawidget and recurses into it.

For this to work, the Inspectors have to play along. They cannot return the same metadata for the nested Metawidget as for the top-level Metawidget. If they do, we'll just recurse infinitely. This is what we're seeing. Actually you can see Metawidget helpfully caps the recursions so that the browser doesn't crash completely!

To fix this, our custom Inspector has to understand what it's being asked for and return appropriate metadata. It can use the names array to do this. A complete example:

<!DOCTYPE html>
<html>
   <head>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
      <script type="text/javascript">
         var person = {
            name: "Homer Simpson",
            age: 40,
            retired: false
         };
      </script>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
            inspector: new metawidget.inspector.CompositeInspector( [
               new metawidget.inspector.PropertyTypeInspector(),
               function( toInspect, type, names ) {
                  if ( names === undefined ) {
                     return {
                        properties: {
                           last_name: {
                              type:"object",
                              required:false,
                           }
                        }
                     };
                  }


                  return {
                     type:"object",
                     required:false,
                     properties: {
                        prefix: {
                           type:"string",
                           required:false
                        },
                        suffix: {
                           type:"number",
                           required:false
                        }
                     }
                  }
               }
            ] )
         } );
         mw.toInspect = person;
         mw.buildWidgets();
      </script>
   </body>
</html>

This will correctly render:

This is the 'native' way of doing things. Another way of thinking of it is: the 'native' result from an Inspector cannot contain nested properties. Of course, for more complex use cases your Inspector will have to consider which names it's being asked for and act accordingly.

However Metawidget can make things a little easier. It comes with a JsonSchemaInspector which understands how to navigate JSON Schemas for nested properties. So you can simply do:

<!DOCTYPE html>
<html>
   <head>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
      <script type="text/javascript">
         var person = {
            name: "Homer Simpson",
            age: 40,
            retired: false
         };
      </script>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
            inspector: new metawidget.inspector.CompositeInspector( [
               new metawidget.inspector.PropertyTypeInspector(),
               new metawidget.inspector.JsonSchemaInspector( {
                  properties: {
                     last_name: {
                        type:"object",
                        required:false,
                        properties: {
                           prefix: {
                              type:"string",
                              required:false
                           },
                           suffix: {
                              type:"number",
                              required:false
                           }
                        }
                     }
                  }
               } )

            ] )
         } );
         mw.toInspect = person;
         mw.buildWidgets();
      </script>
   </body>
</html>

Hope that helps!

Saturday, July 27, 2013

JSON UI Generator: Array Support

I've been asked to blog about array support in Metawidget. Displaying and manipulating arrays is fraught with personal choices about UI design. For example: is item deletion done by a right-click menu? Or a delete icon on the end of each row? Or at the start of each row? Is addition done using a blank row at the end of the table? Or as a footer row? And so on. It's the same problem we encountered for server-side rendering.

Because of this, Metawidget only goes so far out-of-the-box. metawidget.widgetbuilder.HtmlWidgetBuilder will render an array as a read-only table, but no further. Here's a complete example:

<!DOCTYPE HTML>
<html>
   <head>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"/>
      <script type="text/javascript">
         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ));
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            age: 36,
            family: [ {
               id: 0,
               firstname: 'Marge',
               surname: 'Simpson'
            }, {
               id: 1,
               firstname: 'Bart',
               surname: 'Simpson'
            } ]

         };
         mw.buildWidgets();
      </script>
   </body>
</html>

This will render:

For custom use cases, you can create your own WidgetBuilder and chain it together with the original ones using metawidget.widgetbuilder.CompositeWidgetBuilder. Then your own WidgetBuilder can handle special cases (like arrays) and 'fall back' to the original WidgetBuilders for everything else. Remember Metawidget only strives to automate what you'd normally do manually. Metawidget is not trying to be a big new framework. So the general approach is a) work out how to do something manually; b) write a WidgetBuilder to automate it.

As a shortcut, the existing metawidget.widgetbuilder.HtmlWidgetBuilder also has some methods you can override like createTable and addColumn. Here's a complete example:

<!DOCTYPE HTML>
<html>
   <head>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 350px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"/>
      <script type="text/javascript">
         var _myWidgetBuilder = new metawidget.widgetbuilder.HtmlWidgetBuilder();
         var _superAddRow = _myWidgetBuilder.addRow;
         _myWidgetBuilder.addRow = function( tbody, value, columnAttributes, mw ) {

            var tr = _superAddRow.call( this, tbody, value, columnAttributes, mw );
            var anchor = document.createElement( 'a' );
            anchor.setAttribute( 'href', '#' );
            anchor.setAttribute( 'onclick', 'this.parentNode.parentNode.parentNode.removeChild( this.parentNode.parentNode )' );
            anchor.innerHTML = 'Delete';
            var td = document.createElement( 'td' );
            td.appendChild( anchor );
            tr.appendChild( td );

            return tr;
         };

         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
            widgetBuilder: _myWidgetBuilder
         } );
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            age: 36,
            family: [ {
               id: 0,
               firstname: 'Marge',
               surname: 'Simpson'
            }, {
               id: 1,
               firstname: 'Bart',
               surname: 'Simpson'
            } ]
         };
         mw.buildWidgets();
      </script>
   </body>
</html>

UPDATE: signature of addRow has changed in newer releases of Metawidget to be addRow( tbody, value, row, columnAttributes, elementName, attributes, mw ). Please update the function declaration and .call lines appropriately.

This will render:

Of course, you may also need fine-grained control over what columns you display, and in what order. For this, you can use JSON Schema to describe the schema of your array items. Here's a complete example:

<!DOCTYPE HTML>
<html>
   <head>
      <script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 350px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"/>
      <script type="text/javascript">
         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
            inspector: new metawidget.inspector.CompositeInspector( [
               new metawidget.inspector.PropertyTypeInspector(),
               new metawidget.inspector.JsonSchemaInspector( {
                  properties: {
                     family: {
                        items: {
                           properties: {
                              id: {
                                 hidden: true
                              },
                              employer: {
                                 type: 'string'
                              }
                           }
                        }
                     }
                  }
               } )

            ] )
         } );
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            age: 36,
            family: [ {
               id: 0,
               firstname: 'Marge',
               surname: 'Simpson'
            }, {
               id: 1,
               firstname: 'Bart',
               surname: 'Simpson'
            } ]
         };
         mw.buildWidgets();
      </script>
   </body>
</html>

This will render:

Note the JSON Schema is being combined with the inspection results from PropertyTypeInspector, so you don't have to re-specify columns like firstname and surname, or attributes like the type of id.

Feedback welcome!

Wednesday, July 24, 2013

Generate UI from JSON

I received some feedback that my previous minimal post was a little too minimal. So here's an ever-so-slightly extended version.

It shows how to use Metawidget as a lightweight, no-fuss solution to quickly render JSON objects on the client. This extended version also saves the JSON object back again. Here's the entire code:

<!DOCTYPE HTML>
<html>
   <head>
      <script src="http://metawidget.org/js/3.4/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
         #metawidget button {
            display: block;
            margin: 10px auto 0px;
         }
      </style>
   </head>
   <body>
      <div id="metawidget">
         <button onclick="save()">Save</button>
      </div>
      <script type="text/javascript">
         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ));
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            age: 36
         };
         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 handily render:

Clicking the Save button will update the original JSON object, and print the results to the console.

Of course Metawidget can extend this further to support form validation, alternate layouts, third-party components, and much more. To see how, the best place to start is the tutorial.

Thursday, June 20, 2013

Metawidget. In. Spaaace.

Well, sort of.

Metawidget is now being used to power a (ground based) Global Navigation Satellite System called GALILEO, built by the European Union and European Space Agency.

I've written up a White Paper describing the project and Metawidget's role in it.

The team are using Metawidget's pluggable inspection architecture to enable them to reuse the detailed XML Schemas set up for transmission to/from the satellites, and generate a UI without having to respecify anything. They're also using Metawidget's pluggable widget processors to incorporate third-party validation libraries.

My thanks to the team for providing screenshots and details of their architecture!

More details here.

Friday, June 7, 2013

Node.js UI Generator: Metawidget v3.4

Version 3.4 of Metawidget, the Node.js 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.

Metawidget and Node.js

Inspired by a recent forum post and blog the next release of Metawidget (3.4) will be available as a Node.js module. This lets you use Metawidget to perform server side UI generation using JavaScript, if that's your requirement.

To install, simply use npm:

npm install metawidget

Then use the Metawidget API as normal:

var metawidget = require( 'metawidget' );
...
var mw = new metawidget.Metawidget( element );
mw.toInspect = {
   name: "Joe Bloggs",
   "DOB": "1/1/2001"
};
mw.buildWidgets();


// Print what was rendered

console.log( element.toString() );

This will render:

<table>
   <tbody>
      <tr id="table-name-row">
         <th id="table-name-label-cell"><label for="name" id="table-name-label">Name:</label></th>
         <td id="table-name-cell"><input type="text" id="name" name="name" value="Joe Bloggs"/></td>
         <td></td>
      </tr><tr id="table-DOB-row">
         <th id="table-DOB-label-cell"><label for="DOB" id="table-DOB-label">DOB:</label></th>
         <td id="table-DOB-cell"><input type="text" id="DOB" name="DOB" value="1/1/2001"/></td>
         <td></td>
      </tr>
   </tbody>
</table>

Which of course can be customized using Metawidget's pipeline architecture.

Metawidget must be used in combination with a DOM implementation. This can either be jsdom, envjs, or even a simple implementation of your own. For example:

var simpleDocument = {
   createElement: function( elementName ) {

      return {
         nodeType: 1,
         tagName: elementName.toUpperCase(),
         attributes: [],
         childNodes: [],
         setAttribute: function( name, value ) {

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               if ( this.attributes[loop].nodeName === name ) {
                  this.attributes[loop].nodeValue = value;
                  return;
               }
            }

            this.attributes.push( {
               nodeName: name,
               nodeValue: value
            } );
         },
         hasAttribute: function( name ) {

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               if ( this.attributes[loop].nodeName === name ) {
                  return true;
               }
            }

            return false;
         },
         getAttribute: function( name ) {

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               if ( this.attributes[loop].nodeName === name ) {
                  return this.attributes[loop].nodeValue;
               }
            }

            return null;
         },
         appendChild: function( childNode ) {

            this.childNodes.push( childNode );
         },
         cloneNode: function() {

            var clone = simpleDocument.createElement( elementName );

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               var attribute = this.attributes[loop];
               clone.setAttribute( attribute.nodeName, attribute.nodeValue );
            }
            for ( var loop = 0, length = this.childNodes.length; loop < length; loop++ ) {
               clone.appendChild( this.childNodes[loop].cloneNode() );
            }
            return clone;
         },
         removeChild: function( childNode ) {

            for ( var loop = 0, length = this.childNodes.length; loop < length; loop++ ) {
               if ( this.childNodes[loop] === childNode ) {
                  this.childNodes.splice( loop, 1 );
                  return childNode;
               }
            }

            throw new Error( "childNode not found: " + childNode );
         },
         ownerDocument: this,
         toString: function() {

            var toString = "<" + elementName;

            for ( var loop = 0, length = this.attributes.length; loop < length; loop++ ) {
               var attribute = this.attributes[loop];
               toString += ' ' + attribute.nodeName + '="' + attribute.nodeValue + '"';
            }

            if ( this.value !== undefined ) {
               toString += ' value="' + this.value + '"';
            }

            toString += ">";

            for ( var loop = 0, length = this.childNodes.length; loop < length; loop++ ) {
               toString += this.childNodes[loop].toString();
            }

            if ( this.innerHTML !== undefined ) {
               toString += this.innerHTML;
            }

            toString += "</" + elementName + ">";
            return toString;
         }
      };
   },
   createTextNode: function( data ) {

      return {
         nodeType: 3,
         toString: function() {

            return data;
         }
      }
   }
};

var element = simpleDocument.createElement( 'div' );

Metawidget must be wrapped around a DOM element. The Metawidget constructor takes this element, and thereafter always uses element.ownerDocument rather than referencing any global document object.