Validation

Chapter 13. How Do I ...

Topics in This Chapter

  • "Web User Interface Design" on page 611

  • "Validation" on page 658

  • "Programming" on page 669

  • "Debugging and Logging" on page 684

The preceding chapters covered the JSF technology in a systematic manner, organized by core concepts. However, every technology has certain aspects that defy systematic exposure, and JSF is no exception. At times, you will ask yourself "How do I..." and not find an answer, perhaps because JSF does not really offer support for the feature or because the solution is unintuitive. This chapter was designed to help out. We answer, in somewhat random order, common questions that we found in discussion groups or that we received from readers.

Web User Interface Design

In this section, we show you how to use features such as pop-ups, applets, and file upload dialogs in your web pages. We hope that future versions of JSF will include ready-made components for these tasks. Here, we show you how to implement and configure the required components.

How Do I Find More Components?

The JSF standard defines a minimal set of components. The only standard component that goes beyond basic HTML is the data table. This comes as a disappointment to anyone who is lured by the promise of JSF to be "Swing for the web."

You may well wonder why the JSF specification developers did not include a set of professionally designed components such as trees, date and time pickers, and the like. However, it takes tremendous skill to do this, and it is a skill that is entirely different from being able to produce a technology specification.

To see just how difficult it is to implement a coherent component library, have a look at the open source Apache MyFaces components (http://myfaces.apache.org/tomahawk/index.html). In isolation, the components are perfectly functional and some are even nice to look at (see Figure 13-1). However, there is little commonality in visual design or the programming interface between the Tomahawk components.

Figure 13-1. MyFaces components


You will have to wait for a rich set of standard JSF components, at least until JSF 2.0. In the meantime, here are several component libraries that are worth investigating.

  1. The Apache MyFaces components may not be ideal, but they have two great advantages: They are freely available, and they are available right now. Table 13-1 shows some of the most interesting components.

    Table 13-1. MyFaces Components
    Tag Description
    tree A tree component
    treeColumn A table in which one column is a tree
    tree2 Another tree component
    jscookMenu A JavaScript-based menu
    panelNavigation A vertical hierarchical menu
    panelNavigation2 The successor to panelNavigation
    calendar A calendar input component
    inputDate A date/time input component
    schedule A schedule with day, week, and month views of tasks
    inputHtml A JavaScript-based input component for HTML text
    fileUpload A file upload component
    rssTicker Retrieves an RSS feed
    tabbedPane A tabbed pane, similar to the one in Chapter 9
    panelStack Displays one panel from multiple choices
    popup A pop-up that is rendered when the mouse is moved over a target
    dataTable An extension of the standard dataTable, with support for clickable sort headers and model state saving
    dataScroller A component for scrolling a table
    newspaperTable Wraps a long table into newspaper columns
    dataList Displays data as numbered lists, bulleted lists, or comma-separated lists
    saveState Saves arbitrary state with the client
    aliasBean Defines an alias for a bean that is included in a subview
    buffer Renders part of a page into a buffer for later use
    stylesheet Loads a style sheet from a location relative to the base of the web application
    jsValueChangeListener A client-side value change listener


  2. The ADF Faces components set by Oracle (http://www.cle.com/technology/products/jdev/htdocs/partners/addins/exchange/jsf/index.html) has a professionally designed look. It contains advanced components for trees, tabbed panes, tables, and so on, as well as stylish analogs to the basic HTML buttons and input fields. These components were donated to the Apache organization in 2006 and should be freely available to the general public after some time in the Apache incubator.

  3. ICEfaces (http://icefaces.org) is an open source library of components with Ajax support.

  4. Java Studio Creator (http://developers.sun.com/prodtech/javatools/jscreator) is a tool for visually designing JSF components. Creator also includes a set of professionally designed components. At the 2006 Java One conference, Sun announced that it will open-source these components.

  5. The Java BluePrints project has developed a set of Ajax components (https://blueprints.dev.java.net/ajaxcomponents.html). These include autocompletion, Google map interfaces, pop-up balloons, a file upload with a progress indicator, and several other pretty and useful components.

You can find additional component listings at http://jsfcentral.com/products/components and http://www.jamesholmes.com/JavaServerFaces/#software-comp.

How Do I Support File Uploads?

The users of your application may want to upload files, such as photos or documents (see Figure 13-2 and Figure 13-3).

Figure 13-2. Uploading an image file


Figure 13-3. Uploaded image


Unfortunately, there is no standard file upload component in JSF. However, it turns out that it is fairly straightforward to implement one. The hard work has already been done by the folks at the Apache organization in the Commons file upload library (see http://jakarta.apache.org/commons/fileupload). We will show you how to incorporate the library into a JSF component.

Note

The MyFaces project contains a file upload component with slightly different attributes from ours (see http://myfaces.apache.org/tomahawk/fileUpload.html).


A file upload is different from all other form requests. When the form data (including the uploaded file) is sent from the client to the server, it is encoded with the "multipart/form-data" encoding instead of the usual "application x-www-form-urlencoded" encoding.

Unfortunately, JSF does not handle this encoding at all. To overcome this issue, we install a servlet filter that intercepts a file upload and turns uploaded files into request attributes and all other form data into request parameters. (We use a utility method in the Commons file upload library for the dirty work of decoding a multipart/form-data request.)

The JSF application then processes the request parameters, blissfully unaware that they were not URL encoded. The decode method of the file upload component either places the uploaded data into a disk file or stores it in a value expression.

The code for the servlet filter is in Listing 13-1.

Note

You can find general information about servlet filters at http://java.sun.com/products/servlet/Filters.html.


You need to install the filter in the web-inf.xml file, using this syntax:

  <filter>      <filter-name>Upload Filter</filter-name>      <filter-class>com.corejsf.UploadFilter</filter-class>      <init-param>         <param-name>com.corejsf.UploadFilter.sizeThreshold</param-name>         <param-value>1024</param-value>      </init-param>   </filter>   <filter-mapping>      <filter-name>Upload Filter</filter-name>      <url-pattern>/upload/*</url-pattern>   </filter-mapping>

The filter uses the sizeThreshold initialization parameter to configure the file upload object. Files larger than 1024 bytes are saved to a temporary disk location rather than being held in memory. Our filter supports an additional initialization parameter, com.corejsf.UploadFilter.repositoryPath, the temporary location for uploaded files before they are moved to a permanent place. The filter sets the corresponding properties of the DiskFileUpload object of the Commons file upload library.

The filter mapping restricts the filter to URLs that start with /upload/. Thus, we avoid unnecessary filtering of other requests.

Figure 13-4 shows the directory structure of the sample application.

Figure 13-4. The directory structure of the file upload application


Listing 13-1. fileupload/src/java/com/corejsf/UploadFilter.java

  1. package com.corejsf;   2.   3. import java.io.File;   4. import java.io.IOException;   5. import java.util.Collections;   6. import java.util.Enumeration;   7. import java.util.HashMap;   8. import java.util.List;   9. import java.util.Map;  10. import javax.servlet.Filter;  11. import javax.servlet.FilterChain;  12. import javax.servlet.FilterConfig;  13. import javax.servlet.ServletException;  14. import javax.servlet.ServletRequest;  15. import javax.servlet.ServletResponse;  16. import javax.servlet.http.HttpServletRequest;  17. import javax.servlet.http.HttpServletRequestWrapper;  18. import org.apache.commons.fileupload.FileItem;  19. import org.apache.commons.fileupload.FileUploadException;  20. import org.apache.commons.fileupload.disk.DiskFileItemFactory;  21. import org.apache.commons.fileupload.servlet.ServletFileUpload;  22.  23.  24. public class UploadFilter implements Filter {  25.    private int sizeThreshold = -1;  26.    private String repositoryPath;  27.  28.    public void init(FilterConfig config) throws ServletException {  29.       repositoryPath = config.getInitParameter(  30.          "com.corejsf.UploadFilter.repositoryPath");  31.       try {  32.          String paramValue = config.getInitParameter(  33.             "com.corejsf.UploadFilter.sizeThreshold");  34.          if (paramValue != null)  35.             sizeThreshold = Integer.parseInt(paramValue);  36.       }  37.       catch (NumberFormatException ex) {  38.          ServletException servletEx = new ServletException();  39.          servletEx.initCause(ex);  40.          throw servletEx;  41.       }  42.    }  43.  44.    public void destroy() {  45.    }  46.  47.    public void doFilter(ServletRequest request,  48.       ServletResponse response, FilterChain chain)  49.       throws IOException, ServletException {  50.  51.       if (!(request instanceof HttpServletRequest)) {  52.          chain.doFilter(request, response);  53.          return;  54.       }  55.  56.       HttpServletRequest httpRequest = (HttpServletRequest) request;  57.  58.       boolean isMultipartContent  59.          = ServletFileUpload.isMultipartContent(httpRequest);  60.       if (!isMultipartContent) {  61.          chain.doFilter(request, response);  62.          return;  63.       }  64.  65.       DiskFileItemFactory factory = new DiskFileItemFactory();  66.       if (sizeThreshold >= 0)  67.          factory.setSizeThreshold(sizeThreshold);  68.       if (repositoryPath != null)  69.          factory.setRepository(new File(repositoryPath));  70.       ServletFileUpload upload = new ServletFileUpload(factory);  71.  72.       try {  73.          List<FileItem> items = (List<FileItem>) upload.parseRequest(httpRequest);  74.          final Map<String, String[]> map = new HashMap<String, String[]>();  75.          for (FileItem item : items) {  76.             String str = item.getString();  77.             if (item.isFormField())  78.                map.put(item.getFieldName(), new String[] { str });  79.             else  80.                httpRequest.setAttribute(item.getFieldName(), item);  81.          }  82.  83.          chain.doFilter(new  84.             HttpServletRequestWrapper(httpRequest) {  85.                public Map<String, String[]> getParameterMap() {  86.                   return map;  87.                }  88.                // busywork follows ... should have been part of the wrapper  89.                public String[] getParameterValues(String name) {  90.                   Map<String, String[]> map = getParameterMap();  91.                   return (String[]) map.get(name);  92.                }  93.                public String getParameter(String name) {  94.                   String[] params = getParameterValues(name);  95.                   if (params == null) return null;  96.                   return params[0];  97.                }  98.                public Enumeration<String> getParameterNames() {  99.                   Map<String, String[]> map = getParameterMap(); 100.                   return Collections.enumeration(map.keySet()); 101.                } 102.             }, response); 103.       } catch (FileUploadException ex) { 104.          ServletException servletEx = new ServletException(); 105.          servletEx.initCause(ex); 106.          throw servletEx; 107.       } 108.    } 109. }     

Now we move on to the upload component. It supports two attributes: value and target. The value denotes a value expression into which the file contents are stored. This makes sense for short files. More commonly, you will use the target attribute to specify the target location of the file.

The implementation of the FileUploadRenderer class in Listing 13-2 is straightforward. The encodeBegin method renders the HTML element. The decode method retrieves the file items that the servlet filter placed into the request attributes and disposes of them as directed by the tag attributes. The target attribute denotes a file relative to the server directory containing the root of the web application.

The associated tag handler class, in Listing 13-3, is as dull as ever.

Finally, when using the file upload tag, you need to remember to set the form encoding to "multipart/form-data" (see Listing 13-4).

Listing 13-2. fileupload/src/java/com/corejsf/UploadRenderer.java

  1. package com.corejsf;   2.   3. import java.io.File;   4. import java.io.IOException;   5. import java.io.InputStream;   6. import java.io.UnsupportedEncodingException;   7. import javax.el.ValueExpression;   8. import javax.faces.FacesException;   9. import javax.faces.component.EditableValueHolder;  10. import javax.faces.component.UIComponent;  11. import javax.faces.context.ExternalContext;  12. import javax.faces.context.FacesContext;  13. import javax.faces.context.ResponseWriter;  14. import javax.faces.render.Renderer;  15. import javax.servlet.ServletContext;  16. import javax.servlet.http.HttpServletRequest;  17. import org.apache.commons.fileupload.FileItem;  18.  19. public class UploadRenderer extends Renderer {  20.    public void encodeBegin(FacesContext context, UIComponent component)  21.       throws IOException {  22.       if (!component.isRendered()) return;  23.       ResponseWriter writer = context.getResponseWriter();  24.  25.       String clientId = component.getClientId(context);  26.  27.       writer.startElement("input", component);  28.       writer.writeAttribute("type", "file", "type");  29.       writer.writeAttribute("name", clientId, "clientId");  30.       writer.endElement("input");  31.       writer.flush();  32.    }  33.  34.    public void decode(FacesContext context, UIComponent component) {  35.       ExternalContext external = context.getExternalContext();  36.       HttpServletRequest request = (HttpServletRequest) external.getRequest();  37.       String clientId = component.getClientId(context);  38.       FileItem item = (FileItem) request.getAttribute(clientId);  39.  40.       Object newValue;  41.       ValueExpression valueExpr = component.getValueExpression("value");  42.       if (valueExpr != null) {  43.          Class valueType = valueExpr.getType(context.getELContext());  44.          if (valueType == byte[].class) {  45.             newValue = item.get();  46.          }  47.          else if (valueType == InputStream.class) {  48.             try {  49.                newValue = item.getInputStream();  50.             } catch (IOException ex) {  51.                throw new FacesException(ex);  52.             }  53.          }  54.          else {  55.             String encoding = request.getCharacterEncoding();  56.             if (encoding != null)  57.                try {  58.                   newValue = item.getString(encoding);  59.                } catch (UnsupportedEncodingException ex) {  60.                   newValue = item.getString();  61.                }  62.             else  63.                newValue = item.getString();  64.          }  65.          ((EditableValueHolder) component).setSubmittedValue(newValue);  66.          ((EditableValueHolder) component).setValid(true);  67.       }  68.  69.       Object target = component.getAttributes().get("target");  70.  71.       if (target != null) {  72.          File file;  73.          if (target instanceof File)  74.             file = (File) target;  75.          else {  76.             ServletContext servletContext  77.                = (ServletContext) external.getContext();  78.             String realPath = servletContext.getRealPath(target.toString());  79.             file = new File(realPath);  80.          }  81.  82.          try { // ugh--write is declared with "throws Exception"  83.             item.write(file);  84.          } catch (Exception ex) {  85.             throw new FacesException(ex);  86.          }  87.       }  88.    }  89. }     

Listing 13-3. fileupload/src/java/com/corejsf/UploadTag.java

  1. package com.corejsf;   2.   3. import javax.el.ValueExpression;   4. import javax.faces.component.UIComponent;   5. import javax.faces.webapp.UIComponentELTag;   6.   7. public class UploadTag extends UIComponentELTag {   8.    private ValueExpression value;   9.    private ValueExpression target;  10.  11.    public String getRendererType() { return "com.corejsf.Upload"; }  12.    public String getComponentType() { return "com.corejsf.Upload"; }  13.  14.    public void setValue(ValueExpression newValue) { value = newValue; }  15.    public void setTarget(ValueExpression newValue) { target = newValue; }  16.  17.    public void setProperties(UIComponent component) {  18.       super.setProperties(component);  19.       component.setValueExpression("target", target);  20.       component.setValueExpression("value", value);  21.    }  22.  23.    public void release() {  24.       super.release();  25.       value = null;  26.       target = null;  27.    }  28. }     

Listing 13-4. fileupload/web/upload/uploadImage.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.    <%@ taglib uri="http://corejsf.com/upload" prefix="corejsf" %>   5.   6.    <f:view>   7.       <head>   8.          <title>A file upload test</title>   9.       </head>  10.       <body>  11.          <h:form enctype="multipart/form-data">  12.             Upload a photo of yourself:  13.             <corejsf:upload target="upload/#{user.id}_image.jpg"/>  14.             <h:commandButton value="Submit" action="submit"/>  15.          </h:form>  16.       </body>  17.    </f:view>  18. </html>

How Do I Show an Image Map?

To implement a client-side image map, supply the usemap attribute with the h:outputImage element:

  <h:outputImage value="image location" usemap="#aLabel"/>

You can then specify the map in HTML in the JSF page:

  <map name="aLabel">      <area shape="polygon" coords="..." href="...">      <area shape="rect" coords="..." href="...">      ...   </map>

However, this approach does not integrate well with JSF navigation. It would be nicer if the map areas acted like command buttons or links.

Chapter 12 of the Java EE 5 tutorial (http://java.sun.com/javaee/5/docs/tutorial/doc) includes sample map and area tags that overcome this limitation.

To see the image map in action, load the bookstore6 web application that is included with the tutorial (see Figure 13-5). Here is how the tags are used in the tutorial application:

  <h:graphicImage  url="/template/world.jpg" alt="#{bundle.ChooseLocale}"         usemap="#worldMap" />   <b:map  current="NAmericas" immediate="true" action="bookstore"         actionListener="#{localeBean.chooseLocaleFromMap}"  >      <b:area  value="#{NA}" onmouseover="/template/world_namer.jpg"         onmouseout="/template/world.jpg" targetImage="mapImage" />      <b:area  value="#{SA}" onmouseover="/template/world_samer.jpg"         onmouseout="/template/world.jpg" targetImage="mapImage" />         ...   </b:map>     

Figure 13-5. Image map sample component


The area values are defined in faces-config.xml, such as

  <managed-bean>      <managed-bean-name> NA </managed-bean-name>      <managed-bean-class> com.sun.bookstore6.model.ImageArea </managed-bean-class>      <managed-bean-scope> application </managed-bean-scope>      <managed-property>         <property-name>coords</property-name>         <value>   53,109,1,110,2,167,19,168,52,149,67,164,67,165,68,167,70,168,72,170,74,172,75,174,77,   175,79,177,81,179,80,179,77,179,81,179,81,178,80,178,82,211,28,238,15,233,15,242,31,   252,36,247,36,246,32,239,89,209,92,216,93,216,100,216,103,218,113,217,116,224,124,221,   128,230,163,234,185,189,178,177,162,188,143,173,79,173,73,163,79,157,64,142,54,139,53,   109         </value>      </managed-property>   </managed-bean>     

Alternatively, you can use a technique that we showed in Chapter 7. Put the image inside a command button, and process the x and y coordinates on the server side:

  <h:commandButton image="..." actionListener="..."/>

Attach an action listener that gets the client ID of the button, attaches the suffixes .x and .y, and looks up the coordinate values in the request map. Process the values in any desired way. With this technique, the server application needs to know the geometry of the image.

How Do I Include an Applet in My Page?

Include the applet tag in the usual way (see, for example, Listing 13-5). This page displays the chart applet from Horstmann and Cornell, 2004, 2005. Core Java 2, vol. 1, chap. 10 (see Figure 13-6).

Figure 13-6. The chart applet


Just keep a couple of points in mind:

  • If you use JSF 1.1 and you include the applet tag inside a JSF component that renders its children (such as a panel grid), then you need to enclose it inside

    <f:verbatim>...</f:verbatim>
  • You may want to consider using the jsp:plugin tag instead of the applet tag. That tag generates the appropriate markup for the Java Plug-in. For example,

    <jsp:plugin type="applet" code="Chart.class"    width="400" height="300">    <jsp:params>       <jsp:param name="title" value="Diameters of the Planets"/>       <jsp:param name="values" value="9"/>       ...    </jsp:params> </jsp:plugin>

See http://java.sun.com/products/plugin for more information on the Java Plug-in.

Listing 13-5. applet/web/index.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.    <f:view>   5.       <head>   6.          <title><h:outputText value="#{msgs.title}"/></title>   7.       </head>   8.       <body>   9.          <h:form>  10.             <h:panelGrid columns="1">  11.                <h:column>  12.                   <h:outputText value="#{msgs.header}"/>  13.                </h:column>  14.  15.                <h:column>  16.                   <applet code="Chart.class" width="400" height="300">  17.                      <param name="title" value="Diameters of the Planets"/>  18.                      <param name="values" value="9"/>  19.                      <param name="name.1" value="Mercury"/>  20.                      <param name="name.2" value="Venus"/>  21.                      <param name="name.3" value="Earth"/>  22.                      <param name="name.4" value="Mars"/>  23.                      <param name="name.5" value="Jupiter"/>  24.                      <param name="name.6" value="Saturn"/>  25.                      <param name="name.7" value="Uranus"/>  26.                      <param name="name.8" value="Neptune"/>  27.                      <param name="name.9" value="Pluto"/>  28.                      <param name="value.1" value="3100"/>  29.                      <param name="value.2" value="7500"/>  30.                      <param name="value.3" value="8000"/>  31.                      <param name="value.4" value="4200"/>  32.                      <param name="value.5" value="88000"/>  33.                      <param name="value.6" value="71000"/>  34.                      <param name="value.7" value="32000"/>  35.                      <param name="value.8" value="30600"/>  36.                      <param name="value.9" value="1430"/>  37.                   </applet>  38.                </h:column>  39.             </h:panelGrid>  40.          </h:form>  41.       </body>  42.    </f:view>  43. </html>     

How Do I Produce Binary Data in a JSF Page?

Sometimes you will want to dynamically produce binary data, such as an image or a PDF file. It is difficult to do this in JSF because the default view handler sends text output to a writer, not a stream. It would theoretically be possible to replace the view handler, but it is far easier to use a helper servlet for producing the binary data. Of course, you still want to use the comforts of JSF in particular, value expressions to customize the output. Therefore, you want to provide a JSF tag that gathers the customization data and sends it to a servlet.

As an example, we implement a JSF tag that creates a chart image (see Figure 13-7). The image contains JPEG-formatted data that was dynamically generated.

Figure 13-7. Producing binary data


Note

We use the chart as an example for the binary data technique. If you want to display sophisticated graphs in your web application, check out the ChartCreator component at http://jsf-comp.sourceforge.net/components/chartcreator.


Listing 13-6 includes the chart with the following tag:

  <corejsf:chart width="500" height="500"      title="Diameters of the Planets"      names="#{planets.names}" values="#{planets.values}"/>

Here, names and values are value expression of type String[] and double[]. The renderer, whose code is shown in Listing 13-7, produces an image tag:

  <img width="500" height="500" src="/books/2/24/1/html/2//binary/BinaryServlet?id=a unique ID" />     

The image is produced by the BinaryServlet (see Listing 13-8). Of course, the servlet needs to know the customization data. The renderer gathers the data from the component attributes in the usual way, bundles them into a transfer object (see Listing 13-10), and places the transfer object into the session map.

  Map<String, Object> attributes = component.getAttributes();   Integer width = (Integer) attributes.get("width");   if (width == null) width = DEFAULT_WIDTH;   Integer height = (Integer) attributes.get("height");   if (height == null) height = DEFAULT_HEIGHT;   String title = (String) attributes.get("title");   if (title == null) title = "";   String[] names = (String[]) attributes.get("names");   double[] values = (double[]) attributes.get("values");   ChartData data = new ChartData();   data.setWidth(width);   data.setHeight(height);   data.setTitle(title);   data.setNames(names);   data.setValues(values);   String id = component.getClientId(context);   ExternalContext external = FacesContext.getCurrentInstance().getExternalContext();   Map<String, Object> session = external.getSessionMap();   session.put(id, data);     

The servlet retrieves the transfer object from the session map and calls the transfer object's write method, which renders the image into the response stream.

  HttpSession session = request.getSession();   String id = request.getParameter("id");   BinaryData data = (BinaryData) session.getAttribute(id);   response.setContentType(data.getContentType());   OutputStream out = response.getOutputStream();   data.write(out);   out.close();

To keep the servlet code general, we require that the transfer class implements an interface BinaryData (see Listing 13-9).

You use the same approach to generate any kind of binary data. The only difference is the code for writing data to the output stream.

Listing 13-6. binary1/web/index.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.    <%@ taglib uri="http://corejsf.com/chart" prefix="corejsf" %>   5.    <f:view>   6.       <head>   7.          <title>Generating binary data</title>   8.       </head>   9.       <body>  10.          <h:form>  11.             <p>Here is your image:</p>  12.             <corejsf:chart width="500" height="500"  13.                title="Diameters of the Planets"  14.                names="#{planets.names}" values="#{planets.values}"/>  15.          </h:form>  16.       </body>  17.    </f:view>  18. </html>

Listing 13-7. binary1/src/java/com/corejsf/ChartRenderer.java

  1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.util.Map;   5. import javax.faces.component.UIComponent;   6. import javax.faces.context.ExternalContext;   7. import javax.faces.context.FacesContext;   8. import javax.faces.context.ResponseWriter;   9. import javax.faces.render.Renderer;  10.  11. public class ChartRenderer extends Renderer {  12.    private static final int DEFAULT_WIDTH = 200;  13.    private static final int DEFAULT_HEIGHT = 200;  14.  15.    public void encodeBegin(FacesContext context, UIComponent component)  16.       throws IOException {  17.       if (!component.isRendered()) return;  18.  19.       Map<String, Object> attributes = component.getAttributes();  20.       Integer width = (Integer) attributes.get("width");  21.       if (width == null) width = DEFAULT_WIDTH;  22.       Integer height = (Integer) attributes.get("height");  23.       if (height == null) height = DEFAULT_HEIGHT;  24.       String title = (String) attributes.get("title");  25.       if (title == null) title = "";  26.       String[] names = (String[]) attributes.get("names");  27.       double[] values = (double[]) attributes.get("values");  28.       if (names == null || values == null) return;  29.  30.       ChartData data = new ChartData();  31.       data.setWidth(width);  32.       data.setHeight(height);  33.       data.setTitle(title);  34.       data.setNames(names);  35.       data.setValues(values);  36.  37.       String id = component.getClientId(context);  38.       ExternalContext external  39.             = FacesContext.getCurrentInstance().getExternalContext();  40.       Map<String, Object> session = external.getSessionMap();  41.       session.put(id, data);  42.  43.       ResponseWriter writer = context.getResponseWriter();  44.       writer.startElement("img", component);  45.  46.       writer.writeAttribute("width", width, null);  47.       writer.writeAttribute("height", height, null);  48.       String path = external.getRequestContextPath();  49.       writer.writeAttribute("src", path + "/BinaryServlet?img");  51.  52.       context.responseComplete();  53.    }  54. }     

Listing 13-8. binary1/src/java/com/corejsf/BinaryServlet.java

  1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.io.OutputStream;   5.   6. import javax.servlet.ServletException;   7. import javax.servlet.http.HttpServlet;   8. import javax.servlet.http.HttpServletRequest;   9. import javax.servlet.http.HttpServletResponse;  10. import javax.servlet.http.HttpSession;  11.  12. public class BinaryServlet extends HttpServlet {  13.    protected void processRequest(HttpServletRequest request,  14.          HttpServletResponse response)  15.       throws ServletException, IOException {  16.  17.       HttpSession session = request.getSession();  18.       String id = request.getParameter("id");  19.       BinaryData data = (BinaryData) session.getAttribute(id);  20.  21.       response.setContentType(data.getContentType());  22.       OutputStream out = response.getOutputStream();  23.       data.write(out);  24.       out.close();  25.    }  26.  27.    protected void doGet(HttpServletRequest request, HttpServletResponse response)  28.    throws ServletException, IOException {  29.       processRequest(request, response);  30.    }  31.  32.    protected void doPost(HttpServletRequest request, HttpServletResponse response)  33.    throws ServletException, IOException {  34.       processRequest(request, response);  35.    }  36. }     

Listing 13-9. binary1/src/java/com/corejsf/BinaryData.java

  1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.io.OutputStream;   5.   6. public interface BinaryData {   7.    String getContentType();   8.    void write(OutputStream out) throws IOException;   9. }

Listing 13-10. binary1/src/java/com/corejsf/ChartData.java

  1. package com.corejsf;   2.   3. import java.awt.Color;   4. import java.awt.Font;   5. import java.awt.Graphics2D;   6. import java.awt.font.FontRenderContext;   7. import java.awt.font.LineMetrics;   8. import java.awt.geom.Rectangle2D;   9. import java.awt.image.BufferedImage;  10. import java.io.IOException;  11. import java.io.OutputStream;  12. import java.util.Iterator;  13. import javax.imageio.ImageIO;  14. import javax.imageio.ImageWriter;  15.  16. public class ChartData implements BinaryData {  17.  18.    private int width, height;  19.    private String title;  20.    private String[] names;  21.    private double[] values;  22.  23.    private static final int DEFAULT_WIDTH = 200;  24.    private static final int DEFAULT_HEIGHT = 200;  25.  26.    public ChartData() {  27.       width = DEFAULT_WIDTH;  28.       height = DEFAULT_HEIGHT;  29.    }  30.  31.    public void setWidth(int width) {  32.       this.width = width;  33.    }  34.  35.    public void setHeight(int height) {  36.       this.height = height;  37.    }  38.  39.    public void setTitle(String title) {  40.       this.title = title;  41.    }  42.  43.    public void setNames(String[] names) {  44.       this.names = names;  45.    }  46.  47.    public void setValues(double[] values) {  48.       this.values = values;  49.    }  50.  51.    public String getContentType() {  52.       return "image/jpeg";  53.    }  54.  55.    public void write(OutputStream out) throws IOException {  56.       BufferedImage image = new BufferedImage(width, height,  57.          BufferedImage.TYPE_INT_RGB);  58.       Graphics2D g2 = (Graphics2D) image.getGraphics();  59.       drawChart(g2, width, height, title, names, values);  60.  61.       Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");  62.       ImageWriter writer = iter.next();  63.       writer.setOutput(ImageIO.createImageOutputStream(out));  64.       writer.write(image);  65.    }  66.  67.    private static void drawChart(Graphics2D g2, int width, int height,  68.       String title, String[] names, double[] values)  69.    {  70.       // clear the background  71.       g2.setPaint(Color.WHITE);  72.       g2.fill(new Rectangle2D.Double(0, 0, width, height));  73.       g2.setPaint(Color.BLACK);  74.  75.       if (names == null || values == null || names.length != values.length)  76.          return;  77.  78.       // compute the minimum and maximum values  79.       if (values == null) return;  80.       double minValue = 0;  81.       double maxValue = 0;  82.       for (double v : values) {  83.          if (minValue > v) minValue = v;  84.          if (maxValue < v) maxValue = v;  85.       }  86.       if (maxValue == minValue) return;  87.  88.       Font titleFont = new Font("SansSerif", Font.BOLD, 20);  89.       Font labelFont = new Font("SansSerif", Font.PLAIN, 10);  90.  91.       // compute the extent of the title  92.       FontRenderContext context = g2.getFontRenderContext();  93.       Rectangle2D titleBounds  94.          = titleFont.getStringBounds(title, context);  95.       double titleWidth = titleBounds.getWidth();  96.       double top = titleBounds.getHeight();  97.  98.       // draw the title  99.       double y = -titleBounds.getY(); // ascent 100.       double x = (width - titleWidth) / 2; 101.       g2.setFont(titleFont); 102.       g2.drawString(title, (float)x, (float)y); 103. 104.       // compute the extent of the bar labels 105.       LineMetrics labelMetrics 106.          = labelFont.getLineMetrics("", context); 107.       double bottom = labelMetrics.getHeight(); 108. 109.       y = height - labelMetrics.getDescent(); 110.       g2.setFont(labelFont); 111. 112.       // get the scale factor and width for the bars 113.       double scale = (height - top - bottom) 114.          / (maxValue - minValue); 115.       int barWidth = width / values.length; 116. 117.       // draw the bars 118.       for (int i = 0; i < values.length; i++) { 119.          // get the coordinates of the bar rectangle 120.          double x1 = i * barWidth + 1; 121.          double y1 = top; 122.          double barHeight = values[i] * scale; 123.          if (values[i] >= 0) 124.             y1 += (maxValue - values[i]) * scale; 125.          else { 126.             y1 += maxValue * scale; 127.             barHeight = -barHeight; 128.          } 129. 130.          // fill the bar and draw the bar outline 131.          Rectangle2D rect = new Rectangle2D.Double(x1, y1, 132.             barWidth - 2, barHeight); 133.          g2.setPaint(Color.RED); 134.          g2.fill(rect); 135.          g2.setPaint(Color.BLACK); 136.          g2.draw(rect); 137. 138.          // draw the centered label below the bar 139.          Rectangle2D labelBounds 140.             = labelFont.getStringBounds(names[i], context); 141. 142.          double labelWidth = labelBounds.getWidth(); 143.          x = i * barWidth + (barWidth - labelWidth) / 2; 144.          g2.drawString(names[i], (float)x, (float)y); 145.       } 146.    } 147. }     

It is also possible to generate binary data directly from JSF, without a servlet. However, you must be very careful with the timing and grab the servlet output stream before the JSF implementation starts writing the response. Grabbing the servlet output stream cannot happen in a component renderer. A JSF component contributes to the page output, but it does not replace it.

Instead, we install a phase listener that is activated after the Restore View phase. It writes the binary data and then calls the responseComplete method to skip the other phases.

  public class BinaryPhaseListener implements PhaseListener {      public PhaseId getPhaseId() {         return PhaseId.RESTORE_VIEW;      }     ...     public void afterPhase(PhaseEvent event) {        if (!event.getFacesContext().getViewRoot().getViewId()              .startsWith("/binary")) return;        HttpServletResponse servletResponse           = (HttpServletResponse) external.getResponse();        servletResponse.setContentType(data.getContentType());        OutputStream out = servletResponse.getOutputStream();        write data to out        context.responseComplete();     }  }

The filter action happens only with view IDs that start with /binary. As with the servlet solution, the key for the data transfer object is included as a GET parameter.

To trigger the filter, the image URL needs to be a valid JSF URL such as appname/binary.faces?id=key or appname/faces/binary?id=key. The exact type depends on the mapping of the Faces servlet. The renderer obtains the correct format from the view handler's getActionURL method:

  ViewHandler handler = context.getApplication().getViewHandler();   String url = handler.getActionURL(context, "/binary);

Listing 13-11 shows the phase listener. The following element is required in faces-config.xml to install the listener:

  <lifecycle>      <phase-listener>com.corejsf.BinaryPhaseListener</phase-listener>   </lifecycle>

Listing 13-11. binary2/src/java/com/corejsf/BinaryPhaseListener.java

  1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.io.OutputStream;   5. import java.util.Map;   6. import javax.faces.FacesException;   7. import javax.faces.context.ExternalContext;   8. import javax.faces.context.FacesContext;   9. import javax.faces.event.PhaseEvent;  10. import javax.faces.event.PhaseId;  11. import javax.faces.event.PhaseListener;  12. import javax.servlet.http.HttpServletResponse;  13.  14. public class BinaryPhaseListener implements PhaseListener {  15.    public static final String BINARY_PREFIX = "/binary";  16.  17.    public static final String DATA_ID_PARAM = "id";  18.  19.    public PhaseId getPhaseId() {  20.       return PhaseId.RESTORE_VIEW;  21.    }  22.  23.    public void beforePhase(PhaseEvent event) {  24.    }  25.  26.    public void afterPhase(PhaseEvent event) {  27.       if (!event.getFacesContext().getViewRoot().getViewId().startsWith(  28.             BINARY_PREFIX))  29.          return;  30.  31.       FacesContext context = event.getFacesContext();  32.       ExternalContext external = context.getExternalContext();  33.  34.       String id = (String) external.getRequestParameterMap().get(DATA_ID_PARAM);  35.       HttpServletResponse servletResponse =  36.             (HttpServletResponse) external.getResponse();  37.       try {  38.          Map<String, Object> session = external.getSessionMap();  39.          BinaryData data = (BinaryData) session.get(id);  40.          if (data != null) {  41.             servletResponse.setContentType(data.getContentType());  42.             OutputStream out = servletResponse.getOutputStream();  43.             data.write(out);  44.          }  45.       } catch (IOException ex) {  46.          throw new FacesException(ex);  47.       }  48.       context.responseComplete();  49.    }  50. }     

How Do I Show a Large Data Set, One Page at a Time?

As you saw in Chapter 5, you can add scrollbars to a table. But if the table is truly large, you don't want it sent to the client in its entirety. Downloading the table takes a long time, and chances are that the application user wants to see only the first few rows anyway.

The standard user interface for navigating a large table is a pager, a set of links to each page of the table, to the next and previous pages, and if there are a great number of pages, to the next and previous batch of pages. Figure 13-8 shows a pager that scrolls through a large data set the predefined time zones, obtained by a call to java.util.TimeZone.getAvailableIDs().

Figure 13-8. Table with a pager


Unfortunately, JSF does not include a pager component. However, it is fairly easy to write one, and we give you the code to use or modify in your own applications.

The pager is a companion to a data table. You specify the ID of the data table, the number of pages that the pager displays, and the styles for the selected and unselected links. For example,

  <h:dataTable  value="#{bb.data}" var="row" rows="10">      ...   </h:dataTable>   <corejsf:pager dataTable showpages="20"   selectedStyle/>

Suppose the user clicks the ">" link to move to the next page. The pager locates the data table and updates its first property, adding the value of the rows property. You will find that code in the decode method of the PagerRenderer in Listing 13-12.

The encode method is a bit more involved. It generates a set of links. Similar to a commandLink, clicking the link activates JavaScript code that sets a value in a hidden field and submits the form.

Listing 13-13 shows the index.jsp page that generates the table and the pager. Listing 13-14 shows the trivial backing bean.

Note

The MyFaces data scroller component (http://myfaces.apache.org/tomahawk/dataScroller.html) offers similar functionality.


Listing 13-12. pager/src/java/com/corejsf/PagerRenderer.java

  1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.util.Map;   5. import javax.faces.component.UIComponent;   6. import javax.faces.component.UIData;   7. import javax.faces.component.UIForm;   8. import javax.faces.context.FacesContext;   9. import javax.faces.context.ResponseWriter;  10. import javax.faces.render.Renderer;  11.  12. public class PagerRenderer extends Renderer {  13.    public void encodeBegin(FacesContext context, UIComponent component)  14.       throws IOException {  15.       String id = component.getClientId(context);  16.       UIComponent parent = component;  17.       while (!(parent instanceof UIForm)) parent = parent.getParent();  18.       String formId = parent.getClientId(context);  19.  20.       ResponseWriter writer = context.getResponseWriter();  21.  22.       String styleClass = (String) component.getAttributes().get("styleClass");  23.       String selectedStyleClass  24.          = (String) component.getAttributes().get("selectedStyleClass");  25.       String dataTableId = (String) component.getAttributes().get("dataTableId");  26.       Integer a = (Integer) component.getAttributes().get("showpages");  27.       int showpages = a == null ? 0 : a.intValue();  28.  29.       // find the component with the given ID  30.  31.       UIData data = (UIData) component.findComponent(dataTableId);  32.  33.       int first = data.getFirst();  34.       int itemcount = data.getRowCount();  35.       int pagesize = data.getRows();  36.       if (pagesize <= 0) pagesize = itemcount;  37.  38.       int pages = itemcount / pagesize;  39.       if (itemcount % pagesize != 0) pages++;  40.  41.       int currentPage = first / pagesize;  42.       if (first >= itemcount - pagesize) currentPage = pages - 1;  43.       int startPage = 0;  44.       int endPage = pages;  45.       if (showpages > 0) {  46.          startPage = (currentPage / showpages) * showpages;  47.          endPage = Math.min(startPage + showpages, pages);  48.       }  49.  50.       if (currentPage > 0)  51.          writeLink(writer, component, formId, id, "<", styleClass);  52.  53.       if (startPage > 0)  54.          writeLink(writer, component, formId, id, "<<", styleClass);  55.  56.       for (int i = startPage; i < endPage; i++) {  57.           writeLink(writer, component, formId, id, "" + (i + 1),  58.              i == currentPage ? selectedStyleClass : styleClass);  59.       }  60.  61.       if (endPage < pages)  62.          writeLink(writer, component, formId, id, ">>", styleClass);  63.  64.       if (first < itemcount - pagesize)  65.          writeLink(writer, component, formId, id, ">", styleClass);  66.  67.       // hidden field to hold result  68.       writeHiddenField(writer, component, id);  69.    }  70.  71.    private void writeLink(ResponseWriter writer, UIComponent component,  72.       String formId, String id, String value, String styleClass)  73.       throws IOException {  74.       writer.writeText(" ", null);  75.       writer.startElement("a", component);  76.       writer.writeAttribute("href", "#", null);  77.       writer.writeAttribute("onclick", onclickCode(formId, id, value), null);  78.       if (styleClass != null)  79.          writer.writeAttribute("class", styleClass, "styleClass");  80.       writer.writeText(value, null);  81.       writer.endElement("a");  82.    }  83.  84.    private String onclickCode(String formId, String id, String value) {  85.       StringBuilder builder = new StringBuilder();  86.       builder.append("document.forms[");  87.       builder.append("'");  88.       builder.append(formId);  89.       builder.append("'");  90.       builder.append("]['");  91.       builder.append(id);  92.       builder.append("'].value='");  93.       builder.append(value);  94.       builder.append("';");  95.       builder.append(" document.forms[");  96.       builder.append("'");  97.       builder.append(formId);  98.       builder.append("'");  99.       builder.append("].submit()"); 100.       builder.append("; return false;"); 101.       return builder.toString(); 102.    } 103. 104.    private void writeHiddenField(ResponseWriter writer, UIComponent component, 105.       String id) throws IOException { 106.       writer.startElement("input", component); 107.       writer.writeAttribute("type", "hidden", null); 108.       writer.writeAttribute("name", id, null); 109.       writer.endElement("input"); 110.    } 111. 112.    public void decode(FacesContext context, UIComponent component) { 113.       String id = component.getClientId(context); 114.       Map<String, String> parameters 115.          = context.getExternalContext().getRequestParameterMap(); 116. 117.       String response = (String) parameters.get(id); 118.       if (response == null || response.equals("")) return; 119. 120.       String dataTableId = (String) component.getAttributes().get("dataTableId"); 121.       Integer a = (Integer) component.getAttributes().get("showpages"); 122.       int showpages = a == null ? 0 : a.intValue(); 123. 124.       UIData data = (UIData) component.findComponent(dataTableId); 125. 126.       int first = data.getFirst(); 127.       int itemcount = data.getRowCount(); 128.       int pagesize = data.getRows(); 129.       if (pagesize <= 0) pagesize = itemcount; 130. 131.       if (response.equals("<")) first -= pagesize; 132.       else if (response.equals(">")) first += pagesize; 133.       else if (response.equals("<<")) first -= pagesize * showpages; 134.       else if (response.equals(">>")) first += pagesize * showpages; 135.       else { 136.          int page = Integer.parseInt(response); 137.          first = (page - 1) * pagesize; 138.       } 139.       if (first + pagesize > itemcount) first = itemcount - pagesize; 140.       if (first < 0) first = 0; 141.       data.setFirst(first); 142.    } 143. }     

Listing 13-13. pager/web/index.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.    <%@ taglib uri="http://corejsf.com/pager" prefix="corejsf" %>   5.   6.    <f:view>   7.       <head>   8.          <link href="styles.css" rel="stylesheet" type="text/css"/>   9.          <title>Pager Test</title>  10.        </head>  11.        <body>  12.           <h:form>  13.              <h:dataTable  value="#{bb.data}" var="row" rows="10">  14.                 <h:column>  15.                   <h:selectBooleanCheckbox value="{bb.dummy}" onchange="submit()"/>  16.                 </h:column>  17.                 <h:column>  18.                    <h:outputText value="#{row}" />  19.                 </h:column>  20.              </h:dataTable>  21.              <corejsf:pager dataTable  22.                 showpages="20" selectedStyle/>  23.                 <h:commandButton value="foo"/>  24.           </h:form>  25.        </body>  26.     </f:view>  27. </html>     

Listing 13-14. pager/src/java/com/corejsf/BackingBean.java

  1. package com.corejsf;   2.   3. public class BackingBean {   4.    private String[] data = java.util.TimeZone.getAvailableIDs();   5.    public String[] getData() { return data; }   6.   7.    public boolean getDummy() { return false; }   8.    public void setDummy(boolean b) {}   9. }

How Do I Generate a Pop-up Window?

The basic method for a pop-up window is simple. Use the JavaScript calls

  popup = window.open(url, name, features);   popup.focus();

The features parameter is a string, such as

  "height=300,width=200,toolbar=no,menubar=no"

The pop-up window should be displayed when the user clicks a button or link. Attach a function to the onclick handler of the button or link, and have the function return false so that the browser does not submit the form or follow the link. For example,

  <h:commandButton value="..." onclick="doPopup(this); return false;"/>

The doPopup function contains the JavaScript instructions for popping up the window. It is contained in a script tag inside the page header.

However, challenges arise when you need to transfer data between the main window and the pop-up.

Now we look at a specific example. Figure 13-9 shows a page with a pop-up window that lists the states of the USA or the provinces of Canada, depending on the setting of the radio buttons. The list is generated by a backing bean on the server.

Figure 13-9. Popping up a window to select a state or province


How does the backing bean know which state was selected? After all, the form has not yet been posted back to the server when the user requests the pop-up. We show you two solutions each of them is interesting in its own right and may give you ideas for solving similar problems.

In the first solution, we pass the selection parameter to the pop-up URL, like this:

  window.open("popup.faces?country=" + country[i].value, "popup", features);     

The popup.faces page retrieves the value of the country request parameter as param.country:

  <h:dataTable value="#{bb.states[param.country]}" var="state">

Here, the states property of the backing bean bb yields a map whose index is the country name.

The second solution (suggested by Marion Bass and Sergey Smirnov) is more involved but also more powerful. In this technique, the pop-up window is first created as a blank window and then filled with the response to a JSF command.

The JSF command is issued by a form that contains a hidden field and an invisible link, like this:

  <h:form  target="popup">      <h:inputHidden  value="#{bb.country}"/>      <h:commandLink  action="showStates"/>   </h:form>

Note the following details:

  • The target of the form has the same name as the pop-up window. Therefore, the browser will show the result of the action inside the pop-up.

  • The hidden country field will be populated before the form is submitted. It sets the bb.country value expression. This enables the backing bean to return the appropriate set of states or provinces.

  • The action attribute of the command link is used by the navigation handler to select the JSF page that generates the pop-up contents.

The doPopup function initializes the hidden field and fires the link action:

  document.getElementById("hidden:country").value = country[i].value;   document.getElementById("hidden:go").onclick(null);

The value of the selected state or province is transferred into the hidden field. When the hidden form is submitted, that value will be stored in the backing bean.

In this solution, the JSF page for the pop-up is more straightforward. The table of states or provinces is populated by the bean property call

  <h:dataTable value="#{bb.statesForCountry}" var="state">

The statesForCountry property takes the country property into account it was set when the hidden form was decoded. This approach is more flexible than the first approach because it allows arbitrary bean properties to be set before the pop-up contents are computed.

With both approaches, it is necessary to send the pop-up data back to the original page. However, this can be achieved with straightforward JavaScript. The pop-up's opener property is the window that opened the pop-up. When the user clicks a link in the pop-up, we set the value of the corresponding text field in the original page:

  opener.document.forms[formId][formId + ":state"].value = value;

How does the pop-up know the form ID of the original form? Here we take advantage of the flexibility of JavaScript. You can add instance fields to any object on-the-fly. We set an openerFormId field in the pop-up window when it is constructed:

  popup = window.open(...);   popup.openerFormId = source.form.id;

When we are ready to modify the form variables, we retrieve it from the popup window, like this:

  var formId = window.openerFormId;

These are the tricks that you need to know to deal with pop-up windows. The following example shows the two approaches that we discussed. The index.jsp and popup.jsp files in Listing 13-15 and Listing 13-16 show the first approach, using a request parameter to configure the pop-up page.

The index2.jsp and popup2.jsp files in Listing 13-17 and Listing 13-18 show the second approach, filling the pop-up page with the result of a JSF action. Listing 13-19 shows the backing bean, and Listing 13-20 shows the configuration file. Note how the showStates action leads to the popup2.jsp page.

Listing 13-15. popup/web/index.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.   5.    <f:view>   6.       <head>   7.          <script language="JavaScript1.1">   8.             function doPopup(source) {   9.                country = source.form[source.form.id + ":country"];  10.                for (var i = 0; i < country.length; i++) {  11.                   if (country[i].checked) {  12.                      popup = window.open("popup.faces?country="  13.                         + country[i].value, "popup",  14.                         "height=300,width=200,toolbar=no,menubar=no,"  15.                         + "scrollbars=yes");  16.                      popup.openerFormId = source.form.id;  17.                      popup.focus();  18.                   }  19.                }  20.             }  21.       </script>  22.          <title>A Simple Java Server Faces Application</title>  23.       </head>  24.       <body>  25.          <h:form>  26.             <table>  27.                <tr>  28.                   <td>Country:</td>  29.                   <td>  30.                      <h:selectOneRadio  value="#{bb.country}">  31.                         <f:selectItem itemLabel="USA"  itemValue="USA"/>  32.                         <f:selectItem itemLabel="Canada"  itemValue="Canada"/>  33.                      </h:selectOneRadio>  34.                   </td>  35.                </tr>  36.                <tr>  37.                   <td>State/Province:</td>  38.                   <td>  39.                      <h:inputText  value="#{bb.state}"/>  40.                   </td>  41.                   <td>  42.                      <h:commandButton value="..."  43.                         onclick="doPopup(this); return false;"/>  44.                   </td>  45.                </tr>  46.             </table>  47.             <p>  48.                <h:commandButton value="Next" action="next"/>  49.             </p>  50.          </h:form>  51.       </body>  52.    </f:view>  53. </html>     

Listing 13-16. popup/web/popup.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.   5.    <f:view>   6.       <head>   7.          <script type="text/javascript" language="JavaScript1.2">   8.             function doSave(value) {   9.                var formId = window.openerFormId;  10.                opener.document.forms[formId][formId + ":state"].value = value;  11.                window.close();  12.             }  13.          </script>  14.          <title>Select a state/province</title>  15.       </head>  16.       <body>  17.          <h:form>  18.             <h:dataTable value="#{bb.states[param.country]}" var="state">  19.                <h:column>  20.                   <h:outputLink value="#"  21.                      onclick="doSave('#{state}');">  22.                      <h:outputText value="#{state}" />  23.                   </h:outputLink>  24.                </h:column>  25.             </h:dataTable>  26.          </h:form>  27.       </body>  28.    </f:view>  29. </html>     

Listing 13-17. popup/web/index2.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.   5.    <f:view>   6.       <head>   7.          <script language="JavaScript1.1">   8.             function doPopup(source) {   9.                country = source.form[source.form.id + ":country"];  10.                for (var i = 0; i < country.length; i++) {  11.                   if (country[i].checked) {  12.                      popup = window.open("",  13.                         "popup",  14.                         "height=300,width=200,toolbar=no,menubar=no,"  15.                         + "scrollbars=yes");  16.                      popup.openerFormId = source.form.id;  17.                      popup.focus();  18.                      document.getElementById("hidden:country").value  19.                         = country[i].value;  20.                      document.getElementById("hidden:go").onclick(null);  21.                   }  22.                }  23.             }  24.          </script>  25.          <title>A Simple Java Server Faces Application</title>  26.       </head>  27.       <body>  28.          <h:form>  29.             <table>  30.                <tr>  31.                   <td>Country:</td>  32.                   <td>  33.                      <h:selectOneRadio  value="#{bb.country}">  34.                         <f:selectItem itemLabel="USA"  itemValue="USA"/>  35.                         <f:selectItem itemLabel="Canada"  itemValue="Canada"/>  36.                      </h:selectOneRadio>  37.                   </td>  38.                </tr>  39.                <tr>  40.                   <td>State/Province:</td>  41.                   <td>  42.                      <h:inputText  value="#{bb.state}"/>  43.                   </td>  44.                   <td>  45.                      <h:commandButton value="..."  46.                         onclick="doPopup(this); return false;"/>  47.                   </td>  48.                </tr>  49.             </table>  50.             <p>  51.                <h:commandButton value="Next" action="next"/>  52.             </p>  53.          </h:form>  54.  55.          <%-- This hidden form sends a request to a popup window. --%>  56.          <h:form  target="popup">  57.             <h:inputHidden  value="#{bb.country}"/>  58.             <h:commandLink  action="showStates"/>  59.          </h:form>  60.       </body>  61.    </f:view>  62. </html>     

Listing 13-18. popup/web/popup2.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.   5.    <f:view>   6.       <head>   7.          <script language="JavaScript1.1">   8.             function doSave(value) {   9.                var formId = window.openerFormId;  10.                opener.document.forms[formId][formId + ":state"].value = value;  11.                window.close();  12.             }  13.          </script>  14.          <title>Select a state/province</title>  15.       </head>  16.       <body>  17.          <h:form>  18.             <h:dataTable value="#{bb.statesForCountry}" var="state">  19.                <h:column>  20.                   <h:outputLink value="#"  21.                      onclick="doSave('#{state}');">  22.                      <h:outputText value="#{state}" />  23.                   </h:outputLink>  24.                </h:column>  25.             </h:dataTable>  26.          </h:form>  27.       </body>  28.    </f:view>  29. </html>     

Listing 13-19. popup/src/java/com/corejsf/BackingBean.java

  1. package com.corejsf;   2.   3. import java.util.HashMap;   4. import java.util.Map;   5.   6. public class BackingBean {   7.    private String country = "USA";   8.    private String state = "";   9.    private static Map<String, String[]> states;  10.  11.    // PROPERTY: country  12.    public String getCountry() { return country; }  13.    public void setCountry(String newValue) { country = newValue; }  14.  15.    // PROPERTY: state  16.    public String getState() { return state; }  17.    public void setState(String newValue) { state = newValue; }  18.  19.    public Map<String, String[]> getStates() { return states; }  20.  21.    public String[] getStatesForCountry() { return (String[]) states.get(country); }  22.  23.    static {  24.       states = new HashMap<String, String[]>();  25.       states.put("USA",  26.          new String[] {  27.             "Alabama", "Alaska", "Arizona", "Arkansas", "California",  28.             "Colorado", "Connecticut", "Delaware", "Florida", "Georgia",  29.             "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas",  30.             "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts",  31.             "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana",  32.             "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico",  33.             "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma",  34.             "Oregon", "Pennsylvania", "Rhode Island", "South Carolina",  35.             "South Dakota", "Tennessee", "Texas", "Utah", "Vermont",  36.             "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"  37.          });  38.  39.       states.put("Canada",  40.          new String[] {  41.             "Alberta", "British Columbia", "Manitoba", "New Brunswick",  42.             "Newfoundland and Labrador", "Northwest Territories",  43.             "Nova Scotia", "Nunavut", "Ontario", "Prince Edward Island",  44.             "Quebec", "Saskatchewan", "Yukon"  45.          });  46.    }  47. }     

Listing 13-20. popup/web/WEB-INF/faces-config.xml

  1. <?xml version="1.0"?>   2.   3. <faces-config xmlns="http://java.sun.com/xml/ns/javaee"   4.    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   5.    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   6.         http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"   7.    version="1.2">   8.    <navigation-rule>   9.       <navigation-case>  10.          <from-outcome>next</from-outcome>  11.          <to-view-id>/welcome.jsp</to-view-id>  12.       </navigation-case>  13.       <navigation-case>  14.          <from-outcome>showStates</from-outcome>  15.          <to-view-id>/popup2.jsp</to-view-id>  16.       </navigation-case>  17.       <navigation-case>  18.          <from-outcome>technique1</from-outcome>  19.          <to-view-id>/index.jsp</to-view-id>  20.       </navigation-case>  21.       <navigation-case>  22.          <from-outcome>technique2</from-outcome>  23.          <to-view-id>/index2.jsp</to-view-id>  24.       </navigation-case>  25.    </navigation-rule>  26.  27.    <managed-bean>  28.       <managed-bean-name>bb</managed-bean-name>  29.       <managed-bean-class>com.corejsf.BackingBean</managed-bean-class>  30.       <managed-bean-scope>session</managed-bean-scope>  31.    </managed-bean>  32. </faces-config>     

How Do I Selectively Show and Hide Components?

It is very common to show or hide parts of a page, depending on some condition. For example, when a user is not logged on, you may want to show input fields for the username and password. But if a user is logged on, you would want to show the username and a logout button.

It would be wasteful to design two separate pages that differ in this small detail. Instead, we want to include all components in our page and selectively display them.

You can solve this issue with the JSTL c:if construct. However, mixing JSF and JSTL tags is unsightly. It is easy to achieve the same effect with JSF alone.

If you want to enable or disable one component (or a container like a panel group), use the rendered property, such as

  <h:panelGroup rendered="#{userBean.loggedIn}">...</h:panelGroup>

If you want to switch between two component sets, you can use complementary rendered attributes:

  <h:panelGroup rendered="#{!userBean.loggedIn}">...</h:panelGroup>   <h:panelGroup rendered="#{userBean.loggedIn}">...</h:panelGroup>

For more than two choices, it is best to use a component, such as panelStack in the Apache MyFaces components library (http://myfaces.apache.org/tomahawk/panelStack.html). A panel stack is similar to the tabbed pane that you saw in Chapter 9, except that there are no tabs. Instead, one of the child components is selected programmatically.

With the panelStack, each child component must have an ID. The selectedPanel attribute specifies the ID of the child that is rendered:

  <t:panelStack selectedPanel="#{userBean.status}>      <h:panelGroup >...</h:panelGroup>      <h:panelGroup >...</h:panelGroup>      <h:panelGroup >...</h:panelGroup>   </t:panelStack>

The getStatus method of the user bean should return a string "new", "loggedIn, or "loggedOut".

How Do I Customize Error Pages?

You probably do not want your users to see scary stack traces when they run into an error in your web application. There are two mechanisms for customizing the display of errors.

You can specify an error page for a specific JSF page with the following JSP directive:

  <%@ page errorPage="error.jsp" %>

When an error occurs during execution of the Java code of the compiled page, the error.jsp page is displayed. However, this mechanism is not often useful for JSF programmers. Errors that happen during page compilation or during execution of deferred expressions do not trigger the JSP error page.

It is better to use the error-page tag in the web.xml file. Specify either a Java exception class or an HTTP error code. For example,

  <error-page>      <exception-type>java.lang.Exception</exception-type>      <location>/exception.jsp</location>   </error-page>   <error-page>      <error-code>500</error-code>      <location>/error.jsp</location>   </error-page>   <error-page>      <error-code>404</error-code>      <location>/notfound.jsp</location>   </error-page>

If an exception occurs and an error page matches its type, then the matching error page is displayed. Otherwise, an HTTP error 500 is generated.

If an HTTP error occurs and there is a matching error page, it is displayed. Otherwise, the default error page is displayed.

Caution

If an error occurs while your application is trying to display a custom error page, the default error page is displayed instead. If your custom error page stubbornly refuses to appear, check the log files for messages relating to your error page.


If you use the JSP errorPage directive, the exception object is available in the request map with the key "javax.servlet.jsp.jspException". If you use the servlet error-page mechanism, several objects related to the error are placed in the request map (see Table 13-2). You can use these values to display information that describes the error.

Table 13-2. Servlet Exception Attributes
Key Value Type
javax.servlet.error.status_code The HTTP error code Integer
javax.servlet.error.message A description of the error String
javax.servlet.error.exception_type The class of the exception Class
javax.servlet.error.exception The exception object Throwable
javax.servlet.error.request_uri The path to the application resource that encountered the error String
javax.servlet.error.servlet_name The name of the servlet that encountered the error String


The following sample application uses this technique. We purposely produce a null pointer exception in the password property of the UserBean, resulting in the error report shown in Figure 13-10. Listing 13-21 shows the web.xml file that sets the error page to errorDisplay.jsp (Listing 13-22).

Figure 13-10. A customized error display


Listing 13-23 shows the ErrorBean class. Its getStackTrace method assembles a complete stack trace that contains all nested exceptions.

Note

The errorDisplay.jsp page uses an f:subview tag. This is a workaround for an anomaly in the JSF reference implementation using f:view in an error page causes an assertion error in the framework code.


Listing 13-21. error/web/WEB-INF/web.xml

  1. <?xml version="1.0"?>   2. <web-app xmlns="http://java.sun.com/xml/ns/javaee"   3.    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   4.    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   5.       http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"   6.    version="2.5">   7.    <servlet>   8.       <servlet-name>Faces Servlet</servlet-name>   9.       <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>  10.       <load-on-startup>1</load-on-startup>  11.    </servlet>  12.  13.    <servlet-mapping>  14.       <servlet-name>Faces Servlet</servlet-name>  15.       <url-pattern>*.faces</url-pattern>  16.    </servlet-mapping>  17.  18.    <welcome-file-list>  19.       <welcome-file>/index.html</welcome-file>  20.    </welcome-file-list>  21.  22.    <error-page>  23.       <error-code>500</error-code>  24.       <location>/errorDisplay.faces</location>  25.    </error-page>  26. </web-app>     

Listing 13-22. error/web/errorDisplay.jsp

  1. <html>   2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>   3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>   4.   5.    <f:subview >   6.       <head>   7.          <title><h:outputText value="#{msgs.title}"/></title>   8.       </head>   9.       <body>  10.          <h:form>  11.             <p><h:outputText value="#{msgs.errorOccurred}"/></p>  12.             <p><h:outputText value="#{msgs.copyReport}"/></p>  13.             <h:inputTextarea value="#{error.stackTrace}"  14.                rows="40" cols="80" readonly="true"/>  15.          </h:form>  16.       </body>  17.    </f:subview>  18. </html>

Listing 13-23. error/src/java/com/corejsf/ErrorBean.java

  1. package com.corejsf;   2.   3. import java.io.PrintWriter;   4. import java.io.StringWriter;   5. import java.sql.SQLException;   6. import java.util.Map;   7. import javax.faces.context.FacesContext;   8. import javax.servlet.ServletException;   9.  10. public class ErrorBean {  11.    public String getStackTrace() {  12.       FacesContext context = FacesContext.getCurrentInstance();  13.       Map<String, Object> request  14.          = context.getExternalContext().getRequestMap();  15.       Throwable ex = (Throwable) request.get("javax.servlet.error.exception");  16.       StringWriter sw = new StringWriter();  17.       PrintWriter pw = new PrintWriter(sw);  18.       fillStackTrace(ex, pw);  19.       return sw.toString();  20.    }  21.  22.    private static void fillStackTrace(Throwable t, PrintWriter w) {  23.       if (t == null) return;  24.       t.printStackTrace(w);  25.       if (t instanceof ServletException) {  26.          Throwable cause = ((ServletException) t).getRootCause();  27.          if (cause != null) {  28.             w.println("Root cause:");  29.             fillStackTrace(cause, w);  30.          }  31.       } else if (t instanceof SQLException) {  32.          Throwable cause = ((SQLException) t).getNextException();  33.          if (cause != null) {  34.             w.println("Next exception:");  35.             fillStackTrace(cause, w);  36.          }  37.       } else {  38.          Throwable cause = t.getCause();  39.          if (cause != null) {  40.             w.println("Cause:");  41.             fillStackTrace(cause, w);  42.          }  43.       }  44.    }  45. }     



Core JavaServerT Faces
Core JavaServer(TM) Faces (2nd Edition)
ISBN: 0131738860
EAN: 2147483647
Year: 2004
Pages: 84

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net