Thursday, August 11, 2016

Metawidget and React

Congratulations to the University of Technology, Sydney's Metawidget Team.

For their first semester Software Development Studio project, they prototyped a React version of Metawidget and demonstrated retrofitting it into an existing application to deliver significant reductions in error-prone, boilerplate code.

Monday, July 18, 2016

500 on Air

I was interviewed on ABC Radio 612 Brisbane about our online 500 card game website.

Here's a link to the audio from the show.

Thursday, July 7, 2016

Wealth Projector featured in Queensland Country Life

Congratulations to AgriHive Wealth Projector for their write-up in Queensland Country Life:

"I think this is the direction we’re heading in - this type of thing will be important as an advocacy tool... [farming businesses] all believe in this."

Thursday, May 5, 2016

Metawidget: associating EventListeners with widgets

I was recently asked how to "associate further rules with widgets, depending on the populated data [when using the pure JavaScript Metawidget]". The answer is to use a WidgetProcessor. Here's a complete example:

<!DOCTYPE html>
<html>
   <head>
      <script src="lib/metawidget/core/metawidget-core.min.js" type="text/javascript"></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( {

                  // Insert custom attribute into the metadata

                  properties: {
                     surname: {
                        disabledUnless: 'firstname'
                     }
                  }
               } ) ] ),
            appendWidgetProcessors: function( widget, elementName, attributes, mw ) {

               // Watch for custom attribute, and add an EventListener

               if ( attributes.disabledUnless !== undefined ) {
                  var triggerWidgetId = metawidget.util.getId( 'property', { name: attributes.disabledUnless }, mw );
                  var triggerWidget = document.getElementById( triggerWidgetId );
                  triggerWidget.addEventListener( 'keyup', function() {
                     widget.disabled = ( triggerWidget.value === '' );
                  } );
                  widget.disabled = true;
               }
               return widget;
            }
         } );

         mw.toInspect = {
            firstname: '',
            surname: ''
         };

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

Friday, April 8, 2016

Metawidget and Angular: arrays

I was recently asked how AngularJS Metawidget handles arrays of items. Let's start with a simple out-of-the-box example:

<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="app">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/4.2/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/4.2/metawidget-angular.min.js" type="text/javascript"></script>
      <script>
         angular.module( 'app', [ 'metawidget' ] ).controller( 'myController', function( $scope ) {

            $scope.person = {
               firstname: 'Homer',
               age: 43,
               children: [ {
                  id: 1,
                  firstname: 'Bart',
                  age: 10
               }, {
                  id: 2,
                  firstname: 'Lisa',
                  age: 8
               } ]
            }
         } );
      </script>
   </head>
   <body ng-controller="myController">
      <metawidget ng-model="person"></metawidget>
   </body>
</html>

Metawidget does reasonably well here:

It generates the correct labels, given the field names in the JavaScript object. It also renders the correct types of controls (text inputs for strings, number inputs for numbers, tables for arrays) and determines the columns in the table by looking at the array elements. Of course, it doesn't look very pretty - but you can always add some CSS or plug in BootstrapWidgetProcessor to fix that.

The original question asked about hiding the id column within the table. Visibility is not something which is expressed by the JavaScript object format, so we need an additional way to determine which fields should be visible/hidden. Metawidget emphasises using your existing architecture, so it provides lots of options for how to do this. For example, you could plug in an InspectionResultProcessor that hides any field called id. Or perhaps any field whose name starts with _. Here's an alternate approach, plugging in a JsonSchemaInspector to supply the missing metadata:

<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="app">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/4.2/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/4.2/metawidget-angular.min.js" type="text/javascript"></script>
      <script>
         angular.module( 'app', [ 'metawidget' ] ).controller( 'myController', function( $scope ) {

            $scope.metawidgetConfig = {
               inspector: new metawidget.inspector.CompositeInspector( [
                  new metawidget.inspector.PropertyTypeInspector(),
                  new metawidget.inspector.JsonSchemaInspector( {
                     properties: {
                        children: {
                           items: {
                              properties: {
                                 id: {
                                    hidden: true
                                 }
                              }
                           }
                        }
                     }
                  } )
               ] )
            };


            $scope.person = {
               firstname: 'Homer',
               age: 43,
               children: [ {
                  id: 1,
                  firstname: 'Bart',
                  age: 10
               }, {
                  id: 2,
                  firstname: 'Lisa',
                  age: 8
               } ]
            }
         } );
      </script>
   </head>
   <body ng-controller="myController">
      <metawidget ng-model="person" config="metawidgetConfig"></metawidget>
   </body>
