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!

0 comments: