Help

Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

This page describes an experimental, model-driven technique for constructing JSF views. The technique aims to reduce boilerplate and improve maintainablity for some normal seam-gen use-cases, and it doesn't require deep JSF knowledge.

The core idea: Suppose we have a CRUD page that needs to display a property of a Java bean. In seam-gen, an FTL template includes logic which examines the property and prints out several lines of JSF. In this document's alternative approach, a runtime component (PropertyTemplateManager) searches for a template file that specifies how to display the property.

To see how this idea plays out, this document describes some key code snippets in an example app. The code-snippets require files from the property-templates-0.1.jar (in this directory).

(Aside: The original JavaBeans specification includes a mechanism for model-driven view construction. This seems similar. To my understanding, though, the JavaBeans spec is geared toward AWT/Swing applications. This mechanism is designed for JSF/Seam applications.)

Motivation

First, a little glib CW: Code generators, like seam-gen, can help developers get going quickly, and they provide many spots where developers can replace the output with hand-crafted code. Unfortunately, code generators also produce large volumes of slightly-different code, and maintaining that code can suck. In particular, how do we handle systematic changes to code? Perhaps we re-run the generator (and lose our customizations), or we perhaps we get familiar with copy/paste.

Here are a few cases in which one may wish to make systematic changes to code produced by seam-gen:

  • Implement a click-to-edit UI that applies to all inputs
  • Add extra widgets depending on a property's type or annotations (e.g. whenever we display or edit an @Email property, we should show a mailto link)
  • Hook-up converters and validators depending on a property's type or annotations. (e.g. whenever we edit a @PhoneNumber property, hook up a phoneNumberConverter)
  • Change the type-information for a property.

In all of these cases, I was tempted to copy-paste like a maniac, to edit the seam-gen templates, and/or to re-run seam-gen. I settled on an approach which simplifies the seam-gen templates, removes some logic from seam-gen, and handles the logic at runtime.

Example: The Model

@Entity
public class Contact ... {
    @Email
    @Length(max=80)
    private contactEmail;
  
    @PhoneNumber
    @Length(max=16)
    private String cellPhone;

    ...
}

Example: An Edit page with normal seam-gen

Given the above model, seam-gen would produce an edit page that looks a bit like this:

<!-- File: /ContactEdit.xhtml -->
...
        <rich:panel>
            <f:facet name="header">#{contactHome}.managed ? 'Edit' : 'Add'} Seminar</f:facet>

            <s:decorate id="emailDecoration" template="layout/edit.xhtml">
                <ui:define name="label">email</ui:define>
                <h:inputText id="email"
                           size="60"
                      maxlength="80"
                          value="#{contactHome}.instance.email}">
                    <a:support event="onblur" reRender="emailDecoration" bypassUpdates="true" ajaxSingle="true"/>
                </h:inputText>
            </s:decorate>

            <s:decorate id="cellPhoneDecoration" template="layout/edit.xhtml">
                <ui:define name="label">cellPhone</ui:define>
                <h:inputText id="cellPhone"
                           size="16"
                      maxlength="16"
                          value="#{contactHome}.instance.cellPhone}">
                    <a:support event="onblur" reRender="cellPhoneDecoration" bypassUpdates="true" ajaxSingle="true"/>
                </h:inputText>
            </s:decorate>

        </rich:panel>
...

Example: An Edit page with property-templates

With property-templates, the code-generator doesn't make as many decisions about the markup produced for each property. Instead, that decision is delegated to the <dui:include> tag.

<!-- File: /SeminarEdit.xhtml -->
...
        <rich:panel>
            <f:facet name="header">#{contactHome}.managed ? 'Edit' : 'Add'} Contact</f:facet>

            <dui:bean beanClass="org.example.entity.Contact" bean="#{contactHome.instance}" viewType="edit">
              <dui:include id="emailDecoration" property="email" />
              <dui:include id="cellPhoneDecoration" property="cellPhone" />
            </dui:bean>

        </rich:panel>
...

Example: Locating property templates

The <dui:include> tags are similar to <ui:include>, but the dui version is a little more dynamic -- it triggers a search (using the PropertyTemplateManager) to dynamically select a template file. For example, given property=email, the search will select the first available file from this list:

  1. /WEB-INF/property/com/example/entity/Contact/email-edit.xhtml
  2. /WEB-INF/property/com/example/annotations/Email-edit.xhtml
  3. /WEB-INF/property/org/hibernate/validator/Length-edit.xhtml
  4. /WEB-INF/property/java/lang/String-edit.xhtml
  5. /WEB-INF/property/java/lang/Object-edit.xhtml
  6. /WEB-INF/property/default.xhtml

The search for cellPhone will choose the first available file from this list:

  1. /WEB-INF/property/com/example/entity/Contact/cellPhone-edit.xhtml
  2. /WEB-INF/property/com/example/annotations/PhoneNumber-edit.xhtml
  3. /WEB-INF/property/org/hibernate/validator/Length-edit.xhtml
  4. /WEB-INF/property/java/lang/String-edit.xhtml
  5. /WEB-INF/property/java/lang/Object-edit.xhtml
  6. /WEB-INF/property/default.xhtml

