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.

Table of Contents

Getting Started

Dynamic Attribute Binding

Generating The Dynamic Form

Collections

Enums

Dates

Nested Entities

Generating The Dynamic Table View

Customization

Assumptions

Getting Started

Dynamic CRUD through Facelets Composite Templates and Custom EL functions.

Installation Instructions:

  • Download the seam-dynamic-crud.jar from this directory and copy it to the WEB-INF/lib folder of your webapp (JBDS) or to the lib folder of your project and define in deployed-jars.list (seam-gen).
  • This has currently only been tested on Seam 2.0.2.SP1 (http://www.seamframework.org/Download) and Richfaces 3.2.1.GA (http://www.jboss.org/jbossrichfaces/downloads/) but you can experiment with other versions and let me know. When deploying a WAR Seam project just copy over the 3 richfaces jars to your WEB-INF and delete the old ones. If you are using an EAR Seam project copy the richfaces-ui-*.jar and richfaces-impl-*.jar to the WEB-INF/lib and the richfaces-api-*.jar to the <project>-ear/EarContent/ directory.
  • Create a Seam project in seam-gen or JBDS (Or using an existing project) and create a new Entity and name it Dog. This will also create dog.xhtml, dogList.xhtml, DogHome.java, DogList.java, and of course Dog.java
  • Edit your web.xml and include:
<context-param>
    <param-name>facelets.REFRESH_PERIOD</param-name>
    <param-value>0</param-value>
</context-param>

This gets around a refresh issue with the templates in the jar.

  • Download the img.zip uploaded in this directory and extract the zip which contains a folder img into your view (seam-gen) or WebContent (JBDS). This folder contains the necessary img files for some richfaces actions like spinner.gif for ajax requests.
  • Open dog.xhtml and modify to be:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:crud="http://enhancements.seam/jsf"
                template="layout/template.xhtml">
                       
	<ui:define name="body">
	    <crud:masterEdit controlKey="dog"/>
	</ui:define>

</ui:composition>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
		xmlns:s="http://jboss.com/products/seam/taglib"
		xmlns:ui="http://java.sun.com/jsf/facelets"
		xmlns:f="http://java.sun.com/jsf/core"
		xmlns:h="http://java.sun.com/jsf/html"
		xmlns:crud="http://enhancements.seam/jsf"
		template="layout/template.xhtml">
    
    <ui:define name="body">
    	<crud:masterView controlKey="dog"/>
    </ui:define>
    
</ui:composition>
  • Open DogList.xhtml and modify to be:
@Name("dogList")
public class DogList extends EntityQuery<Dog> {
	@Override
	public String getEjbql() {
		return "select dog from Dog dog";
	}

        //Required with current version
	private Dog dog = new Dog();
	public Dog getDog() { return dog; }
}

It is necessary to create a new entity like above with the private Dog dog = new Dog(); and have the getter. The EL looks for an Object based on the controlKey in the List code, on my list to find a work around.

  • Now, just re-deploy the application (Always required when adding an Entity) and go to http://localhost:8080/<webapp>/dog.seam. You'll see an extremely simple page displayed with just the Name input, nothing fancy.
  • Edit your Dog.java Entity and String ownerName and Integer age:
@Entity
public class Dog implements Serializable {
	
	//seam-gen attributes (you should probably edit these)
	private Long id;
	private Integer version;
	private String name;
	private String ownerName;
	private Integer age;
    //add additional entity attributes
	
	//seam-gen attribute getters/setters with annotations (you probably should edit)
		
	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getOwnerName() {
		return ownerName;
	}

	public void setOwnerName(String ownerName) {
		this.ownerName = ownerName;
	}

	@Id @GeneratedValue
	public Long getId() {
	     return id;
	}

	public void setId(Long id) {
	     this.id = id;
	}
	
	@Version
	public Integer getVersion() {
	     return version;
	}

	private void setVersion(Integer version) {
	     this.version = version;
	}   	
	
	@Length(max=20)
	public String getName() {
	     return name;
	}

	public void setName(String name) {
	     this.name = name;
	}   	
}

And redeploy and refresh http://localhost:8080/<webapp>/dog.seam. Now you'll see the additional fields, go ahead and create a dog if you'd like

  • How about making a field required Let's make the fields name and age required:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:crud="http://enhancements.seam/jsf"
                template="layout/template.xhtml">
                       
	<ui:define name="body">
	    <crud:masterEdit controlKey="dog" requiredFields="name, age"/>
	</ui:define>

</ui:composition>

No need to redeploy here as this is an xhtml file, just refresh your browser after you've exploded or allowed JBDS to synchronize. Now you'll see the two fields are required in dog.seam and if you fail to enter a value a validation error will pop up automatically via an ajax update.

  • Now onto searching, go ahead to the http://localhost:8080/<webapp>/dogList.seam page and you'll see the search togglePanel along with your list of created dogs. To get the search to work, edit dogList.seam:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
		xmlns:s="http://jboss.com/products/seam/taglib"
		xmlns:ui="http://java.sun.com/jsf/facelets"
		xmlns:f="http://java.sun.com/jsf/core"
		xmlns:h="http://java.sun.com/jsf/html"
		xmlns:crud="http://enhancements.seam/jsf"
		template="layout/template.xhtml">
    
    <ui:define name="body">
    	<crud:masterView controlKey="dog" searchFields="ownerName, name"/>
    </ui:define>
    
</ui:composition>

but don't refresh yet, one more change:

  • Modify DogList.java and add the necessary restrictions:
@Name("dogList")
public class DogList extends EntityQuery
{
	private static final String[] RESTRICTIONS = {
		"lower(dog.name) like lower( concat(#{dogList.dog.name},'%') )",
		"lower(dog.ownerName) like lower( concat(#{dogList.dog.ownerName},'%') )"
	}; 
	
    @Override
    public String getEjbql() { 
        return "select dog from Dog dog";
    }
    
    private Dog dog = new Dog();
	public Dog getDog() { return dog; }
	
	@Override
    public List<String> getRestrictions() {
		System.out.println("Adding restrictions: " + RESTRICTIONS);
            return Arrays.asList(RESTRICTIONS);
    }
    
}

now re-deploy the app and refresh dogList.seam, given your app has a create-drop policy, create a few more Dogs, then you'll be able to search via the search fields.

If you have any problems running the above, please email me smendenh@redhat.com with the versions of Seam/Richfaces you are using along with the error messages you are getting and how you got those messages. Or feel free to make any feature requests/comments/criticisms/suggestions.

Code Explanation

Note that I am not writing this initially with performance in mind, purely function. And none of the following should ever be used in production in its current incantation.

Dynamic Attribute Binding

This allows the following <crud:masterEdit ... eyeColorEnum="#{eyeColors}" eyeColorEnumLabel="label" ... /> where eyeColorEnum and eyeColorEnumLabel are not known to the composite template in the backend. This is accomplished via a TagHandler which exposes the attribute in the FaceletsContext (VariableMapper is private and not accessible, has to be a better way than forcing it) to the the FacesContext. This allows the attributes to be references via EL in the composite template.

Continuing the example above, eyeColorEnum gets exposed to the FacesContext in the edit.xhtml composite template here: <e:setPropertyBindings valueBinding="#{field.name}" endsWith="Enum"/>. Meaning, look for a variable in the Facelets VariableMapper that ends with Enum, and starts with #{field.name} which #{field.name} resolves to eyeColor, and put that in the FacesContext.

Then the following with comments inline:

<e:isEnum id="vb">
    <!-- Bind the var enum to the previously exposed value ie eyeColorEnum -->
    <e:setValueBinding var="enum" valueBinding="#{e:evalEl(e:concat(field.name, 'Enum'))}"/>
    <!-- Bind eyeColorEnumLabel like above-->
    <e:setValueBinding var="enumLabel" valueBinding="#{e:evalEl(e:concat(field.name, 'EnumLabel'))}"/>
    
    <!-- Now the value can just be referenced like always #{enum} and the label can be referenced via #{e[enumLabel]} which is standard composite EL -->
    <h:selectOneMenu value="#{entity[field.name]}">
		<s:selectItems value="#{enum}" var="e" label="#{e[enumLabel]}" noSelectionLabel="Please select" />
		<s:convertEnum />
    </h:selectOneMenu>
</e:isEnum>

Generating The Dynamic Form

<crud:masterEdit /> Usage

<crud:masterEdit /> is backed by masterEdit.xhtml which is composed of <crud:masterEdit /> backed by edit.xhtml and <crud:actionButtons /> backed by actionButtons.xhtml and it contains all of the necessary formatting for a typical crud page, ie. messages, rich panels, ect...

Available attributes crud:masterEdit:

  • Attribute
  • Default
  • Description
  • controlKey
  • If not set, MUST set other attributes accordingly, can only use controlKey if using default EntityHome/EntityQuery Seam conventions
  • entityHome
  • #{e:evalEl(e:concat(controlKey, 'Home'))}
  • EntityHome is not used anywhere else in the backing code, purely to allow the entity variable to resolve by default
  • entity
  • #{entityHomeinstance}
  • Ex. <crud:masterEdit entity="#{personHome.instance}" ... /> but this is not necessary if the controlKey is available and you are using default EntityHome conventions, so the easiest is to do <crud:masterEdit controlKey="person" />" which would result in the entity being #{personHome.instance}
  • excludedFields
  • version, id
  • Excludes fields from being parsed for input in the form Ex. <crud:masterEdit controlKey="person" excludedFields="version, id, password" />
  • requiredFields
  • Setting the required fields adds the * through the s:decorate seam tag and makes the field required in JSF Ex. <crud:masterEdit controlKey="person" excludedFields="version, id, password" requiredFields="firstName, lastName, email" />
  • ajax
  • true
  • Causes form validation to happen per input via a:support (Recommended to keep true) Ex. <crud:masterEdit controlKey="person" excludedFields="version, id, password" requiredFields="firstName, lastName, email" ajax="false" />
  • eventsQueue
  • #{controlKey}Queue
  • The eventsQueue ensures all ajax request happen in the same queue (Recommended to leave this alone if using the controlKey) Ex. <crud:masterEdit controlKey="person" excludedFields="version, id, password" requiredFields="firstName, lastName, email" eventsQueue="someQueue" />

Collections

Now how do we deal with displaying @OneToMany or @ManyToMany List collections This is extremely easy as long as you observe a few rules:

  • The name of field must end with the String List if it is a java.util.List or the like. Say you have a person and want to display children for selection. You must have the actual field name in the Person Entity has private List<Child> childrenList. The edit.xhtml composite facelet reflects the entity and looks for the #{field.name}List.

Ex.

<crud:masterEdit controlKey="person"
	   	 requiredFields="firstName, email"
	   	 childrenList="#{childrenList}" />
  • Bind the field you want to display in the select items to <field name>Label, in this case childrenListLabel="firstName" because I want the firstName of the child to be displayed when I go to select children. Remember childrenListLabel means nothing unless the childrenList part actually exists in the Child Entity.

Ex.

@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
private List<Child> children = new ArrayList<Child>();

<crud:masterEdit controlKey="person"
	   	 requiredFields="firstName, email"
	   	 childrenList="#{childrenList}" 
	   	 childrenListLabel="firstName"/>

The way this looks in the backend is such:

<h:selectManyListbox value="#{entity[field.name]}">
	<s:selectItems value="#{list}" var="e" label="#{e:evalEl('e')}"/>
	<s:convertEntity />

</h:selectManyListbox>

Where list is defined as:

<e:setValueBinding var="list" valueBinding="#{e:evalEl(e:concat(field.name, 'List'))}"/>

Note that there is currently a problem on Post back using the actual Label defined, so in reality the label should be "#{eEevalEleconcatfield.nameListLabel}" so right now I am assuming the toString of the var e, it is not a high priority atm.

Enums

Dealing with Enums:

1) Same rules as above, except the field MUST end with the String Enum so if my Person.java has a field:

@Enumerated(EnumType.STRING)
private EyeColor eyeColor;

then

<crud:masterEdit controlKey="person"
	   	 requiredFields="firstName, email"
	   	 childrenList="#{childrenList}" 
	   	 childrenListLabel="firstName"
	   	 eyeColorEnum="#{eyeColors}"
	   	 eyeColorEnumLabel="label"/>

The backing code being:

<e:isEnum id="vb">
    <e:setValueBinding var="enum" valueBinding="#{e:evalEl(e:concat(field.name, 'Enum'))}"/>
    <e:setValueBinding var="enumLabel" valueBinding="#{e:evalEl(e:concat(field.name, 'EnumLabel'))}"/>
    
    <h:selectOneMenu value="#{entity[field.name]}">
	<s:selectItems value="#{enum}" var="e" label="#{e[enumLabel]}" noSelectionLabel="Please select" />
	<s:convertEnum />
    </h:selectOneMenu>
</e:isEnum>

Dates

For dates, the really only concern you need to worry about is if you want a custom pattern, to do this, create an attribute like so: <field name>DatePattern="MM/dd/yyyy" where the field is:

@Temporal(TemporalType.TIMESTAMP)
private Date birthDate;

Ex.

<crud:masterEdit   controlKey="person"
	     requiredFields="firstName, email"
	     childrenList="#{childrenList}"
	     childrenListLabel="firstName"
	     eyeColorEnum="#{eyeColors}"
	     eyeColorEnumLabel="label"
	     birthDateDatePattern="MM/dd/yyyy"
	     />

And the backing code:

<e:isDate id="vb">
				
	<e:setValueBinding var="datePattern" valueBinding="#{e:evalConcattedEL(field.name, 'DatePattern')}"/>

	<c:if test="${not empty Display}">
		<ui:include src="${Display}" />
	</c:if>
	<c:if test="${empty Display}">
    		<rich:calendar id="#{field.name}Input" 
    			   value="#{entity[field.name]}" 
    			   required="#{e:csvContains(requiredFields, field.name)}" 
    			   datePattern="#{datePattern}"
    			   enableManualInput="true">
   			<a:support event="ondateselected" status="#{field.name}Status" reRender="#{field.name}Decorate" ajaxSingle="true" bypassUpdates="true" eventsQueue="#{eventsQueue}" ignoreDupResponses="true" requestDelay="200" rendered="#{ajax}"/>
		</rich:calendar>
	    <a:status forceId="true" id="#{field.name}Status">
        	<f:facet name="start">
           		<h:graphicImage  value="/img/spinner.gif"/>
        	</f:facet>
    	    </a:status>
	</c:if>
</e:isDate>

Generating The Dynamic Table View

<crud:masterView /> Usage

<crud:masterView /> is backed by masterView.xhtml which is composed of <crud:searchView /> backed by searchView.xhtml and <crud:view /> backed by view.xhtml and it contains all of the necessary xhtml for a typical search+List page.

Available attributes crud:masterView:

  • Attribute
  • Default
  • Description
  • controlKey
  • If not set, must set other attributes accordingly, can only use controlKey if using default EntityHome/EntityQuery Seam conventions. Ex. <crud:masterView controlKey="dog" /> will generate a full functional view page given you have the EntityList to back it up. In this case EntityList would also need to contain private Dog dog = new Dog(); and also the getter due to current architecture
  • entityQuery
  • #{e:evalEl(e:concat(controlKey, 'List'))}
  • Defines the search bean. This has only been tested using EntityQuery subclasses. By default assumes naming of entityList Ex. controlKey="dog" then entityQuery would be dogList which should be backed by DogList extends EntityQuery
  • excludedFields
  • version
  • Excludes fields from being processed and showing up in the table
  • entityView
  • /#{controlKey}.xhtml
  • Ex. <crud:masterView controlKey="dog" entityView="/dog.xhtml" ... /> but this is not necessary if the controlKey is available and you are using default EntityHome conventions, so the easiest is to do <crud:masterEdit controlKey="dog" /> which would result in the entityView as /dog.xhtml
  • linkedFields
  • id
  • Defines which fields in the Entity are clickable to edit the Entity
  • entityIdName
  • #{controlKey}Id
  • This binds to the id name in the EntityHome sublcass. In this case DogHome would have a field @RequestParameter Long dogId; which is what, by default, given the controlKey, the entityIdName would bind to.
  • entityIdField
  • id
  • Defines the name of the actual @Id field in the Entity, assumes it is id by default.
  • eventsQueue
  • #{controlKey}Queue
  • The eventsQueue ensures all ajax request happen in the same queue (Recommended to leave this alone if using the controlKey) Ex. <crud:masterEdit controlKey="person" excludedFields="version, id, password" requiredFields="firstName, lastName, email" eventsQueue="someQueue" />
  • tableId
  • #{controlKey}Table
  • Allows the table to be reRendered

A more complex example would look like:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
		xmlns:s="http://jboss.com/products/seam/taglib"
		xmlns:ui="http://java.sun.com/jsf/facelets"
		xmlns:f="http://java.sun.com/jsf/core"
		xmlns:h="http://java.sun.com/jsf/html"
		xmlns:rich="http://richfaces.org/rich"
		xmlns:a="http://richfaces.org/a4j"
		xmlns:crud="http://enhancements.seam/jsf"
		xmlns:ft="http://facestrace.sourceforge.net"
		template="layout/template.xhtml">
    
    <ui:define name="body">
    
    	<crud:masterView controlKey="person"
			 birthDateDatePattern="MM/dd/yyyy"
		         hobbiesListLabel="hobbyName"
			 eyeColorEnumLabel="label"
			 searchFields="firstName, id, email"/>
				
    </ui:define>
    
</ui:composition>

Customization

A few points of interest here

  • To define the location for a custom input on the form use the attribute <field name>Display="<location to xhtml>"

Ex.

<crud:masterEdit controlKey="person"
		  excludedFields="id, version"
		  childrenList="#{childrenList}"
		  childrenListLabel="firstName"
		  hobbiesList="#{hobbiesList}"
		  hobbiesDisplay="../manyCheckboxDisplay.xhtml"
		  hobbiesListLabel="hobbyName"
		  eyeColorEnum="#{eyeColors}"
		  eyeColorEnumLabel="label"
		  hairColorEnum="#{hairColors}"
		  hairColorEnumLabel="label"
		  birthDateDatePattern="MM/dd/yyyy"/>

Where ../manyCheckboxDisplay.xhtml resolves to WebContent/layout/manyCheckboxDisplay.xhtml (JBDS) or view/layout/manyCheckboxDisplay.xhtml (seam-gen) and looks like:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:s="http://jboss.com/products/seam/taglib"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:rich="http://richfaces.org/rich"
	xmlns:a="http://richfaces.org/a4j">

	<h:selectManyCheckbox value="#{entity[field.name]}">
		<s:selectItems value="#{List}" var="e" label="#{e[ListLabel]}" />
		<s:convertEntity />
	</h:selectManyCheckbox>
</ui:composition>

The above may be a bit confusing, but to explain: When you override the default behavior, you must retain the actual EL notations defined in the edit.xhtml code in seam-dynamic-crud.jar, otherwise the dynamic crud won't work if you change the EL. You can change layout, components, whatever within the bounds of what is being displayed, but retain the naming scheme.

The default code for displaying Lists (Many) looks like:

<c:if test="${not empty Display}">
    <ui:include src="${Display}" />
</c:if>
<c:if test="${empty Display}">				
    <h:selectManyListbox value="#{entity[field.name]}">
        <s:selectItems value="#{List}" var="e" label="#{e[ListLabel]}" />
        <s:convertEntity />
    </h:selectManyListbox>
</c:if>

Which the only thing of concern is what is between the <c:if test="${empty Display}"> being:

<h:selectManyListbox value="#{entity[field.name]}">
    <s:selectItems value="#{List}" var="e" label="#{e[ListLabel]}" />
    <s:convertEntity />
</h:selectManyListbox>

which is what is customizable. Overriding the Display of a field also has the advantage of being hot-deployable. Now, any changes made to manyCheckboxDisplay.xhtml does not require a redeploy. An important distinction must be made with overriding field inputs. The xhtml you use to override will also be quite generic, so avoid namings like hobbiesDisplay="../hobbiesDisplay.xhtml" as that would look like hobbiesDisplay.xhtml can only be used for the hobbies field, which is not true. Naming hobbiesDisplay="../manyCheckboxDisplay.xhtml" is much more concise since manyCheckboxDisplay.xhtml can be reused for any other List field.

Assumptions

  • The composite templates rely on s:decorate with template="../edit.xhtml" so the Seam edit.xhtml MUST be located in WebContent(or)view/layout/edit.xhtml (I also call my edit screen edit.xhtml but this resides in enhancements)
  • EntityQuery subclass must have an object of the parametarized type, like if PersonList extends EntityQuery<Person> then the PersonList must have private Person person = new Person(); and have a getter for the Person.

Otherwise the following expensive operation is necessary: <c:forEach items="#{entityQuery.resultList.size() == 0 ? null : entityQuery.resultList.get(0).getClass().getDeclaredFields()}" var="field"> Which with the assumption can be rewritten as so: <c:forEach items="#{entityQuery[controlKey].getClass().getDeclaredFields()}" var="field">

And this: <e:setValueBinding var="vb" valueBinding="#{entityQuery.resultList.get(uiComponent[tableId].rowIndex)[field.name]}"/> rewritten as: <e:setValueBinding var="vb" valueBinding="#{entityQuery[ControlKey][Field.name]}"/>

Contact

If you have any problems or bugs to report send to Samuel.Mendenhall@gmail.com