|     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 DesignIn 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.      
 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.       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 |  
   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.   ICEfaces (http://icefaces.org) is an open  source library of components with Ajax support.   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.   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).      
      
 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.   
 
 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>     
      
 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).      
 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.      
   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().      
 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.      
 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).      
 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. }     
 |  |