Showing posts with label Metawidget. Show all posts
Showing posts with label Metawidget. Show all posts

Thursday, February 9, 2017

Metawidget Ported to Angular 2

Metawidget development has slowed lately, thanks to two factors: the framework itself is very mature; and I've been consumed by my startup.

However, thanks to the Open Source community others have taken up the mantle. I look forward to integrating their work into the main branch in the future! For now, keep an eye on:

In addition, I'm still doing small changes and fixes as they come up.

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.

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, March 2, 2015

Metawidget on Air

I was excited to be a guest on the latest episode of the JavaScript Jabber podcast. The panel had a wide-ranging discussion on Object Interface Mapping (OIM) technologies, different implementations and frameworks.

My thanks to the panel, Charles Max Wood and AJ O'Neal, and the other guests, David Luecke and Geraint Luff. You can download the episode here:

http://devchat.tv/js-jabber/150-jsj-oims

Friday, February 20, 2015

Create HTML forms from JavaScript objects: Metawidget 4.1

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

This release was focused on:
  • JQuery Mobile layout improvements (suppressDivAroundLabel, suppressDivAroundWidget)
  • Improved support for JQuery Mobile overridden widgets
  • Top-level styleClass (JavaScript Metawidget)
  • Fix recursive save on Web Components
  • Bootstrap improvements (wrapInsideLabels, wrapWithExtraDiv)
  • Support table footers in HtmlWidgetBuilder (JavaScript Metawidget)
  • Windows Mobile Internet Explorer compatibility
  • 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 13, 2015

JavaScript Form Generator: from Java EE to Bootstrap 3 (Part 2)

I was recently asked to extend one of my previous blog posts with the ability to inspect nested, annotation-based, Java EE back-end domain objects using a JavaScript, Bootstrap 3 front-end.

Metawidget generates widgets for domain object properties using a five-stage pipeline, specifically WidgetBuilders. There are WidgetBuilders for creating text boxes for string types, number inputs for number types, and so on. However whenever Metawidget encounters a property that its WidgetBuilders don't have a widget for, it creates a nested Metawidget and starts the pipeline process all over again:

The nested Metawidget shares the same pipeline objects as the outer Metawidget (i.e. same Inspectors, WidgetBuilders, etc). This makes nested Metawidgets very lightweight and performant. However it also means the Inspectors must be aware they can be used in multiple modes: in 'top-level' mode, and in 'nested mode'.

Inspectors can tell the difference because of the names array they're passed. In nested mode, the names array will be populated with a path of sub-properties to traverse. It's important the Inspectors honour this.

Back to our Java EE example. We'll expect two back-end REST schema calls: one for /rest/schema/person and one for /rest/schema/person/address. Clearly the back-end must also be expecting this and return different schemas for each (we recommend using client-side expiry headers on REST-based schemas: schemas don't change very often, and you can increase the performance of your UI by not making calls unnecessarily).

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

Of course, you may find multiple REST calls unacceptable for your use-case. Another approach would be to have your REST service return a JSON schema containing all your nested schemas at once. Then use metawidget.inspector.JsonSchemaInspector to inspect it. JsonSchemaInspector includes the ability to automatically traverse nested schemas, based on the names array.

Sunday, November 2, 2014

Domain Driven Forms: Metawidget 4.0

Version 4.0 of Metawidget, the library for domain driven forms is now available!

This release was focused on:
  • Web Components support
  • clearWidgets support (JavaScript version)
  • JsonSchemaTypeMappingProcessorConfig
  • Improved Node.js support
  • Boolean radio buttons (JavaScript version)
  • Minor refactoring
  • 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.

Breaking Changes

We've taken advantage of the major version number bump to make some minor breaking changes for the sake of clarity and consistency. Specifically:
  • For metawidget.js, addInspectionResultProcessors has been renamed to appendInspectionResultProcessors
  • Similarly for metawidget.js, addWidgetProcessors has been renamed to appendWidgetProcessors
  • XmlUtils.elementToJsonSchema has been renamed to XmlUtils.inspectionResultToJsonSchema
  • For SwtMetawidget, setInspectionPath has been renamed to setPath

Saturday, November 1, 2014

Metawidget meets Web Components

The next release of Metawidget (v4.0) introduces Web Components!

Web Components are an exciting new HTML standard that promise to bring much of the UI goodness of frameworks like AngularJS to HTML without any additional libraries. As usual with emerging technologies, not all browsers fully support Web Components, but there are fantastic 'stop gap' libraries such as Polymer that can get you most of the way there today.

