Monday, April 21, 2008

Useful Bounds of Automatic UI Generation

A recent post on the Metawidget forums asked why Metawidget doesn't 'do more'. Given we can automatically create forms from domain objects, and given there are solutions that can automatically persist domain objects (eg. JPA), why not 'fill in the gap' and automatically create the entire CRUD app?

And indeed, there are solutions that do this. The most notable being http://www.nakedobjects.org. The drawback with these solutions is they necessarily create very generic UIs, which bear little resemblance to how they would have appeared and functioned had they been designed by hand, with due consideration to their problem domain. For example, with Naked Objects you get this...


...which is simply not how many clients want their apps to look.

Good UI design is both art and science. When you try to 'fill in the gap' you realize there's a lot of 'art' in that gap, and trying to automate that art results in less effective UIs.

So Metawidget is not trying to be that solution. I believe that solution is less useful for many real world apps. Instead, Metawidget tries to identify the bounds between where UI generation can be useful and practical and where it becomes too generic and impractical, and stays within those bounds. I call these the 'Useful Bounds of Generation'.

Staying within the Useful Bounds of Generation lets you apply Metawidget to a large category of real world applications that fully automated CRUD solutions simply aren't interested in. You can even retrofit an existing app and remove lots of your boilerplate code.

Wednesday, April 16, 2008

Metawidget: now twice as useful!

The latest release of Metawidget includes a 'read-only mode' on all supported platforms. So now Metawidget can not only automatically generate the UI for your data entry screens...


...it can also automatically generate your data display screens too:


The mode can be toggled on and off through the setReadOnly method. It potentially doubles the number of places in your app where a chunk of boilerplate code can be replaced with a single Metawidget call.

Monday, April 14, 2008

Using Metawidget with Seam, Facelets, JPA and Hibernate Validator

This is a short tutorial on using Metawidget with JBoss Seam, Facelets, JPA and Hibernate Validator. It should take about 10 minutes.

Seam - the fusion of the best

Metawidget is a great fit for Seam, because both Metawidget and Seam are all about leveraging and integrating existing technologies. For this tutorial, we will use Seam 2.0.1.GA and JBoss 4.2.2.GA, so you'll need to download those first. Next, build the Seam Booking example using:

cd \jboss-seam-2.0.1.GA\examples\booking
ant

...and then...

cd \jboss-4.2.2.GA
copy \Applications\jboss-4.2.2.GA\server\default\deploy
   server\default\deploy
bin\run

...and finally open a Web browser to http://localhost:8080/seam-booking and check it's all running okay. We'll assume you're familiar with the standard Seam Booking app (if not, it's covered in detail in the Seam documentation).

Seam, meet Metawidget

Metawidget ships with an updated Seam Booking example. To build it...

cd \metawidget-0.43\examples\faces\seam-booking
ant

...then stop JBoss if it's still running and, as before...

cd \jboss-4.2.2.GA
copy \Applications\jboss-4.2.2.GA\server\default\deploy
   server\default\deploy
bin\run

Open a Web browser to http://localhost:8080/seam-booking and check it's running okay. The app should look remarkably similar, but the Metawidget version uses less code, is less error-prone, and is more 'proper'. What do we mean by that?

Less code

The Metawidget version of the Seam Booking app replaces book.xhtml, confirm.xhtml and hotelview.xhtml. In each case, most of the code has been replaced by a single tag. So instead of...

<s:decorate id="checkinDateDecorate" template="edit.xhtml">
  <ui:define name="label">Check In Date:</ui:define>
  <rich:calendar id="checkinDate"
   value="#{booking.checkinDate}"
   required="true"
   datePattern="MM/dd/yyyy"
   event="onblur"
   reRender="checkinDateDecorate"
   style="width: auto;"/>

   ...some 60 lines of code...

</s:decorate>

...we simply have...

<m:metawidget value="#{booking}" rendererType="div">
  <f:param name="divStyleClasses"
   value="entry,label,required,input,error errors"/>
</m:metawidget>

