1.2 Part 1 (JavaScript version) - The First Metawidget Application

Part 1 starts with a pure JavaScript application and develops it in easy to understand steps. Metawidget supports many JavaScript-based UI frameworks, but we start with pure JavaScript because it requires minimal setup.

This tutorial should take around 20 minutes. We recommend you use your preferred JavaScript development environment.

1.2.1 The Object

Metawidget is an Object/User Interface Mapping tool (OIM), so first we need an object to map from - the O in OIM. Create a new HTML page in your project called index.html with the following code:

<!DOCTYPE html>
<html>
	<head>
		<script type="text/javascript">
			var person = {
				name: "Homer Simpson",
				age: 40,
				retired: false
			};
		</script>
	</head>
	<body>
	</body>
</html>

1.2.2 The Interface

Next we need a User Interface - the I in OIM. Add the following code to your HTML page (lines to add are highlighted):

<!DOCTYPE html>
<html>
	<head>
		<script src="lib/metawidget/core/metawidget-core.min.js" type="text/javascript"></script>
		<script type="text/javascript">
			var person = {
				name: "Homer Simpson",
				age: 40,
				retired: false
			};
		</script>
	</head>
	<body>
		<div id="metawidget"></div>
		<script type="text/javascript">
			var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ));
			mw.toInspect = person;
			mw.buildWidgets();
		</script>
	</body>
</html>

You will also need to copy js/lib/metawidget/core/metawidget-core.min.js from the Metawidget binary distribution into your project.

1.2.3 The Output

Open your HTML page in your browser. You should see the page in Figure 1.11.

Metawidget rendering of person object

Figure 1.11. Metawidget rendering of person object


Metawidget has automatically populated the div with child widgets at runtime. It has chosen text, number and checkbox inputs based on the types of the properties of the person object. This is the First Goal Of Metawidget:

[Important]First Goal Of Metawidget
Metawidget creates UI widgets by inspecting existing architectures

By default, Metawidget has laid out the HTML input components using a table. This may not be your preferred approach, and either way this is clearly not a complete UI. There are no Save or Cancel buttons, for example. This is explained by the Second Goal Of Metawidget:

[Important]Second Goal Of Metawidget
Metawidget does not try to 'own' the entire UI - it focuses on creating native sub-widgets for slotting into existing UIs

You slot Metawidget alongside your standard UI components, often combining several Metawidgets on the same screen. We'll see how this works later.

1.2.4 Inspectors

Suppose we want the name property to be a required field. Such metadata cannot be represented using standard JavaScript Object Notation (JSON) so Metawidget needs a way to gather the additional information. There are several ways to do this, but the simplest for now is to add a custom Inspector (text to add is highlighted) that returns a JSON Schema:

var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
	
	inspector: function( toInspect, type, names ) {

		return {
			properties: {
				name: {
					type: "string",
					required: true
				}
			}
		};
	}
} );

Refresh the page in your browser. It does not yield the correct result - the name property appears as a required field, but the age and retired properties have disappeared! What happened?

Name is required, but other properties are missing

Figure 1.12. Name is required, but other properties are missing


Metawidget Inspectors are very targeted in what they inspect. Our custom Inspector returns information about name being a required field - but it does not return anything else, such as properties from our JSON object. Before we explicitly specified a new Inspector, Metawidget had been implictly using another Inspector for us, called PropertyTypeInspector.

1.2.5 Combining Multiple Inspection Results

What we need is to combine the results of PropertyTypeInspector and our custom Inspector before returning them to the Metawidget. We do this using CompositeInspector:

var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
	
	inspector: new metawidget.inspector.CompositeInspector( [ new metawidget.inspector.PropertyTypeInspector(),
		function( toInspect, type, names ) {
		
				return {
					properties: {					
						name: {
							type: "string", 1
							required: true
						}
					}
				};
			}
		}
	] )
} );

1

type can now be removed from our custom Inspector, as it will be looked up by PropertyTypeInspector.

Refresh the page again. This time all properties appear and name is marked as a required field.

Using multiple inspectors

Figure 1.13. Using multiple inspectors


This idea of combining multiple Inspectors to inspect different characteristics of your existing architecture is very powerful. Metawidget comes with pre-written Inspectors for many different technologies and makes it easy to add your own, such as for REST services (see Section 5.5, “RestInspectionResultProcessor”). There is a lot of metadata already lurking in back-end systems - it just needs extracting.

1.2.6 Controlling The Layout

There are several ways to control the layout of the components. To demonstrate, Try adding the following metadata for the person object:

function( toInspect, type, names ) {
		
	return {
		properties:
			name: {
				required: true
			},
			notes: {
				type: "string",
				large: true 1
			},
			employer: {
				type: "string",
				section: "Work"
			},
			department: {
				type: "string"
			}
		}
	};
}

1

