2.4 WidgetBuilders

WidgetBuilders build widgets based on inspection results. This section covers WidgetBuilders in general. For in-depth documentation of individual WidgetBuilders see Chapter 6, WidgetBuilders.

2.4.1 Interface

All WidgetBuilders must implement the WidgetBuilder interface. This is a simple interface that defines only one method:

W buildWidget( String elementName, Map<String, String> attributes, M metawidget )

Where W is a widget type (such as JComponent or UIComponent) and M is a Metawidget type (such as SwingMetawidget or UIMetawidget).

Each WidgetBuilder must look to the elementName (which is typically just 'property' or 'action' from the inspection-result) and to the various attributes (as parsed from the inspection-result) and instantiate an appropriate widget. WidgetBuilders can use the given metawidget to help them if needed (e.g. to access a UI context with which to instantiate widgets). Typically the WidgetBuilders do not need to configure the widget beyond simply instantiating it: the job of setting ids, attaching validators, configuring bindings and so forth is done by the WidgetProcessors (see Section 2.5, “WidgetProcessors”).

2.4.2 Usage

Unless explicitly specified, each Metawidget will instantiate default WidgetBuilders to match the target platform. For example, SwingMetawidget will by default instantiate an OverriddenWidgetBuilder, WidgetBuilder and SwingWidgetBuilder.

This default behaviour can be overridden either in code:

metawidget.setWidgetBuilder( new MyWidgetBuilder() );

Or via metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):

<swingMetawidget xmlns="java:org.metawidget.swing">
	<widgetBuilder>
		<myWidgetBuilder xmlns="java:com.myapp"/>
	</widgetBuilder>
</swingMetawidget>

This allows easy plugging in of third-party widget libraries. Note that overriding the default means the default is no longer instantiated. In the example above, this would mean MyWidgetBuilder is used but OverriddenWidgetBuilder, ReadOnlyWidgetBuilder and SwingWidgetBuilder are not. This is usually not what you want, because MyWidgetBuilder will be focused on a particular third party library and will want to leave widget overriding to OverriddenWidgetBuilder, read-only widgets (i.e. labels) to ReadOnlyWidgetBuilder and standard widgets to SwingWidgetBuilder.

To achieve this, use CompositeWidgetBuilder.

2.4.3 Advanced Interface

The WidgetBuilder interface only has a single method. This allows it to take advantage of future Java language features such as:

final Object someObject = ...;

metawidget.setWidgetBuilder(
	#(String name, Map<String, String> attr, SwingMetawidget m)
	{ ... } );

However for those needing more control over the WidgetBuilder lifecycle there is an extended interface AdvancedWidgetBuilder. This interface defines two additional methods:

void onStartBuild( M metawidget );
				
void onEndBuild( M metawidget );

The first method, onStartBuild, is called at the start of the widget building process. WidgetBuilders may wish to act on this event to initialize themselves ready for processing. However it is acceptable to do nothing.

The last method, onEndBuild, is called at the end of the widget building process, after all widgets have been built and added to the Layout. WidgetBuilders may wish to act on this event to clean themselves up following processing. However it is acceptable to do nothing.

2.4.4 CompositeWidgetBuilder

CompositeWidgetBuilder combines the widget libraries of several WidgetBuilders. It defers widget building to an internal list of WidgetBuilders, in order, and goes with the first one that returns non-null (see figure Figure 2.5).

CompositeWidgetBuilder composes multiple WidgetBuilders into one

Figure 2.5. CompositeWidgetBuilder composes multiple WidgetBuilders into one


In this way WidgetBuilders for third party UI component libraries (that provide specialized components) can be listed first, and WidgetBuilders for the platform's standard components can be listed last (as a 'fallback' choice).

CompositeWidgetBuilder can be instantiated either in code:

metawidget.setWidgetBuilder( new CompositeWidetBuilder( new CompositeWidgetBuilderConfig()
	.setWidgetBuilders(
		new OverriddenWidgetBuilder(), new ReadOnlyWidgetBuilder(),
		new MyWidgetBuilder(), new SwingWidgetBuilder()
	)));

Or via metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):

