Thursday, January 12, 2012

Writing a Custom Tag

Writing a Custom Tag involves two things:
1. A Java class which implements the Tag interface
2. A TLD (Tag Library Descriptor) file, which is an XML file, that describes the tag.

Let us create a Custom Tag for the datepicker.

First we need the jsp-api.jar, so let us update our Ivy.xml to get it for us.
Update your ivy.xml so it looks like this:
 <ivy-module version="2.0">  
<info organisation="org.confucius" module="helloworld"/>
<dependencies>
<dependency org="javax.servlet" name="servlet-api" rev="2.5"/>
<dependency org="javax.servlet" name="jsp-api" rev="2.0"/>
<dependency org="log4j" name="log4j" rev="1.2.16"/>
</dependencies>
</ivy-module>


Now run the 'resolve' task in your ant build.xml to retrieve the jsp-api.jar library.
You should see this jar in your /lib folder.

Next, go to Eclipse: Project-->Properties-->Java Build Path-->Add Jars to add the jsp-api.jar

This will include the jsp-api.jar to your Eclipse classpath.

We also need to add jsp-api.jar to the classpath of your build.xml so Ant can find it.

Update your build.xml to look like this:
 <project name="HelloWorld" xmlns:ivy="antlib:org.apache.ivy.ant" >  

<target name="resolve" description="--> retrieve dependencies with ivy">
<ivy:retrieve />
</target>

<target name="init" depends="resolve">
<mkdir dir="classes"/>
<mkdir dir="target"/>
</target>

<target name="compile" depends="init">
<javac srcdir="." destdir="classes">
<classpath>
<pathelement location="lib/servlet-api-2.5.jar"/>
<pathelement location="lib/jsp-api-2.0.jar"/>
<pathelement location="lib/log4j-1.2.16.jar"/>
</classpath>
</javac>
</target>

<target name="dist" depends="compile">
<war destfile="target/HelloWorld.war" webxml="web.xml">
<classes dir="classes"/>
<lib dir="lib">
<exclude name="jsp-api*.jar"/>
</lib>
<fileset dir="web-content"/>
</war>
<echo>Build executed at ${TIME_NOW}</echo>
</target>

<tstamp>
<format property="TIME_NOW" pattern="hh:mm:ss aa MM/dd/yyyy"/>
</tstamp>

</project>


NOTE: We have excluded the jsp-api.jar from the war (see the 'dist' target) because Tomcat already ships with this jar.

Let us now create the Java class to support our Custom Tag.

In your /src/org/confucius folder, create a class DateTag.java which implements the Tag interface.

We will create two variables 'id' and 'label'.
We will modify only the doStartTag() method to generate the javascript.

Your DateTag.java should look like this:
 package org.confucius;  

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.Tag;

public class DateTag implements Tag {

private PageContext pc = null;
private Tag parent = null;
private String id = null;
private String label = null;

public String getLabel() {
return label;
}

public void setLabel(String label) {
this.label = label;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public void setPageContext(PageContext p) {
pc = p;
}

public void setParent(Tag t) {
parent = t;
}

public Tag getParent() {
return parent;
}

public int doStartTag() throws JspException {
try {

if(id == null || label == null) {
pc.getOut().write("Error: id and label must be specified for tag confucius:datetag");
} else {
pc.getOut().write("<p>" + label + ": <input id=\"" + id + "\" type=\"text\"></p>");
pc.getOut().write("<script>");
pc.getOut().write("$(function() {");
pc.getOut().write("$(\"#" + id + "\").datepicker();");
pc.getOut().write("});");
pc.getOut().write("</script>");
}
} catch(IOException e) {
throw new JspTagException("An IOException occurred.");
}
return SKIP_BODY;
}

public int doEndTag() throws JspException {
return EVAL_PAGE;
}

public void release() {
pc = null;
parent = null;
id = null;
label = null;
}

}



Let us now write the Tag Library Descriptor (TLD) so we can start using this tag in our jsp.

In your HelloWorld project create a folder WEB-INF (at the same level as src, lib, classes, web-content)

Inside WEB-INF create a folder 'tlds'

In /tlds, create a file ConfuciusTags.tld (it is just a XML file with .tld extension).

Declare the datetag by typing (or copy-pasting) the following in your ConfuciusTags.tld file:
 <?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>ConfuciusTags</shortname>
<uri>http://www.confucius.org</uri>
<info>Confucius Tags Library</info>

<tag>
<name>datetag</name>
<tagclass>org.confucius.DateTag</tagclass>
<bodycontent>empty</bodycontent>
<info>Date Picker</info>

<attribute>
<name>id</name>
<required>true</required>
</attribute>
<attribute>
<name>label</name>
<required>true</required>
</attribute>
</tag>
</taglib>


Your custom tag is ready for use.

Update your HelloWorld.jsp to use this tag.
We will use the tag 3 times to demonstarte its reusability.

You will need to include the ConfuciusTags.tld in your .

So your HelloWorld.jsp will look like this:
 <html>   
<head>
<link type="text/css" rel="Stylesheet" href="../css/jquery-ui-1.8.17.custom.css"/>
<script src="../js/jquery-1.7.1.min.js"></script>
<script src="../js/jquery-ui-1.8.16.custom.min.js"></script>
<script src="../js/jquery.ui.core.js"></script>
<script src="../js/jquery.ui.datepicker.js"></script>
<%@ taglib uri="/WEB-INF/tlds/ConfuciusTags.tld" prefix="confucius" %>
</head>
<body>
<confucius:datetag id="birthdate" label="Enter your birthday"/>
<confucius:datetag id="graduationdate" label="Enter your graduation day"/>
<confucius:datetag id="weddingdate" label="Enter your wedding day"/>
</body>
</html>


Here we have specified "/WEB-INF/tlds/ConfuciusTags.tld" as the uri for the taglib library. This is considered an absolute path by the JSP container.

We could have also set it to "http://www.confucius.org". In this case, the JSP container would look for it in the CLASSPATH. It would find the first TLD in the CLASSPATH whose uri matches "http://www.confucius.org".

Finally, we will need to update our build.xml to copy the ConfuciusTags.tld to WEB-INF.

Your build.xml will now look like this (notice the 'webinf' task in the 'dist' target)
 <project name="HelloWorld" xmlns:ivy="antlib:org.apache.ivy.ant" >  

<target name="resolve" description="--> retrieve dependencies with ivy">
<ivy:retrieve />
</target>

<target name="init" depends="resolve">
<mkdir dir="classes"/>
<mkdir dir="target"/>
</target>

<target name="compile" depends="init">
<javac srcdir="." destdir="classes">
<classpath>
<pathelement location="lib/servlet-api-2.5.jar"/>
<pathelement location="lib/jsp-api-2.0.jar"/>
<pathelement location="lib/log4j-1.2.16.jar"/>
</classpath>
</javac>
</target>

<target name="dist" depends="compile">
<war destfile="target/HelloWorld.war" webxml="web.xml">
<classes dir="classes"/>
<lib dir="lib">
<exclude name="jsp-api*.jar"/>
</lib>
<fileset dir="web-content"/>
<webinf dir="WEB-INF"/>
</war>
<echo>Build executed at ${TIME_NOW}</echo>
</target>

<tstamp>
<format property="TIME_NOW" pattern="hh:mm:ss aa MM/dd/yyyy"/>
</tstamp>

</project>


If you now build and deploy HelloWorld, and browse to:
http://localhost:8080/HelloWorld/jsp/HelloWorld.jsp

you will see three fields which give the datepicker calendar popup.

The Custom Tag has given you a clean, readable and reusable way to use datepicker.
No scary javascript in template :)

No comments: