Tuesday, May 11, 2010

Custom annotations: UiFacesComponentProperties

I was recently asked on the Metawidget forum whether we could have a @UiFacesComponentProperties annotation, so that you could fine-tune certain properties of a JSF component from within your business object.

In my opinion, this ties the business object a little bit 'too close' to the UI. However I realise that's a very subjective thing. So while Metawidget does not offer this 'out of the box', you can implement it quite easily. Here's how.

The Annotation

First, define the annotation...

package com.myapp;

import java.lang.annotation.*;

/**
 * Array of name/value pairs of arbitrary UIComponent properties.
 */

@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.METHOD )
public @interface UiFacesComponentProperties {
   String[] value();
}

...which you can use like this...

@UiFacesLookup( "#{feeType.summary}" )
@UiFacesComponentProperties( { "layout", "pageDirection" } )
public Set<FeeType> getExcludeFees() {
   return mExcludeFees;
}

The Inspector

Next, define an Inspector to detect this annotation and turn it into a Metawidget attribute...

package com.myapp;

import static org.metawidget.inspector.InspectionResultConstants.*;
import java.util.Map;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.impl.propertystyle.*;
import org.metawidget.util.*;
import com.myapp.UiFacesComponentProperties;

public class MyAnnotationInspector
   extends BaseObjectInspector {

   public CoreAnnotationInspector() {
      this( new BaseObjectInspectorConfig() );
   }

   @Override
   protected Map<String, String> inspectProperty( Property property )
      throws Exception {
      Map<String, String> mapAttributes = CollectionUtils.newHashMap();

      UiFacesComponentProperties componentProperties = property.getAnnotation( UiFacesComponentProperties.class );

      if ( componentProperties != null )
         mapAttributes.put( "faces-component-properties", ArrayUtils.toString( componentProperties.value() ) );

   }
}

...and add it into your CompositeInspector chain.

The WidgetProcessor

Finally, declare a WidgetProcessor to act on the Metawidget attribute...

package com.myapp;

import static org.metawidget.inspector.InspectionResultConstants.*;
import static org.metawidget.inspector.propertytype.PropertyTypeInspectionResultConstants.*;
import java.lang.reflect.Method;
import java.util.*;
import javax.el.ValueExpression;
import javax.faces.component.*;

import org.metawidget.faces.component.*;
import org.metawidget.util.*;
import org.metawidget.widgetprocessor.iface.WidgetProcessor;

public class MyWidgetProcessor
   implements WidgetProcessor {

   @Override
   public UIComponent processWidget( UIComponent component, String elementName, Map<String, String> attributes, UIMetawidget metawidget ) {

      List<String> listComponentProperties = CollectionUtils.fromString( attributes.get( "faces-component-properties" ));

      for ( int loop = 0, length = listComponentProperties.size(); loop < length; loop += 2 ) {
            ClassUtils.setProperty( component, listComponentProperties.get( loop ), listComponentProperties.get( loop + 1 ) );
      }

   }
}

...and add both into your WidgetProcessor list in WEB-INF/metawidget.xml:

<htmlMetawidget xmlns="java:org.metawidget.faces.component.html">
   <inspector>
      <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig">
         <inspectors>
            <array>
               ...
               <myAnnotationInspector xmlns="java:com.myapp"/>
            </array>
         </inspectors>
      </compositeInspector>
   </inspector>
   ...
   <widgetProcessors>
         <array>
            ...
            <myWidgetProcessor xmlns="java:com.myapp"/>
         </array>
   </widgetProcessors>

Note this version will only work with String properties, and you might want to disable it for read-only scenarios (TRUE.equals(attributes.get(READ_ONLY))), but I'll leave it simple for now.

Feedback welcome!

Sunday, May 2, 2010

UI Generation Sucks!

I thought I'd do a blog post about common objections and preconceptions to UI Generation and how Metawidget tries to address them:

Code Generation Sucks!

The Preconception
Static code generation is notorious for generating reams of poorly written, poorly documented and poorly tested code. It's very impressive to be able to click a few buttons and suddenly have a runnable project appear, but less impressive when you realize you have to understand and maintain that code for the rest of your application's life.

And, generally speaking, the re-generation story is not a good one: if you make anything more than a trivial change to the generated code, you can't expect the static code generator to help you much further.


Metawidget's Approach
Metawidget doesn't generate any static code. It's a purely runtime-based approach. In a similar way to how JPA generates SQL on-the-fly rather than, say, pre-building lots of stored procedures, Metawidget inspects objects (not classes) and dynamically creates UI wigets at runtime.



Generated UIs Suck!

The Preconception
Most of the usability aspects of an application's front-end are not stored anywhere in its back-end architecture. So if you try and generate a UI purely from the back-end, you invariably end up with something that looks very generic. For example, perhaps you'll generate a 'UI screen per business class', with Create, Retrieve, Update and Delete (CRUD) functionality for each:


This is simply not how most users want their UIs to look.


Metawidget's Approach
Metawidget does not try and generate your entire UI. It focuses purely on being 'another widget in your toolbox'. It'll render the inside of a form for you (because the field names, and types, and constraints really are stored in the back-end), but it'll leave everything around the form - including appearance, navigation and usability - firmly in your hands.

By not trying to generate too much, Metawidget becomes more useful to more projects.



Won't work for me - I have a specialized back-end architecture!

The Preconception
Given the massive number of different frameworks out there, everything from validation engines to persistence layers to BPM, every project ends up having a subtly different architecture. This architecture is based on choices made by the development team as to what suits their needs best.

Any UI generator that tries to dictate your back-end architecture is unlikely to be a good fit.


Metawidget's Approach
Metawidget does not try to dictate your back-end architecture. It comes with plug-ins for many different kinds of existing technologies, and makes it easy to mix them together or add your own.



Won't work for me - I have specialized widget libraries!

The Preconception
Given the massive number of UI frameworks out there, many projects will choose different UI toolkits. And some projects will even choose multiple - say if you're targeting both Web and mobile platforms.

Any UI generator that tries to dictate your UI framework is unlikely to be a good fit.


Metawidget's Approach
Metawidget does not try to dictate your front-end architecture. It comes with plug-ins for many different kinds of existing UI frameworks (including third-party widget libraries), and makes it easy to mix them together or add your own.

Saturday, May 1, 2010

Model Driven User Interface: Metawidget v0.95

Version 0.95 of Metawidget, the model driven user interface tool, is now available. This release includes the following enhancements:
Special thanks to Stefan Ackermann for his contributions to this release, and to Nebojsa Cvijovic for his help testing!

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

There have, unfortunately, been some minor breaking changes with this release compared to v0.9. Specifically:

  • Many Basexxx classes have been removed in favour of a 'simplified' interface and an 'advanced' interface. For example there is no BaseWidgetProcessor any more - there is just the WidgetProcessor interface and an AdvancedWidgetProcessor interface. All the 'simplified' interfaces have only one method. This was done in preparation for automatic function objects (closures) in Java 7