OUT OF DATE
The latest version of this article is located @ http://www.seamframework.org/Documentation/SeamDynamicCRUD
Reference Links
Creating Custom EL Functions, TagHandlers, and Facelet's Compositions
Introduction
Dynamic CRUD through Facelets Composite Templates and Custom EL functions.
Installation Instructions:
Download the latest timestamped seam-dynamic-crud.jar from this page 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 -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 attached to this wiki 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"></crud:masterEdit> </ui:define> </ui:composition>
Open dogList.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:masterView controlKey="dog"></crud:masterView> </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//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"></crud:masterEdit> </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"></crud:masterView> </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="" 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'))}"></e:setValueBinding> <!-- Bind eyeColorEnumLabel like above--> <e:setValueBinding var="enumLabel" valueBinding="#{e:evalEl(e:concat(field.name, 'EnumLabel'))}"></e:setValueBinding> <!-- 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:selectItems> <s:convertEnum ></s:convertEnum> </h:selectOneMenu> </e:isEnum>
<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 |
| EntityHome is not used anywhere else in the backing code, purely to allow the entity variable to resolve by default | |
entity | {entityHome[instance | 'instance']} | 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 | 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" />|
| 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 | 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|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 | 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"></crud:masterView> </ui:define> </ui:composition>
Assumptions:
The composite templates rely on s:decorate with template="../edit.xhtml" so the Seam edit.xhtml MUST be located in WebContent|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|controlKey].getClass().getDeclaredFields()}" var="field">
And this:
<e:setValueBinding var="vb" valueBinding="{entityQuery.resultList.get(uiComponent[TableId|tableId].rowIndex)[Field.name|field.name]}"/>
rewritten as:
<e:setValueBinding var="vb" valueBinding="{entityQuery[ControlKey|controlKey][Field.name|field.name]}"/>
Referenced by:
Comments