</html>

This succeeds in hiding the id column:

Note we are using CompositeInspector and PropertyTypeInspector so that Metawidget is still extracting most of its metadata from the raw JavaScript object. This means we only have to specify additional metadata in JsonSchemaInspector, not redefine every field.

The original question also had an unusual, nested format for displaying names. Again, there are a few ways Metawidget can support this, depending on your preferred architecture. Here's an example that plugs in a WidgetBuilder to render the nested format.

<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="app">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/4.2/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/4.2/metawidget-angular.min.js" type="text/javascript"></script>
      <script>
         angular.module( 'app', [ 'metawidget' ] ).controller( 'myController', function( $scope ) {

            $scope.metawidgetConfig = {
               inspector: new metawidget.inspector.CompositeInspector( [
                  new metawidget.inspector.PropertyTypeInspector(),
                  new metawidget.inspector.JsonSchemaInspector( {
                     properties: {
                        children: {
                           readOnly: true,
                           items: {
                              properties: {
                                 id: {
                                    hidden: true
                                 }
                              }
                           }
                        }
                     }
                  } )
               ] ),
               widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder( [
                  function( elementName, attributes, mw ) {
                     if ( attributes.name === 'name' ) {
                        return angular.element( '<output ng-bind="' + mw.path + '.firstname + \' \' + ' + mw.path + '.surname">' )[0];
                     }
                  },
                  new metawidget.widgetbuilder.HtmlWidgetBuilder()
               ] )

            };

            $scope.person = {
               firstname: 'Homer',
               age: 43,
               children: [ {
                  id: 1,
                  name: {
                      firstname: 'Bart',
                      surname: 'Simpson'
                   },

                  age: 10
               }, {
                  id: 2,
                  name: {
                      firstname: 'Lisa',
                      surname: 'Simpson'
                   },

                  age: 8
               } ]
            }
         } );
      </script>
   </head>
   <body ng-controller="myController">
      <metawidget ng-model="person" config="metawidgetConfig"></metawidget>
   </body>
</html>

Note we are using CompositeWidgetBuilder and HtmlWidgetBuilder so that Metawidget is still building most widgets automatically. This means we only have to specify the additional widget handling, not redefine every widget. In this example, we choose our new widget based on the name of the field. But you could use any of the attributes Metawidget inspects (including custom ones you define):

Finally, a follow up question asked how to display only the table, and none of the surrounding fields. This needs a change to the ng-model path, and also we should swap out the default TableLayout (which wraps widgets with labels in a table) for a layout that doesn't wrap the widgets at all:

<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="app">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/4.2/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/4.2/metawidget-angular.min.js" type="text/javascript"></script>
      <script>
         angular.module( 'app', [ 'metawidget' ] ).controller( 'myController', function( $scope ) {

            $scope.metawidgetConfig = {
               inspector: new metawidget.inspector.CompositeInspector( [
                  new metawidget.inspector.PropertyTypeInspector(),
                  new metawidget.inspector.JsonSchemaInspector( {
                     properties: {
                        children: {
                           readOnly: true,
                           items: {
                              properties: {
                                 id: {
                                    hidden: true
                                 }
                              }
                           }
                        }
                     }
                  } )
               ] ),
               widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder( [
                  function( elementName, attributes, mw ) {
                     if ( mw.path.indexOf( 'person.children' ) === 0 && attributes.name === 'name' ) {
                        return angular.element( '<output ng-bind="' + mw.path + '.firstname + \' \' + ' + mw.path + '.surname">' )[0];
                     }
                  },
                  new metawidget.widgetbuilder.HtmlWidgetBuilder()
               ] ),
               layout: new metawidget.layout.SimpleLayout()
            };

            $scope.person = {
               firstname: 'Homer',
               age: 43,
               children: [ {
                  id: 1,
                  name: {
                     firstname: 'Bart',
                     surname: 'Simpson'
                  },
                  age: 10
               }, {
                  id: 2,
                  name: {
                     firstname: 'Lisa',
                     surname: 'Simpson'
                  },
                  age: 8
               } ]
            }
         } );
      </script>
   </head>
   <body ng-controller="myController">
      <metawidget ng-model="person.children" config="metawidgetConfig"></metawidget>
   </body>
</html>

Hope that helps!