View Javadoc

1   /***
2    * Copyright (c) 2003 held jointly by the individual authors.
3    * 
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    * 
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12   * for more details.
13   * 
14   * You should have received a copy of the GNU Lesser General Public License
15   * along with this library; if not, write to the Free Software Foundation, Inc.,
16   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17   *  > http://www.gnu.org/copyleft/lesser.html >
18   * http://www.opensource.org/licenses/lgpl-license.php
19   */
20  package net.mlw.vlh.web.tag;
21  
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.regex.Pattern;
27  
28  import javax.servlet.http.HttpServletRequest;
29  import javax.servlet.jsp.JspException;
30  
31  import net.mlw.vlh.ValueList;
32  import net.mlw.vlh.ValueListInfo;
33  import net.mlw.vlh.web.ValueListConfigBean;
34  import net.mlw.vlh.web.util.DisplayHelper;
35  import net.mlw.vlh.web.util.JspUtils;
36  
37  import org.springframework.context.MessageSource;
38  import org.springframework.context.NoSuchMessageException;
39  
40  /***
41   * Generate buttons to navigate through pages of data using i18n
42   * (internationalization). The following keys are required to be define in
43   * message sources.
44   * 
45   * <p>
46   * If you like to, you can add your properties file in your locale and add this
47   * lines of code in your language:
48   * </p>
49   * Summary info:
50   * <ol>
51   * <code>
52   *     <li>paging.text.totalRow={0} Total  </li>
53   *     <li>paging.text.pageFromTotal= <b>{0}</b> of  {1} page(s) </li>
54   *  </code>
55   * </ol>
56   * Paging info:
57   * <ol>
58   * <li>paging.first(off), paging.first(on)</li>
59   * <li>paging.previous(off), paging.previous(on)</li>
60   * <li>paging.forward(off), paging.forward(on)</li>
61   * <li>paging.last(off), paging.last(on)</li>
62   * <li>paging.delim</li>
63   * <li>paging.text.totalRow</li>
64   * <li>paging.text.pageFromTotal</li>
65   * </ol>
66   * Focus info:
67   * <ol>
68   * <li>paging.focus(on), paging.focus(off), paging.focus(disabled),
69   * paging.focus(error)</li>
70   * </ol>
71   * Items per page info:
72   * <ol>
73   *   <code>
74   *	   <li>paging.itemsPerPage.label = Items Per Page:</li>
75   *      <li>paging.itemsPerPage.title = Number of items per page.</li>
76   *      <li>paging.itemsPerPage.button = Set</li>
77   *   </code>
78   * </ol>
79   * @todo Document this tag. AAA separate summary to differnt tag, find better
80   *       pictures for paging.focus
81   * @author Matthew L. Wilson, Andrej Zachar
82   * @version $Revision: 1.38 $ $Date: 2005/11/23 15:02:16 $
83   */
84  public class DefaultPagingTag extends ConfigurableTag
85  {
86  
87     /***
88      * Pattern for ...border=...
89      */
90     private static final Pattern BORDER_ATTRIBUTE_PATTERN = Pattern.compile("//b(border//s*=//s*//\"?\'?)", Pattern.CASE_INSENSITIVE);
91  
92     /***
93      * Pattern for ...cellspacing=...
94      */
95     private static final Pattern CELLSPACING_ATTRIBUTE_PATTERN = Pattern.compile("//b(cellspacing//s*=//s*//\"?\'?)",
96           Pattern.CASE_INSENSITIVE);
97  
98     /***
99      * Pattern for ...cellpadding=...
100     */
101    private static final Pattern CELLPADDING_ATTRIBUTE_PATTERN = Pattern.compile("//b(cellpadding//s*=//s*//\"?\'?)",
102          Pattern.CASE_INSENSITIVE);
103 
104    private static final String PAGING_TAG_STYLE = "PagingTag";
105 
106    private static final String SUMMARY_STYLE = "Summary";
107 
108    private static final String ITEMS_PER_PAGE_STYLE = "ItemsPerPage";
109 
110    private static final String INPUT_BOX_STYLE = ITEMS_PER_PAGE_STYLE + "InputBox";
111 
112    private static final String LABEL_STYLE = ITEMS_PER_PAGE_STYLE + "Label";
113 
114    private static final String SUBMIT_BUTTON_STYLE = ITEMS_PER_PAGE_STYLE + "SubmitButton";
115 
116    private static final String PAGING_STYLE = "Paging";
117 
118    /* Tag attributes */
119 
120    private int pages = 0;
121 
122    private boolean showSummary = false;
123 
124    private boolean showItemsPerPage = false;
125 
126    private boolean generateItemsPerPageForm = true;
127 
128    /* State variables */
129 
130    private int currentPage = 0;
131 
132    private int maxPage = 0;
133 
134    private Map parameters;
135 
136    private ValueListSpaceTag rootTag;
137 
138    private DisplayHelper displayHelper;
139 
140    private MessageSource messageSource;
141 
142    private Locale currentLocale;
143 
144    protected ValueListConfigBean getConfig()
145    {
146       return rootTag.getConfig();
147    }
148 
149    protected ValueList getValueList()
150    {
151       return rootTag.getValueList();
152    }
153 
154    protected ValueListInfo getValueListInfo()
155    {
156       return rootTag.getValueList().getValueListInfo();
157    }
158 
159    protected TableInfo getTableInfo()
160    {
161       return rootTag.getTableInfo();
162    }
163 
164    protected Locale getLocale()
165    {
166       return this.currentLocale;
167    }
168 
169    protected void init(HttpServletRequest request) throws JspException
170    {
171       this.rootTag = (ValueListSpaceTag) JspUtils.getParent(this, ValueListSpaceTag.class);
172       ValueListConfigBean config = rootTag.getConfig();
173       this.displayHelper = config.getDisplayHelper();
174       this.messageSource = config.getMessageSource();
175       this.currentLocale = config.getLocaleResolver().resolveLocale(request);
176    }
177 
178    /***
179     * @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
180     */
181    public int doStartTag() throws JspException
182    {
183 
184       init((HttpServletRequest) pageContext.getRequest());
185 
186       Locale locale = getLocale();
187 
188       TableInfo tableInfo = getTableInfo();
189       ValueListInfo valueListInfo = getValueListInfo();
190 
191       // Create a map of parameters that are used to generate the links.
192       parameters = new HashMap(tableInfo.getParameters());
193       parameters.put(ValueListInfo.SORT_COLUMN + tableInfo.getId(), valueListInfo.getSortingColumn());
194       parameters.put(ValueListInfo.SORT_DIRECTION + tableInfo.getId(), valueListInfo.getSortingDirection());
195 
196       int page = valueListInfo.getPagingPage();
197       int numberOfPages = valueListInfo.getTotalNumberOfPages();
198 
199       this.currentPage = page - (pages / 2);
200       if (this.currentPage < 1)
201       {
202          this.currentPage = 1;
203       }
204 
205       this.maxPage = (this.currentPage - 1) + pages;
206       if (this.maxPage > numberOfPages)
207       {
208          this.currentPage -= (this.maxPage - numberOfPages);
209          this.maxPage = numberOfPages;
210       }
211       if (this.maxPage < 2)
212       {
213          this.maxPage = 0;
214       }
215       if (this.currentPage < 1)
216       {
217          this.currentPage = 1;
218       }
219 
220       StringBuffer sb = new StringBuffer();
221 
222       final String stylePrefix = getConfig().getStylePrefix();
223       final String pagingTagStyle = stylePrefix + PAGING_TAG_STYLE + " " + PAGING_TAG_STYLE;
224       final String summaryStyle = stylePrefix + SUMMARY_STYLE + " " + SUMMARY_STYLE;
225       final String itemsPerPageStyle = stylePrefix + ITEMS_PER_PAGE_STYLE + " " + ITEMS_PER_PAGE_STYLE;
226       final String pagingStyle = stylePrefix + PAGING_STYLE + " " + PAGING_STYLE;
227 
228       sb.append("<table class='").append(pagingTagStyle).append("'");
229 
230       String attributes = getAttributes();
231       if (attributes != null)
232       {
233          if (!BORDER_ATTRIBUTE_PATTERN.matcher(attributes).find())
234          {
235             sb.append(" border=0");
236          }
237          if (!CELLSPACING_ATTRIBUTE_PATTERN.matcher(attributes).find())
238          {
239             sb.append(" cellspacing=0");
240          }
241          if (!CELLPADDING_ATTRIBUTE_PATTERN.matcher(attributes).find())
242          {
243             sb.append(" cellpadding=0");
244          }
245          sb.append(" ").append(attributes);
246       }
247 
248       sb.append(">");
249       sb.append("\n <tr class='").append(pagingTagStyle).append("'>");
250 
251       if (showSummary)
252       {
253          sb.append("\n  <td class='").append(summaryStyle).append("'>");
254          renderSumary(sb);
255          sb.append("</td>");
256          if (showItemsPerPage)
257          {
258             HashMap itemsPerPageParameters = new HashMap(parameters);
259             sb.append("\n  <td class='").append(itemsPerPageStyle).append("'>");
260             renderItemsPerPage(sb, itemsPerPageParameters);
261             sb.append("</td>");
262          }
263          sb.append("\n  <td class='").append(pagingStyle).append("'>");
264          sb.append("<table border='0' cellspacing='0' cellpadding='0' class='").append(pagingStyle).append("'>");
265          sb.append("\n    <tr class='").append(pagingStyle).append("'>");
266 
267       }
268 
269       parameters.put(ValueListInfo.PAGING_NUMBER_PER + tableInfo.getId(), String.valueOf(valueListInfo.getPagingNumberPer()));
270 
271       renderFocusControl(sb);
272 
273       String value;
274 
275       if (page > 1)
276       {
277          parameters.put(ValueListInfo.PAGING_PAGE + tableInfo.getId(), "1");
278 
279          value = getMessage("paging.first(on)", null, locale);
280 
281          sb.append("\n     <td>");
282          renderPagingLink(sb, parameters, value);
283          sb.append("</td>");
284 
285          String delim = getMessage("paging.delim", null, "", locale);
286 
287          if (value.length() > 0 && delim.length() > 0)
288          {
289             sb.append("\n     <td>").append(delim).append("</td>");
290          }
291 
292          parameters.put(ValueListInfo.PAGING_PAGE + tableInfo.getId(), String.valueOf(page - 1));
293 
294          value = getMessage("paging.previous(on)", null, locale);
295 
296          sb.append("\n     <td>");
297          renderPagingLink(sb, parameters, value);
298          sb.append("</td>");
299 
300          if (value.length() > 0 && delim.length() > 0)
301          {
302             sb.append("\n     <td>").append(delim).append("</td>");
303          }
304       }
305       else
306       {
307          sb.append("\n     <td>").append(getMessage("paging.first(off)", null, locale)).append("</td>");
308          sb.append("\n     <td>").append(getMessage("paging.previous(off)", null, locale)).append("</td>");
309       }
310 
311       JspUtils.write(pageContext, sb.toString());
312       pageContext.setAttribute("page" + tableInfo.getId(), new Integer(this.currentPage));
313 
314       return EVAL_BODY_BUFFERED;
315    }
316 
317    /***
318     * Try to resolve the message. Treat as an error if the message can't be found.
319     * @param code the code to lookup up, such as 'calculator.noRateSet'
320     * @param args Array of arguments that will be filled in for params within
321     * the message (params look like "{0}", "{1,date}", "{2,time}" within a message),
322     * or null if none.
323     * @param locale the Locale in which to do the lookup
324     * @return the resolved message
325     * @throws NoSuchMessageException if the message wasn't found
326     * @see <a href="http://java.sun.com/j2se/1.3/docs/api/java/text/MessageFormat.html">java.text.MessageFormat</a>
327     */
328    protected String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException, JspException
329    {
330       return displayHelper.help(pageContext, messageSource.getMessage(code, args, locale));
331    }
332 
333    /***
334     * Try to resolve the message. Return default message if no message was found.
335     * @param code the code to lookup up, such as 'calculator.noRateSet'. Users of
336     * this class are encouraged to base message names on the relevant fully
337     * qualified class name, thus avoiding conflict and ensuring maximum clarity.
338     * @param args array of arguments that will be filled in for params within
339     * the message (params look like "{0}", "{1,date}", "{2,time}" within a message),
340     * or null if none.
341     * @param locale the Locale in which to do the lookup
342     * @param defaultMessage String to return if the lookup fails
343     * @return the resolved message if the lookup was successful;
344     * otherwise the default message passed as a parameter
345     * @see <a href="http://java.sun.com/j2se/1.3/docs/api/java/text/MessageFormat.html">java.text.MessageFormat</a>
346     */
347    protected String getMessage(String code, Object[] args, String defaultMessage, Locale locale) throws NoSuchMessageException,
348          JspException
349    {
350       return displayHelper.help(pageContext, messageSource.getMessage(code, args, defaultMessage, locale));
351    }
352 
353    /***
354     *
355     * @param sb
356     * @param itemsPerPageParameters
357     * @throws NoSuchMessageException
358     * @throws JspException
359     * @todo DO IT WIHTOUT JAVASCRIPT!
360     */
361    protected void renderItemsPerPage(StringBuffer sb, HashMap itemsPerPageParameters) throws NoSuchMessageException, JspException
362    {
363       Locale locale = getLocale();
364       ValueListInfo valueListInfo = getValueListInfo();
365       TableInfo tableInfo = getTableInfo();
366 
367       itemsPerPageParameters.remove(ValueListInfo.PAGING_NUMBER_PER + tableInfo.getId());
368       itemsPerPageParameters.put(ValueListInfo.PAGING_PAGE + tableInfo.getId(), String.valueOf(valueListInfo.getPagingPage()));
369 
370       if (valueListInfo.isFocusEnabled())
371       {
372          itemsPerPageParameters.put(ValueListInfo.DO_FOCUS + tableInfo.getId(), valueListInfo.isDoFocusAgain() ? "true" : "false");
373          if (valueListInfo.getFocusProperty() != null)
374          {
375             itemsPerPageParameters.put(ValueListInfo.FOCUS_PROPERTY + tableInfo.getId(), valueListInfo.getFocusProperty());
376          }
377          if (valueListInfo.getFocusValue() != null)
378          {
379             itemsPerPageParameters.put(ValueListInfo.FOCUS_VALUE + tableInfo.getId(), valueListInfo.getFocusValue());
380          }
381       }
382 
383       String propertyName = ValueListInfo.PAGING_NUMBER_PER + tableInfo.getId();
384 
385       String delim = getMessage("paging.delim", null, "", locale);
386       sb.append(delim);
387 
388       final String stylePrefix = getConfig().getStylePrefix();
389       final String commonStyle = stylePrefix + ITEMS_PER_PAGE_STYLE + " " + ITEMS_PER_PAGE_STYLE;
390       final String labelStyle = stylePrefix + LABEL_STYLE + " " + LABEL_STYLE;
391       final String inputBoxStyle = stylePrefix + INPUT_BOX_STYLE + " " + INPUT_BOX_STYLE;
392       final String submitButtonStyle = stylePrefix + SUBMIT_BUTTON_STYLE + " " + SUBMIT_BUTTON_STYLE;
393 
394       if (isGenerateItemsPerPageForm())
395       {
396          sb.append("<form action='#' onsubmit='return false;' class='").append(commonStyle).append("'>");
397       }
398 
399       sb.append("<table border='0' cellspacing='0' cellpadding='0' class='").append(commonStyle).append("'>");
400 
401       sb.append("\n    <tr class='").append(commonStyle).append("'>");
402 
403       Map labelAttrs = new HashMap();
404       labelAttrs.put("class", labelStyle);
405 
406       sb.append("\n     <td nowrap='true' class='").append(labelStyle).append("'>");
407       renderPerPageLabel(sb, labelAttrs, getMessage("paging.itemsPerPage.label", null, locale));
408       sb.append("</td>");
409 
410       Map inputBoxAttrs = new HashMap();
411       inputBoxAttrs.put("id", propertyName);
412       inputBoxAttrs.put("name", propertyName);
413       inputBoxAttrs.put("class", inputBoxStyle);
414       inputBoxAttrs.put("title", getMessage("paging.itemsPerPage.title", null, locale));
415       inputBoxAttrs.put("value", new Integer(valueListInfo.getPagingNumberPer()));
416 
417       sb.append("\n     <td class='").append(inputBoxStyle).append("'>");
418       renderPerPageInputBox(sb, inputBoxAttrs);
419       sb.append("</td>");
420 
421       Map submitButtonAttrs = new HashMap();
422       submitButtonAttrs.put("class", submitButtonStyle);
423       submitButtonAttrs.put("value", getMessage("paging.itemsPerPage.button", null, locale));
424       submitButtonAttrs.put("onclick", "window.location.href = \"" + tableInfo.getUrl()
425             + getConfig().getLinkEncoder().encode(pageContext, itemsPerPageParameters) + propertyName + "=\" + document.getElementById(\""
426             + propertyName + "\").value;");
427 
428       sb.append("\n     <td class='").append(submitButtonStyle).append("'>");
429       renderPerPageSubmitButton(sb, submitButtonAttrs);
430       sb.append("</td>");
431 
432       sb.append("\n    </tr>");
433       sb.append("\n  </table>");
434 
435       if (isGenerateItemsPerPageForm())
436       {
437          sb.append("</form>");
438       }
439    }
440 
441    /***
442     * Render label for items per page input box. Subclasses can overide or extend the method to provide different behaviour.
443     * 
444     * @param sb StringBuffer to render into
445     * @param attributes
446     * @param text
447     */
448    protected void renderPerPageLabel(StringBuffer sb, Map attributes, String text)
449    {
450       renderBodyTag(sb, "label", attributes, text);
451    }
452 
453    /***
454     * Render input box for items per page. Subclasses can overide or extend the method to provide different behaviour.
455     * 
456     * @param sb StringBuffer to tender into
457     * @param attributes Attributes for rendering a tag
458     */
459    protected void renderPerPageInputBox(StringBuffer sb, Map attributes)
460    {
461       attributes.put("type", "text");
462       renderSimpleTag(sb, "input", attributes);
463    }
464 
465    /***
466     * Render submit button for items per page input box. Subclasses can overide or extend the method to provide different behaviour.
467     * 
468     * @param sb
469     * @param attributes
470     */
471    protected void renderPerPageSubmitButton(StringBuffer sb, Map attributes)
472    {
473       if (!attributes.containsKey("type"))
474       {
475          attributes.put("type", "button");
476       }
477       renderSimpleTag(sb, "input", attributes);
478    }
479 
480    private static void renderSimpleTag(StringBuffer sb, String name, Map attributes)
481    {
482       sb.append("<").append(name);
483       if (attributes != null)
484       {
485          Iterator i = attributes.entrySet().iterator();
486          while (i.hasNext())
487          {
488             Map.Entry entry = (Map.Entry) i.next();
489             sb.append(" ").append(entry.getKey()).append("='").append(entry.getValue()).append("'");
490          }
491       }
492       sb.append(">");
493    }
494 
495    private static void renderBodyTag(StringBuffer sb, String name, Map attributes, String content)
496    {
497       renderSimpleTag(sb, name, attributes);
498       if (content != null)
499       {
500          sb.append(content);
501       }
502       sb.append("</").append(name).append(">");
503    }
504 
505    /***
506     * @param sb
507     * @throws JspException
508     * @throws NoSuchMessageException
509     */
510    protected void renderSumary(StringBuffer sb) throws NoSuchMessageException, JspException
511    {
512       Locale locale = getLocale();
513       ValueListInfo valueListInfo = getValueListInfo();
514       sb.append(getMessage("paging.text.totalRow", new Object[]
515       { new Integer(valueListInfo.getTotalNumberOfEntries()) }, locale));
516       sb.append(getMessage("paging.text.pageFromTotal", new Object[]
517       { new Integer(valueListInfo.getPagingPage()), new Integer(valueListInfo.getTotalNumberOfPages()) }, locale));
518    }
519 
520    /***
521     *
522     * @param sb
523     * @throws JspException
524     */
525    protected void renderFocusControl(StringBuffer sb) throws JspException
526    {
527 
528       Locale locale = getLocale();
529       ValueListInfo valueListInfo = getValueListInfo();
530       TableInfo tableInfo = getTableInfo();
531 
532       String value;
533 
534       if (valueListInfo.isFocusEnabled())
535       {
536          parameters.put(ValueListInfo.FOCUS_PROPERTY + tableInfo.getId(), valueListInfo.getFocusProperty());
537 
538          if (valueListInfo.getFocusValue() != null)
539          {
540             parameters.put(ValueListInfo.FOCUS_VALUE + tableInfo.getId(), valueListInfo.getFocusValue());
541          }
542          // AAA focus error behavier
543          HashMap focusParameters = new HashMap(parameters);
544          if (valueListInfo.getFocusStatus() != ValueListInfo.FOCUS_TOO_MANY_ITEMS)
545          {
546             focusParameters.put(ValueListInfo.DO_FOCUS + tableInfo.getId(), valueListInfo.isDoFocusAgain() ? "false" : "true");
547 
548             value = getMessage(valueListInfo.isDoFocusAgain() ? "paging.focus(off)" : "paging.focus(on)", null, locale);
549 
550             sb.append("\n     <td>");
551             renderPagingLink(sb, parameters, value);
552             sb.append("</td>");
553 
554             String delim = getMessage("paging.delim", null, "", locale);
555             if (value.length() > 0 && delim.length() > 0)
556             {
557                sb.append("\n     <td>").append(delim).append("</td>");
558             }
559          }
560          else
561          {
562             if (valueListInfo.isFocusEnabled())
563             {
564                sb.append("\n     <td>").append(getMessage("paging.focus(error)", null, locale)).append("</td>");
565             }
566          }
567 
568       }
569       else
570       {
571          sb.append("\n     <td>").append(getMessage("paging.focus(disabled)", null, locale)).append("</td>");
572       }
573    }
574 
575    /***
576     * @see javax.servlet.jsp.tagext.IterationTag#doAfterBody()
577     */
578    public int doAfterBody() throws JspException
579    {
580 
581       // method doAfterBody() is always invoked on WebLogic (even if the tag has no body)
582 
583       if (this.currentPage <= this.maxPage)
584       {
585 
586          TableInfo tableInfo = getTableInfo();
587 
588          String label = null;
589 
590          if (getBodyContent() != null)
591          {
592             label = getBodyContent().getString();
593             if (label != null)
594             {
595                label = label.trim();
596             }
597             getBodyContent().clearBody();
598          }
599 
600          if (label == null || label.length() == 0)
601          {
602             return SKIP_BODY;
603          }
604 
605          StringBuffer sb = new StringBuffer();
606 
607          renderContent(sb, label);
608 
609          JspUtils.writePrevious(pageContext, sb.toString());
610 
611          pageContext.setAttribute("page" + tableInfo.getId(), new Integer(++this.currentPage));
612 
613          return EVAL_BODY_AGAIN;
614 
615       }
616       else
617       {
618          return SKIP_BODY;
619       }
620    }
621 
622    /***
623     *
624     * @param sb
625     * @param label
626     */
627    private void renderContent(StringBuffer sb, String label)
628    {
629       ValueListInfo valueListInfo = getValueListInfo();
630       TableInfo tableInfo = getTableInfo();
631       if (this.currentPage == valueListInfo.getPagingPage())
632       {
633          sb.append("\n     <th>").append(label).append("</th>");
634       }
635       else
636       {
637          parameters.put(ValueListInfo.PAGING_PAGE + tableInfo.getId(), String.valueOf(this.currentPage));
638 
639          sb.append("\n     <td>");
640          renderPagingLink(sb, parameters, label);
641          sb.append("</td>");
642       }
643    }
644 
645    /***
646     * @see javax.servlet.jsp.tagext.TagSupport#doEndTag()
647     */
648    public int doEndTag() throws JspException
649    {
650       StringBuffer sb = new StringBuffer();
651 
652       Locale locale = getLocale();
653 
654       String delim = getMessage("paging.delim", null, locale);
655 
656       ValueListInfo valueListInfo = getValueListInfo();
657       TableInfo tableInfo = getTableInfo();
658 
659       int page = valueListInfo.getPagingPage();
660       int numberOfPages = valueListInfo.getTotalNumberOfPages();
661 
662       if (getBodyContent() == null || getBodyContent().getString() == null || getBodyContent().getString().trim().length() == 0)
663       {
664          while (this.currentPage <= this.maxPage)
665          {
666             renderContent(sb, String.valueOf(this.currentPage));
667             this.currentPage++;
668          }
669       }
670 
671       if (page < numberOfPages)
672       {
673 
674          parameters.put(ValueListInfo.PAGING_PAGE + tableInfo.getId(), String.valueOf(page + 1));
675 
676          String value = getMessage("paging.forward(on)", null, locale);
677 
678          sb.append("\n     <td>");
679          renderPagingLink(sb, parameters, value);
680          sb.append("</td>");
681 
682          if (value.length() > 0 && delim.length() > 0)
683          {
684             sb.append("\n     <td>").append(delim).append("</td>");
685          }
686 
687          parameters.put(ValueListInfo.PAGING_PAGE + tableInfo.getId(), String.valueOf(numberOfPages));
688 
689          value = getMessage("paging.last(on)", null, locale);
690 
691          sb.append("\n     <td>");
692          renderPagingLink(sb, parameters, value);
693          sb.append("</td>");
694       }
695       else
696       {
697          sb.append("\n     <td>").append(getMessage("paging.forward(off)", null, locale)).append("</td>");
698          sb.append("\n     <td>").append(getMessage("paging.last(off)", null, locale)).append("</td>");
699       }
700 
701       sb.append("\n    </tr>");
702       sb.append("\n   </table>");
703 
704       if (showSummary)
705       {
706          sb.append("</td>");
707          sb.append("\n </tr>");
708          sb.append("\n</table>");
709       }
710       JspUtils.write(pageContext, sb.toString());
711 
712       release();
713 
714       return EVAL_PAGE;
715    }
716 
717    /***
718     * Renders <code>&lt;a&gt;</code> tag for control buttons. Subclasses can overide or extend the method to provide different behaviour.
719     * 
720     * @param sb StringBuffer to render into
721     * @param parameters Map of parameters
722     * @param value Label
723     */
724    protected void renderPagingLink(StringBuffer sb, Map parameters, String value)
725    {
726       TableInfo tableInfo = getTableInfo();
727       sb.append("<a href=\"").append(tableInfo.getUrl());
728       sb.append(rootTag.getConfig().getLinkEncoder().encode(pageContext, parameters));
729       sb.append("\">").append(value).append("</a>");
730    }
731 
732    /***
733     * @return Returns the pages.
734     */
735    public int getPages()
736    {
737       return this.pages;
738    }
739 
740    /***
741     * @param pages The pages to set.
742     */
743    public void setPages(int pages)
744    {
745       this.pages = pages;
746    }
747 
748    /***
749     *
750     * @return showSummary
751     */
752    public boolean isShowSummary()
753    {
754       return showSummary;
755    }
756 
757    /***
758     * @param showSummary The showSummary to set.
759     */
760    public void setShowSummary(boolean showSummary)
761    {
762       this.showSummary = showSummary;
763    }
764 
765    /***
766     * 
767     * @return showItemsPerPage
768     */
769    public boolean isShowItemsPerPage()
770    {
771       return showItemsPerPage;
772    }
773 
774    /***
775     * Enable or disable generating form for number of items per page.
776     * @param showItemsPerPage
777     */
778    public void setShowItemsPerPage(boolean showItemsPerPage)
779    {
780       this.showItemsPerPage = showItemsPerPage;
781    }
782 
783    /***
784     * @return Returns the generateItemsPerPageForm.
785     */
786    public boolean isGenerateItemsPerPageForm()
787    {
788       return generateItemsPerPageForm;
789    }
790 
791    /***
792     * Enable or disable generating of HTML form tag wrapping input box "items per page".
793     * 
794     * @param generateItemsPerPageForm The generateItemsPerPageForm to set.
795     */
796    /***
797     *
798     * @param nestedForm
799     */
800    public void setGenerateItemsPerPageForm(boolean nestedForm)
801    {
802       this.generateItemsPerPageForm = nestedForm;
803    }
804 
805    private void reset()
806    {
807       this.rootTag = null;
808       this.currentPage = 0;
809       this.generateItemsPerPageForm = true;
810       this.maxPage = 0;
811       this.pages = 0;
812       this.parameters = null;
813       this.showItemsPerPage = false;
814       this.showSummary = false;
815       this.displayHelper = null;
816       this.messageSource = null;
817       this.currentLocale = null;
818    }
819 
820    /***
821     * Called on a Tag handler to release state.
822     * The page compiler guarantees that JSP page implementation
823     * objects will invoke this method on all tag handlers,
824     * but there may be multiple invocations on doStartTag and doEndTag in between.
825     * 
826     * @see javax.servlet.jsp.tagext.Tag#release()
827     */
828    public void release()
829    {
830       super.release();
831       reset();
832    }
833 }