Section 7.1. Creating a Tag Library


7.1. Creating a Tag Library

In the two previous chapters, we developed code that enabled us to look up city and state information based on a user-entered zip code. Now we'll explore how to add this feature to a tag library, encapsulating the details of the Ajax code. To start, we must define our tag. Tags are defined in Tag Library Definition (TLD) files. The names of these files end with the extension .tld; in this example, the TLD file will be called ajax-oreilly.tld.

7.1.1. Writing a TLD

A TLD is written in XML and describes the attributes of each tag in the tag library. The parent tag is <taglib>; within that tag, you need a <tag> tag for each type of tag that the TLD defines. Our library will have only a single tag, but most libraries define many tags.

Each <tag> definition must have a <name> tag, which specifies the tag's name, and a <tagclass> tag. The <tagclass> tag specifies the tag handler class that handles the tag.

The <bodycontent> tag is used if the tag handler needs to read or modify the content of the body. The body is the content between the start and end of the tag. For example, if the zipCode tag had a body, it would be expressed like this:

 <ajax:zipCode>     Body </ajax:zipCode> 

The tag that is presented here will not have a body; therefore, we'll declare the body as empty in the .tld file.

There can be any number of attributes in a tag. Each attribute must have a <name> tag and a <required> tag. If the value of the <required> tag is TRue, that attribute must be present whenever the tag is used. For example, because all the attributes for the zipCode tag defined in the oreillyAjax.tld file presented in Example 7-1 have <required> set to true, all those attributes must be set when the zipCode tag is used or the taglib will throw an exception.

Example 7-1. oreillyAjax.tld

 <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib     PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"     "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd"> <taglib>     <tlibversion>1.0</tlibversion>     <jspversion>1.1</jspversion>     <shortname>ajax</shortname>     <info>adds ajax enabled tags to your jsp </info>     <tag>         <name>zipCode</name>         <tagclass>com.oreilly.ajax.ZipCodeTag</tagclass>         <bodycontent>empty</bodycontent>         <attribute>             <name>zipcodeId</name>             <required>true</required>         </attribute>         <attribute>             <name>stateId</name>             <required>true</required>         </attribute>         <attribute>             <name>cityId</name>             <required>true</required>         </attribute>         <attribute>             <name>url</name>             <required>true</required>             <rtexprvalue>true</rtexprvalue>             <description>                 the url of the servlet that handles the request for city and state                 based on zip code             </description>         </attribute>     </tag> </taglib> 

The zipcodeId, stateId, cityId, and url attributes are all required. The zipcodeId gets the HTML input element into which the user enters the zip code. The stateId and cityId attributes represent the IDs of the elements that this tag populates with the city and state. Finally, the url attribute specifies the URL of the servlet that the tag invokes. In our case, the tag will insert an Ajaxian JavaScript function into the web page. This function invokes the zipcodes URL, which must match the URL of a servlet defined in web.xml.

7.1.2. Using the Tags in a JSP

Armed with a .tld file, we can now put a declaration referencing the tag library in a JSP and give it a handle. In this case, the handle is ajax, and it is defined by the prefix as follows:

 <%@ taglib uri="/WEB-INF/oreillyAjax.tld" prefix="ajax" %> 

Once we've assigned a handle to the library, we can use it to reference any tag the library contains. Here's how to use the zipCode tag in a JSP page:

 <ajax:zipCode zipcode state city url="zipcodes" /> 

ajax is the handle; zipCode is the tag we're invoking. Again, note that all four attributes must be present. The stateId is the id of the input field for the state, the cityId is the id of the input field for the city, the zipcodeId is the id of the zip code input field, and the url is the URL of the servlet the tag invokes. The entire JSP file is shown in Example 7-2.

Example 7-2. The custom <ajax:zipCode> tag in oreillyajaxtags.jsp

 <%@ page language="java" contentType="text/html" %> <%@ taglib uri="/WEB-INF/oreillyAjax.tld" prefix="ajax" %> <html> <head>     <h1>O'Reilly Ajax with Custom Tags</H1> </head> <body>     Enter the zip code, then TAB.<br />     The State and City fields will automatically populate.<br />     <ajax:zipCode zipcode state city                   url="zipcodes" />     <table>         <tr>             <td> Zipcode: </td>             <td> <input type="text"  /> </td>         </tr>         <tr>             <td> City: </td>             <td> <input type="text"  /> </td>         </tr>         <tr>             <td> State: </td>             <td> <input type="text"  /><br /> </td>         </tr>     </table> </body> </html> 

See how each HTML <input> tag has an id that matches one of the attributes of the zipCode tag? The tag library uses those ids to populate the fields, so an Ajax request is sent when a user fills in the zip code field and the results that come back from the server are used to populate the city and state fields.

7.1.3. Writing the TagSupport Class

To support the tag, we need a class that extends javax.servlet.jsp.tagext.TagSupport.[*] The ZipCodeTag class presented in Example 7-3 provides the support we need.

[*] The .jar file in which TagSupport is defined has changed over the years. It was originally in servlet.jar, which is the .jar file you should use for Tomcat 4. It's currently in jsp-api.jar; use this .jar file for Tomcat 5. If you're using another servlet engine, it's up to you to find the .jar file that contains TagSupport.

Example 7-3. The ZipCodeTag class

 package com.oreilly.ajax; import java.io.IOException; import javax.servlet.jsp.tagext.TagSupport; public class ZipCodeTag extends TagSupport {     private String zipcodeId = "0";     private String stateId = "";     private String cityId = "";     private String url = "";     public int doStartTag( ) {         try {             this.pageContext.getOut( ).print(                     JavaScriptCode.getZipCodeScript(stateId, cityId, zipcodeId,                     url));         }         catch (IOException e) {             System.out.println("couldn't write JavaScript to jsp"                     + e.getMessage( ));         }         return SKIP_BODY;     }     public String getCityId( ) {         return cityId;     }     public void setCityId(String city) {         this.cityId = city;     }     public String getStateId( ) {         return stateId;     }     public void setStateId(String state) {         this.stateId = state;     }     public String getUrl( ) {         return url;     }     public void setUrl(String url) {         this.url = url;     }     public String getZipcodeId( ) {         return zipcodeId;     }     public void setZipcodeId(String zipcodeId) {         this.zipcodeId = zipcodeId;     } } 

TagSupport provides the coupling between ZipCodeTag and the JSP. The set and get accessor methods are the key to this coupling; the set and get methods for each instance variable are used to pass in values from the JSP or, conversely, to write results back to the JSP. There must be set and get accessor methods for every attribute in the JSP tag (in the case of the zipCode tag, url, zipcodeId, stateId, and cityId).

Most tag classes also need to add some code to the HTML page the JSP produces. To do so, the tag class overrides one or more of the methods provided by the TagSupport class (usually doStartTag( ), doEndTag( ), or both). doStartTag( ) inserts code at the point where the start tag is inserted into the JSP. doEndTag( ) inserts code at the point where the tag is terminated. In this application, we don't expect anything to intervene between <zipCode> and </zipCode>, so the code inserted by doEndTag( ) would immediately follow the code coming from doStartTag( ). The zipCode tag really only needs to override one of these methods. In this case, doStartTag( ) was used, but it makes sense to override doEndTag( ) if the code logically should be inserted at the end of the tag block.

The following line from doStartTag( ) injects the code into the JSP:

 this.pageContext.getOut( ).print(         JavaScriptCode.getZipCodeScript(stateId, cityId, zipcodeId,         url)); 

pageContext.getOut.print( ) inserts the text of our JavaScript into the JSP page. To keep the JavaScript details out of the tag class itself, I created a JavaScriptCode class (shown in Example 7-4) that has a single method: getZipCodeScript( ). This method returns a String containing our JavaScript code.

Example 7-4. The JavaScriptCode class

 package com.oreilly.ajax; public class JavaScriptCode {     public static String getZipCodeScript(String stateId, String cityId,             String zipcodeId, String url) {         StringBuffer sb = new StringBuffer( );         sb.append("<script>    ");         sb.append("function retrieveCityState( ) { ");         sb.append("    var zip = document.getElementById('"+zipcodeId+"'");         sb.append("    var url = '"+url+"?zip=' + escape(zip.value); ");         sb.append("    if (window.XMLHttpRequest) { ");         sb.append("        req = new XMLHttpRequest( ); ");         sb.append("    } " );         sb.append("    else if (window.ActiveXObject) { ");         sb.append("        req = new ActiveXObject('Microsoft.XMLHTTP'); ");         sb.append("    } ");         sb.append("    req.open('Get',url,true); ");         sb.append("    req.onreadystatechange = callbackCityState; ");         sb.append("    alert ('sending request to '+url);");         sb.append("    req.send(null);");         sb.append("}");         sb.append("function populateCityState( ) {");         sb.append("    var jsonData = req.responseText;");         sb.append("    var myJSONObject = eval('(' + jsonData + ')');");         sb.append("    var city = document.getElementById('"+cityId+"');");         sb.append("    city.value=myJSONObject.location.city;");         sb.append("    var state = document.getElementById('"+stateId+"');");         sb.append("    state.value=myJSONObject.location.state;");         sb.append("}");         sb.append("function callbackCityState( ) { ");         sb.append("    if (req.readyState==4) { ");         sb.append("        if (req.status == 200) { ");         sb.append("            populateCityState( );");         sb.append("        }");         sb.append("    }");         sb.append("}");         sb.append("</script> ");         return sb.toString( );     } } 

This is not the neatest or most flexible way to get the JavaScript text, though it is the fastest in some cases. Keeping the JavaScript in a file is a more flexible alternative to hard-coding the JavaScript into a class; it's slower because the application has to read the file at runtime, but it does allow you to debug the JavaScript without recompiling after each change.

Example 7-5, which gives another version of the doStartTag( ) method, shows how to retrieve the JavaScript from a file.

Example 7-5. ZipCodeTag::getJavaScript( ) retrieves JavaScript from a file

 private String getJavaScript( ) {     if (javaScript != null) {         return javaScript;     }     else {         String tempString = "";         String outString = "";         try {             InputStream is = getClass( ).getResourceAsStream("oreillyajaxtags.js");             BufferedReader br = new BufferedReader(new InputStreamReader(is));             while ((tempString = br.readLine( )) != null) {                 outString += tempString;             }             outString = outString.replaceAll("stateId", stateId);             outString = outString.replaceAll("cityId", cityId);             outString = outString.replaceAll("zipcodeId", zipcodeId);             outString = outString.replaceAll("urlName", url);             br.close( );             is.close( );             javaScript = outString;         } catch (IOException e) {             System.out.println("couldn't get JavaScript from oreillyajaxtags.js"                                + e.getMessage( ));         }     }     return javaScript; } 

The doStartTag( ) method can then simply call getJavaScript( ) to get the JavaScript String. A call to pageContext.getOut().print( ) will put the JavaScript into the place where the tag occurs in the JSP:

 public int doStartTag( ) {     try {         pageContext.getOut().print(getJavaScript( ));     } catch (IOException e) {         System.out.println("couldn't write JavaScript to jsp" + e.getMessage( ));     }     return SKIP_BODY; } 

The preceding code assumes that the JavaScript file is in the same directory as the ZipCodeTag class. It is simpler to keep the JavaScript code in the same directory as the class using it because it is not used anywhere else and both files stay within a .jar file. (See the build.xml file in Example 7-7 to see how the .jar file can be built.)


As mentioned earlier, the body is the portion of code between the start tag and the end tag. In this case there is no body, and either EVAL_BODY_INCLUDE or SKIP_BODY can be returned. EVAL_BODY_INCLUDE is returned to indicate that the body is to be evaluated and then included in the page at the location of the tag; SKIP_BODY is returned to indicate that if there is a body, it should be neither evaluated nor included. In this case, because the tag does not have a body, it is more accurate to return SKIP_BODY.

Now, in order to add flexibility to the tag, the ids of the input text fields are passed into the tag handler:

 <ajax:zipCode zipcode state city url="zipcodes" /> <table>     <tr>         <td> Zipcode: </td>         <td> <input type="text"  onblur="retrieveCityState( )"/> </td>     </tr> 

The input field for the zip code, for example, has an id of zipcode. That id is passed into the tag as a parameter: zipcode.

Later, when the tag handler processes the tag, the ids are put into the JavaScript with the following code:

 outString = outString.replaceAll("stateId", stateId); outString = outString.replaceAll("cityId", cityId); outString = outString.replaceAll("zipcodeId", zipcodeId); outString = outString.replaceAll("urlName", url); 

Let's face it: debugging JavaScript is not easy. There are some debugging tools available, such as Venkman and the JavaScript debugger for Mozilla, but these tools have trouble with code embedded in Java Strings. The file approach allows you to debug the JavaScript before you use it in the tag library; then, when the code is ready for prime time, you can just put the JavaScript file in the .jar file and load it with an InputStream, as shown in Example 7-5.

7.1.4. Writing the Support Servlet

The tag library needs the URL of a service to satisfy its request. In this example, that service is provided by a servlet named AjaxZipCodesServlet, but you may want to use some other kind of servicenothing requires you to stick with Java. The AjaxZipCodesServlet servlet (shown in Example 7-6) returns the city and state for a given zip code. The tag library uses this information to populate the city and state fields in a form.