Less error-prone

Ironically, the Seam Booking example actually contains the exact sort of bug Metawidget is designed to avoid! In the original hotelview.xhtml, we see...

<ui:define name="label">Nightly rate:</ui:define>
<h:outputText value="#{hotel.name}">

...clearly this is wrong - nightly rate should display #{hotel.price}, not #{hotel.name}. It's a simple cut-and-paste, 'my UI has gotten out of sync with my business object' sort of bug.

Metawidget gets it right, because Metawidget outputs the code for you.

More 'proper'

Often, there's a bunch of business object metadata that really should be mapped to the UI, but it's too laborious to do. For example, Booking.java uses Hibernate Validator's @Length annotation. Ideally, we would put...

<input type="text" length="16">

...on every such occurance, but who has time to do that?

Metawidget gets it right, because Metawidget inspects the back-end, discovers the annotation, and outputs the laborious code for you.

Conclusion

That concludes this short tutorial. As we have seen, combining Metawidget with Seam saves you code and bugs, whilst making your UI more 'proper'. To learn more about Metawidget, the best place to start is the Reference Documentation.

Sunday, April 13, 2008

Automatic User Interface Generation: Metawidget v0.43

Version 0.43 of Metawidget, the tool for automatic user interface generation, is now available. This release includes:
  • Read-only mode for displaying (rather than editing) business objects
  • Facelets support
  • Mixin to ease development of custom Metawidgets
  • Yet more 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.

Saturday, March 1, 2008

Plugging in to Metawidget

Update: the APIs shown in this blog entry have changed slightly in newer releases of Metawidget. Specifically the new WidgetBuilder, WidgetProcessor and Layout interfaces make it easier to plug in without needing to override the Metawidget base class. Please download the latest documentation from http://metawidget.org

I was recently asked how to go about 'plugging in' to Metawidget, and whether some places are easier to plug in to than others. There are a few options, so here we go:

Inspectors

Inspectors inspect back-end sources and turn the information they find into a common format that the front-end Metawidgets understand. It's generally less work to write an Inspector than to write a front-end Metawidget.

You can:
  • inspect XML files. For example, you could inspect Commons Validator files for 'required' fields. The base class AbstractXmlInspector should be very handy for this. Good examples of extending it are StrutsInspector and HibernateInspector
  • inspect annotations. For example, you could inspect some custom Spring annotations you might have. The base class AbstractPojoInspector should be very handy. Good examples of extending it are JpaInspector and HibernateValidatorInspector
  • inspect other sources. For example, you could inspect a database schema for field lengths. The base class AbstractInspector should be handy, but this is the most work of the 3 inspector options

For more information, see Implementing your Own Inspector in the Reference Documentation.

Metawidgets

Metawidgets look at inspection results and choose the best components available for the target platform. Therefore it's important to be familiar writing UIs for the target platform 'by hand' before trying to build a Metawidget for it.

Desktop Metawidgets

  • new layout manager. For example, you could support MigLayout and have it understand all manner of clever SwingMetawidget.setParameters. You have to extend the base class org.metawidget.swing.layout.Layout. A good example is TableGridBagLayout, which uses setParameters for numberOfColumns
  • new component library. For example, you could support some SwingX components. You'd need to extend SwingMetawidget and override its buildWidget method
  • new framework. For example, you could support SWT. You can leverage all Metawidget's inspector architecture, and model your code off SwingMetawidget, but this is the most work of the 3 desktop options

Web Metawidgets

  • new JSP-based framework. For example, you could support Struts 2. The base class AbstractHtmlMetawidgetTag should be handy. Good examples are StrutsMetawidgetTag and SpringMetawidgetTag
  • new JSF component library. For example, you could support Tomahawk. The base class HtmlMetawidget should be handy. A good example is RichFacesMetawidget
  • new JSF validators. For example, you could support some custom validators you might have. You'll need to extend org.metawidget.faces.component.validator.Validator. A good example is StandardValidator
  • new layout manager. For example, you could lay out components in a 'newspaper' fashion. You'll need to extend org.metawidget.jsp.tagext.Layout (for JSP) or org.metawidget.faces.renderkit.LayoutRenderer (for JSF). Good examples are HtmlTableLayout and HtmlTableLayoutRenderer, but this is the most work of the 4 web options