Example: Designing a property template

The first property-template will be a generic one that works for almost any property of any simple type (String, Boolean, double, etc). It relies on JSF/Seam to provide an appropriate converter:

<!-- File: /WEB-INF/property/java/lang/Object-edit.xhtml -->
<ui:composition>
        <s:decorate id="#{id}" template="/layout/edit.xhtml">
                <ui:define name="label">#{messages[property]}</ui:define>
                <h:inputText
                        value="#{bean[property]}"
                        required="#{requiredProperty == null ? false : requiredProperty}"
                        size="#{(size != null) ? size : 32}"
                        maxlength="#{(maxlength != null) ? maxlength : (size != null ? size : 32) }">
                        <a:support event="onblur" reRender="#{id}" bypassUpdates="true" ajaxSingle="true"/>
                </h:inputText>
        </s:decorate>
</ui:composition>

This template resembles the seam-gen code, but it plugs in the parameters from <dui:bean> and <dui:include>. In particular, notice expressions like <s:decorate id=#{id}> and <h:inputText value=#{bean[property]}>.

Of course, like the seam-gen code, this template is pretty generic -- it's designed to work with almost any property. It doesn't provide a very rich interface. We should provide specialized templates based on the type of information we're trying to edit. In the following example, we define a richer UI for @Email properties. The UI includes a mailto link.

<!-- File: /WEB-INF/property/com/example/annotations/Email-edit.xhtml -->
<ui:composition>
        <s:decorate id="#{id}" template="/layout/edit.xhtml" styleClass="emailProperty">
                <ui:define name="label">#{messages[property]}</ui:define>
                <h:inputText
                        value="#{bean[property]}"
                        styleClass="emailPropertyInput"
                        required="#{requiredProperty == null ? false : requiredProperty}"
                        size="#{(size != null) ? size : 32}"
                        maxlength="#{(maxlength != null) ? maxlength : (size != null ? size : 32) }">
                        <a:support event="onblur" reRender="#{id}" bypassUpdates="true" ajaxSingle="true"/>
                </h:inputText>

                <s:span rendered="${! empty bean[property]}">
                        [<a href="#" onclick="window.location = 'mailto:'+findCousinsByClassName(this,'emailProperty','emailPropertyInput')[0].value);return false;">Open</a>
                </s:span>
        </s:decorate>

The second template resembles the first: both of them include an <s:decorate template=/layout/edit.xhtml> tag. Both define a label. Both use the id parameter. It is very handy to ensure that all PT's that are designed for viewType=edit have these similar features.

More generally, for viewType=edit, there is an implicit contract between the broader page (/ContactEdit.xhtml) and the property templates (/WEB-INF/property/foo-edit.xhtml). The broader page assumes that any template will include an <s:decorate> tag.

We can adapt the <dui:include> technique to other kinds of views -- such as display views and list views. Of course, the contract would be different. With list views, the broader page assumes that the template will include an <h:column> tag instead of an <s:decorate> tag.

Considerations, Limitations, Issues

The <dui:include> is useful for many properties, but it is not mandatory. You can easily mix-and-match property templates with hand-crafted code.

Sometimes, the view for one property may be connected to the view for another column, and we need a way to wire them together. In these cases, you might tweak the contract or rip-out the <dui:include>.

The <dui:bean> and <dui:include> tag require you to explicitly define beanClass and property. There are some alternative formulations (such as <dui:include property=#{myHome.instance.foo}>) which look nicer but are difficult or impossible to implement. There are a few limitations which lead to the current formulation:

  • JSF facelets are easiest to manage and re-use if the component graph remains static. When page processing begins, we try to construct the Right(tm) graph. The graph should not change as the data changes. Moreover, we must be able to construct the graph even if the data is null.
  • A static examination of a ValueExpression could tell us the property's type and annotations... but not reliably. For example, any EL that references a Map will lose type information.
  • The ValueExpressions API doesn't provide access to annotations.

If you create a new .xhtml file, the file may not be used automatically. A Full Publish will fix this. (It seems like there should be a less drastic way, but I haven't found it.)

The templates don't currently have access to the annotation data, but this could be very useful:

  • e.g. in /WEB-INF/property/java/lang/Object-edit.xhtml, the <h:inputText maxlength=XYZ> could be harmonized with @Length(max=XYZ).
  • e.g. in /WEB-INF/property/java/util/Date-edit.xhtml, we should exploit the @Temporal annotation.

The <dui:include> tag is basically re-entrant -- e.g. a <dui:include> tag may include a template which includes another <dui:include> tag. This functionality provides a natural way to handle JPA's @Embeddable feature. However, a developer must be conscientious about which parameters are passed to which tags: by default, parameters for the outer template will propagate to the inner template. This will not happen, however, if you ensure that the inner <dui:include> explicitly specifies all parameters required for its children. If you're aware of this, then you can easily avoid re-entrancy problems.