With Web Components, you can use Metawidget in fewer lines of code than ever before! Here's a complete example:

<!DOCTYPE HTML>
<html>
   <head>
      <script src="metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
            display: block;
         }
      </style>
      <script>
         var person = {
            firstname: 'Homer',
            surname: 'Simpson',
            age: 36,
            save: function() {
               document.getElementById( 'metawidget' ).save();
               console.log( person );
            }
         }
      </script>
   </head>
   <body>
      <x-metawidget id="metawidget" path="person"></x-metawidget>
   </body>
</html>

Note the new x-metawidget tag. This is registered automatically just by including metawidget-core.min.js. It's available to your code just like any other HTML tag. It has custom methods such as save to make it easy to bind data back from the Metawidget, and automatically watches attributes such as path to rebuild the Metawidget.

Try it today in Chrome:

We've also ported the existing Address Book sample application to Web Components to provide a more meaty example:

Download it and give it a try! And welcome to the future :)

Thursday, June 26, 2014

Metawidget meets Bower

I was recently asked whether I could add Bower support to Metawidget.

I'm delighted to say this has now been set up. The repository is here and you can install it just like any Bower repo...

bower install metawidget

...or add it into your project's bower.json file...

{
   "name": "my-app",
   "dependencies": {
      ...
      "metawidget": "~3.9.0"
   },
   "private": true
}

Other Metawidget modules, such as metawidget-angular.min.js and metawidget-bootstrap.min.js, are also included in this repository.

Feedback welcome!

Monday, May 26, 2014

"Hey Bro, I heard you liked Dynamic UIs, so I put a Dynamic UI inside your Dynamic UI"

I was recently asked:

What is the best approach to implement conditional widgets in Javascript, i.e., to selectively show/hide widgets based on other widget selection(s)?

As discussed, Metawidget tries hard not to 'dictate' anything about your architecture. Therefore the 'best' approach depends on your needs. However here are two broad solutions:

1. Have Metawidget Do All The Work

This approach is the most flexible. Basically:

  • First, define an Inspector or InspectionResultProcessor that knows how to return different results based on different conditions. For example, Angular Metawidget has an InspectionResultProcessor that can process Angular expressions
  • Next, define a WidgetProcessor that knows how to kick off a complete Metawidget re-inspection based on some change

Combining these two gives you some awesome capabilites. Here's a complete example:

<html ng-app="myApp">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.8/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.8/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.JsonSchemaInspector( {
               properties: {
                  type: {
                     enum: [ 'Car', 'Truck', 'Aeroplane' ]
                  },
                  model: {
                     enum: [ 'Saloon', '4WD', 'Sports' ],
                     hidden: '{{vehicle.type != "Car"}}'
                  },
                  description: {
                     type: 'string',
                     large: true,
                     hidden: '{{vehicle.type != "Truck"}}'
                  },
                  details: {
                     hidden: '{{vehicle.type != "Aeroplane"}}',
                     properties: {
                        wingspan: {
                           type: 'number'
                        },
                        speed: {
                           type: 'number'
                        }
                     }
                  }
               }
            } )
         }
         $scope.vehicle = {};
      } );
   </script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 300px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
            display: block;
         }
      </style>
   </head>
   <body ng-controller="myController">
      <metawidget id="metawidget" ng-model="vehicle" config="metawidgetConfig" />
   </body>
</html>

This UI will show/hide multiple widgets depending on the select box, and it's all defined declaratively!

2. Leverage Standard DOM Manipulation

This approach is less flexible, but also has less 'magic' in how it works. Basically:

  • First, add metadata to denote when widgets are conditional
  • Next, define a WidgetProcessor to attach standard DOM manipulation to those widgets. Metawidget helps by generating reliable IDs for everything

Here's a complete example using pure JavaScript:

<!DOCTYPE HTML>
<html>
   <head>
      <script src="http://metawidget.org/js/3.8/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 250px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
         #table-model-row {
            visibility: hidden;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
         inspector: new metawidget.inspector.JsonSchemaInspector( {
               properties: {
                  make: {
                     enum: [ 'BMW', 'Ford', 'Toytota' ],
                     onSelect: 'model'
                  },
                  model: {
                     enum: [ 'Saloon', '4WD', 'Sports' ]
                  }
               }
            } ),
            addWidgetProcessors: [
               function( widget, elementName, attributes, mw ) {

                  if ( attributes.onSelect !== undefined ) {
                     widget.setAttribute( 'onchange', 'document.getElementById("table-' + attributes.onSelect + '-row").style.visibility="visible"' );
                  }


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

Feedback welcome!

Thursday, May 22, 2014

AngularJS: Changing the date format of dynamically generated components

I was recently asked:

"...is a better approach to set a different date formatting, besides from hacking it in metawidget-angular.js"

The answer is yes! Metawidget is designed around a five stage pluggable pipeline, which offers well-researched extension points to accomodate most UI requirements.

The easiest approach to this particular problem is to add a little WidgetProcessor to tweak Metawidget's standard date formatting (if you want to introduce a whole new control instead, better to go with a WidgetBuilder, as shown here). Here's a complete example:

<html ng-app="myApp">
   <head>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.8/metawidget-core.min.js" type="text/javascript"></script>
      <script src="http://metawidget.org/js/3.8/metawidget-angular.min.js" type="text/javascript"></script>
      <script type="text/javascript">
         angular.module( 'myApp', [ 'metawidget' ] ).controller( 'myController', function( $scope ) {
   
         $scope.metawidgetConfig = {
            addWidgetProcessors: [ function( widget, elementName, attributes, mw ) {
   
               if ( attributes.type === 'date' ) {
                  widget.setAttribute( 'ng-bind', widget.getAttribute( 'ng-bind' ) + ":'d MMMM yyyy'" );
               }
               
               return widget;
            } ]

         };
         $scope.person = {
            firstname: 'Homer',
            surname: 'Simpson',
            date: new Date( 1953, 5, 12 )
         };
      } );
   </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" read-only="true" config="metawidgetConfig" />
   </body>
</html>

Feedback welcome!

Thursday, May 15, 2014

AngularJS: Create editable tables with dynamic columns (Part 3)

Further to my previous post, and as mentioned in Part 1, there are lots of different UI approaches to creating editable tables.

For example: a table where you click an edit button next to each row to open a pop-up window; a table where you click the row itself to open a pop-up window; a table where you click each row and edit it 'in place'; a table where all rows are editable at once; a table where each row has both an edit button and a delete button; etc. etc. I'm sure you can think of plenty more. All of them are valid choices depending on your UI design.

It's because of this that Metawidget doesn't try to 'own' your UI. Metawidget only strives to be a piece of your overall solution: a useful widget you can sprinkle liberally all over your UI, wherever you need UI generation, but nowhere that you don't.

The downside of this flexibility is that you have to do a bit more work to wire everything together. In this post I'm going to implement one of the above approaches: a table where all rows are editable at once. It's very similar to my previous post. This time, instead of the editable table instantiating a plain piece of HTML, it instantiates a Metawidget for every row and every column:

var columnMetawidget = $( '<metawidget ng-model="row.' + columns[loop] + '" config="metawidgetConfig">' );
tr1.append( $( '<td>' ).append( columnMetawidget ) );

This works because Metawidget is very lightweight. All parts of the metawidgetConfig are immutable, so the same objects are reused for every Metawidget in the table. The result looks like this:

You can download a complete example here. Feedback welcome!

UPDATE: the built-in metawidget.widgetbuilder.HtmlWidgetBuilder now supports alwaysUseNestedMetawidgetInTables which may be useful in these cases

Wednesday, May 14, 2014

AngularJS: Create editable tables with dynamic columns (Part 2)

Following on from my previous post, I was asked:

"[In your post the] table columns are rendered using html input control with type="text"... [Can the user] add/edit new object in accordance with json schema (checkbox for boolean, dropdown for enum,...)?"

This is where Metawidget really shines. Metawidget is designed to be lightweight and embedded multiple times on a page - wherever you need small pieces of UI generation. So we can easily use a Metawidget at the foot of each column:

The implementation is almost the same as before, except instead of instantiating an <input type="text">, the editable table instantiates another <metawidget> tag:

var nestedMetawidget = $( '<metawidget ng-model="newRow.' + columns[loop] + '" config="metawidgetConfig">' );
tr2.append( $( '<td>' ).append( nestedMetawidget ) );

The hardest part is passing the row schema metadata through to the editable table:

$scope[rowSchemaKey] = inspectionResult;
var widget = $( '<table>' ).attr( 'edit-table', '' ).attr( 'schema', rowSchemaKey );

You can download a complete example here. You can also take this approach further. Feedback welcome!

Tuesday, May 13, 2014

AngularJS: Create editable tables with dynamic columns

I was recently asked:

"[how do I] implement functionality for new/delete for arrays in AngularJS... [when] object structure is unknown until runtime execution."

There are lots of ways to do this using Metawidget, which is why Metawidget doesn't dictate one out-of-the-box. By default, AngularJS Metawidget will only render arrays as read-only:

Adding editing capabilities depends on your particular UI needs. Here's one approach. Essentially we:

  • Create an Angular directive to render a simple <table> with editable rows. This directive is not specific to Metawidget
  • Create a Metawidget WidgetBuilder that can instantiate and configure the editable table. This includes reading metadata at runtime to determine the table's columns

So you'll end up with something like this:

The important Metawidget bit looks like this:

$scope.metawidgetConfig = {

   ...custom WidgetBuilder...
   
   widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder( [ function( elementName, attributes, mw ) {

      if ( attributes.type === 'array' ) {

         ...inspects array metadata...
         
         var inspectionResult = mw.inspect( mw.toInspect, typeAndNames.type, typeAndNames.names );
         
         ...creates editable table

         var widget = $( '<table>' ).attr( 'edit-table', '' )
               .attr( 'columns', columns ).attr( 'ng-model', mw.path + '.' + attributes.name );
         return widget[0];
      }

You can download a complete project here. Feedback welcome!

Thursday, March 6, 2014

Metawidget In The Cloud

The awesome folks over at CloudBees have kindly donated a FOSS account for our nightly builds! See our Customer Story here.

This means you can now download snapshot bundles of Metawidget.

These are complete ZIPs containing all binaries, minified JavaScript libraries, API documentation, Reference Guide PDFs and pre-built examples. Of course, Maven snapshot JARs will still be available.

Tuesday, February 25, 2014

JavaScript Form Generator: Metawidget 3.8

Version 3.8 of Metawidget, the JavaScript form generator is now available!

This release was focused on:
  • Re-licensed under LGPL/EPL and a commercial license
  • Bootstrap 3 support
  • AngularJS date support, ngShow/ngHide support, memory leak fix
  • Spring 3.2.6 support
  • HTML 5 color picker support
  • 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.

Wednesday, February 12, 2014

JavaScript Form Generator: from Java EE to Bootstrap 3

I've been asked to provide a complete example of using Metawidget to bridge the gap between an annotation-based, Java EE back-end and a JavaScript, Bootstrap 3 front-end.

This requires metadata to be fed from the 'Java world' into the 'JavaScript world'. Metawidget excels at this, using a process it calls 'remote inspection'. Metawidget's five-stage pipeline is designed to be split across multiple tiers and disparate architectures.

First, create the annotated Java object. We'll use Metawidget's built-in annotations here, but you can of course use JPA annotations, Hibernate Validator annotations etc.

public class Person {

   private String   mFirstname;

   private String   mSurname;

   private int      mAge;

   private boolean   mRetired;

   @UiRequired
   public String getFirstname() {...}

   @UiComesAfter( "firstname" )
   public String getSurname() {...}

   @UiComesAfter( "surname" )
   public int getAge() {...}


   ...other getters and setters...
}

Then, create a REST service that uses Metawidget's inspectors to inspect that object, and returns the inspection result in JSON format:

mInspector = new CompositeInspector( new CompositeInspectorConfig().setInspectors(
      new PropertyTypeInspector(),
      new MetawidgetAnnotationInspector()
      ) );

mInspectionResultProcessors = new DomInspectionResultProcessor[] {
      new ComesAfterInspectionResultProcessor<Object>(),
      new JsonSchemaMappingProcessor<Object>(),
      new JsonTypeMappingProcessor<Object>()
};

...

// Inspect

Element inspectionResult = mInspector.inspectAsDom( null, entityClass.getName() );
for ( DomInspectionResultProcessor<Element, Object> inspectionResultProcessor : mInspectionResultProcessors ) {
   inspectionResult = inspectionResultProcessor.processInspectionResultAsDom(
      inspectionResult, null, null, entityClass.getName(), (String[]) null );
}

// Convert to JSON Schema

return XmlUtils.elementToJsonSchema( inspectionResult );

Finally, create a JavaScript Metawidget that uses Bootstrap 3 and calls the REST service asynchronously:

<!DOCTYPE HTML>
<html>
   <head>
      <link href="lib/bootstrap/css/bootstrap.min.css" type="text/css" rel="stylesheet"/>
      <script src="lib/jquery/jquery.min.js"></script>
      <script src="lib/bootstrap/js/bootstrap.min.js"></script>
      <script src="js/metawidget-core.min.js"></script>
      <script src="js/metawidget-bootstrap.min.js"></script>
   </head>
<body>
   <div style="width: 500px; margin: 50px auto">
      <form class="form-horizontal">
         <div id="metawidget">
            <button class="btn btn-primary" onclick="save(); return false">Save</button>
         </div>
      </form>
   </div>
   <script type="text/javascript">
      var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {

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

            var xhr = new XMLHttpRequest();
            xhr.open( "GET", "rest/schema/person", 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
         } ]
,
         
         addWidgetProcessors: [ new metawidget.bootstrap.widgetprocessor.BootstrapWidgetProcessor() ],
         layout: new metawidget.bootstrap.layout.BootstrapDivLayout()
      } );
      mw.toInspect = {};
      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 produce:

Here's a link to a complete Eclipse project. Feedback welcome!

Sunday, January 26, 2014

Easy Forms For JavaScript: Dynamically Generating A Map Widget

I was recently asked if a JavaScript Metawidget could be used to display a map when given a location.

Metawidget is designed around a 5 stage pluggable pipeline. To incorporate a map widget, the best plugin point is a WidgetBuilder.

WidgetBuilders are passed all metadata for a given property, and try to return the most suitable widget. Or they can return nothing, and let another WidgetBuilder further down the chain have a try. In our case, we could inspect the property name (say, zip) and return either a map widget or nothing (in which case the standard HtmlWidgetBuilder can have a try).

Here's a complete example:

<html>
   <head>
      <script src="http://metawidget.org/js/3.7/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 385px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var config = {
            widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder([
               function(elementName, attributes, mw) {

                     // If metadata indicates this property needs a map...

                     if (attributes.name === 'zip') {

                        // ...read the value...

                        var typeAndNames = metawidget.util.splitPath(mw.path);
                        var toInspect = metawidget.util.traversePath(mw.toInspect, typeAndNames.names);
                        var value = toInspect[attributes.name];

                        // ...and build a map widget

                        var img = document.createElement('img');
                        img.setAttribute('src', 'http://maps.googleapis.com/maps/api/staticmap?center=' + value + '&size=300x300&sensor=false');
                        return img;
                     }
               }
,
               new metawidget.widgetbuilder.HtmlWidgetBuilder()
            ])
         }
         var mw = new metawidget.Metawidget(document.getElementById('metawidget'), config);
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            zip: 90212
         };
         mw.buildWidgets();
      </script>
   </body>
</html>

This will display:

Of course in a real-world scenario inspecting a property's name may not be a good approach. Better to have some orthogonal metadata that determines whether to return a map widget. Metawidget supports many ways to supply such orthogonal metadata. Here's an example using JSON Schema:

<html>
   <head>
      <script src="http://metawidget.org/js/3.7/metawidget-core.min.js"></script>
      <style>
         #metawidget {
            border: 1px solid #cccccc;
            width: 385px;
            border-radius: 10px;
            padding: 10px;
            margin: 50px auto;
         }
      </style>
   </head>
   <body>
      <div id="metawidget"></div>
      <script type="text/javascript">
         var config = {
            inspector: new metawidget.inspector.CompositeInspector( [
               new metawidget.inspector.PropertyTypeInspector(),
               function() {
                  return { properties: { zip: { type: 'location' } } }
               }

            ] ),
            widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder([
               function(elementName, attributes, mw) {

                     // If metadata indicates this property needs a map...

                     if (attributes.type === 'location') {

                        // ...read the value...

                        var typeAndNames = metawidget.util.splitPath(mw.path);
                        var toInspect = metawidget.util.traversePath(mw.toInspect, typeAndNames.names);
                        var value = toInspect[attributes.name];

                        // ...and build a map widget

                        var img = document.createElement('img');
                        img.setAttribute('src', 'http://maps.googleapis.com/maps/api/staticmap?center=' + value + '&size=300x300&sensor=false');
                        return img;
                     }
               },
               new metawidget.widgetbuilder.HtmlWidgetBuilder()
            ])
         }
         var mw = new metawidget.Metawidget(document.getElementById('metawidget'), config);
         mw.toInspect = {
            firstname: 'Homer',
            surname: 'Simpson',
            zip: 90212
         };
         mw.buildWidgets();
      </script>
   </body>
</html>

Hope that helps. Feedback welcome!