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.)
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:
click-to-editUI that applies to all inputs
mailtolink)
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.
@Entity public class Contact ... { @Email @Length(max=80) private contactEmail; @PhoneNumber @Length(max=16) private String cellPhone; ... }
Editpage 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> ...
Editpage 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> ...
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:
The search for cellPhone will choose the first available file from this list:
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.
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:
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:
XYZ> could be harmonized with @Length(max=XYZ).
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.