JSF2: Creating Custom Component [OutputObject]

adevedo's picture
0
No votes yet

JavaServerâ„¢ Faces - JSF - is the well-established standard for web-development frameworks in Java. The standard is based on the MVC paradigm, but is additionally to most web-frameworks also component-based and event-oriented. While JSF comes with a standard set of UI components, one of the most-publicized features is the easy addition of new components. Here, you will see just how easy it is to create new components that are fully functional and integrated into your web applications by creating a new component that takes a simple - with primative attributes only - java bean object as a value and his output will be the bean details printed in HTML table.

Example

A JSF page with the following code:

  1.         xmlns:ui="http://java.sun.com/jsf/facelets"
  2.         xmlns:h="http://java.sun.com/jsf/html"
  3.         xmlns:f="http://java.sun.com/jsf/core"
  4.         xmlns:cc="http://myapplication.com/com">
  5.         <h:head></h:head>
  6.         <h:body>
  7.                 <h1>User Bean</h1>
  8.                 <cc:outputObject value="#{mybean.user}" />
  9.                 <h1>Address Bean</h1>
  10.                 <cc:outputObject value="#{mybean.address}" />
  11.         </h:body>
  12. </html>

Where "mybean.user" and "mybean.address" will return beans of type User and Address, the result of these lines will be the following:

Creating the OutputObject custom component

Now lets start with creating the component in steps

Step 1: Creating the beans

The User bean:

  1. package com.myapp.beans;
  2.  
  3. import java.net.URL;
  4. import java.util.Date;
  5.  
  6. import com.myapp.components.outputoject.OutputObjectAttribute;
  7.  
  8. public class User {
  9.  
  10.         private int id;
  11.         private String username;
  12.         private String firstName;
  13.         private String lastName;
  14.         private String address;
  15.         private Date birthDate;
  16.         private URL website;
  17.  
  18.         public User(int id, String username, String firstName, String lastName,
  19.                         String address, Date birthDate, URL website) {
  20.                 this.id = id;
  21.                 this.username = username;
  22.                 this.firstName = firstName;
  23.                 this.lastName = lastName;
  24.                 this.address = address;
  25.                 this.birthDate = birthDate;
  26.                 this.website = website;
  27.         }
  28.  
  29.         public int getId() { return id; }
  30.         public String getUsername() { return username; }
  31.         public String getFirstName() { return firstName; }
  32.         public String getLastName() { return lastName; }
  33.         public String getAddress() { return address; }
  34.         public Date getBirthDate() { return birthDate; }
  35.         public URL getWebsite() { return website; }
  36. }

The Address bean:

  1. package com.myapp.beans;
  2.  
  3. import com.myapp.components.outputoject.OutputObjectAttribute;
  4.  
  5. public class Address {
  6.  
  7.         private int flatNo;
  8.         private int buildingNo;
  9.         private String address;
  10.         private String city;
  11.  
  12.         public Address(int flatNo, int buildingNo, String address, String city) {
  13.                 this.flatNo = flatNo;
  14.                 this.buildingNo = buildingNo;
  15.                 this.address = address;
  16.                 this.city = city;
  17.         }
  18.  
  19.         public int getFlatNo() { return flatNo; }
  20.         public int getBuildingNo() { return buildingNo; }
  21.         public String getAddress() { return address; }
  22.         public String getCity() { return city; }
  23. }

Step 2: Creating the backing bean "mybean"

  1. package com.myapp.backingbeans;
  2.  
  3. import java.net.MalformedURLException;
  4. import java.net.URL;
  5. import java.util.Date;
  6.  
  7. import javax.faces.bean.ManagedBean;
  8. import javax.faces.bean.RequestScoped;
  9.  
  10. import com.myapp.beans.Address;
  11. import com.myapp.beans.User;
  12.  
  13. @ManagedBean(name = "mybean")
  14. @RequestScoped
  15. public class MyBean {
  16.  
  17.         private User user;
  18.         private Address address;
  19.  
  20.         public User getUser() {
  21.                 if (user == null) {
  22.                         int id = (int) (Math.random() * 1000);
  23.                         try {
  24.                                 user = new User(id, "Username Of Id [" + id + "]",
  25.                                                 "First Name Of Id [" + id + "]", "Last Name Of Id ["
  26.                                                                 + id + "]",
  27.                                                 "This is the full address of the user", new Date(),
  28.                                                 new URL("http://www.adevedo.com"));
  29.                         } catch (MalformedURLException e) {
  30.  
  31.                         }
  32.                 }
  33.                 return user;
  34.         }
  35.  
  36.         public Address getAddress() {
  37.                 if (address == null) {
  38.                         address = new Address((int) (Math.random() * 100),
  39.                                         (int) (Math.random() * 100), "this is the street address",
  40.                                         "Cairo");
  41.                 }
  42.                 return address;
  43.         }
  44. }

What we want to do is a custom component that takes any object with primative attributes (User/Address) and prints the object details in an HTML table

Step 3: Creating the taglib and the component configuration

create a file with name "custom-taglib.xml" under location "WebContent/WEB-INF", the file contents will be:

  1.    version="2.0"
  2.     <namespace>http://myapplication.com/com</namespace>
  3.     <tag>
  4.         <tag-name>outputObject</tag-name>
  5.         <component>
  6.             <component-type>outputObject</component-type>
  7.         </component>
  8.     </tag>
  9. </facelet-taglib>

That file define a new taglib called "http://myapplication.com/com" with 1 tag inside called "outputObject", the tag component type - will be used to refere to the tag component class - is "outputObject"

Step 4: Registering the taglib in web.xml file

in the web.xml file, add the following lines to register our custom taglib

  1. <context-param>
  2.         <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
  3.         <param-value>/WEB-INF/custom-taglib.xml</param-value>
  4. </context-param>

These lines will tell our server to load the custom facelets library from location "/WEB-INF/custom-taglib.xml"

Step 5: Creating the "OutputObjectAttribute" annotation

Why we need such annotation, suppose you have a bean with 3 attributes "x", "y" and "z", when printing the bean details on the screen, you should use informative labels for "x", "y" and "z", also you should define the order of the attributes on the screen, print "z" then "x" and at the end print the value of "y". Our annotation will hold the needed information (label/order) of each attribute we want to be displayed on the screen.

  1. package com.myapp.components.outputoject;
  2.  
  3. @java.lang.annotation.Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
  4. @java.lang.annotation.Target(value={java.lang.annotation.ElementType.METHOD})
  5. public @interface OutputObjectAttribute {
  6.         String label();
  7.         int order();
  8. }

Step 6: Creating the component class

Our component class will simply do the following:

  1. Takes an object as a value
  2. Get all methods declared in the object class that are market with annotation OutputObjectAttribute
  3. Order all the methods with the value of OutputObjectAttribute.order
  4. Print a row for each method with the value of OutputObjectAttribute.label as the label column and the method return value as the value column

The class code:

  1. package com.myapp.components.outputoject;
  2.  
  3. import java.io.IOException;
  4. import java.lang.reflect.Method;
  5. import java.net.URL;
  6. import java.util.HashMap;
  7.  
  8. import javax.faces.component.FacesComponent;
  9. import javax.faces.component.UIOutput;
  10. import javax.faces.context.FacesContext;
  11. import javax.faces.context.ResponseWriter;
  12.  
  13. @FacesComponent("outputObject")
  14. public class HtmlOutputObject extends UIOutput {
  15.         @Override
  16.         public void encodeEnd(FacesContext context) throws IOException {
  17.                 Object object = getAttributes().get("value");
  18.                 ResponseWriter responseWriter = context.getResponseWriter();
  19.                 responseWriter.startElement("div", null);
  20.                 responseWriter.startElement("table", null);
  21.                 responseWriter.writeAttribute("border", "1", "border");
  22.                 responseWriter.writeAttribute("cellpadding", "5px", "cellpadding");
  23.                 HashMap<Integer, Method> methods = new HashMap<Integer, Method>();
  24.                 for (Method method : object.getClass().getDeclaredMethods()) {
  25.                         if (method.isAnnotationPresent(OutputObjectAttribute.class)) {
  26.                                 OutputObjectAttribute objectAttribute = method
  27.                                                 .getAnnotation(OutputObjectAttribute.class);
  28.                                 methods.put(objectAttribute.order(), method);
  29.                         }
  30.                 }
  31.                 for (int i = 1; i <= methods.size(); ++i) {
  32.                         Method method = methods.get(i);
  33.                         if (method.isAnnotationPresent(OutputObjectAttribute.class)) {
  34.                                 printObjectAttribute(object, responseWriter, method);
  35.                         }
  36.                 }
  37.                 responseWriter.endElement("table");
  38.                 responseWriter.endElement("div");
  39.         }
  40.  
  41.         private void printObjectAttribute(Object object,
  42.                         ResponseWriter responseWriter, Method method) throws IOException {
  43.                 responseWriter.startElement("tr", null);
  44.                 responseWriter.startElement("td", null);
  45.                 OutputObjectAttribute objectAttribute = method
  46.                                 .getAnnotation(OutputObjectAttribute.class);
  47.                 responseWriter.startElement("label", null);
  48.                 responseWriter.write(objectAttribute.label());
  49.                 responseWriter.endElement("label");
  50.                 responseWriter.endElement("td");
  51.                 responseWriter.startElement("td", null);
  52.                 printObjectAttributeValue(responseWriter, object, method);
  53.                 responseWriter.endElement("td");
  54.                 responseWriter.endElement("tr");
  55.         }
  56.  
  57.         private void printObjectAttributeValue(ResponseWriter responseWriter,
  58.                         Object object, Method method) throws IOException {
  59.                 Object value = null;
  60.                 try {
  61.                         value = method.invoke(object, new Object[] {});
  62.                 } catch (Exception e) {
  63.                         value = "Exception Thrown";
  64.                 }
  65.                 if (value instanceof URL) {
  66.                         responseWriter.startElement("a", null);
  67.                         responseWriter.writeAttribute("href", value.toString(), "href");
  68.                         responseWriter.write(value.toString());
  69.                         responseWriter.endElement("a");
  70.                 } else
  71.                         responseWriter.write(value.toString());
  72.         }
  73. }

The value "outputObject" in @FacesComponent("outputObject") should match the entry "outputObject" in the custom taglib XML file.

The component check on the attribute value type (method return value type), in case of "Integer, String..etc", it prints the value.toString(), in case of URL, the printed value will be an HTML A tag that refere (href) to the URL value

Step 7: Marking User/Address bean with the OutputObjectAttribute annotation

Our final step is to mark the beans User and Address with the annotatoin OutputObjectAttribute to define each attribute label and the order of the attribute in the resulted table

Our beans will be:

  1. package com.myapp.beans;
  2.  
  3. import java.net.URL;
  4. import java.util.Date;
  5.  
  6. import com.myapp.components.outputoject.OutputObjectAttribute;
  7.  
  8. public class User {
  9.  
  10.         private int id;
  11.         private String username;
  12.         private String firstName;
  13.         private String lastName;
  14.         private String address;
  15.         private Date birthDate;
  16.         private URL website;
  17.  
  18.         public User(int id, String username, String firstName, String lastName,
  19.                         String address, Date birthDate, URL website) {
  20.                 this.id = id;
  21.                 this.username = username;
  22.                 this.firstName = firstName;
  23.                 this.lastName = lastName;
  24.                 this.address = address;
  25.                 this.birthDate = birthDate;
  26.                 this.website = website;
  27.         }
  28.  
  29.         @OutputObjectAttribute(label = "User Id", order=1)
  30.         public int getId() { return id; }
  31.         @OutputObjectAttribute(label = "Username", order=2)
  32.         public String getUsername() { return username; }
  33.         @OutputObjectAttribute(label = "First Name", order=3)
  34.         public String getFirstName() { return firstName; }
  35.         @OutputObjectAttribute(label = "Last Name", order=4)
  36.         public String getLastName() { return lastName; }
  37.         @OutputObjectAttribute(label = "Address", order=5)
  38.         public String getAddress() { return address; }
  39.         @OutputObjectAttribute(label = "Birth Date", order=6)
  40.         public Date getBirthDate() { return birthDate; }
  41.         @OutputObjectAttribute(label = "Website", order=7)
  42.         public URL getWebsite() { return website; }
  43. }

The Address bean:

  1. package com.myapp.beans;
  2.  
  3. import com.myapp.components.outputoject.OutputObjectAttribute;
  4.  
  5. public class Address {
  6.  
  7.         private int flatNo;
  8.         private int buildingNo;
  9.         private String address;
  10.         private String city;
  11.  
  12.         public Address(int flatNo, int buildingNo, String address, String city) {
  13.                 this.flatNo = flatNo;
  14.                 this.buildingNo = buildingNo;
  15.                 this.address = address;
  16.                 this.city = city;
  17.         }
  18.  
  19.         @OutputObjectAttribute(label = "Flat No.", order = 1)
  20.         public int getFlatNo() { return flatNo; }
  21.         @OutputObjectAttribute(label = "Building No.", order = 2)
  22.         public int getBuildingNo() { return buildingNo; }
  23.         @OutputObjectAttribute(label = "Street Address", order = 3)
  24.         public String getAddress() { return address; }
  25.         @OutputObjectAttribute(label = "City", order = 4)
  26.         public String getCity() { return city; }
  27. }

That's all for our OutputObject component, now our component is very simple one, but it holds the basics of creating other larger complex components

For an Eclipse project for the component tested with JBOSS AS 7.1, check the article attachements

Attachements: 

Add new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.
By submitting this form, you accept the Mollom privacy policy.