The original implementation of the calendar demo was fairly simple; it had to be when using my original hosting service. They were running IIS and didn't even offer ASP (Active Server Pages). I couldn't get the server to recognize my perl scripts but then that's old and inefficient technology anyway. So I would run a C program on my Linux box to generate the calendar tables and simply serve them up, using JavaScript to figure out the current month. Not very interesting!
Since I've migrated everything to the servers at home, I decided to update the application using currently available tools and technology. I'm running Apache/Tomcat (among other things) and have been spending a fair bit of time with JSP recently. Custom tags are way cool and go a long way toward separating the visual presentation from the back-end Java coding. There's a bit of overlap in my example code but I think that it's justifiable. So here's the JSP page source:
<%@ taglib uri="../utils.tld" prefix="utils" %>
<head>
<title>Calendar Demo</title>
<style type="text/css">
<!--
th.calendar { font: 12pt Arial; font-weight: bold; color: white; background-color: black }
td.weekday { font: 14pt Arial; text-align: right; vertical-align: top; height: 70; background-color:lightblue }
td.weekend { font: 14pt Arial; text-align: right; vertical-align: top; height: 70; background-color:lightgreen }
th.navigation { font-weight: bold; text-align: center }
td.navigation { font: 14pt Arial; font-weigth: bold; text-align: center }
-->
</style>
</head>
<body>
<center>
<p>
<utils:setDate/>
<p>
<form>
<table width="500" border="2">
<utils:showCalendar/>
</table>
<p>
<table cellpadding="7">
<utils:showNavigation/>
</table>
</form>
</center>
</body>
|
What I've tried to do is separate the generation of the table data from the rest of the HTML code. I also use various classes for the table elements so that the HTML author can control the presentation. Take a look at the CSS block in the document head. By twiddling those values as well as the table begin elements you can significantly alter the appearance of the calendar. You can adjust the width of the table and the height of the cells. You can specify font sizes, styles, colours, background colours, justification, etc. In a production environment this would typically be stored in a separate file but I've chosen to include it in-line.
We use three custom tags in this example and they are described in the utils.tld file, which looks like this:
<?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> <tag> <name>setDate</name> <tagclass>tags.SetDate</tagclass> <bodycontent>empty</bodycontent> </tag> <tag> <name>showCalendar</name> <tagclass>tags.GenerateCalendar</tagclass> <bodycontent>empty</bodycontent> </tag> <tag> <name>showNavigation</name> <tagclass>tags.GenerateNavigation</tagclass> <bodycontent>empty</bodycontent> </tag> </taglib> |
Since we've provided navigation to jump backwards and forwards a month or a year at a time, a java.util.GregorianCalendar object is stored in the session scope. We've also made sure that the table start and end tags are left under the control of the HTML author so that they can apply styles such as border width, cell spacing or cell padding as they see fit. We maintain control of the ids within the tags, however, but share the class definitions with the programmers so that they have control over presentation.
Here's the code for the SetDate tag:
package tags;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.text.DateFormat;
import java.util.StringTokenizer;
import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.http.HttpServletRequest;
public class SetDate extends TagSupport {
private static final String months[] = {
"January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December" };
public int doStartTag() throws JspException {
HttpServletRequest req = null;
GregorianCalendar cal = null;
StringTokenizer st = null;
String cmd = null;
int i = -1;
cal = (GregorianCalendar) pageContext.getAttribute( "date",
PageContext.SESSION_SCOPE );
if( cal == null ) {
cal = new GregorianCalendar();
cal.set( Calendar.DATE, 1 );
pageContext.setAttribute( "date", cal,
PageContext.SESSION_SCOPE );
}
req = (HttpServletRequest) pageContext.getRequest();
cmd = req.getParameter( "submit" );
if( cmd != null ) {
if( cmd.equals( "<<<" ) )
cal.roll( Calendar.YEAR, -1 );
if( cmd.equals( "<<" ) )
cal.roll( Calendar.MONTH, -1 );
if( cmd.equals( "---" ) ) {
cal = new GregorianCalendar();
cal.set( Calendar.DATE, 1 );
}
if( cmd.equals( ">>" ) )
cal.roll( Calendar.MONTH, 1 );
if( cmd.equals( ">>>" ) )
cal.roll( Calendar.YEAR, 1 );
pageContext.setAttribute( "date", cal,
PageContext.SESSION_SCOPE );
}
try {
generate( pageContext.getOut(), cal );
}
catch( IOException e ) {
throw( new JspException( getClass().getName() + ": " +
e.toString() ) );
}
return( SKIP_BODY );
}
private void generate( JspWriter out, GregorianCalendar cal )
throws IOException {
int i = -1;
int mon = -1;
out.println( "<h1>Calendar for " +
months[cal.get( Calendar.MONTH )] + " " +
cal.get( Calendar.YEAR ) + "</h1>" );
}
}
|
Note that we look for the submit button value when we try to determine whether to move back, forward, or reset to current. The identifier is key so we also have to control the buttons presented in the navigation table. This is accomplished with the ShowNavigation tag. Here's the code:
package tags;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.text.DateFormat;
import java.util.StringTokenizer;
import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
public class GenerateNavigation extends TagSupport {
public int doStartTag() throws JspException {
try {
generate( pageContext.getOut() );
}
catch( IOException e ) {
throw( new JspException( getClass().getName() + ": " +
e.toString() ) );
}
return( SKIP_BODY );
}
private void generate( JspWriter out )
throws IOException {
String prefix = "<td class=\"navigation\"><input type=\"submit\" name=\"submit\" " +
"value = \"";
String suffix = "\"></td>";
out.println( "<tr>" );
out.println( "<th class=\"navigation\">Backward<br>" +
"a year</th>" );
out.println( "<th class=\"navigation\">Backward<br>" +
"a month</th>" );
out.println( "<th class=\"navigation\">Reset to<br>" +
"current</th>" );
out.println( "<th class=\"navigation\">Forward<br>" +
"a month</th>" );
out.println( "<th class=\"navigation\">Forward<br>" +
"a year</th>" );
out.println( "</tr>" );
out.println( "<tr>" );
out.println( prefix + "<<<" + suffix );
out.println( prefix + "<<" + suffix );
out.println( prefix + "---" + suffix );
out.println( prefix + ">>" + suffix );
out.println( prefix + ">>>" + suffix );
out.println( "</tr>" );
}
}
|
And here's where the connection is made with the SetDate class. We specify both the id and the value (sandwiched between the prefix and suffix strings) of the form variable such that we can collect it in SetDate. Note also how we use the class attribute on the th and td tags. We communicate those names to the HTML author such that they can define the styles in CSS.
The core tag isn't really all that complex either. The only real concern is the handling of partial weeks, both at the beginning and ending of the month. The class attributes for weekdays vs weekends are easily generated. Here's that code:
package tags;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.text.DateFormat;
import java.util.StringTokenizer;
import javax.servlet.jsp.tagext.TagSupport;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
public class GenerateCalendar extends TagSupport {
public int doStartTag() throws JspException {
GregorianCalendar cal = null;
cal = (GregorianCalendar) pageContext.getAttribute( "date",
PageContext.SESSION_SCOPE );
if( cal == null )
throw( new JspException( "date missing from page" ) );
try {
generate( pageContext.getOut(), cal );
}
catch( IOException e ) {
throw( new JspException( getClass().getName() + ": " +
e.toString() ) );
}
return( SKIP_BODY );
}
private void generate( JspWriter out, GregorianCalendar cal )
throws IOException {
int i = -1;
int mon = -1;
cal.set( Calendar.DATE, 1 );
out.println( "<tr>" );
out.println( "<th width=\"15%\" class=\"calendar\">Sun.</th>" );
out.println( "<th width=\"14%\" class=\"calendar\">Mon.</th>" );
out.println( "<th width=\"14%\" class=\"calendar\">Tue.</th>" );
out.println( "<th width=\"14%\" class=\"calendar\">Wed.</th>" );
out.println( "<th width=\"14%\" class=\"calendar\">Thu.</th>" );
out.println( "<th width=\"14%\" class=\"calendar\">Fri.</th>" );
out.println( "<th width=\"15%\" class=\"calendar\">Sat.</th>" );
out.println( "</tr>" );
out.println( "<tr>" );
for( i = 1; i <= 7; i++ ) {
out.print( getHeader( i ) );
if( i == cal.get( Calendar.DAY_OF_WEEK ) ) {
out.print( cal.get( Calendar.DATE ) );
cal.add( Calendar.DATE, 1 );
}
else
out.println( " " );
out.println( "</td>" );
}
out.println( "</tr>" );
mon = cal.get( Calendar.MONTH );
while( mon == cal.get( Calendar.MONTH ) ) {
out.println( "<tr>" );
for( i = 1; i <= 7; i++ ) {
out.print( getHeader( i ) );
if( mon == cal.get( Calendar.MONTH ) ) {
out.print( cal.get( Calendar.DATE ) );
cal.add( Calendar.DATE, 1 );
}
else
out.println( " " );
out.println( "</td>" );
}
out.println( "</tr>" );
}
cal.set( Calendar.MONTH, mon );
cal.set( Calendar.DATE, 1 );
}
private String getHeader( int dayOfWeek ) {
String style = "weekday";
if( ( dayOfWeek == 1 ) || ( dayOfWeek == 7 ) )
style = "weekend";
return( "<td class=\"" + style + "\">" );
}
}
|
It should be noted that internationalization would be easily accomplished here. Both the days of the week and the order in which they're displayed could be handled without much additional effort. The java.util.Calendar class has a method named getFirstDayOfWeek() which can alert the code to the fact that the traditional North American calendar display is not used everywhere in the world. Days of the week strings could be incorporated into java.util.ResourceBundle property files.
So is this a more complex solution than the simple original JavaScript model? Certainly! Is it more flexible? Absolutely! We could just use the GenerateCalendar tag and extend it to provide all kinds of functionality. We could also adjust the font sizes and the sizes of the table and table data elements to make a nice mini-icon and place it anywhere on a page. Powerful stuff, if you ask me. Just think in terms of resusability and you should be off to the races.
Copyright © 2002 by Phil Selby