Section 7.2. Third-Party Tag Libraries


7.2. Third-Party Tag Libraries

Now you know how to create a tag library that encapsulates your own Ajaxian features. But what about third-party tag libraries? Do such libraries exist?

Yes, they do. In the rest of this chapter, we're going to look at three: AjaxTags, AjaxAnywhere, and JavaWebParts.[*] Being able to create your own tag library is valuable, but relying on someone else's expertise and experience can be even better.

[*] Note that AjaxTags and JavaWebParts started out with the same name, AjaxTags. This can be a source of confusion.

7.2.1. AjaxTags

AjaxTags is a SourceForge project (http://ajaxtags.sourceforge.net) that allows you to use Ajax with minimal effort. It consists of a set of JSP tags that tie into Ajax functionality. You don't need to write JavaScript to use these tags. If you want to use some tested JavaScript and reduce your development time, AjaxTags might be the library you're looking for.

AjaxTags 1.2 currently provides 10 tags, which are listed in Table 7-1. More tags may be available by the time you read this book.

Table 7-1. AjaxTags tags

Tag name

Description

Autocomplete

Displays a list of entries that match the text entered into the autocomplete field. (I've called this a "suggestion" field elsewhere in this book.)

Callout

Displays a pop-up balloon anchored to an HTML element.

HTML Content Replace

Connects a content area to an HTML element's onclick event.

Portlet

Adds a portlet to the JSP page.

Select/Dropdown

Populates a select field based on the selection in another select field.

Tab Panel

Sets up tabs with their respective Ajax-enabled content.

Toggle

Toggles the value of a hidden field (TRue/false) while simultaneously switching the source of an image field.

Update Field

Updates the values of one or more form fields based on the text entered in another field.

Area and Anchor

Shows how to Ajax-enable any area of a page.

Ajax DisplayTag

Shows how to Ajax-enable DisplayTag.


One of the most interesting tags in the library is the Tab Panel tag, which is used to set up tabs in an application that displays the contents of some tables. Let's see what it takes to use that tag. Figure 7-1 shows the application we're heading toward.

Figure 7-1. The Tab Panel tag displaying user information from the database


When you click on a tab, the browser requests the data to populate the tab. Clicking on the Shopping Carts tab displays all the items currently in the shopping cart, and clicking on the Products tab displays all the products in the database.

7.2.1.1. Getting and installing AjaxTags

To get started, go to http://ajaxtags.sourceforge.net, locate the current version of AjaxTags in the Downloads section, and download it. The AjaxTags project will probably come as a zipped archive for Windows and a tar archive for Linux. Uncompress the archive, find the ajaxtags.jar file, and copy it into your WEB-INF/lib directory. The .jar file will probably be in the dist directory and should be named ajaxtags with the version appended to it (e.g., ajaxtags-1.2-beta2.jar).

When you're running Tomcat 5.5 with AjaxTags 1.2, Tomcat may throw several exceptions because of missing classes. One of the missing classes, ExpressionEvaluatorManagerl, is found in standard-1.1.2.jar.

Another missing class is org.apache.commons.lang.StringUtils, which at the time of this writing is found in commons-lang-2.1.jar. You can get the latest version of that .jar file from http://jakarta.apache.org/commons/lang.

To avoid those exceptions, place both .jar files in the WEB-INF/lib directory.


Next, copy ajaxtags.tld into the WEB-INF directory and add the taglib definition to the web.xml definition file in WEB-INF, as follows:

 <taglib>     <uri>http://ajaxtags.org/tags/ajax</uri>     <location>/WEB-INF/ajaxtags.tld</location> </taglib> 

7.2.1.2. Using the <tabPanel> tag

To use a tab panel, put the <ajax:tabPanel> tag in the JSP, as shown in Example 7-9. The <tabPanel> tag sets up a set of tabbed panels. You must define each tab that you would like your page to display within the <ajax:tabPanel> with an <ajax:tab> tag. The <ajax:tab> takes caption, baseUrl, and parameters attributes as input. The tabs display the labels defined by their caption attributes. The baseUrl parameter provides the address of the web service that handles the call (in our case, /tabcontent), and you can use the parameters parameter to set the request parameter for a tab. You can also select one tag in the set to be the default; to do so, set defaultTab="true".

Example 7-9. index.jsp uses AjaxTags to produce a tab panel

 <%@ page language="java" %> <%@ taglib uri="http://ajaxtags.org/tags/ajax" prefix="ajax" %> <html> <head>     <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />     <title>AJAX JSP Tag Library Examples</title>     <script type="text/javascript" src="/books/4/163/1/html/2/<%=request.getContextPath( )%>/js/ajaxtags-             1.2/prototype-1.4.0.js"></script>     <script type="text/javascript" src="/books/4/163/1/html/2/<%=request.getContextPath( )%>/js/ajaxtags-             1.2/scriptaculous.js"></script>     <script type="text/javascript" src="/books/4/163/1/html/2/<%=request.getContextPath( )%>/js/ajaxtags-             1.2/overlibmws.js"></script>     <script type="text/javascript" src="/books/4/163/1/html/2/<%=request.getContextPath( )%>/js/ajaxtags-             1.2/ajaxtags.js"></script>     <link rel="stylesheet" type="text/css" href="css/ajaxtags-sample.css" />     <style>         Table.Product {border: solid 2px; border-color:#CCFF66;}         TD.Product{background-color:#CCCCFF;border: solid 2px; color:#000099}         TH.Product{background-color:#000099; color:#CCCCFF}         Table.User {border: solid 2px; border-color:#CCFF66;}         TR.UserDark {background-color:#CCCCFF;border: solid 2px; color:#6666CC}         TR.UserLight {background-color:#CCFFFF;border: solid 2px; color:#6666CC}         TH.User {background-color:#6666CC; color:#CCCCFF}         Table.Cart {border: solid 2px; border-color:#339966;}         TR.CartLight {background-color:#CCCCFF;border: solid 2px; color:#336666}         TR.CartDark {background-color:#33FF99;border: solid 2px; color:#336666}         TH.Cart {background-color:#336666; color:#CCCCFF}     </style> </head> <body>     <h1>AjaxTags Tab Panel Tag Demo</h1>     <div>     <ajax:tabPanel panelStyle                    contentStyle                    panelStyle                    contentStyle                    currentStyle>         <ajax:tab caption="Users"                   baseUrl="${pageContext.request.contextPath}/tabcontent?tab=Users"                   defaultTab="true"/>         <ajax:tab caption="Shopping Carts"                   baseUrl="${pageContext.request.contextPath}/tabcontent"                   parameters="tab=Carts"/>         <ajax:tab caption="Products"                   baseUrl="${pageContext.request.contextPath}/tabcontent"                   parameters="tab=Products"/>     </ajax:tabPanel>     </div> </body> </html> 

Notice that the Users tab does not have a parameters attribute. That is to illustrate that the parameters attribute is not required and that the tab's request parameters can be passed in the baseUrl attribute, as in /tabcontent?tab=Users.

In addition to including the tag library ajaxtags.tld and the two JavaScript library files prototype-1.4.0.js and ajaxtags.js, the index.jsp page links to an important support file, ajaxtags-sample.css. The CSS support file comes with the AjaxTags demo application. The tabs rely heavily upon these support files. To get them, go to http://ajaxtags.sourceforge.net/index.html and download the Demo War files. The CSS files will be in the web/css directory. (You can customize these files later if you like; the downloadable versions just provide a starting point to get your applications working.)

7.2.1.3. Writing the servlet code

Once you've created the JSP for your tabbed panel, you need a servlet to respond to the Ajax requests generated by the tabs. The response should be an HTML-formatted string to fill the div for each tag. We'll call our servlet TabContentServlet.

The TabContentServlet must return text for the tab that has been selected. This servlet must extend BaseAjaxServlet and override the getXMLContent( ) method. (BaseAjaxServlet is provided by the AjaxTags project and can be found in its JAR file.)

The servlet pulls the parameter tab from the request and uses it to see which tab is requesting content. For this application, a simple if/else block will suffice for the controller. The TabContentServlet code is shown in Example 7-10.

Example 7-10. The TabContentServlet

 package com.oreilly.ajax.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.ajaxtags.servlets.BaseAjaxServlet; import com.oreilly.ajax.ProductManager; import com.oreilly.ajax.ShoppingCartManager; import com.oreilly.ajax.UserManager; public class TabContentServlet extends BaseAjaxServlet {     public String getXmlContent(HttpServletRequest request,             HttpServletResponse response) throws Exception {         String tab = request.getParameter("tab");         String returnString =                 "<H1>Tab parameter is null, please make sure you pass it in the                 request.</H1>";         if (tab == null) {             return (returnString);         }         if (tab.equals("Users")) {             returnString = UserManager.getUsersView( );         }         else if (tab.equalsIgnoreCase("Products")) {             returnString = ProductManager.getProductsView( );         }         else if (tab.equalsIgnoreCase("Carts")) {             returnString = ShoppingCartManager.getShoppingCartView( );         }         return (returnString);     } } 

The value for the tab parameter was set up in our JSP, as part of the request URL. For example, the Users tab includes the tab=Users pair at the end of the URL:

 <ajax:tab caption="Users"           baseUrl="${pageContext.request.contextPath}/tabcontent?tab=Users"           defaultTab="true"/> 

7.2.1.4. Displaying data in the tabs

The next step is to pass formatted data back to the tab for display. I feel squeamish about returning a formatted HTML string from the model, which should not have to deal with formatting issues. For this application, however, it is the best course. The Tab Panel tag in the AjaxTags library expects the contents of the tabs to be returned as formatted HTML. The alternative is to return the data in a format that JavaScript can parse, but that would add a lot of complexity. If you are writing a more sophisticated application and must separate the model from the formatting details, you can simply return the object as an XML or JSON string and either parse and format the details in JavaScript or switch to Struts and use the bean.tld that Struts provides.

Example 7-11 presents the method that returns the HTML for the Users tab. The HTML is simple because most of the heavy-duty formatting is handled by the CSS. Therefore, getUsersView( ) only needs to generate the <table>, <td>, <tr>, and <th> tags. The properties are limited to CSS class names, which allows you to manipulate much of the formatting from the CSS file.

Example 7-11. Excerpt from UserManager.java

 static public String getUsersView( ) {     Connection con = DatabaseConnector.getConnection( );     String sqlString = "";     String userclass = "";     int index = 0;     SimpleDateFormat sf = new SimpleDateFormat("MM-dd-yyyy");     StringBuffer htmlStringBuffer = new StringBuffer("<table class=\"User\">");             htmlStringBuffer.append("\n<tr><th class=\"User\">User Name</th>");             htmlStringBuffer.append("\n<th class=\"User\">First Name</th>");             htmlStringBuffer.append("\n<th class=\"User\">Last Name</th>");             htmlStringBuffer.append("\n<th class=\"User\">City</th>");             htmlStringBuffer.append("\n<th class=\"User\">State</th>");             htmlStringBuffer.append("\n<th class=\"User\">Joined Date</th>");             htmlStringBuffer.append("\n<th class=\"User\">Last Login</th></tr>");     try {         sqlString = "select * from USERS";         Statement select = con.createStatement( );         ResultSet result = select.executeQuery(sqlString);         Date tempDate = null;         while (result.next( )) { // process results one row at a time             if (index++ % 2 == 0)                 userclass = "UserLight";             else                 userclass = "UserDark";             htmlStringBuffer.append("\n<tr class=\""+userclass+"\">");             htmlStringBuffer.append("\n<td class=\"User\">"                     +result.getString("USERNAME")+"</td>");             htmlStringBuffer.append("\n<td class=\"User\">"                     +result.getString("FIRST_NAME")+"</td>");             htmlStringBuffer.append("\n<td class=\"User\">"                     +result.getString("LAST_NAME")+"</td>");             htmlStringBuffer.append("\n<td class=\"User\">"                     +result.getString("CITY")+"</td>");             htmlStringBuffer.append("\n<td class=\"User\">"                     +result.getString("STATE")+"</td>");             tempDate = result.getDate("JOINED");             if (tempDate != null)                 htmlStringBuffer.append("\n<td class=\"User\">"                         +sf.format(tempDate)+"</td>");             else                 htmlStringBuffer.append("\n<td class=\"User\">N/A</td>");             tempDate = result.getDate("LAST_LOGIN");             if (tempDate != null)                 htmlStringBuffer.append("\n<td class=\"User\">"                         +sf.format(tempDate)+"</td>");             else                 htmlStringBuffer.append("\n<td class=\"User\">N/A</td>");             htmlStringBuffer.append("</tr>");         }     }     catch (Exception e) {         System.out.println("exception caught getting USERS"                            + sqlString + " " + e.getMessage( ));     }     finally {         if (con != null) {             try {                 con.close( );             }             catch (SQLException e) {             }         }     }     return htmlStringBuffer.toString( ); } 

getUsersView( ) establishes a connection with the database and performs a query to get the information about the users; the results are then parsed and formatted into an HTML string. The class property of each HTML element is given a different name, so you can control most of the formatting in an external CSS file, without touching the Java code. This makes for as clean a separation as possible when using the AjaxTags library.

Data for the other tabs is managed with similar methods. The view for the Shopping Cart tab is managed with a call to getShoppingCartView( ), as shown in Example 7-12.

Example 7-12. The getShoppingCartView( ) method of ShoppingCartManager

 static public String getShoppingCartView( ) {     Connection con = DatabaseConnector.getConnection( );     String sqlString = "";     String cartclass = "";     SimpleDateFormat sf = new SimpleDateFormat("MM-dd-yyyy");     StringBuffer htmlStringBuffer = new StringBuffer("<table class=\"Cart\">");     htmlStringBuffer.append("\n<tr><th class=\"Cart\">User Name</th>");     htmlStringBuffer.append("\n<th class=\"Cart\">Start Date</th>");     htmlStringBuffer.append("\n<th class=\"Cart\">Last Updated</th>");     htmlStringBuffer.append("\n<th class=\"Cart\">Active </th></tr>");     try {         sqlString = "select  u.USERNAME," + "sc.START_DATE," + "sc.LAST_UPDATED,"                 + "sc.ACTIVE " + "from SHOPPING_CART sc," + "USERS u "                 + "where sc.USER_ID=u.USER_ID;";         Statement select = con.createStatement( );         ResultSet result = select.executeQuery(sqlString);         int index = 0;         while (result.next( )) { // process results one row at a time             if (index++ % 2 == 0)                 cartclass = "CartLight";             else                 cartclass = "CartDark";             htmlStringBuffer.append("\n<tr class=\"" + cartclass + "\">");             htmlStringBuffer.append("\n<td class=\"" + cartclass + "\">"                     + result.getString("USERNAME") + "</td>");             htmlStringBuffer.append("\n<td class=\"" + cartclass + "\">"                     + sf.format(result.getDate("START_DATE")) + "</td>");             htmlStringBuffer.append("\n<td class=\"" + cartclass + "\">"                     + sf.format(result.getDate("LAST_UPDATED")) + "</td>");             if (result.getBoolean("ACTIVE")) {                 htmlStringBuffer.append("\n<td class=\"" + cartclass                         + "\">true</td>");             }             else {                 htmlStringBuffer.append("\n<td class=\"" + cartclass                         + "\">false</td>");             }             htmlStringBuffer.append("</tr>");         }     }     catch (Exception e) {         System.out.println("exception caught getting Shopping Cart"                 + sqlString + " " + e.getMessage( ));     }     finally {         if (con != null) {             try {                 con.close( );             }             catch (SQLException e) {             }         }     }     return htmlStringBuffer.toString( ); } 

Except for the query, this method is similar to getUsersView( ). Both methods use an index value in the while loop to tag every other row with a different CSS class. This allows you to make the contents of the tabbed panel more readable by giving alternate rows different looks. The most common way to do this is to give every other row a gray background:

 if (index++ % 2 == 0)     cartclass = "CartLight"; else     cartclass = "CartDark"; 

The getProductsView( ) method, presented in Example 7-13, gets product information from the database and sends it to the Products tab as an HTML string.

Example 7-13. The getProductsView( ) method

 static public String getProductsView( ) {     Connection con = DatabaseConnector.getConnection( );     String sqlString = "";     StringBuffer htmlStringBuffer = new StringBuffer(             "<table class=\"Product\">");     htmlStringBuffer.append("\n<tr><th class=\"Product\">Product Name</th>");     htmlStringBuffer.append("\n<th class=\"Product\">Description</th>");     htmlStringBuffer.append("\n<th class=\"Product\">Filename</th>");     htmlStringBuffer.append("\n<th class=\"Product\">Price </th></tr>");     try {         sqlString = "select * from PRODUCTS";         Statement select = con.createStatement( );         ResultSet result = select.executeQuery(sqlString);         Date tempDate = null;         while (result.next( )) { // process results one row at a time             htmlStringBuffer.append("\n<tr>");             htmlStringBuffer.append("\n<td class=\"Product\">"                     + result.getString("PRODUCT_NAME") + "</td>");             htmlStringBuffer.append("\n<td class=\"Product\">"                     + result.getString("DESCRIPTION") + "</td>");             htmlStringBuffer.append("\n<td class=\"Product\">"                     + result.getString("FILENAME") + "</td>");             htmlStringBuffer.append("\n<td class=\"Product\">"                     + result.getString("PRICE") + "</td>");             htmlStringBuffer.append("</tr>");         }     }     catch (Exception e) {         System.out.println("exception caught getting PRODUCTS" + sqlString + " "                 + e.getMessage( ));     }     finally {         if (con != null) {             try {                 con.close( );             }             catch (SQLException e) {             }         }     }     return htmlStringBuffer.toString( ); } 

There's nothing particularly new or notable here; this method is similar to getUsersView( ) and getShoppingCartView( ). We've dispensed with using alternating CSS tags, though, so you can't change the background color of every other row to make the tab's contents more readable.

The CSS code for this example was embedded in the JSP, but it should eventually be put into another file, especially if the application is going to see a production environment:

 <style>     Table.Product {border: solid 2px; border-color:#CCFF66;}     TD.Product{background-color:#CCCCFF;border: solid 2px; color:#000099}     TH.Product{background-color:#000099; color:#CCCCFF}     Table.User {border: solid 2px; border-color:#CCFF66;}     TR.UserDark {background-color:#CCCCFF;border: solid 2px; color:#6666CC}     TR.UserLight {background-color:#CCFFFF;border: solid 2px; color:#6666CC}     TH.User {background-color:#6666CC; color:#CCCCFF}     Table.Cart {border: solid 2px; border-color:#339966;}     TR.CartLight {background-color:#CCCCFF;border: solid 2px; color:#336666}     TR.CartDark {background-color:#33FF99;border: solid 2px; color:#336666}     TH.Cart {background-color:#336666; color:#CCCCFF} </style> 

The CartLight and CartDark values for Table.Cart are not used because the Java code does not tag anything with those classes.

The final step is to add the definition of TabContentServlet to the web.xml file:

 <servlet>     <servlet-name>tabcontent</servlet-name>     <servlet-class>com.oreilly.ajax.servlet.TabContentServlet</servlet-class>     <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping>     <servlet-name>tabcontent</servlet-name>     <url-pattern>/tabcontent </url-pattern> </servlet-mapping> 

After you implement this demo, try changing the data in the database and then switching through the tabs to see how each panel is updated. One great thing about using Ajax in an application like this is that real-time updates occur as you shift through the tabs, without any browser caching issues.

7.2.2. JavaWebParts

JavaWebParts is a large library comprised of many separate components. See the documentation at http://javawebparts.sourceforge.net for the latest information on JavaWebParts. The documentation is very sparse, but it gives you an idea of what's available. The SourceForge forum is also useful; Frank Zametti, one of the core developers of the Ajax-enabled tags, is active in answering questions about using those tags.

No JavaScript programming is required to use the JavaWebParts library. You simply insert the tags you need into a JSP and then write the server-side support for those tags in Java. If you're not a fan of JavaScript, this approach has obvious advantages, but the drawback is that you are limited to the tags that have been implemented.

Table 7-2 summarizes the JavaWebParts components. Note that there are many different kinds of components available, not just components implementing Ajax features. They're all worth investigating.

Table 7-2. JavaWebParts components

Component

Description

Filter

Servlet filters

Listener

Context and session listeners

Misc

Components that don't fit in the other categories

Taglib

AjaxTags and other tag libraries

Servlet

Servlets

Request

Components that deal with HTTPRequest

Response

Components that deal with HTTPResponse

Session

Components that deal with HTTPSession

Context

Servlet context components


Rather than providing a library of Ajax-enabled components, JavaWebParts takes a more general approach. Instead of a specific tag with a specific name, you place a generic tag, <ajax:event>, after the HTML element that you want to trigger an Ajax event. Then you connect up that event in a configuration file, which is normally called ajax_config.xml.

To demonstrate how to use JavaWebParts, we'll look at an application that uses Ajax to populate one select field based on the selection in another select field. Figure 7-2 shows the application we're working toward.

Figure 7-2. Populating one select field based on the selection in another


The JSP for this application sets up the select fields so that when the user selects a state, a request is sent to the server, which returns a list of cities in that state. That list is used to fill a <div>, which in turn populates the "Cities:" select field. It starts with a taglib directive that loads the JavaWebParts tag library. The JSP uses only two Ajax-specific tags: <ajax:event>, which follows the state selection box, and <ajax:enable />. Other than that, as you can see in Example 7-14, the JSP contains no extra code to indicate that this is an Ajax-enabled web page.

Example 7-14. index.jsp

 <%@ taglib prefix="ajax" uri="javawebparts/taglib/ajaxtags" %> <html> <head>     <title>         Java Web Parts - Ajax on Java demo     </title> </head> <body>     <h1>         Java Web Parts Dynamic Select     </h1>     <br />     Select a state and JavaWebParts will populate the cities into the second     select box.     <br />     <form name="StateSelectForm">         <br />         <select name="stateSelected">             <option value="" selected="selected">Select a State</option>             <option value="AL">Alabama</option>             ...             <option value="WY">Wyoming</option>         </select>         <ajax:event ajaxRef="StateSelectForm/stateSelectionChange" />     </form>     Cities:     <div >         <select name="citySelected">             <option>Select a state in the above select box.</option>         </select>     </div>     <ajax:enable /> </body> </html> 

The <ajax:enable /> tag injects the JavaScript that will support the Ajax calls to the server. To see what code <ajax:enable /> produces, load the page in your browser and look at its source. You should see something similar to the following:

 <script> var calls = new Array( ); var pendingResponseCount = 0; var shouldDebugAjax = false; var assureUnique = 1; function ajaxRequestSender(form, target, qs, dom, resHandler, resHandlerParam,         method, async, mungedRef, timerObj, ajaxRef) { ... } function onResponseStateChange(callKey) { ... } function createXMLHttpRequest( ) {     var xmlHttpRequest;     if (window.XMLHttpRequest) {         return new XMLHttpRequest( );     } else if (window.ActiveXObject) {         return new ActiveXObject('Microsoft.XMLHTTP')     } } </script> 

The ajax_config.xml file contains the information to set up the tags that are used in the JSP. The ajaxRef attribute of the <form> tag specifies the name given to the form in the JSP (in this case, "StateSelectForm"):

 <ajaxConfig>     <form ajaxRef="StateSelectForm">         <element ajaxRef="stateSelectionChange">             <event type="onchange">                 <requestHandler type="std:QueryString" method="get">                     <target>JWPSelectServlet</target>                     <parameter>state=stateSelected</parameter>                 </requestHandler>                 <responseHandler type="std:InnerHTML">                     <parameter>cities</parameter>                 </responseHandler>             </event>         </element>     </form> </ajaxConfig> 

Within the form, <ajax:event> tags determine which elements trigger Ajax events; within the configuration file, the <element> tag with its ajaxRef attribute specifies the element whose events we're mapping (in this case, the stateSelectionChange element within the StateSelectForm). The <ajax:event> tag in the form has an ajaxRef attribute of "StateSelectForm/stateSelectionChange". Back in the configuration file, the type attribute of the <event> tag specifies what kind of events we're looking for (here, onchange events). So, putting it all together, we're looking for onchange events coming from the stateSelectionChange element within the StateSelectForm.

The next two elements within the <event> tag specify what to do with these events when they arrive. The <requestHandler> tag connects the Ajax request to a handler (in this case, our servlet). This tag also specifies that the JSP will make a "get" request to the JWPSelectServlet, which is the <servlet-mapping> configured in web.xml.

In order to pass data back to the servlet, we need to pass request parameters. The <parameter> tag defines those parameters. In this case, we're passing a key/value pair in a GET request. The name of the key is state; its value is from the element in the JSP with the id stateSelected.

Finally, the <responseHandler> tag sets up the JSP to handle the response from the servlet. The servlet sends HTML text, which is inserted into the JSP element with an id of cities using the innerHTML method. The cities parameter is wrapped in the <parameter> tag.

The web.xml file (shown in Example 7-15) must include the <context-param> tag, which sets the ajaxTagsConfig file to /WEB-INF/ajax_config.xml. web.xml also configures a listener for incoming events (the AjaxInit class) and the servlet that processes the events, and returns the data back to the client (JWPSelectServlet).

Example 7-15. The web.xml file that sets up JavaWebParts

 <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"     "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app>     <context-param>         <param-name>ajaxTagsConfig</param-name>         <param-value>/WEB-INF/ajax_config.xml</param-value>     </context-param>     <listener>         <listener-class>javawebparts.taglib.ajaxtags.AjaxInit</listener-class>     </listener>     <servlet>         <servlet-name>JWPSelectServlet</servlet-name>         <servlet-class>com.oreilly.ajax.servlet.JWPSelectServlet</servlet-class>     </servlet>     <servlet-mapping>         <servlet-name>JWPSelectServlet</servlet-name>         <url-pattern>/JWPSelectServlet</url-pattern>     </servlet-mapping> </web-app> 

We've now connected the listener and the configuration file and configured the servlet that will send the data back to the JavaWebParts tags. The code for this servlet is presented in Example 7-16.

Example 7-16. The JWPSelectServlet

 public class JWPSelectServlet extends HttpServlet {     public void doGet(HttpServletRequest req, HttpServletResponse res)             throws ServletException, IOException {         doPost(req, res);     }     public void doPost(HttpServletRequest req, HttpServletResponse res)             throws ServletException, IOException {         String state = (String) req.getParameter("state");         if (state != null) {             res.setContentType("text/xml");             res.setHeader("Cache-Control", "no-cache");             res.getWriter( ).write(getCities(state));         }     }     private String getCities(String state) {         Connection con = DatabaseConnector.getConnection( );         StringBuffer sb = new StringBuffer("<select name=\"citySelected\">");         try {             Statement statement = con.createStatement( );             String sqlString = "SELECT DISTINCT CITY FROM ZIPCODES WHERE STATE='"                     + state + "' ORDER BY CITY;";             ResultSet resultSet = statement.executeQuery(sqlString);             while (resultSet.next( )) {                 sb.append("<option>" + resultSet.getString(1) + "</option>\n");             }         }         catch (Exception e) {             System.out.println("exception caught getting cities for " + state);         }         finally {             sb.append("</select> <ajax:event                       ajaxRef=\"CitySelectForm/citySelectionChange\"/>");             if (con != null) {                 try {                     con.close( );                 }                 catch (SQLException e) {                 }             }         }         return sb.toString( );     } } 

The servlet builds a list of cities for the selected state, wraps each city name with an <option> tag, and then sends the results back to the form, where they are placed in the element specified by the ajax_config file.

I tried to make this application fancier by taking the list of cities and applying an event on the drop-down list. My plan was to select a city and return all of the states that have a city with the same name. However, JavaWebParts doesn't seem to have a way to enable a derived select field to trigger yet another Ajax event.

AjaxAnywhere does have support for applying an event on a dynamically displayed select element. I'll show how to do that in the next section.


JavaWebParts provides many tags and features that you can add to your applications. Its design is relatively flexible; rather than providing components with fixed functions, it provides capabilities that you can integrate into your applications. Its main disadvantage is that there is limited documentation, and you must live with the way the features work unless you are willing to fix and contribute to the project. JavaWebParts is an active project that's constantly undergoing revision and improvement; I suspect it will continue to evolve into a valuable addition to the Ajax toolkit.

7.2.3. AjaxAnywhere

The AjaxAnywhere project has been available on SourceForge since September 2005. It provides a browser-independent JavaScript API for sending data to a server via XMLHttpRequest. Once data has been sent to the server, AjaxAnywhere uses the response to update "zones" in your web application with the returned data. A zone can be any HTML element that can take an id, such as a <div>.

To start, visit the home page of AjaxAnywhere at http://ajaxanywhere.sourceforge.net, look at the documentation, and download the library (a .jar file).

The server-side application calls the static method AAUtils.addZonesToRefresh( ) to configure which zones are to be updated. This method is usually called in a servlet or JSP. The servlet sends data back to the JSP containing the zone or zones to be updated; a filter intercepts the response and converts it to XML that contains only the HTML needed to update the client. The client receives the XML and updates the zone or zones selected for refresh.

In this section, we'll look at an application that contains two select elements, the first of which is populated with a list of states (Figure 7-3). After the user clicks on a state, the second select area populates with a list of the cities in that state. Then, if the user clicks on one of the cities, the third area populates with a listing of all the states in which a city by that name is found. The code for the application will illustrate how to look up an item and then execute a reverse lookup on the result. I wanted to demonstrate this capability of AjaxAnywherespecifically, the ability to dynamically insert Ajax triggersbecause it is not currently supported by JavaWebParts.

Figure 7-3. AjaxAnywhere Demoselect a state, then a city


When the user clicks one of the states in the State list, the browser sends a call to the AjaxAnywhereSupportServlet. The servlet determines which field has been sent by calling request.getParameter( ). The appropriate zone is configured with a call to AAUtils.addZonesToRefresh( ), and the data needed for that zone is stored as an object in the session. With this data, the browser displays a list of cities that are in the selected state in the second select field. After the user selects a city, the browser displays a list of all the states in which a city with that name is found in the box on the right.

So far, we've said that the servlet handling the request stores the data needed to update a zone in the session. How does that update happen? To do the refresh, we can use either Java in a JSP, or the AjaxAnywhere JavaScript API. For this example, we'll use Java code in the JSP.

7.2.3.1. Enabling AjaxAnywhere in the JSP

The AjaxAnywhere implementation in index.jsp is presented in Example 7-17.

Example 7-17. AjaxAnywhere implementation in index.jsp

 1  <%@ page pageEncoding="UTF-8" import="java.util.*" %> 2  <%@ taglib uri="http://ajaxanywhere.sourceforge.net/" prefix="aa" %> 3  <% 4      ArrayList cityList = (ArrayList)session.getAttribute("cityList"); 5      ArrayList stateList = (ArrayList)session.getAttribute("stateList"); 6  %> 7  <script src="/books/4/163/1/html/2/aa.js"></script> 8  <script>ajaxAnywhere.formName = "main";</script> 9 10  <h1> AjaxAnywhere Demo </h1><br /> 11  <div> 12  Click on a state. The City select box will display all of the cities in that 13  state.<br /> Then click on one of the cities to see how many states have a city 14  with the same name. 15  </div> 16  <br /> 17 18  <form method="POST" ACTION="AjaxAnywhereSupport" name="main"> 19  <table border = "1"> 20      <tr> 21          <th width="30%">State</th> 22          <th width="30%">City</th> 23          <th width="30%">States with that city</th> 24      </tr> 25      <tr> 26          <td align="center" valign="top"> 27              <select size="10" name="state" 28                      onchange="ajaxAnywhere.submitAJAX('function=state');"> 29                  <option value="AL"> 30                      Alabama 31                  </option> 32                  <option value="AK"> 33                      Alaska 34                  </option> 35                  . 36                  . 37                  . 38                  <option value="WI"> 39                      Wisconsin 40                  </option> 41                  <option value="WY"> 42                      Wyoming 43                  </option> 44              </select> 45          </td> 46          <td align="center" valign="top"> 47              <aa:zone name="citiesList"> 48              <select size="10" name="city" 49                      onchange="ajaxAnywhere.submitAJAX('function=city')"> 50                  <% 51                      String cityName = ""; 52                      StringBuffer sb = new StringBuffer( ); 53                      if (cityList != null) { 54                          Iterator it = cityList.iterator( ); 55                          while (it.hasNext( )) { 56                              cityName = (String)it.next( ); 57                              sb.append("<option value=\""+cityName+"\">" 58                                        +cityName+"</option> \n"); 59                          } 60                          out.println(sb.toString( )); 61                      } 62                      else 63                          out.println("<option></option>"); 64                  %> 65              </select> 66              </aa:zone> 67          </td> 68          <td  align="center" valign="top"> 69              <aa:zone name="stateswcityList"> 70              <select size="10" name="statewcity" > 71                  <% 72                      String stateName = ""; 73                      StringBuffer sb2 = new StringBuffer( ); 74                      if (stateList != null) { 75                          Iterator it = stateList.iterator( ); 76                          while (it.hasNext( )) { 77                              stateName = (String)it.next( ); 78                              sb2.append("<option value=\""+stateName+"\">" 79                                         +stateName+"</option> \n"); 80                          } 81                          out.println(sb2.toString( )); 82                      } 83                      else 84                          out.println("<option></option>"); 85                  %> 86              </select> 87              </aa:zone> 88          </td> 89      </tr> 90  </table> 91  </form> 

The first bit of code pulls a couple of ArrayList objects (the cityList and stateList) from the session. The first time through, these ArrayLists will be null; eventually, they will be used to populate the different select fields.

We then import the AjaxAnywhere JavaScript library, aa.js:

 <script src="/books/4/163/1/html/2/aa.js"></script> 

This library provides support for the AjaxAnywhere tag library, including the submitAJAX( ) function. Next, we initialize the variable ajaxAnywhere.formName with the name of the form that will be submitted by the call to submitAjax( ):

 <script>ajaxAnywhere.formName = "main";</script> 

Then we set the JavaScript onchange trigger to call submitAJAX('function=state'):

 <select size="10" name="state"         onchange="ajaxAnywhere.submitAJAX('function=state');"> 

Now when the selection is changed, AjaxAnywhere submits the form via XMLHttpRequest, passing in the request parameter function=state. So, an XMLHttpRequest is generated whenever the user selects a state.

Further down in the JSP there is another call to submitAjax( ), this time with the parameter 'function=city':

 <select size="10" name="city"         onchange="ajaxAnywhere.submitAJAX('function=city')"> 

This call generates the XMLHttpRequest when the user selects a city.

7.2.3.2. Refresh zones

To understand AjaxAnywhere, you must understand the concept of refresh zones. A refresh zone is contained between <aa:zone> and </aa:zone> tags and contains the area that AjaxAnywhere will update when the AjaxAnywhere servlet sends back its response. (Configuring this servlet will be discussed in more detail later.)

When the servlet sends back the response to a submitAjax( ) call, the Java code inside the <aa:zone> tag that is targeted for refresh is executed. (Note that this is the only time we've seen Java code, rather than JavaScript, used to update HTML components.) So, in this case, the servlet calls AAUtils.addZonesToRefresh( ), specifying that it wants to refresh the citiesList zone. When the response comes back, the Java code on lines 5064 populates the city selection box with options taken from an ArrayList that was stored in the session.

Similar code on lines 7185 populates the list of states on the right side of the window after the user selects a city. Again, this code executes only when the servlet has called AAUtils.addZonesToRefresh( ) to target the stateswcityList zone for refresh.

7.2.3.3. Writing support for AjaxAnywhere

Now that we've looked at the JSP that creates the HTML the browser renders, let's look at the AjaxAnywhereSupportServlet. This servlet must be written by the developer to support AjaxAnywhere for a specific application; it is not part of the AjaxAnywhere package.

The AjaxAnywhereSupportServlet (Example 7-18) handles the XMLHttpRequests sent by the browser. First, the servlet verifies that the requests have come from an AjaxAnywhere application by calling AAUtils.isAjaxRequest( ). If this method returns true, the servlet goes on with the rest of the processing. If it isn't dealing with an AjaxAnywhere request, there's clearly no point in dealing with zones and the rest of the AjaxAnywhere mechanism.

Example 7-18. The AjaxAnywhereSupportServlet

 public class AjaxAnywhereSupportServlet extends HttpServlet {     private static final long serialVersionUID = 1L;     public void doGet(HttpServletRequest req, HttpServletResponse res)             throws ServletException, IOException {         doPost(req, res);     }     public void doPost(HttpServletRequest req, HttpServletResponse res)             throws ServletException, IOException {         String state = req.getParameter("state");         String city = req.getParameter("city");         String function = req.getParameter("function");         if (AAUtils.isAjaxRequest(req)) {             HttpSession session = req.getSession( );             if (function.equals("city")) {                 AAUtils.addZonesToRefresh(req, "stateswcityList");                 session.setAttribute("stateList", getStates(city));             }             else if (function.equals("state")) {                 AAUtils.addZonesToRefresh(req, "citiesList");                 session.setAttribute("cityList", getCities(state));             }         }         String url = "/index.jsp";         ServletContext sc = getServletContext( );         RequestDispatcher rd = sc.getRequestDispatcher(url);         rd.forward(req, res);     } } 

If the call to AAUtils.isAjaxRequest( ) returns true, the servlet decides whether to update the list of states or the list of cities, depending on the value of the function parameter that submitAjax( ) added to the request URL. If the value of the function parameter is "city", we're being asked for a list of states that match the city included in the request. In this case, the servlet first calls AAUtils.addZonesToRefresh( ) to say that it is updating the stateswcityList zone. It then calls the getStates( ) method, which returns an ArrayList of states that contain a city of the given name. This list is then added to the session, under the stateList attribute. The logic is similar if a list of cities is requested (i.e., if the value of the function parameter is "state"): the servlet states that it wants to update the citiesList zone and calls getCities( ) to find the list of cities in the given state, and then this ArrayList is added to the session under the cityList attribute.

Why not just check for a null value in either the city or the state? That seems logical, but doing so would constitute an error. If you look back at the JSP code in Example 7-17, you will see that both zones are in the same form. That means that the request will always have a state and a city parameter. This makes it difficult to use those parameters as a way to determine which one was actually passed in fresh.

Once all the hard work is done, the servlet gets the ServletContext, uses the context to get the RequestDispatcher, and then uses the RequestDispatcher to send the response back to index.jsp.

The AjaxAnywhereSupportServlet has two other methods that help it get the data for the cities and states. The getCities( ) method, presented in Example 7-19, returns a collection of strings containing all the cities for a given state.

Example 7-19. The getCities( ) method

 private Collection getCities(String state) {     ArrayList cityList = new ArrayList( );     Connection con = DatabaseConnector.getConnection( );     try {         Statement statement = con.createStatement( );         String sqlString =                 "SELECT DISTINCT CITY FROM ZIPCODES WHERE STATE='" + state                 + "' ORDER BY CITY;";         ResultSet resultSet = statement.executeQuery(sqlString);         while (resultSet.next( )) {             cityList.add(resultSet.getString(1));         }     }     catch(Exception e) {         System.out.println("exception caught getting cities for " + state);     }     finally {         if (con != null) {             try {                 con.close( );             }             catch(SQLException e) {             }         }     }     return cityList; } 

The other method that AjaxAnywhereSupportServlet needs is getStates( ), presented in Example 7-20, which returns a collection of strings containing the names of all the states in which a city with the given name is found.

Example 7-20. The getStates( ) method

 private Collection getStates(String city) {     ArrayList stateList = new ArrayList( );     Connection con = DatabaseConnector.getConnection( );     try {         Statement statement = con.createStatement( );         String sqlString = "SELECT DISTINCT STATE FROM ZIPCODES where CITY='"                 + city + "' ORDER BY STATE;";         ResultSet resultSet = statement.executeQuery(sqlString);         while (resultSet.next( )) {             stateList.add(resultSet.getString(1));         }     }     catch(Exception e) {         System.out.println("exception caught getting states from zipcodes table");     }     finally {         if (con != null) {             try {                 con.close( );             }             catch(SQLException e) {             }         }     }     return stateList; } 

7.2.3.4. The AjaxAnywhere filter

The last thing we need to do before we can get the application up and running is to configure the AjaxAnywhere filter. This filter formats the code coming from the servlet to trigger zone updates. When I first began writing this section, I neglected to add a filter mapping for the servlet, and it took me a while to figure out why the application was not working. The lesson to be learned is to make sure that you have the filter mapping configured correctly in web.xml!

The web.xml file in Example 7-21 contains examples of the different filter mappings that can be used for AjaxAnywhere. The only filter mapping that is actually needed is the one that maps AjaxAnywhere to /AjaxAnywhereSupport; the others have been left in for illustrative purposes.

Example 7-21. web.xml with servlet and filter mappings for AjaxAnywhere

 <!DOCTYPE web-app PUBLIC     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"     "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app>     <servlet>         <servlet-name>AjaxAnywhereSupportServlet</servlet-name>         <servlet-class>             com.oreilly.ajax.servlet.AjaxAnywhereSupportServlet         </servlet-class>     </servlet>     <servlet-mapping>         <servlet-name>AjaxAnywhereSupportServlet</servlet-name>         <url-pattern>/AjaxAnywhereSupport</url-pattern>     </servlet-mapping>     <filter>         <filter-name>AjaxAnywhere</filter-name>         <filter-class>org.ajaxanywhere.AAFilter</filter-class>     </filter>     <filter-mapping>         <filter-name>AjaxAnywhere</filter-name>         <url-pattern>/AjaxAnywhereSupport</url-pattern>     </filter-mapping>     <filter-mapping>         <filter-name>AjaxAnywhere</filter-name>         <url-pattern>*.jsp</url-pattern>     </filter-mapping>     <filter-mapping>         <filter-name>AjaxAnywhere</filter-name>         <url-pattern>*.do</url-pattern> <!-- default Struts mapping -->     </filter-mapping>     <filter-mapping>         <filter-name>AjaxAnywhere</filter-name>         <url-pattern>*.htm</url-pattern> <!-- other frameworks mapping-->     </filter-mapping>     <welcome-file-list>         <welcome-file>index.jsp</welcome-file>     </welcome-file-list> </web-app> 

The AjaxAnywhere tag library is a very flexible tool for building sophisticated Ajaxian applications. Don't expect to find ready-made Ajax applications in this library, but do expect to find some very useful ways to add Ajax to an existing or new application, with hardly any knowledge of JavaScript.

7.2.4. Which Tag Library Should I Use?

Good question. I've tried to cover the major Ajax tag libraries here. None is a clear winner; each has its place.

If you find a component like the Tab Panel in AjaxTags to be useful, use AjaxTags in your project. If you find a component in JavaWebParts to be useful in your code, use JavaWebParts. If you don't need a specific component, but want to have the freedom of writing Ajax-enabled code without JavaScript, try using AjaxAnywhere. The bottom line is to use whatever benefits your project the most. Now that you have seen what each tag library can do, you are in a position to choose wisely.

And of course, if you want to reuse your code as tags and you can't find a tag library that works for you, you can always write your own tag library. But if you do this, try to contribute the result to one of the existing projects so our programming community can benefit. The Java community needs more collaboration, fewer libraries, and more features in each library. Then we can spend less time learning the various libraries and more time implementing their features in our code.




Ajax on Java
Ajax on Java
ISBN: 0596101872
EAN: 2147483647
Year: 2007
Pages: 78

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