<swingMetawidget xmlns="java:org.metawidget.swing">
	<widgetBuilder>
		<compositeWidgetBuilder
			xmlns="java:org.metawidget.widgetbuilder.composite"
			config="CompositeWidgetBuilderConfig">
			<widgetBuilders>
				<array>
					<overriddenWidgetBuilder xmlns="java:org.metawidget.swing.widgetbuilder"/>
					<readOnlyWidgetBuilder xmlns="java:org.metawidget.swing.widgetbuilder"/>
					<myWidgetBuilder xmlns="java:com.myapp"/>
					<swingWidgetBuilder xmlns="java:org.metawidget.swing.widgetbuilder"/>
				</array>
			</widgetBuilders>
		</compositeWidgetBuilder>
	</widgetBuilder>
</swingMetawidget>

2.4.5 OverriddenWidgetBuilder

The first WidgetBuilder in the CompositeWidgetBuilder chain should generally be an OverriddenWidgetBuilder. This looks for existing child widgets that override default generation. What consitutes an 'overridden widget' varies from platform to platform. For example, for Swing any child widget with the same name will be taken as the override (see Section 1.1.8, “Controlling Widget Creation”). Android uses the tag attribute, JSF uses the value binding, and so on. For details on the OverriddenWidgetBuilder for your platform see Chapter 6, WidgetBuilders.

You can also choose to plug-in your own WidgetBuilder that detects 'overridden widgets' based on your own criteria. Here is an example of a custom WidgetBuilder that excludes widgets based on a JComponent client property. It extends the code from the tutorial (see Section 1.1, “Part 1 (Java version) - The First Metawidget Application”).

package com.myapp;
			
import static org.metawidget.inspector.InspectionResultConstants.*;

import java.util.*;

import javax.swing.*;
import org.metawidget.swing.*;
import org.metawidget.swing.widgetbuilder.*;
import org.metawidget.widgetbuilder.composite.*;
import org.metawidget.widgetbuilder.iface.*;
import org.metawidget.util.*;

public class Main {

	public static void main( String[] args ) {
		Person person = new Person();

		SwingMetawidget metawidget = new SwingMetawidget();
		metawidget.setWidgetBuilder( new CompositeWidgetBuilder<JComponent, SwingMetawidget>(
			new CompositeWidgetBuilderConfig<JComponent, SwingMetawidget>().setWidgetBuilders(
				new ExcludingWidgetBuilder(),
				new SwingWidgetBuilder() ) ) );
		metawidget.putClientProperty( "exclude", new String[]{ "age", "retired" } );
		metawidget.setToInspect( person );

		JFrame frame = new JFrame( "Metawidget Tutorial" );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.getContentPane().add( metawidget );
		frame.setSize( 400, 250 );
		frame.setVisible( true );
	}
	
	static class ExcludingWidgetBuilder
		implements WidgetBuilder<JComponent, SwingMetawidget> {
		public JComponent buildWidget( String elementName, Map<String, String> attributes,
										SwingMetawidget metawidget ) {
			String[] exclude = (String[]) metawidget.getClientProperty( "exclude" );

			if ( ArrayUtils.contains( exclude, attributes.get( NAME )))
				return new Stub();

			return null;
		}
	}
}
[Tip]Note
We don't necessarily recommend this approach, as it requires hard-coding business property names into your UI screens and won't refactor well.
[Tip]Note
Although you could adapt this approach to only include (instead of exclude) certain properties, you could not adapt it to include properties in the order specified in the client property. This is because WidgetBuilders only operate on single widgets at a time. Instead, see Section 2.3, “Inspection Result Processors”

2.4.6 ReadOnlyWidgetBuilder

The second WidgetBuilder in the CompositeWidgetBuilder chain should generally be a ReadOnlyWidgetBuilder. This builds standard platform widgets for properties with read-only="true" or no-setter="true" (i.e. labels).

The exception to this rule would be if you wanted to add a custom WidgetBuilder for a widget library that had its own read-only components, or if you wanted to customise the read-only handling. Here is an example of a custom WidgetBuilder that returns non-editable JTextFields (instead of JLabels) for read-only properties. It extends the code from the tutorial (see Section 1.1, “Part 1 (Java version) - The First Metawidget Application”).

package com.myapp;
			
import static org.metawidget.inspector.InspectionResultConstants.*;

import java.util.*;

import javax.swing.*;
import org.metawidget.swing.*;
import org.metawidget.swing.widgetbuilder.*;
import org.metawidget.widgetbuilder.composite.*;
import org.metawidget.widgetbuilder.iface.*;
import org.metawidget.util.*;

public class Main {