Mobile Metawidgets

  • new framework. For example, you could support JavaFX Mobile. You could model your code off AndroidMetawidget, but this would be a bit of work.

For more information, see Implementing your Own Metawidget in the Reference Documentation.

If anyone decides to have a go at any of these, please let me know and I'll be happy to provide all the help you need.

Friday, February 29, 2008

Metawidget and Hibernate

Metawidget's new HibernateInspector knows how to inspect hibernate.cfg.xml files such as:

<hibernate-configuration>
  <session-factory>
    ...
    <mapping resource="mapping1.hbm.xml"/>
    <mapping resource="mapping2.hbm.xml"/>
    <mapping resource="mapping3.hbm.xml"/>
  </session-factory>
</hibernate-configuration>

From there, it traverses into the multiple mapping files such as...

<hibernate-mapping package="org.foo">
  <class name="Foo">
    <id name="baz"/>
    <property name="abc" type="clob"/>
  </class>
</hibernate-mapping>

...to extract useful UI hints. Hints like:
  • not-null fields are required fields
  • clob fields are large text boxes (like HTML textareas)
  • length attributes constrain input length
  • ids are hidden (though this is configurable)

These are the same features JpaInspector looks for in JPA annotations.

Wednesday, February 27, 2008

Beans Binding in Metawidget

Update: the APIs shown in this blog entry have changed slightly in newer releases of Metawidget. Specifically .setBindingClass has been replaced by .addWidgetProcessor. Please download the latest documentation from http://metawidget.org

This is a short tutorial on using Beans Binding in Metawidget. It should take around 10 minutes.

I'd recommend you use your preferred Java development environment. If you use an Integrated Development Environment (IDE), you'll need to start a new Java project and add metawidget.jar to it. Otherwise, you just need to ensure metawidget.jar is on your classpath.

The Object

First, we need an object to map from. Create a Person class under a package com.myapp:

package com.myapp;

public class Person {
  private String fullname = "Homer Simpson";
  private int kids = 3;
  private boolean retired = false;

  public String getFullname() {
    return this.fullname;
  }

  public void setFullname( String fullname ) {
    this.fullname = fullname;
  }

  public int getKids() {
    return this.kids;
  }

  public void setKids( int kids ) {
    this.kids = kids;
  }

  public boolean isRetired() {
    return this.retired;
  }

  public void setRetired( boolean retired ) {
    this.retired = retired;
  }

  public String toString() {
    String toReturn = "Fullname: " + this.fullname + "\n";
    toReturn += "Kids: " + this.kids + "\n";
    toReturn += "Retired: " + this.retired + "\n";
    return toReturn;
  }
}


The Interface

Next, we need a Swing app:

package com.myapp;

import javax.swing.JFrame;
import org.metawidget.inspector.javabean.JavaBeanInspector;
import org.metawidget.swing.SwingMetawidget;

public class Main {
  public static void main( String[] p_args ) {
    final Person person = new Person();

    final SwingMetawidget mw = new SwingMetawidget();
    mw.setInspector( new JavaBeanInspector() );
    mw.setToInspect( person );

    final JFrame frame = new JFrame( "Beans Binding in Metawidget" );
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

    frame.getContentPane().add( mw );
    frame.setSize( 400, 150 );
    frame.setVisible( true );
  }
}


The Output

Run the code. You will see the screen below:


The SwingMetawidget has automatically populated itself with child components at runtime. It has chosen JTextField, JSpinner, and JCheckBox components to suit the fields of the Person class.

Metawidget is all about being native to the existing platform: it doesn't impose any additional dependencies on your code.
By default, Swing doesn't provide an Object-to-JComponent mapping mechanism, so Metawidget doesn't either. If you have Beans Binding available, however, Metawidget will use it.

