Ajax4jsf

Ajax Components

Ajax is cool, but implementing and especially reimplementing and debugging low-level Ajax code is not cool. To rid ourselves of that burden entirely, we now turn to JSF custom components, which happen to be an excellent vehicle for encapsulating Ajax code. Once encapsulated, our custom components can be used via JSP tags to create compelling user experiences.

Hybrid Components

It should be fairly obvious that the road to Ajax bliss can be paved by implementing custom renderers that emit JavaScript code.

Even more interesting, however, are JSF components that wrap existing Java-Script components. After all, why would you want to implement components, such as accordions or drag-and-drop, from scratch when you have a wide variety of existing components to choose from: Prototype, Scriptaculous, Dojo, and Rico? Wrapping those components with JSF components, so that you can use them in your JSF applications, is a straightforward task.

The Rico Accordion

Rico is a one of a number of frameworks based on Prototype. Rico provides amenities such as drag-and-drop and a handful of useful components. One of those components is an accordion, in the Flash tradition, shown in Figure 11-7.

Figure 11-7. The Rico accordion


The Rico accordion component is similar to a tabbed pane with fancy transitions when you click the header of an accordion panel, the header animates either up or down to reveal its associated panel. Here is how you implement the accordion, shown in Figure 11-7, using HTML:

  <html>      <head>         <link href="styles.css" rel="stylesheet" type="text/css"/>         <script type='text/javascript' src='/books/2/24/1/html/2/prototype.js'></script>         <script type='text/javascript' src='/books/2/24/1/html/2/rico-1.1.2.js'></script>         <script type='text/javascript'>            function createAccordion() {               new Rico.Accordion($("theDiv"));            }         </script>      </head>      <body onload="createAccordion();">         <div  >            <div >            <div >               Fruits            </div>            <div >               <ul>                  <li>Oranges</li>                  <li>Apples</li>                  <li>Watermelon</li>                  <li>Kiwi</li>               </ul>            </div>         </div>         <div >            <div >               Vegetables            </div>            <div >               <ul>                  <li>Radishes</li>                  <li>Carrots</li>                  <li>Spinach</li>                  <li>Celery</li>               </ul>            </div>         </div>       </div>     </body>   </html>     

When the preceding page loads, Rico creates an instance of Rico.Accordion, which adds behaviors to the DIV that it's passed. In this case, Rico endows the DIV with JavaScript event handlers that react to mouse clicks in the header of each panel.

In the next section, we see how to wrap the Rico accordion in a JSF component.

The JSF-Rico Accordion Hybrid

The application shown in Figure 11-8 is a hybrid component, meaning a JSF component that wraps a JavaScript component. In this case, that JavaScript component is a Rico accordion component.

Figure 11-8. A JSF component that wraps a Rico accordion component


The Rico component automatically adds a scrollbar if the content of a panel overflows the size of the panel, so we get that functionality for free. As Figure 11-9 illustrates, you can put anything you want in an accordion panel, including forms.

Figure 11-9. Putting forms inside accordion panels


Using the accordion component is simple:

  <html>      <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>      <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>      <%@ taglib uri="http://corejsf/rico" prefix="rico"%>      <f:view>         ...         <rico:accordion name="bookAccordion"               panelHeight="175"               style               panel               header               content>            <rico:accordionPanel heading="#{msgs.whatIsAHybrid}">                     <jsp:include page="/whatIsAHybrid.jsp"/>            </rico:accordionPanel>            <rico:accordionPanel heading="#{msgs.aboutThisComponent}">               <jsp:include page="/aboutTheAccordion.jsp"/>            </rico:accordionPanel>            <rico:accordionPanel heading="#{msgs.aboutRico}">               <jsp:include page="/aboutRico.jsp"/>            </rico:accordionPanel>            <rico:accordionPanel heading="#{msgs.lookWhatYouCanDo}">               <jsp:include page="/lookWhatYouCanDo.jsp"/>            </rico:accordionPanel>         </rico:accordion>         ...      </f:view>   </html>     

The rico:accordion and rico:accordionPanel tags represent custom renderers that we pair with UICommand components. Those renderers generate the Rico-aware Java-Script that creates the Rico accordion.

The Rico-aware renderers do two things you may find useful if you decide to implement JSF components with Ajax capabilities of your own: keeping your JavaScript separate from your renderers and transmitting JSP tag attributes to that JavaScript code.

Keeping JavaScript Out of Renderers

One thing quickly becomes apparent if you start implementing Ajax-enabled custom components: You do not want to generate JavaScript with PrintWriter. write statements. It is much easier to maintain JavaScript if it is in a file of its own. Finally, it is convenient to colocate JavaScript files with the Java classes that use them. Let's see how we can do those things.