	public static void main( String[] args ) {
		Person person = new Person();

		SwingMetawidget metawidget = new SwingMetawidget();
		metawidget.setWidgetBuilder( new CompositeWidgetBuilder<JComponent, SwingMetawidget>(
			new CompositeWidgetBuilderConfig<JComponent, SwingMetawidget>().setWidgetBuilders(
				new ReadOnlyTextFieldWidgetBuilder(),
				new SwingWidgetBuilder() ) ) );
		metawidget.setToInspect( person );

		JFrame frame = new JFrame( "Metawidget Tutorial" );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.getContentPane().add( metawidget );
		frame.setSize( 400, 250 );
		frame.setVisible( true );
	}
	
	static class ReadOnlyTextFieldWidgetBuilder
		implements WidgetBuilder<JComponent, SwingMetawidget> {
		public JComponent buildWidget( String elementName, Map<String, String> attributes,
										SwingMetawidget metawidget ) {
			if ( !WidgetBuilderUtils.isReadOnly( attributes ) )
				return null;

			if ( TRUE.equals( attributes.get( HIDDEN )))
				return null;

			Class<?> clazz = ClassUtils.niceForName( attributes.get( TYPE ) );

			if ( String.class.equals( clazz ) || clazz.isPrimitive() ) {
				JTextField textField = new JTextField();
				textField.setEditable( false );

				return textField;
			}

			return null;
		}
	}
}

2.4.7 Defaults

All Metawidgets have default WidgetBuilders. Overriding the default means the default is no longer instantiated. Usually this is not what you want, so you should consider instantiating the default along with your new WidgetBuilder (i.e. use CompositeWidgetBuilder). You can see the default by looking in the Metawidget JAR for the file metawidget-xxx-default.xml (where xxx is your target platform, such as swing or struts).

For reference, the defaults are:

Platform Default
Android
<compositeWidgetBuilder config="CompositeWidgetBuilderConfig">
	<widgetBuilders>
		<array>
			<overriddenWidgetBuilder />
			<readOnlyWidgetBuilder />
			<androidWidgetBuilder />
		</array>
	</widgetBuilders>
</compositeWidgetBuilder>
GWT CompositeWidgetBuilder containg an OverriddenWidgetBuilder, ReadOnlyWidgetBuilder and GwtWidgetBuilder
JavaScript (incl. AngularJS)
new metawidget.widgetbuilder.CompositeWidgetBuilder( [
	new metawidget.widgetbuilder.OverriddenWidgetBuilder(),
	new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),
	new metawidget.widgetbuilder.HtmlWidgetBuilder()
] )
JQuery UI
new metawidget.widgetbuilder.CompositeWidgetBuilder( [
	new metawidget.widgetbuilder.OverriddenWidgetBuilder(),
	new metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder(),	
	new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),
	new metawidget.widgetbuilder.HtmlWidgetBuilder()
] )
JSF
<compositeWidgetBuilder config="CompositeWidgetBuilderConfig">
	<widgetBuilders>
		<array>
			<overriddenWidgetBuilder />
			<readOnlyWidgetBuilder />
			<htmlWidgetBuilder />
		</array>
	</widgetBuilders>
</compositeWidgetBuilder>
JSP
<compositeWidgetBuilder config="CompositeWidgetBuilderConfig">
	<widgetBuilders>
		<array>
			<overriddenWidgetBuilder />
			<readOnlyWidgetBuilder />
			<htmlWidgetBuilder />
		</array>
	</widgetBuilders>
</compositeWidgetBuilder>
Spring
<compositeWidgetBuilder config="CompositeWidgetBuilderConfig">
	<widgetBuilders>
		<array>
			<overriddenWidgetBuilder />
			<readOnlyWidgetBuilder />
			<springWidgetBuilder />
		</array>
	</widgetBuilders>
</compositeWidgetBuilder>
Struts
<compositeWidgetBuilder config="CompositeWidgetBuilderConfig">
	<widgetBuilders>
		<array>
			<overriddenWidgetBuilder />
			<readOnlyWidgetBuilder />
			<strutsWidgetBuilder />
		</array>
	</widgetBuilders>
</compositeWidgetBuilder>
Swing
<compositeWidgetBuilder config="CompositeWidgetBuilderConfig">
	<widgetBuilders>
		<array>
			<overriddenWidgetBuilder />
			<readOnlyWidgetBuilder />
			<swingWidgetBuilder />
		</array>
	</widgetBuilders>
