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!

Tuesday, December 15, 2015

Create HTML forms from JavaScript objects: Metawidget 4.2

Version 4.2 of Metawidget, the library for creating HTML forms from JavaScript objects is now available!

This release was focused on:
  • Improved PrimeFaces support
  • HeadingTagLayoutDecorator supports starting level
  • DivLayoutDecorator supports styleClass
  • Improved AngularJS support
  • prepend/append InspectionResultProcessors/WidgetProcessors can now be done without an array (JavaScript Metawidget)
  • 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.

Monday, November 16, 2015

I've Written A Children's Book!

In a move inspired by...

  • having read hundreds of bedtime stories to my kids over the years
  • wanting to teach them about the exciting new world of self-publishing
  • because I love hobbies

...I've written a children's book!

This is not, as you'll be able to tell from the title, a serious treatise on anything. But it's been beautifully illustrated by Gaspar Sabater and is exactly the kind of increasingly silly story that my kids love.

Both print and Kindle versions are available on Amazon, just in time for Christmas! So if you have little people in your life and are looking for something different, please check it out. Here are some sample images from inside the book. You can preview the story itself using Amazon's Look Inside feature:

Thursday, November 12, 2015

Wealth Projector Licensee Wins NSW Adviser of the Year

Congratulations to Wealth Projector licensee Announcer Financial Planning, winner of the Association of Financial Advisers NSW Practice of the Year. Great job guys!

Wednesday, November 11, 2015

Citi Mobile Challenge Asia Pacific 2015

We had a great time presenting Wealth Projector at the Citi Mobile Challenge Asia Pacific 2015 in Sydney.

Wealth Projector was selected to be among the top 75 finalists from more than 2,000 entrants. Final judging will be in December.

Our thanks to the judges, Citi, and the FinTech community for hosting such an awesome event!

Monday, October 26, 2015

Wealth Projector: Finalist for Citi Mobile Challenge Asia Pacific

Wealth Projector has been selected as a finalist for Citi Mobile Challenge Asia Pacific 2015.

Citi Mobile Challenge is a next-generation accelerator that combines a virtual hackathon with an incubator, a worldwide network of FinTech experts and Citi's unparalleled global sponsors and clients to discover solutions across more than 100 markets.

The inaugural Asia Pacific Challenge has received record-breaking participations with 1,900 registrations from 376 cities. As a finalist, Wealth Projector is among the top submissions out of the hundreds that Citi reviewed.

"We're excited to have been selected as a finalist at this global competition, and humbled to be joining Citi's ecosystem of innovative FinTech developers and leading technology sponsors" said Tim Woodhouse, CEO of Wealth Projector.

Wealth Projector will now join Citi and FinTech thought leaders at the Demo Day event in Sydney on November 10th.