Web Applications
Glossary Application Tag Libraries
In the Glossary application, JSP tag libraries are used extensively. Figure 13-4 shows the main home page classes: index.jsp / index.jsp_Client. These classes are realized by the index.jsp component, which also uses the Entry Tags tag library and includes a utility page, which in this implementation contains a simple static operation definition to clip long strings when placing them in table cells. The source for the index page follows (Listing 13-7).
Figure 13-4. Class diagram including Glossary application's main home page index.jsp
Listing 13-7 The index.jsp source
<%@ page errorPage="error.jsp" %> <%@ taglib uri="/glytlb" prefix="gly" %> <%@ include file="util.jsp" %> <% String msg = (String) session.getAttribute("message"); if( msg == null ) msg = ""; %>
Glossary Home
<a href="javascript:window.showMenu(window.actionMenu);"> |
Home |
<%=msg%>
Lastest Changes
Term | Description | Last Modified |
---|---|---|
<a href="entry.jsp?id=<%=id%>"><%=longTerm%></a> | <%=clip(description,100)%> | <%=lastmodified%> |
Search
Keywords:
Term
Acronym
Description
In the source for this page, we see that the majority of the code appears to be defining the pop-up menu, which is a modeled with the «ClientScript Object» Menu class. This class is realized by the Menu.js script library component and included in the
element. This excellent example of a JavaScript class was created by Gary Smith of Netscape Communications, and the full copyright can be found in the source code file for the menu included in the application's files.
All the interaction with the entity tier is done via JSP tags (Figure 13-5). Three tags are defined. The tag, required to be the parent tag to the two other tags, provides the contextSQL result setfor the child tags. The child tag is an iterator tag and executes a loop, processing its body a certain number of times, specified as a tag attribute. The tag provides access to a single entry.
Figure 13-5. Modeled JSP tags
The source code for the tag follows. This class extends the required BodyTagSupport class and provides get and set operations for all attributes defined in the tag library. Most of the functionality is in the do start and the do end tag operations. This class defines a helper operation to make the query to the entity tier. This tag expects to have a child tag access it and query it for the entry information (Listing 13-8).
One of the child tags, EntryLoopTag, followssee Listing 13-9 starting on page 310.
Listing 13-8 EntryListTag.java source
Package app.glossary.taglib; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; import javax.servlet.ServletRequest; import javax.servlet.http.HttpSession; import java.util.*; import java.io.*; import java.lang.Integer; import app.glossary.*; /** *
A logical representation of a query with multiple results.
* *
An entry list tag represents a particular type of query made to the * glossary. The query type is a tag attribute and must be one of { search, * changes, history }. This tag also examines the request object for the index * into the list and the page size to provide scripting variables for the next * and previous indicies. In addition to the indices the total size of the * result set, number of pages and current page number is provided as a scripting variable. *
* @author jim conallen * @version 2.0 */ public class EntryListTag extends BodyTagSupport { /** The type of query for the list { changes | search | history } */ private String queryType = ""; /** The starting index for the list loop. */ private int index = 0; /** The current page number beginning this list */ private int page; /** The index value beginning the next page. */ private int next; /** The index value beginning the previous page. */ private int prev; /** The total number of pages that this list spans. */ private int pageCount; /** The size of each page in the list. */ private int pageSize = 0; /** The total number of results in the list. */ private int resultSize; /** The category id. */ private long entryId; /** The category id. */ private Enumeration entries; /** * Tag attribute that sets the type of query to be made in this list. * * @param queryType the type of query to be made for this list. */ public void setQuery( String _queryType ){ queryType = _queryType; } /** * Returns the type of query for this list. * Although not used directly by this application or the glossary code, * this getter seems to be required by the container. * * @returns The type of query this tag makes. */ public String getQuery() { return queryType; } /** * Tag attribute that sets the entry id for a version history list. * * @param the id of the entry to list the versions for. Should be parseable into a Long. */ public void setEntryId( String _entryId ){ try{ entryId = Long.parseLong(_entryId); } catch( NumberFormatException ex ) { entryId = 0; } } /** * Returns the entry id set for a version history type of list. * Not required by the application, but it seems to be required by the servlet container. * * @returns The type of query this tag makes. */ public String getEntryId() { return Long.toString(entryId); } /** * Tag attribute that sets the page size for the loop of this page. * * @param the length in rows for this page. */ public void setLength( String _length ){ try{ pageSize = Integer.parseInt(_length); } catch( NumberFormatException ex ) { pageSize = 10; } } /** * Returns the page size for the loop of entries in this page. * Not required by the application, but it seems to be required by the servlet container. * * @returns The page size for this page. */ public String getLength() { return Integer.toString(pageSize); } /** * Called as the tag is first seen in a JSP page. * The query type should be set first, if it is a known type, then the appropriate query * is made to the database. The requested index and page size is taken from the request * and if it is consistent with the results of the query, is set appropriately. * Ultimately the results of the query determine indices and sizes. * * @return EVAL_BODY_TAG if the query type is a known value, otherwise this tag is skipped. */ public int doStartTag() throws JspException { if( queryType.equals("changes") || queryType.equals("search") || queryType.equals("history") ) { HttpSession session = pageContext.getSession(); index = getIntFromRequest("index", 0); if( index == 0 ) index = getIntFromSession("index",1); if( pageSize == 0 ) pageSize = getIntFromSession("pagesize",10); if( index<=0 ) index = 1; if( pageSize<=0 ) pageSize = 10; executeQuery(); if( !entries.hasMoreElements() ) { String message = "The query you requested resulted in no matches."; pageContext.getSession().setAttribute("message", message); next = 1; prev = 1; pageCount = 0; page = 0; } else { next = getNext(); prev = getPrev(); pageCount = getPageCount(); page = getPage(); } return EVAL_BODY_TAG; } return SKIP_BODY; } /** * Called just before the contents of the body are evaluated, all script variables are set * in this call. */ public void doInitBody() throws JspException { pageContext.setAttribute("index", Integer.toString(index) ); pageContext.setAttribute("currentpage", Integer.toString(page)); pageContext.setAttribute("pageCount", Integer.toString(pageCount)); pageContext.setAttribute("nextIndex", Integer.toString(next)); pageContext.setAttribute("prevIndex", Integer.toString(prev)); } /** * Called child tags that process the entries. * Child tags get the entries and typically iterate through them to provide lists of * results. This tag represents a query, while child tags implement the iterative loops * where each value is used. * * @return An enumeration of entries that meets the criteria of the list. */ public Enumeration getEntries(){ return entries; } /** * Called child tags that process the entries. * Child tags get the entries and typically iterate through them to provide lists of * results. This tag represents a query, while child tags implement the iterative loops * where each value is used. * * @return An enumeration of entries that meets the criteria of the list. */ private void executeQuery(){ Glossary glossary = new Glossary(); if( queryType.equals("changes") ) { index = 1; if( pageSize <= 0 ) pageSize = 5; entries = glossary.mostRecentChanges(pageSize); } else if( queryType.equals("history") ) { entries = glossary.history(entryId, index, pageSize); } else { HttpSession session = pageContext.getSession(); EntryQueryCriteria criteria = (EntryQueryCriteria) session.getAttribute("query"); if( criteria == null ){ entries = new Vector().elements(); } else{ if( criteria.isSearch() ){ entries = glossary.search( criteria.getTerm(), criteria.getAcronym(), criteria.getDescription(), index, pageSize); } else { entries = glossary.browse( criteria.browseWith, index, pageSize ); } } } resultSize = glossary.getResultSize(); } /** * Utility method that gets an int from a request parameter. * * @return An int from the request */ private int getIntFromRequest(String param, int defaultVal) { String intStr; int intVal; ServletRequest request = pageContext.getRequest(); intStr = request.getParameter(param); if( intStr == null ) { intVal = defaultVal; }else{ try{ intVal = Integer.parseInt(intStr); } catch( NumberFormatException ex ){ intVal = defaultVal; } } return intVal; } /** * Utility method that gets an int from the session. * * @return An int from the session */ private int getIntFromSession(String key, int defaultVal) { Integer intObj; int intVal; HttpSession session = pageContext.getSession(); intObj = (Integer) session.getAttribute(key); if( intObj == null ) { intVal = defaultVal; }else{ intVal = intObj.intValue(); } return intVal; } /** * Determines the current page number given the index and page size. * * @return The current page (one based) */ private int getPage(){ int page = (index / pageSize) + 1; return page; } /** * Determines the total number of pages in the result. * * @return The number of pages in the result set, given the page size. */ private int getPageCount(){ int pageCount = (resultSize / pageSize) + 1; if( resultSize == 0 ) pageCount = 0; return pageCount; } /** * Determines the next starting index, given the current page, page size and total number of * results. * * @return The next index */ private int getNext(){ int next = index + pageSize; if( next > resultSize ) next = index; return next; } /** * Determines the previous page's starting index, given the current page, page size and * total number of results. * * @return The prev index */ private int getPrev(){ int prev = index - pageSize; if( prev <=0 ) prev = 1; return prev; } /** * Called when the end tag is identified. * The method simply writes out the processed body. * * @return The prev index */ public int doEndTag() throws JspTagException { BodyContent body = getBodyContent(); try { body.writeOut(getPreviousOut()); } catch (IOException e) { throw new JspTagException("EntryList: " + e.getMessage()); } return SKIP_BODY; } /** * Clean up time, all internal variables are reset. */ public void release() { queryType = ""; index = 1; page = 1; next = 1; prev = 1; pageCount = 0; pageSize = 10; resultSize = 0; entries = null; } }
Listing 13-9 EntryTag.java source
package app.glossary.taglib; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; import javax.servlet.ServletRequest; import javax.servlet.http.HttpSession; import java.util.*; import java.io.*; import java.lang.Integer; import app.glossary.*; import java.text.DateFormat; /** *
A loop of entries.
* *
This tag looks to a parent tag (EntryList) for a definition of a set of entries. The * list is iterated through and for each entry a set of scripting variables is set. *
* @author jim conallen * @version 2.0 */ public class EntryLoopTag extends BodyTagSupport { private Enumeration entries; private IEntry entry; private boolean convertLineBreaks = true; public String getConvertLineBreaks(){ return new Boolean(convertLineBreaks).toString(); } public void setConvertLineBreaks(String _convertLineBreaks ){ if( _convertLineBreaks != null ) { _convertLineBreaks = _convertLineBreaks.toLowerCase().trim(); if( _convertLineBreaks.equals("false") || _convertLineBreaks.equals("no") ){ convertLineBreaks = false; } } } public int doStartTag() throws JspException { //get the criteria and execute the query try{ EntryListTag list = (EntryListTag) findAncestorWithClass(this, Class.forName("app.glossary.taglib.EntryListTag")); entries = list.getEntries(); }catch( ClassNotFoundException ex ){ throw new JspTagException("EntryLoop: " + ex.getMessage()); } if(entries.hasMoreElements()) { entry = (IEntry) entries.nextElement(); setEntryAttributes(); return EVAL_BODY_TAG; } else { return SKIP_BODY; } } public void setEntryAttributes() { pageContext.setAttribute("id", Long.toString(entry.getId()) ); String term = EntryTag.prepareHTML( entry.getTerm() ); pageContext.setAttribute("term", term); String formattedAcronym = EntryTag.prepareHTML( entry.getAcronym() ); pageContext.setAttribute("acronym", formattedAcronym); if( formattedAcronym.equals("") ) pageContext.setAttribute("longTerm", term ); else pageContext.setAttribute("longTerm", term + " (" + formattedAcronym + ")" ); String description = entry.getDescription(); if( convertLineBreaks && !entry.getIncludesHTML() ) { description = insertHTMLBreaks(description); } pageContext.setAttribute("description", description); if( entry.getIncludesHTML() ) pageContext.setAttribute("includesHTML", "CHECKED"); else pageContext.setAttribute("includesHTML", ""); pageContext.setAttribute("lastmodified", DateFormat.getDateInstance().format(entry.lastModified())); } public String insertHTMLBreaks( String str ) { return replaceString(str, " ", "
"); } public int doAfterBody() throws JspException { BodyContent body = getBodyContent(); try { body.writeOut(getPreviousOut()); } catch (IOException e) { throw new JspTagException("EntryLoop: " + e.getMessage()); } // clear up so the next time the body content is empty body.clearBody(); if(entries.hasMoreElements()) { entry = (IEntry) entries.nextElement(); setEntryAttributes(); return EVAL_BODY_TAG; } else { return SKIP_BODY; } } private static String replaceString(String str, String sep, String rep) { StringBuffer retVal = new StringBuffer(); int idx = 0; int jdx = str.indexOf(sep); while (jdx >= 0) { retVal.append(str.substring(idx, jdx)); retVal.append(rep); idx = jdx + sep.length(); jdx = str.indexOf(sep, idx); } retVal.append(str.substring(idx)); return retVal.toString(); } /** * Clean up time, all internal variables are reset. */ public void release() { entries = null; entry = null; } public static String formatHtml( String str ) { str = replaceString(str, "<", "<"); str = replaceString(str, ">", ">"); return str; } }
As with all tags, extends the BodyTagSupport class and provides operations for the start and end tag operations. What is notable in this class is how it gets a reference to its parent tag. This is done in the start tag operation by calling the findAncestorWithClass() base class operation. This tag iterates by returning the value EVAL_BODY_TAG each time the operation doAfterBody() is called and there is another row to display.
This class sets a number of scripting variables that are used in the JSP source as dynamic content items. The UX team is free to position and to format these values according to the UX guidelines document. The engineering team, on the other hand, is able to control all access to the entity tier through the custom tags.