</compositeWidgetBuilder>
SWT
<compositeWidgetBuilder config="CompositeWidgetBuilderConfig">
	<widgetBuilders>
		<array>
			<overriddenWidgetBuilder />
			<readOnlyWidgetBuilder />
			<swtWidgetBuilder />
		</array>
	</widgetBuilders>
</compositeWidgetBuilder>
Vaadin
<compositeWidgetBuilder config="CompositeWidgetBuilderConfig">
	<widgetBuilders>
		<array>
			<overriddenWidgetBuilder />
			<readOnlyWidgetBuilder />
			<vaadinWidgetBuilder />
		</array>
	</widgetBuilders>
</compositeWidgetBuilder>

2.4.8 Immutability

All WidgetBuilders are required to be immutable. This means you only need a single instance of a WidgetBuilder for your entire application. If you are using metawidget.xml then ConfigReader takes care of this for you, but if you are instantiating WidgetBuilders in Java code you should reuse instances.

Note that immutable only means WidgetBuilders cannot be changed once instantiated - it does not mean they cannot be configured. Many WidgetBuilders have corresponding xxxConfig classes that allow them to be configured prior to instantation in a type-safe way. For example, a HtmlWidgetBuilder can be configured in code:

metawidget.setWidgetBuilder( new HtmlWidgetBuilder( new HtmlWidgetBuilderConfig()
			.setDataTableStyleClass( "data-table" )));

Or in metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):

<htmlWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"
	config="HtmlWidgetBuilderConfig">
	<dataTableStyleClass>
		<string>data-table</string>
	</dataTableStyleClass>
</htmlWidgetBuilder>

2.4.9 Implementing Your Own WidgetBuilder

The pluggable nature of WidgetBuilders makes it easy to add your own. Because CompositeWidgetBuilder can be used to chain WidgetBuilders together, you only need worry about supporting your particular widgets. You can simply return null for all other types of property and rely on another WidgetBuilder further down the chain to instantiate one of the usual widgets. For example, RichFacesWidgetBuilder only instantiates the RichFaces components, and returns null for everything else.

Here is an example of a custom WidgetBuilder that uses two JRadioButtons, instead of the usual JCheckBox, to represent boolean properties. It extends the code from the tutorial (see Section 1.1, “Part 1 (Java version) - The First Metawidget Application”).

package com.myapp;
			
import static org.metawidget.inspector.InspectionResultConstants.*;

import java.awt.*;
import java.util.*;
import javax.swing.*;
import org.metawidget.swing.*;
import org.metawidget.swing.widgetbuilder.*;
import org.metawidget.widgetbuilder.composite.*;
import org.metawidget.widgetbuilder.iface.*;

public class Main {

	public static void main( String[] args ) {
		Person person = new Person();

		SwingMetawidget metawidget = new SwingMetawidget();
		metawidget.setWidgetBuilder( new CompositeWidgetBuilder<JComponent, SwingMetawidget>(
			new CompositeWidgetBuilderConfig<JComponent, SwingMetawidget>().setWidgetBuilders(
				new JRadioButtonWidgetBuilder(),
				new SwingWidgetBuilder() ) ) );
		metawidget.setToInspect( person );

		JFrame frame = new JFrame( "Metawidget Tutorial" );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.getContentPane().add( metawidget );
		frame.setSize( 400, 250 );
		frame.setVisible( true );
	}
	
	static class JRadioButtonWidgetBuilder
		implements WidgetBuilder<JComponent, SwingMetawidget> {

		public JComponent buildWidget( String elementName, Map<String, String> attributes,
			SwingMetawidget metawidget ) {
			
			if ( !"boolean".equals( attributes.get( TYPE ) ) )
				return null;

			JRadioButton trueButton = new JRadioButton( "True" );
			JRadioButton falseButton = new JRadioButton( "False" );
			JPanel panel = new JPanel();
			panel.setLayout( new GridLayout( 2, 1 ) );
			panel.add( trueButton );
			panel.add( falseButton );

			ButtonGroup buttonGroup = new ButtonGroup();
			buttonGroup.add( trueButton );
			buttonGroup.add( falseButton );

			return panel;
		}
	}
}