The AccordionRenderer class generates a script element whose src attribute's value is rico-script.jsf:

  public class AccordionRenderer extends Renderer {       public void encodeBegin(FacesContext fc,               UIComponent component)               throws IOException {           ResponseWriter writer = fc.getResponseWriter();           // write the script for loading the Rico JS file           writer.write("<script type='text/javascript'"                + "src='rico-script.jsf'>"                + "</script>");           ...      }   }

That src attribute rico-script.jsf results in a call to the server with the URL rico-script.jsf. That URL is handled by a phase listener:

  public class AjaxPhaseListener implements PhaseListener {       private static final String RICO_SCRIPT_REQUEST = "rico-script";       private static final String PROTOTYPE_SCRIPT_FILE = "prototype.js";       private static final String SCRIPTACULOUS_SCRIPT_FILE = "scriptaculous.js";       private static final String RICO_SCRIPT_FILE = "rico-1.1.2.js";       public PhaseId getPhaseId() {    // We need access to the view state           return PhaseId.RESTORE_VIEW; // in afterPhase()       }       public void beforePhase(PhaseEvent phaseEvent) { // not interested       }       public void afterPhase(PhaseEvent phaseEvent) { // After the RESTORE VIEW phase           FacesContext fc = FacesContext.getCurrentInstance();           if(((HttpServletRequest)fc.getExternalContext()                                     .getRequest()).getRequestURI()                                     .contains(RICO_SCRIPT_REQUEST)) {               try {                   readAndWriteFiles(fc, phaseEvent, new String[] {                           PROTOTYPE_SCRIPT_FILE,                           SCRIPTACULOUS_SCRIPT_FILE,                           RICO_SCRIPT_FILE                   });               }               catch(java.io.IOException ex) {                   ex.printStackTrace();               }               phaseEvent.getFacesContext().responseComplete();           }       }       private void readAndWriteFiles(FacesContext fc, PhaseEvent pe, STring[] files) {           // Read files and write them to the response       }   }     

If the request URI contains the string rico-script, the phase listener reads three files and writes them to the response: prototype.js, scriptaculous.js, and rico-1.1.2.js.

Realize that this roundabout way of reading JavaScript files could be avoided by simply specifying the files themselves in the script element generated by the accordion renderer; however, that would require us to hardcode the location of that file. Because we have used a phase listener to load the JavaScript files, we can colocate those JavaScript files with the phase listener, without having to explicitly specify the JavaScript file locations in the JSP pages.

Transmitting JSP Tag Attributes to JavaScript Code

If you implement Ajax-enabled JSF components, you will most likely need to transfer tag attributes, specified in a JSP page, to JavaScript that is stored in a file of its own, as described in "Keeping JavaScript Out of Renderers" on page 551. Let's see how that is done with the accordion component.

First, the accordion tag class provides setters and getters, which are called by JSP, for its tag attribute values. After JSP transmits tag attribute values to tag properties, JSF calls the tag's setProperties method, which passes those attribute values through to the component.

  public class AccordionTag extends UIComponentELTag {       private ValueExpression name = null;       ...       public void setName(ValueExpression name) { // Called by JSP           this.name = name;        }       ...       protected void setProperties(UIComponent component) { // Called by JSF           ...           component.setValueExpression("name", name);           ...       }   }     

When the component is rendered, the accordion renderer obtains the tag values from the component and generates a small snippet of JavaScript that passes the component values through to the JavaScript; in this case, we are passing the name of the DIV that Rico will endow with accordion functionality. That DIV was originally specified as the name attribute of the rico:accordion tag.

  public class AccordionRenderer extends Renderer {       ...       public void encodeEnd(FacesContext fc,               UIComponent component)               throws IOException {           ResponseWriter writer = fc.getResponseWriter();           // Finish enclosing DIV started in encodeBegin()           writer.write("</div>");           // Write the JS that creates the Rico Accordian component           Map accordionAttributes = component.getAttributes();           String div = (String)accordionAttributes.get("name");           writer.write("<script type='text/javascript'>");           writer.write("new Rico.Accordion( $('" + div  + "'), ");           writeAccordionAttributes(writer, accordionAttributes);           writer.write(");");           writer.write("</script>");       }       public boolean getRendersChildren() {           return false;       }       private void writeAccordionAttributes(ResponseWriter writer, Map attrs) {           try {               // Add the rest of the Accordion's properties here.               // See rico-1.1.2.js, line 179.               writer.write(" { ");               writer.write("   panelHeight: " + attrs.get("panelHeight"));               writer.write(" } ");           } catch (IOException e) {               e.printStackTrace();           }       }   } 


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