Tags with Attributes

Attributes are used to pass information into a custom tag to configure the behavior of the tag. Tag attributes are like XML or HTML attributes and are name/value pairs of data. The values must be quoted with single or double quotes.

A simple example might be a tag that lists the rows in a table given a JNDI DataSource and table name as attributes. Listing 14.6 shows how such a tag could be used.

Listing 14.6. Full Text of lookup.jsp
<%@ taglib prefix="demo" uri="/demo.tld" %>
<HTML>
  <HEAD><TITLE>Tag Library Lookup Demo</TITLE></HEAD>
  <BODY>
    <demo:lookup
        dataSource="java:comp/env/jdbc/Agency"
        table="${param.table}"/>
    <H2>Select a table to display:</H2>
    <FORM action='lookup' >
      <SELECT name="table">
        <OPTION>Applicant</OPTION>
        <OPTION>ApplicantSkill</OPTION>
        <OPTION>Customer</OPTION>
        <OPTION>Job</OPTION>
        <OPTION>JobSkill</OPTION>
        <OPTION>Location</OPTION>
        <OPTION>Matched</OPTION>
        <OPTION>Skill</OPTION>
      </SELECT>
      <P><INPUT type="submit"></P>
    </FORM>
  </BODY>
</HTML>

The JSP specification allows attributes to use the Expression Language. In Listing 14.6 the dataSource attribute uses a simple string value whereas the table attribute uses a request time expression.

The TLD description for the tag must define any attributes it uses. Each supported attribute name must be listed together with details of the attribute. Every attribute must have an <attribute> tag with the sub-components shown in Table 14.6.

Table 14.6. TLD Tags for Defining Attributes
TagDescription
attributeIntroduces an attribute definition in the <tag> component of the TLD.
nameDefines the attribute name.
requiredFollowed by true if the attribute must be provided; otherwise, false.
rtexprvalueDefines whether the attribute can be specified with a request time expression. Set this element to true to allow EL in the value of the attribute; otherwise, the value must be a string literal.
typeDefines the type of an attribute and defaults to java.lang.String. This element must be defined if the rtexprvalue is true and the attribute value is not a String.

Listing 14.7 shows the complete TLD entry for the lookup tag.

Listing 14.7. TLD Entry for the lookup Tag
<tag>
  <name>lookup</name>
  <tag-class>demo.LookupTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
    <name>dataSource</name>
    <required>true</required>
    <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
    <name>table</name>
    <required>true</required>
    <rtexprvalue>true</rtexprvalue>
  </attribute>
</tag>

For a tag to support attributes, it must follow the JavaBean idiom of providing get and set methods for manipulating the attributes as shown in Listing 14.8.

Listing 14.8. Full Text of LookupTag.java
package demo;

import java.io.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class LookupTag extends TagSupport {
    private String dataSource;
    private String table;

    public String getDataSource() {
        return dataSource;
    }

    public void setDataSource(String dataSource) {
        this.dataSource = dataSource;
    }

    public String getTable() {
        return table;
    }

    public void setTable(String table) {
        this.table = table;
    }

    public int doStartTag() throws JspException {
        if (dataSource.length()==0 || table.length()==0)
            return SKIP_BODY;
        String msg = null;
        try {
            InitialContext ic = new InitialContext();
            DataSource ds = (DataSource)ic.lookup(dataSource);
            doSelect(pageContext.getOut(), ds, table);
        }
        catch (Exception ex) {
            throw new JspTagException("LookupTag: "+ex);
        }
        return SKIP_BODY;
     }

   private void doSelect (JspWriter out, DataSource ds, String table)
      throws SQLException, IOException
   {
      out.println("<H2>"+table+"</H2>");
      Connection con = null;
      PreparedStatement stmt = null;
      ResultSet rs = null;
      try {
          con = ds.getConnection();
          stmt = con.prepareStatement("select * from "+table);
          rs = stmt.executeQuery();
          out.print("<TABLE border=1>");
          ResultSetMetaData rsmd = rs.getMetaData();
          int numCols = rsmd.getColumnCount();
          out.print("<TR>");
          for (int i=1; i <= numCols; i++) {
              out.print("<TH>");
              out.print(rsmd.getColumnLabel(i));
              out.print("</TH>");
          }
          out.print("</TR>");
          while (rs.next()) {
              out.print("</TR>");
              for (int i=1; i <= numCols; i++) {
                  out.print("<TD>");
                  out.print(rs.getString(i));
                  out.print("</TD>");
              }
              out.print("</TR>");
          }
          out.print("</TABLE>");
      }
      finally {
          try {rs.close();} catch (Exception ex) {}
          try {stmt.close();} catch (Exception ex) {}
          try {con.close();} catch (Exception ex) {}
      }
   }
}

The set method for each attribute specified for the tag is called prior to the doStartTag() method. The doStartTag() and other tag lifecycle methods can use the values of attributes to modify their behavior.

The lookup tag is included the examples.war file on the accompanying Web site and, after deploying the examples, can be viewed using the URL:

http://localhost:8000/examples/lookup

Note how the JSP and tag combine to suppress the table listing section of the returned Web page if no table name is specified as an HTTP request parameter.

Updating the Agency Case Study

Now that you have seen how to write simple tags, you can use them to clean up complex functionality in your applications. Consider the skills.jsf page you were shown yesterday, which displayed a list of selected skills in an HTML <SELECT> statement asfollows:

<SELECT name="skills" multiple size="6">
<%
  Iterator allSkills = agency.getSkills().iterator();
  while (allSkills.hasNext()) {
    String s = (String)allSkills.next();
    boolean found = false;
    for (int si=0; !found && si<skills.length; si++)
        found = s.equals(skills[si]);
    if (found)
        out.print("<OPTION selected>");
    else
        out.print("<OPTION>");
    out.print(s);
    out.print("</OPTION>");
  }
%>
</SELECT>

Using just the JSTL you can refactor the job list as follows:

<SELECT name="skills" multiple size="6">
  <c:forEach var="agencySkill" items="${agency.skills}" >
    <c:set var="skillFound" value="false"/>
    <c:forEach var="jobSkill" items="${job.skills}" >
      <c:if test="${jobSkill == agencySkill}" >
        <c:set var="skillFound" value="true"/>
      </c:if>
    </c:forEach>
    <c:choose>
      <c:when test="${skillFound}" >
        <OPTION selected>${agencySkill}
      </c:when>
      <c:otherwise>
        <OPTION>${agencySkill}</OPTION>
      </c:otherwise>
    </c:choose>
  </c:forEach>
</SELECT>

This is not much better than the original JSP version with embedded Java; in fact, you might think that the restricted syntax of JSTL makes this worse than the original. A custom tag can be used to simplify the code, as follows:

<SELECT name="skills" multiple size="6">
  <c:forEach var="agencySkill" items="${agency.skills}" >
    <agency:option option="${agencySkill}" selected="${job.skills}"/>
  </c:forEach>
</SELECT>

The custom tag here is called <agency:option> and takes two attributes:

  • option is the value of the current HTML option tag.

  • selected is a list of selected options so that the tag can flag whether the <OPTION> is SELECTED or not.

Emulating the style of the items attribute of the JSTL forEach action, this tag handles collections, arrays, and even scalar Java objects for the selected attribute by accepting a java.lang.Object as the attribute type. This is shown in the following TLD entry for this tag:

<tag>
  <name>option</name>
  <tag-class>web.OptionTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
    <name>selected</name>
    <required>true</required>
    <rtexprvalue>true</rtexprvalue>
    <type>java.lang.Object</type>
  </attribute>
  <attribute>
    <name>option</name>
    <required>true</required>
    <rtexprvalue>true</rtexprvalue>
    <type>java.lang.String</type>
  </attribute>
</tag>

Implementing the tag requires a bit of thought on how to handle the different types of parameter that can be given for the selected attribute. Listing 14.9 shows the actual tag.

Listing 14.9. Full Text of OptionTag.java
package webagency;

import java.io.*;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class OptionTag extends TagSupport {
    private String option = "";
    private Object selected = new String[0];

    public void setOption (String option) {
        this.option = option;
    }

    public String getOption () {
        return option;
    }

    public void setSelected (Object selected) {
        this.selected = selected;
    }

    public Object getSelected () {
        return selected;
    }

    public int doStartTag() throws JspTagException {
        try {
            Iterator it = selectedIterator();
            while (it.hasNext()) {
                String value = it.next().toString();
                if (option.equals(value)) {
                    pageContext.getOut().print("<OPTION selected>"+option+"</OPTION>");
                    return SKIP_BODY;
                }
            }
            pageContext.getOut().print("<OPTION>"+option+"</OPTION>");
        }
        catch (IOException ex) {
            throw new JspTagException("OptionTag: "+ex);
        }
        return SKIP_BODY;
     }

     private Iterator selectedIterator() {
        if (selected == null)
            return new ArrayIterator (new Object[0]);
        if (selected instanceof Collection)
            return ((Collection)selected).iterator();
        if (selected instanceof Object[])
            return new ArrayIterator ((Object[])selected);
        return new ArrayIterator(new Object[] {selected} );
     }

     private static class ArrayIterator implements Iterator {
        private Object[] arr = null;
        private int i=0;
        public ArrayIterator(final Object[] arr)
            {this.arr = arr;}
        public boolean hasNext ()
            {return i<arr.length;}
        public Object next()
            {return arr[i++];}
        public void remove()
            {throw new UnsupportedOperationException("Remove not supported");}
     }
}

The complexity in the tag shown in Listing 14.9 is in handling the different possible Java objects that can be passed in as the selected attribute. The doStartTag() method uses the helper method selectedIterator() to obtain an iterator for the list of selected options and uses this to determine whether to output <OPTION> or <OPTION SELECTED> before the toString() value of the object. The selectedIterator() method returns the iterator of a collection or creates an object of the nested static class ArrayIterator to create an iterator for an array object.

Writing custom tags like the <agency:option> tag to simplify the HTML tags is such an obvious technique that several tag libraries already do this. Don't rush off and write your own custom tags to replace HTML tags but use those already provided. Both the JavaServer Faces (http://java.sun.com/j2ee/javaserverfaces) and Apache Struts (http://jakarta.apache.org/struts) implementations include custom tags for many of the HTML tags.

There are other simple improvements you can make to the Agency case study if you know how to declare EL variables from within a custom tag, as described in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.168.203