Turn on Beans Binding

Add beansbinding-1.2.1.jar to your classpath, and the following line to the Main class (highlighted in bold):

package com.myapp;

import javax.swing.JFrame;
import org.metawidget.inspector.javabean.JavaBeanInspector;
import org.metawidget.swing.SwingMetawidget;
import org.metawidget.swing.binding.beansbinding.BeansBinding;

public class Main {
  public static void main( String[] p_args ) {
    final Person person = new Person();

    final SwingMetawidget mw = new SwingMetawidget();
    mw.setInspector( new JavaBeanInspector() );
    mw.setBindingClass( BeansBinding.class );
    mw.setToInspect( person );

    final JFrame frame = new JFrame( "Beans Binding in Metawidget" );
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

    frame.getContentPane().add( metawidget );
    frame.setSize( 400, 150 );
    frame.setVisible( true );
  }
}


Run the code again. You will see the same screen, but this time the JComponents are automatically populated by Beans Binding:


Populate the Values Back

To have Metawidget populate the values back, add the following code to the Main class (highlighted in bold):

package com.myapp;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import org.metawidget.inspector.javabean.JavaBeanInspector;
import org.metawidget.swing.SwingMetawidget;
import org.metawidget.swing.binding.beansbinding.BeansBinding;

public class Main {
  public static void main( String[] p_args ) {
    final Person person = new Person();

    final SwingMetawidget mw = new SwingMetawidget();
    mw.setInspector( new JavaBeanInspector() );
    mw.setBindingClass( BeansBinding.class );
    mw.setToInspect( person );

    final JFrame frame = new JFrame( "Beans Binding in Metawidget" );
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

    frame.getContentPane().add( mw );
    frame.getContentPane().add(
    new JButton( new AbstractAction( "Save" ) {
      public void actionPerformed( ActionEvent e ) {
        mw.save();
        JOptionPane.showMessageDialog( frame, person.toString() );
      }
    } ), BorderLayout.SOUTH );

    frame.setSize( 400, 150 );
    frame.setVisible( true );
  }
}


Run the code again. This time, change some of the values and click the Save button. Metawidget will populate the values back, and display the resulting toString of the Person class:


Fine-Tune Component Creation

Metawidget supports several ways to control the components it creates. Here, we use the 'child components' approach: if you add a child JComponent with the same name as Metawidget would normally have given its automatically created component, Metawidget will use it in preference - but it will still apply Beans Binding for you.

Add the following code to the Main class (highlighted in bold):

package com.myapp;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import org.metawidget.inspector.javabean.JavaBeanInspector;
import org.metawidget.swing.SwingMetawidget;
import org.metawidget.swing.binding.beansbinding.BeansBinding;

public class Main {
  public static void main( String[] p_args ) {
    final Person person = new Person();

    final SwingMetawidget mw = new SwingMetawidget();
    mw.setInspector( new JavaBeanInspector() );
    mw.setBindingClass( BeansBinding.class );
    mw.setToInspect( person );

    JComboBox combo = new JComboBox(
      new Object[]{ null, 1, 2, 3, 4 } );
    combo.setName( "kids" );
    mw.add( combo );


    final JFrame frame = new JFrame( "Beans Binding in Metawidget" );
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

    frame.getContentPane().add( mw );
    frame.getContentPane().add(
    new JButton( new AbstractAction( "Save" ) {
      public void actionPerformed( ActionEvent e ) {
        mw.save();
        JOptionPane.showMessageDialog( frame, person.toString() );
      }
    } ), BorderLayout.SOUTH );
    frame.setSize( 400, 150 );
    frame.setVisible( true );
}
}


Run the code. The JComboBox will take the place of the JSpinner, but it will still be initialized by Beans Binding and data will still be saved back when clicking the Save button.


Conclusion

That concludes our simple Beans Binding example. For a more 'real world' example (including using Beans Binding converters), please see the Address Book example in the Metawidget distribution.