Example 7-6. The AjaxZipCodesServlet

 package com.oreilly.ajax.servlet; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.oreilly.ajax.DatabaseConnector; import com.oreilly.ajax.JSONUtil; public class AjaxZipCodesServlet extends HttpServlet {     public void doGet(HttpServletRequest req, HttpServletResponse res)             throws ServletException, IOException {         String responseString = null;         String zipCode = req.getParameter("zip");         if (zipCode != null) {             HashMap location = getCityState(zipCode);             responseString = JSONUtil.buildJSON(location, "location");         }         if (responseString != null) {             res.setContentType("text/xml");             res.setHeader("Cache-Control", "no-cache");             res.getWriter( ).write(responseString);         } else {             // if key comes back as a null, return a question mark             res.setContentType("text/xml");             res.setHeader("Cache-Control", "no-cache");             res.getWriter( ).write("?");         }     }     private HashMap getCityState(String zipCode) {         Connection con = DatabaseConnector.getConnection( );         HashMap cityStateMap = new HashMap( );         cityStateMap.put("zip", "zipCode");         String queryString = "";         try {             queryString = "SELECT CITY, STATE FROM ZIPCODES where ZIPCODE="                     + zipCode + ";";             Statement select = con.createStatement( );             ResultSet result = select.executeQuery(queryString);             while (result.next( )) { // process results one row at a time                 String city;                 String state;                 city = result.getString("CITY");                 if (result.wasNull( )) {                     city = "";                 }                 cityStateMap.put("city", city);                 state = result.getString("state");                 if (result.wasNull( )) {                     state = "";                 }                 cityStateMap.put("state", state);             }         } catch (Exception e) {             System.out.println("exception caught getting city/state:"                                +queryString + " " + e.getMessage( ));         } finally {             if (con != null) {                 try {                     con.close( );                 } catch (SQLException e) {                 }             }         }         return cityStateMap;     } } 

This is the same servlet that we used in previous examples to get the city and state from a zip code.

7.1.5. Using Ant to Put It All Together

The build.xml file for this project (presented in Example 7-7) builds the tag library's class files, packages the class files and support files in ajax-ora.jar, and deploys them to the web server. It assumes that the environment variable TOMCAT_HOME is set properly, as discussed in Chapter 1. If you are using another web server, you will need to change webapp.dir to match the directory where your server's web applications are deployed. The jarit target builds the .jar file for the tag library.

Example 7-7. build.xml for the custom Ajax tag library

 <?xml version="1.0"?> <project name="AJAX on Java LAB8" default="compile" basedir=".">     <property environment="env"/>     <property name="src.dir" value="src"/>     <property name="war.dir" value="war"/>     <property name="db.dir" value="db"/>     <property name="lib.dir" value="${war.dir}/WEB-INF/lib"/>     <property name="class.dir" value="${war.dir}/WEB-INF/classes"/>     <property name="tomcat.common" value="${env.TOMCAT_HOME}/common/lib"/>     <property name="webapp.dir" value="${env.TOMCAT_HOME}/webapps/ajax-JSPTag"/>     <path >         <fileset dir="${lib.dir}">             <include name="*.jar"/>         </fileset>         <fileset dir="${tomcat.common}">             <include name="*.jar"/>         </fileset>     </path>     <target name="init">         <mkdir dir="${class.dir}"/>     </target>     <target name="jarit">         <jar destfile="${lib.dir}/ajax-ora.jar"              basedir="${class.dir}"              excludes="**/Test.class"         />     </target>     <target name="compile" depends="init"             description="Compiles all source code.">         <javac srcdir="${src.dir}" destdir="${class.dir}" debug="on"                classpathref="ajax.class.path"/>     </target>     <target name="clean" description="Erases contents of classes dir">         <delete dir="${class.dir}"/>     </target>     <target name="deploy" depends="compile,jarit"             description="Copies the jar, servlet, etc to destination dir">         <copy todir="${webapp.dir}">             <fileset dir="${war.dir}">                 <exclude name="**/*.class"/>             </fileset>             <fileset dir="${war.dir}">                 <include name="**/*Servlet.class"/>             </fileset>         </copy>     </target> </project> 

The web.xml file for the project is shown in Example 7-8. Again, notice that the <url-pattern> in the <servlet-mapping> is /zipcodes. This must match the URL value in the taglib so that when the tag makes an Ajax call to that URL, it will find a servlet waiting to handle the request.

Example 7-8. The servlet configuration file, web.xml

 <web-app>     <servlet>         <servlet-name>AjaxZipCodesServlet</servlet-name>         <servlet-class>             com.oreilly.ajax.servlet.AjaxZipCodesServlet         </servlet-class>         <load-on-startup>1</load-on-startup>     </servlet>     <servlet-mapping>         <servlet-name>AjaxZipCodesServlet</servlet-name>         <url-pattern>/zipcodes</url-pattern>     </servlet-mapping>     <!-- taglibs -->     <taglib>         <taglib-uri>/WEB-INF/oreillyAjax.tld</taglib-uri>         <taglib-location>/WEB-INF/oreillyAjax.tld</taglib-location>     </taglib>     <!-- The Welcome File List -->     <welcome-file-list>         <welcome-file>oreillyajaxtags.jsp</welcome-file>     </welcome-file-list> </web-app> 




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