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.

Saturday, July 11, 2015

Wealth Projector: Focus Group

We recently showed Wealth Projector to a select group of finance enthusiasts, and wanted to share some of the feedback we received:

We asked: what was great?

  • "The nuggets of insight are fantastic! (e.g. based on your weekly rent, that's the equivalent of a $430K mortgage). They seem to straddle the line nicely between personalised information and insight, but without offering recommendations, advice or flogging a product"
  • "Great fun - easy to use"
  • "Fun"
  • "Friendly user interface processing speed of inputs and the depth of information provided was really good"
  • "It is interactive and fun"
  • "Simplicity"
  • "Looks great. Still going through the actual data and information. The family timeline and important dates section is a great idea"
  • "It was intuitive and fun. Plenty of visual feedback"
  • "With a few clicks of a button you get a whole bunch of fairly relevant information in regard to a lot of different areas of your life that you may not generally comprehend or overlook when assessing your financial stability in both short and long-term"
  • "Easy to use, helpful to see if I was on the right track"
  • "Quality and icons"
  • "Really detailed and provides a great consolidated picture of where you're at financially. Pulling in data from ABS, property industry etc is also a great feature"

We asked: what needs improvement?

  • "The UI seemed a little 'mickey mouse' and childish given that you're entering financial data and talking about 'heavy' topics like retirement etc. Nonetheless, this was a nice improvement on swathes of open text form fields"
  • "Couldn't work out how to adjust my retirement income - I won't need as much then as I do now..."
  • "HECs debt with CPI calculator in loan section"
  • "The Excel spreadsheet lacks the polish the rest of it has"
  • "Graphics are great but the animation could be slicker. Some elements seemed a bit stiff, like dropping an item"
  • "A little harder on a mobile but still good"
  • "Transition to start using icons needs to be a little more obvious. Also preview wealth report drops in a little too early"
  • "Some user experience issues. Anytime I slid the indicator across to complete a section, a login box came up. This is a turn off. Ideally you should only force a login at the end. Allowing people to Preview their report before completing everything is a distraction. Once I did, I lost where I was at in the process. When you drag and drop items, you should group them in the centre circle in various categories (eg My Liabilities, etc). You should also label them - eg Parramatta home, City investment property, etc. Currently there's just a lot of icons all over the place"

Thanks to everyone who took part. We're delighted with the answers to 'what was great', and rest assured we'll be working on 'what needs improvement' over the coming weeks!