Metawidget supports a superset of JSON Schema (v3). Metadata such as large and section are not part of the JSON Schema specification, but are useful for UIs.

This produces the screen in Figure 1.14. Metadata has been added to define section headings and 'large' properties (i.e. a textarea).

Additional properties and a section heading

Figure 1.14. Additional properties and a section heading


By default, Metawidget lays out components using an HTML table. You can configure this layout or swap it for a different one. Modify the code as follows:

var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
	
	inspector: new metawidget.inspector.CompositeInspector( [ new metawidget.inspector.PropertyTypeInspector(),
		function( toInspect, type, names ) {
				
			return {
				properties:
					name: {
						required: true
					},
					notes: {
						type: "string",
						large: true
					},
					employer: {
						type: "string",
						section: "Work"
					},
					department: {
						type: "string"
					}
				}
			};
		} ] ),
		
	layout: new metawidget.layout.HeadingTagLayoutDecorator(
		new metawidget.layout.TableLayout( { numberOfColumns: 2 } ))
} );

Refresh the page. The components are now arranged across two columns:

A two-column layout

Figure 1.15. A two-column layout


[Tip]Note
Your browser may not have stretched the textarea across the full width of the table, even though the colspan has been set to 4. You can add <style>textarea { width: 100% }</style> into the head to confirm this visually

You may have noticed the TableLayout is nested inside a HeadingTagLayoutDecorator. This is responsible for separating widgets in different sections using h1 tags. However there are other choices for separating widgets. Modify the code to use a JQuery UI-based TabLayoutDecorator instead:

<!DOCTYPE html>
<html>
	<head>
		<script src="lib/metawidget/core/metawidget-core.min.js" type="text/javascript"></script>	
		<link rel="stylesheet" href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" />
		<script src="http://code.jquery.com/jquery-1.8.3.js"></script>
		<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
		<script src="lib/metawidget/jquery-ui/metawidget-jqueryui.min.js" type="text/javascript"></script>
		<script type="text/javascript">
			var person = {
				name: "Homer Simpson",
				age: 40,
				retired: false
			};
		</script>
	</head>
	<body>
		<form>
			<div id="metawidget"></div>
		</form>
		<script type="text/javascript">
			var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
				
				inspector: new metawidget.inspector.CompositeInspector( [ new metawidget.inspector.PropertyTypeInspector(),
					function( toInspect, type, names ) {
							
						return {
							properties:
								name: {
									required: true
								},
								notes: {
									type: "string",
									large: true
								},
								employer: {
									type: "string",
									section: "Work"
								},
								department: {
									type: "string"
								}
							}
						};
					} ] ),
					
				layout: new metawidget.jqueryui.layout.TabLayoutDecorator(
					new metawidget.layout.TableLayout( { numberOfColumns: 2 } ))
			} );		
			mw.toInspect = person;
			mw.buildWidgets();
		</script>
	</body>
</html>

You will also need to copy js/lib/metawidget/jquery-ui/metawidget-jqueryui.min.js from the Metawidget binary distribution into your project.

Refresh the page. The section heading is now a tab:

Two-column layout with a JQuery UI tab

Figure 1.16. Two-column layout with a JQuery UI tab


Metawidget makes it easy to apply consistent layouts across all pages of your application.

1.2.7 Controlling Widget Creation

There are several ways to control widget creation. One way is to add child controls inside the Metawidget:

<form>
	<div id="metawidget">
		<select id="retired">
			<option />
			<option>true</option>
			<option>false</option>
		</select>
	</div>
</form>

Refresh the page. The select box appears in place of the checkbox, because it has the same id (i.e. 'retired') as Metawidget would have given the checkbox. Notice Metawidget has still bound its value:

The 'retired' property has been overridden

Figure 1.17. The 'retired' property has been overridden


[Tip]Note
The default algorithm looks for child controls with the same id, but you can plug in your own implementation if you need to. See Section 2.4.5, “OverriddenWidgetBuilder”.

To suppress a widget's creation entirely, simply supplying an empty div with id 'retired' will not work as Metawidget will still create an accompanying label in the left hand column. Instead, Metawidget includes special stub widgets for this purpose:

<form>
	<div id="metawidget">
		<stub id="retired"></stub>
	</div>
</form>

Refresh the page. The retired property and its label will not appear:

The 'retired' property has been suppressed

Figure 1.18. The 'retired' property has been suppressed


Another way is to add a hidden attribute to the metadata:

function( toInspect, type, names ) {
	return {
		properties:
			name: {
				required: true
			},
			retired: {
				hidden: true
			},			
			notes: {
				type: "string",
				large: true
			},
			employer: {
				type: "string",
				section: "Work"
			},
			department: {
				type: "string"
			}
		}
	};
}

In both cases, metawidget.layout.TableLayout is smart enough to always give large components like textarea the full colspan of the table.