CUSTOM COMPONENTS WITH JAVASERVER FACES TECHNOLOGY
The March 24, 2004 Tech Tip Improving Designs With the MVC Design Pattern introduced the architectural pattern known as Model, View, Controller (MVC or Model2). MVC is a pervasive pattern throughout the world of computer science, and is fundamental to understanding JavaServer Faces (JSF) technology. The pattern separates the data and business logic of an application from its visual representation. The data and business logic is stored in an object called the Model. The visual representation is stored in a separate object called the View. The two objects are linked together with a third object called the Controller. The controller reacts to input from the view and updates the model data accordingly. The advantage of using this design is that any changes to the business logic or data can be isolated to the model without affecting the view. You can create multiple views without affecting the model.
A second tip in the March 24, 2004, titled Introducing JavaServer Faces Technology showed how to create a JSF application that includes GUI components that are modeled by the JSF framework. In this tip, you'll learn how to create custom components using JSF technology. More specifically, you'll learn how to create a custom JSF technology component that represents a simple stock display. Through an accompanying JavaServer Pages (JSP) page, a user can enter a stock symbol into a input text field and then press the Submit button. In response, the custom component displays a table below the text field. The table contains the stock's symbol, the current price of the stock, and the daily change in the stock price.
This tip assumes that you are familiar with the basics of JSF technology, and you know how to create JSP technology custom tag libraries. For information on the basics of JSF technology, see An Introduction to JavaServer Faces. For information on creating your own JSP custom tag libraries, see Using Custom Tags in the J2EE 1.4 Tutorial.
To create the custom component, you need to:
Create a JavaBean Model class that can be used to retrieve the stock data
Create a custom JSF View output component class that extends javax.faces.component.UIComponent.
Create a custom JSF View class that extends javax.faces.render.Renderer.
Integrate the custom component into the JSF framework using a custom tag.
Create a JSP page, including an input text field, and a backing bean for the input text field to assist the custom component.
Here is a visual representaion of how these objects fit in the MVC architecture.
Create a JavaBean Model
The model for the stock component is simple JavaBean class that has get and set accessor methods to store and retrieve data. The bean also has a method, retrieveStock(), that the UI component calls when the user enters the stock symbol. This method takes the entered stock symbol and updates the model information accordingly.
The sample archive that accompanies this tip includes all the source code for the stock component. In it you'll find the following source code for the class StockModel, the Model component: public class StockModel {
private String selectedStock = "UNKNOWN";
private String currentValue = "0";
private String dailyChange = "0";
public String getSelectedStock() { return selectedStock; }
public String getCurrentValue() { return currentValue; }
public String getDailyChange() { return dailyChange; }
public void retrieveStock(String stockSubmitted) {
selectedStock = stockSubmitted;
currentValue = "23.5";
dailyChange = "+2.5";
}
}
Typically a method such as retrieveStock() would query a database to obtain the data, but for simplicity, the method in this class updates the rest of the model with the constant strings specified in the class.
Create a UI Component
The primary purpose of the UI Component, which is part of the view, is to take data from the model and either display it itself, or relay it to a custom renderer to display the data as necessary. A custom UI component extends javax.faces.component.UIComponent. This class extends javax.faces.component.UIOutput, where the UIOutput class is a subclass of the UIComponent class. With UIOutput, most of the view functionality that is needed by the custom component is already provided. The only addition that this class needs is the getFamily() method, which returns a String that describes the component family (this must match an entry in the resource descriptor later). Here is the source code for the class UIStockChoiceOutput: public class UIStockChoiceOutput extends UIOutput {
public static final String STOCK_FAMILY = "StockFamily";
public UIStockChoiceOutput() {
super();
}
public String getFamily() {
return STOCK_FAMILY;
}
}
Create a Custom Renderer
The next step is to create a renderer for how this JSF component should appear in the client browser. This is a custom View class that extends the javax.faces.render.Renderer class. The purpose of this renderer is to create the output HTML table for the stock information display. Although it is not the case here, renderers can also be responsible for rendering input. Hence, a renderer can perform two types of translation services: decoding any input data into a format that can be passed on to the controller, and encoding any data from the controller object and rendering it to the browser. Decoding is done by the decode() method in the renderer. Encoding can be done by a number of encoding methods. This example uses the encodeEnd() method. Here is an extract of the source code for SimpleStockRenderer, the renderer (and custom View component):
public class SimpleStockRenderer extends Renderer {
public void encodeEnd(FacesContext context,
UIComponent component) throws IOException
{
if (!component.isRendered())
return;
String selectedStock =
(String)component.getAttributes().get("selectedStock");
String currentValue =
(String)component.getAttributes().get("currentValue");
String dailyChange =
(String)component.getAttributes().get("dailyChange");
String id = (String)component.getClientId(context);
ResponseWriter out = context.getResponseWriter();
out.startElement("table", component);
out.writeAttribute("border", "0", "border");
out.startElement("tr", component);
out.writeAttribute("bgcolor", "#000077", "bgcolor");
writeTableHeader(out, component, "Stock Symbol");
writeTableHeader(out, component, "Current Value");
writeTableHeader(out, component, "Daily Change");
out.endElement("tr");
out.startElement("tr", component);
out.writeAttribute("bgcolor", "#FFFFFF", "bgcolor");
writeTableEntry(out, component, selectedStock);
writeTableEntry(out, component, currentValue);
writeTableEntry(out, component, dailyChange);
out.endElement("tr");
out.endElement("table");
}
// Methods writeTableEntry() and writeTableHeader() omitted
}
Notice that a FacesContext object and a UIComponent object are passed in to the encodeEnd() method. The FacesContext object contains information about the JSF environment. The UIComponent is a reference to the Controller object. The encodeEnd() method retrieves the model data through the UIComponent controller and creates a ResponseWriter object. The method uses this object to write the HTML elements and their attributes for the HTML table, including its headers. There are a number of other encode methods in the Renderer class. See the JSF specification for information about these methods.
Create the Backing Bean
The next step is to create a backing bean for the input component. The backing bean provides a String-based property accessor for the input stock, as well as a boolean (rendered) indicating whether the output table should appear. Note that a validator has been added to the source code as well. If the input is not valid, the rendered boolean is set to false, and the renderer will not display the output table. The source code for the backing bean is shown here:
public class InputBackingBean {
private String inputStock = "";
private boolean rendered = false;
public void setStockOutputRendered(boolean r)
{ rendered = r; }
public boolean getStockOutputRendered()
{ return rendered; }
public String getInputStock() { return inputStock; }
public void setInputStock(String stock)
{ inputStock = stock; }
public void validateStock
(FacesContext ctx, UIComponent component, Object value)
throws ValidatorException
{
public void validateStock(FacesContext ctx,
UIComponent component, Object value)
throws ValidatorException
{
String string = (String)value;
if ((value.equals("AAA")) || (value.equals("BBB")) ||
(value.equals("CCC")) || (value.equals("DDD")) ||
(value.equals("EEE")) || (value.equals("FFF")) ||
(value.equals("GGG")) || (value.equals("HHH"))) {
rendered = true;
} else {
rendered = false;
throw new ValidatorException(
new FacesMessage
("Invalid Stock Entry: (Enter AAA,BBB, etc.)"));
}
}
public String inputStockSubmitted() {
FacesContext context = FacesContext.getCurrentInstance();
StockModel model = (StockModel)
context.getApplication().getVariableResolver().resolveVariable(
context, "StockModel");
model.retrieveStock(inputStock);
return "Input Stock Accepted";
}
}
The inputStockSubmitted() method is very important. This method is tied to an action attribute of a <h:commandButton> (which is a Submit button in the JSP page). When invoked, the method retrieves the StockModel by resolving it through the current faces context. The retrieveStock() method is then called on the model to update the stock information in the output table.
Final Steps
There are a few other things that need to be done to use the custom component. The component requires a custom tag library class that the JSP framework uses to communicate data to and from a JSP file. The class SimpleStockInputTag.java is the custom tag library class used in this tip. The custom tag library for this tip is called "stock".
The JSF framework also needs to way to tie the Model bean to the custom tag. It uses a standard JSP tag library descriptor file to do this. You can find the JSP tag library descriptor file, stock.tld, in the WEB-INF/lib directory of the sample archive.
The custom component objects must be registered with the JSF architecture. This is done through a faces-config.xml file in the WEB-INF directory.
Finally, you need a JSP page to display the component, provide a Submit button, and display the component. Here is most of the JSP page, Stock.jsp, that displays the component:
...
<%@taglib prefix="stock"
uri="http://nexes.org/example/stock"%>
<html>
<head>
<title>Stock Choice Example</title>
</head>
<body>
<h2>Please Enter a Stock Symbol</h2>
<f:view>
<p>
<h:messages id="messageList" showSummary="true"/>
</p>
<h:form>
<h:inputText value="#{InputBackingBean.inputStock}"
validator="#{InputBackingBean.validateStock}" />
<h:commandButton value="Submit"
action="#{InputBackingBean.inputStockSubmitted}" />
<stock:stockOutput id="symbol"
rendered="#{InputBackingBean.stockOutputRendered}"
selectedStock="#{StockModel.selectedStock}"
currentValue="#{StockModel.currentValue}"
dailyChange="#{StockModel.dailyChange}" />
</h:form>
</f:view>
</body>
</html>
Running the Sample Code
Download the sample archive for these tips. The application's context root is ttnov2004. The downloaded war file also contains the complete source code for the sample.
You can deploy the application archive (ttnov2004.war) on the J2EE 1.4 Application Server using the deploytool program or the admin console. You can also deploy it by issuing the asadmin command as follows:
asadmin deploy install_dir/ttnov2004.war
Replace install_dir with the directory in which you installed the war file.
You can access the application at http://localhost:8000/ttnov2004
For a J2EE 1.4-compliant implementation other than the J2EE 1.4 Application Server, use your J2EE product's deployment tools to deploy the application on your platform.
When you start the application, you should see a page that looks like this (not all of the page is shown):
Click on the custom component link. You should see a page that prompts you for a stock symbol. Enter a stock symbol, such as AAA or BBB into the data field. In response you should see the appropriate stock information displayed in a table.
If you enter an incorrect stock symbol, as judged by the validator, the component creates a FacesMessage that is displayed with the <h:messages> tag in the JSP page. For example, if you enter the symbol ABC, you will see the message: "Invalid Stock Entry ...".