Like Inspectors, InspectionResultProcessors, WidgetProcessors and Layouts, WidgetBuilders are required to be immutable. However they will occasionally need to store some internal state, such as which CSS styls to use. This can be achieved in two ways:

  1. For state that will remain constant throughout the life of the WidgetBuilder, such as which CSS style to use, use xxxInspectorConfig classes. For example:

    public class MyWidgetBuilderConfig {
    	private String mCssStyle;
    	
    	public MyWidgetBuilderConfig setCssStyle( String cssStyle ) {
    		mCssStyle = cssStyle;
    		return this;
    	}
    
    	public String getCssStyle() {
    		return mCssStyle;
    	}
    	
    	// ...must override equals and hashCode too...
    }

    These xxxWidgetBuilderConfig classes are then passed to the WidgetBuilder at construction time, and stored internally:

    public class MyWidgetBuilder {
    	private String mCssStyle;
    	
    	public MyWidgetBuilder( MyWidgetBuilderConfig config ) {
    		mCssStyle = config.getCssStyle();
    	}
    }

    This mechanism can then be controlled either programmatically:

    metawidget.setWidgetBuilder( new MyWidgetBuilder(
    	new MyWidgetBuilderConfig().setCssStyle( "widget" )));

    Or in metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):

    <myWidgetBuilder xmlns="java:com.foo" config="MyWidgetBuilderConfig">
    	<cssStyle>
    		<string>widget</string>
    	</cssStyle>
    </myWidgetBuilder>
    [Important]Config classes must override equals and hashCode
    If you want your configurable WidgetBuilder to be cacheable and reusable by ConfigReader and metawidget.xml, the xxxWidgetBuilderConfig class must override equals and hashCode.
    [Tip]Generate an XML Schema
    If you intend your WidgetBuilder to be configurable via metawidget.xml, consider defining an XML Schema for it. This is optional, but allows users to validate their use of your WidgetBuilder in their metawidget.xml at development time. There is an Ant task, org.metawidget.config.XmlSchemaGeneratorTask, provided in the source distribution that can help with this by auto-generating the schema. All the existing Metawidget schemas are generated using this Ant task.
  2. For state that will change across multiple widgets, such as the name of the previous widget, store it in the Metawidget that is passed to buildWidget. You may want to further wrap the state in a small helper class. For example:

    public JComponent buildWidget( String elementName, Map<String, String> attributes,
    			SwingMetawidget metawidget ) {
    
    	...build new widget...
    	getState( metawidget ).previousName = attributes.get( NAME );
    }
    
    private State getState( SwingMetawidget metawidget ) {
    	State state = (State) metawidget.getClientProperty( getClass() );
    
    	if ( state == null ) {
    		state = new State();
    		metawidget.putClientProperty( getClass(), state );
    	}
    
    	return state;
    }
    
    static class State {
    	String previousName;
    	...other state variables...
    }

    You could also consider storing the state in a ThreadLocal. This is straightforward because WidgetBuilders are not re-entrant.

[Important]Config classes must override equals and hashCode
If you want your configurable WidgetBuilder to be cacheable and reusable by ConfigReader and metawidget.xml, the xxxWidgetBuilderConfig class must override equals and hashCode.
[Tip]Generate an XML Schema
If you intend your WidgetBuilder to be configurable via metawidget.xml, consider defining an XML Schema for it. This is optional, but allows users to validate their use of your WidgetBuilder in their metawidget.xml at development time. There is an Ant task, org.metawidget.config.XmlSchemaGeneratorTask, provided in the source distribution that can help with this by auto-generating the schema. All the existing Metawidget schemas are generated using this Ant task.

Special considerations for Java Server Faces

When developing WidgetBuilders for JSF component libraries, be aware that Metawidget integrates with the JSF lifecycle in a slightly unorthodox way. Upon POSTback, Metawidget first decodes, processes validators and updates the business model as usual. Upon encodeBegin, however, Metawidget destroys and recreates all previously generated UIComponents. This is so the UIComponents can adapt to changes in the business model. For example, they might need to be changed from a UIOutputText to a UIInputText if the user clicks an Edit button.

In most cases such recreation works well, but on occasion a component may not function properly because it is not expecting to be recreated. For example, the ICEfaces SelectInputDate component stores the state of its date popup inside the UIComponent. If it is recreated, this state is lost and the popup never appears. For such components, WidgetBuilder authors can set the attribute UIMetawidget.COMPONENT_ATTRIBUTE_NOT_RECREATABLE (must be used in conjunction with OverriddenWidgetBuilder) on the UIComponent to prevent its destruction and recreation. Of course, this somewhat impacts its flexibility. For example, a SelectInputDate would not be able to change its date format in response to another component on the form.