1 /**
2 * Licensed under the Artistic License; you may not use this file
3 * except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://displaytag.sourceforge.net/license.html
7 *
8 * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
9 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 */
12 package org.displaytag.render;
13
14 import java.io.IOException;
15 import java.text.MessageFormat;
16 import java.util.Iterator;
17 import java.util.Map;
18
19 import javax.servlet.jsp.JspException;
20 import javax.servlet.jsp.JspWriter;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.displaytag.exception.DecoratorException;
25 import org.displaytag.exception.ObjectLookupException;
26 import org.displaytag.exception.WrappedRuntimeException;
27 import org.displaytag.model.Column;
28 import org.displaytag.model.HeaderCell;
29 import org.displaytag.model.Row;
30 import org.displaytag.model.TableModel;
31 import org.displaytag.pagination.PaginatedList;
32 import org.displaytag.pagination.SmartListHelper;
33 import org.displaytag.properties.MediaTypeEnum;
34 import org.displaytag.properties.SortOrderEnum;
35 import org.displaytag.properties.TableProperties;
36 import org.displaytag.tags.CaptionTag;
37 import org.displaytag.tags.TableTagParameters;
38 import org.displaytag.util.Anchor;
39 import org.displaytag.util.Href;
40 import org.displaytag.util.HtmlAttributeMap;
41 import org.displaytag.util.ParamEncoder;
42 import org.displaytag.util.TagConstants;
43
44
45 /**
46 * A table writer that formats a table in HTML and writes it to a JSP page.
47 * @author Fabrizio Giustina
48 * @author Jorge L. Barroso
49 * @version $Id$
50 * @see org.displaytag.render.TableWriterTemplate
51 * @since 1.1
52 */
53 public class HtmlTableWriter extends TableWriterAdapter
54 {
55
56 /**
57 * Logger.
58 */
59 private static Log log = LogFactory.getLog(HtmlTableWriter.class);
60
61 /**
62 * <code>TableModel</code>
63 */
64 private TableModel tableModel;
65
66 /**
67 * <code>TableProperties</code>
68 */
69 private TableProperties properties;
70
71 /**
72 * Output destination.
73 */
74 private JspWriter out;
75
76 /**
77 * The param encoder used to generate unique parameter names. Initialized at the first use of encodeParameter().
78 */
79 private ParamEncoder paramEncoder;
80
81 /**
82 * base href used for links.
83 */
84 private Href baseHref;
85
86 /**
87 * add export links.
88 */
89 private boolean export;
90
91 private CaptionTag captionTag;
92
93 /**
94 * The paginated list containing the external pagination and sort parameters The presence of this paginated list is
95 * what determines if external pagination and sorting is used or not.
96 */
97 private PaginatedList paginatedList;
98
99 /**
100 * Used by various functions when the person wants to do paging.
101 */
102 private SmartListHelper listHelper;
103
104 /**
105 * page size.
106 */
107 private int pagesize;
108
109 private HtmlAttributeMap attributeMap;
110
111 /**
112 * Unique table id.
113 */
114 private String uid;
115
116 /**
117 * This table writer uses a <code>TableTag</code> and a <code>JspWriter</code> to do its work.
118 * @param tableTag <code>TableTag</code> instance called back by this writer.
119 * @param out The output destination.
120 */
121 public HtmlTableWriter(
122 TableModel tableModel,
123 TableProperties tableProperties,
124 Href baseHref,
125 boolean export,
126 JspWriter out,
127 CaptionTag captionTag,
128 PaginatedList paginatedList,
129 SmartListHelper listHelper,
130 int pagesize,
131 HtmlAttributeMap attributeMap,
132 String uid)
133 {
134 this.tableModel = tableModel;
135 this.properties = tableProperties;
136 this.baseHref = baseHref;
137 this.export = export;
138 this.out = out;
139 this.captionTag = captionTag;
140 this.paginatedList = paginatedList;
141 this.listHelper = listHelper;
142 this.pagesize = pagesize;
143 this.attributeMap = attributeMap;
144 this.uid = uid;
145 }
146
147 /**
148 * Writes a banner containing search result and paging navigation above an HTML table to a JSP page.
149 * @see org.displaytag.render.TableWriterTemplate#writeTopBanner(org.displaytag.model.TableModel)
150 */
151 protected void writeTopBanner(TableModel model)
152 {
153 writeSearchResultAndNavigation();
154 }
155
156 /**
157 * Writes an HTML table's opening tags to a JSP page.
158 * @see org.displaytag.render.TableWriterTemplate#writeTableOpener(org.displaytag.model.TableModel)
159 */
160 protected void writeTableOpener(TableModel model)
161 {
162 this.write(getOpenTag());
163 }
164
165 /**
166 * Writes an HTML table's caption to a JSP page.
167 * @see org.displaytag.render.TableWriterTemplate#writeCaption(org.displaytag.model.TableModel)
168 */
169 protected void writeCaption(TableModel model)
170 {
171 this.write(captionTag.getOpenTag() + model.getCaption() + captionTag.getCloseTag());
172 }
173
174 /**
175 * Writes an HTML table's footer to a JSP page; HTML requires tfoot to appear before tbody.
176 * @see org.displaytag.render.TableWriterTemplate#writeFooter(org.displaytag.model.TableModel)
177 */
178 protected void writePreBodyFooter(TableModel model)
179 {
180 this.write(TagConstants.TAG_TFOOTER_OPEN);
181 this.write(model.getFooter());
182 this.write(TagConstants.TAG_TFOOTER_CLOSE);
183 }
184
185 /**
186 * Writes the start of an HTML table's body to a JSP page.
187 * @see org.displaytag.render.TableWriterTemplate#writeTableBodyOpener(org.displaytag.model.TableModel)
188 */
189 protected void writeTableBodyOpener(TableModel model)
190 {
191 this.write(TagConstants.TAG_TBODY_OPEN);
192
193 }
194
195 /**
196 * Writes the end of an HTML table's body to a JSP page.
197 * @see org.displaytag.render.TableWriterTemplate#writeTableBodyCloser(org.displaytag.model.TableModel)
198 */
199 protected void writeTableBodyCloser(TableModel model)
200 {
201 this.write(TagConstants.TAG_TBODY_CLOSE);
202 }
203
204 /**
205 * Writes the closing structure of an HTML table to a JSP page.
206 * @see org.displaytag.render.TableWriterTemplate#writeTableCloser(org.displaytag.model.TableModel)
207 */
208 protected void writeTableCloser(TableModel model)
209 {
210 this.write(TagConstants.TAG_OPENCLOSING);
211 this.write(TagConstants.TABLE_TAG_NAME);
212 this.write(TagConstants.TAG_CLOSE);
213 }
214
215 /**
216 * Writes a banner containing search result, paging navigation, and export links below an HTML table to a JSP page.
217 * @see org.displaytag.render.TableWriterTemplate#writeBottomBanner(org.displaytag.model.TableModel)
218 */
219 protected void writeBottomBanner(TableModel model)
220 {
221 writeNavigationAndExportLinks();
222 }
223
224 /**
225 * @see org.displaytag.render.TableWriterTemplate#writeDecoratedTableFinish(org.displaytag.model.TableModel)
226 */
227 protected void writeDecoratedTableFinish(TableModel model)
228 {
229 model.getTableDecorator().finish();
230 }
231
232 /**
233 * @see org.displaytag.render.TableWriterTemplate#writeDecoratedRowStart(org.displaytag.model.TableModel)
234 */
235 protected void writeDecoratedRowStart(TableModel model)
236 {
237 this.write(model.getTableDecorator().startRow());
238 }
239
240 /**
241 * Writes an HTML table's row-opening tag to a JSP page.
242 * @see org.displaytag.render.TableWriterTemplate#writeRowOpener(org.displaytag.model.Row)
243 */
244 protected void writeRowOpener(Row row)
245 {
246 this.write(row.getOpenTag());
247 }
248
249 /**
250 * Writes an HTML table's column-opening tag to a JSP page.
251 * @see org.displaytag.render.TableWriterTemplate#writeColumnOpener(org.displaytag.model.Column)
252 */
253 protected void writeColumnOpener(Column column) throws ObjectLookupException, DecoratorException
254 {
255 this.write(column.getOpenTag());
256 }
257
258 /**
259 * Writes an HTML table's column-closing tag to a JSP page.
260 * @see org.displaytag.render.TableWriterTemplate#writeColumnCloser(org.displaytag.model.Column)
261 */
262 protected void writeColumnCloser(Column column)
263 {
264 this.write(column.getCloseTag());
265 }
266
267 /**
268 * Writes to a JSP page an HTML table row that has no columns.
269 * @see org.displaytag.render.TableWriterTemplate#writeRowWithNoColumns(java.lang.String)
270 */
271 protected void writeRowWithNoColumns(String rowValue)
272 {
273 this.write(TagConstants.TAG_TD_OPEN);
274 this.write(rowValue);
275 this.write(TagConstants.TAG_TD_CLOSE);
276 }
277
278 /**
279 * Writes an HTML table's row-closing tag to a JSP page.
280 * @see org.displaytag.render.TableWriterTemplate#writeRowCloser(org.displaytag.model.Row)
281 */
282 protected void writeRowCloser(Row row)
283 {
284 this.write(row.getCloseTag());
285 }
286
287 /**
288 * @see org.displaytag.render.TableWriterTemplate#writeDecoratedRowFinish(org.displaytag.model.TableModel)
289 */
290 protected void writeDecoratedRowFinish(TableModel model)
291 {
292 this.write(model.getTableDecorator().finishRow());
293 }
294
295 /**
296 * Writes an HTML message to a JSP page explaining that the table model contains no data.
297 * @see org.displaytag.render.TableWriterTemplate#writeEmptyListMessage(java.lang.String)
298 */
299 protected void writeEmptyListMessage(String emptyListMessage)
300 {
301 this.write(emptyListMessage);
302 }
303
304 /**
305 * Writes a HTML table column value to a JSP page.
306 * @see org.displaytag.render.TableWriterTemplate#writeColumnValue(java.lang.String,org.displaytag.model.Column)
307 */
308 protected void writeColumnValue(Object value, Column column)
309 {
310 this.write(value);
311 }
312
313 /**
314 * Writes an HTML message to a JSP page explaining that the row contains no data.
315 * @see org.displaytag.render.TableWriterTemplate#writeEmptyListRowMessage(java.lang.String)
316 */
317 protected void writeEmptyListRowMessage(String message)
318 {
319 this.write(message);
320 }
321
322 /**
323 * Writes an HTML table's column header to a JSP page.
324 * @see org.displaytag.render.TableWriterTemplate#writeTableHeader(org.displaytag.model.TableModel)
325 */
326 protected void writeTableHeader(TableModel model)
327 {
328
329 if (log.isDebugEnabled())
330 {
331 log.debug("[" + tableModel.getId() + "] getTableHeader called");
332 }
333
334 // open thead
335 write(TagConstants.TAG_THEAD_OPEN);
336
337 // open tr
338 write(TagConstants.TAG_TR_OPEN);
339
340 // no columns?
341 if (this.tableModel.isEmpty())
342 {
343 write(TagConstants.TAG_TH_OPEN);
344 write(TagConstants.TAG_TH_CLOSE);
345 }
346
347 // iterator on columns for header
348 Iterator iterator = this.tableModel.getHeaderCellList().iterator();
349
350 while (iterator.hasNext())
351 {
352 // get the header cell
353 HeaderCell headerCell = (HeaderCell) iterator.next();
354
355 if (headerCell.getSortable())
356 {
357 String cssSortable = this.properties.getCssSortable();
358 headerCell.addHeaderClass(cssSortable);
359 }
360
361 // if sorted add styles
362 if (headerCell.isAlreadySorted())
363 {
364 // sorted css class
365 headerCell.addHeaderClass(this.properties.getCssSorted());
366
367 // sort order css class
368 headerCell.addHeaderClass(this.properties.getCssOrder(this.tableModel.isSortOrderAscending()));
369 }
370
371 // append th with html attributes
372 write(headerCell.getHeaderOpenTag());
373
374 // title
375 String header = headerCell.getTitle();
376
377 // column is sortable, create link
378 if (headerCell.getSortable())
379 {
380 // creates the link for sorting
381 Anchor anchor = new Anchor(getSortingHref(headerCell), header);
382
383 // append to buffer
384 header = anchor.toString();
385 }
386
387 write(header);
388 write(headerCell.getHeaderCloseTag());
389 }
390
391 // close tr
392 write(TagConstants.TAG_TR_CLOSE);
393
394 // close thead
395 write(TagConstants.TAG_THEAD_CLOSE);
396
397 if (log.isDebugEnabled())
398 {
399 log.debug("[" + tableModel.getId() + "] getTableHeader end");
400 }
401 }
402
403 /**
404 * Generates the link to be added to a column header for sorting.
405 * @param headerCell header cell the link should be added to
406 * @return Href for sorting
407 */
408 private Href getSortingHref(HeaderCell headerCell)
409 {
410 // costruct Href from base href, preserving parameters
411 Href href = (Href) this.baseHref.clone();
412
413 if (this.paginatedList == null)
414 {
415 // add column number as link parameter
416 if (!this.tableModel.isLocalSort() && (headerCell.getSortName() != null))
417 {
418 href.addParameter(encodeParameter(TableTagParameters.PARAMETER_SORT), headerCell.getSortName());
419 href.addParameter(encodeParameter(TableTagParameters.PARAMETER_SORTUSINGNAME), "1");
420 }
421 else
422 {
423 href.addParameter(encodeParameter(TableTagParameters.PARAMETER_SORT), headerCell.getColumnNumber());
424 }
425
426 boolean nowOrderAscending = true;
427
428 if (headerCell.getDefaultSortOrder() != null)
429 {
430 boolean sortAscending = SortOrderEnum.ASCENDING.equals(headerCell.getDefaultSortOrder());
431 nowOrderAscending = headerCell.isAlreadySorted()
432 ? !this.tableModel.isSortOrderAscending()
433 : sortAscending;
434 }
435 else
436 {
437 nowOrderAscending = !(headerCell.isAlreadySorted() && this.tableModel.isSortOrderAscending());
438 }
439
440 int sortOrderParam = nowOrderAscending ? SortOrderEnum.ASCENDING.getCode() : SortOrderEnum.DESCENDING
441 .getCode();
442 href.addParameter(encodeParameter(TableTagParameters.PARAMETER_ORDER), sortOrderParam);
443
444 // If user want to sort the full table I need to reset the page number.
445 // or if we aren't sorting locally we need to reset the page as well.
446 if (this.tableModel.isSortFullTable() || !this.tableModel.isLocalSort())
447 {
448 href.addParameter(encodeParameter(TableTagParameters.PARAMETER_PAGE), 1);
449 }
450 }
451 else
452 {
453 if (properties.getPaginationSkipPageNumberInSort())
454 {
455 href.removeParameter(properties.getPaginationPageNumberParam());
456 }
457
458 String sortProperty = headerCell.getSortProperty();
459 if (sortProperty == null)
460 {
461 sortProperty = headerCell.getBeanPropertyName();
462 }
463
464 href.addParameter(properties.getPaginationSortParam(), sortProperty);
465 String dirParam;
466 if (headerCell.isAlreadySorted())
467 {
468 dirParam = tableModel.isSortOrderAscending() ? properties.getPaginationDescValue() : properties
469 .getPaginationAscValue();
470 }
471 else
472 {
473 dirParam = properties.getPaginationAscValue();
474 }
475 href.addParameter(properties.getPaginationSortDirectionParam(), dirParam);
476 if (paginatedList.getSearchId() != null)
477 {
478 href.addParameter(properties.getPaginationSearchIdParam(), paginatedList.getSearchId());
479 }
480 }
481
482 return href;
483 }
484
485 /**
486 * encode a parameter name to be unique in the page using ParamEncoder.
487 * @param parameterName parameter name to encode
488 * @return String encoded parameter name
489 */
490 private String encodeParameter(String parameterName)
491 {
492 // paramEncoder has been already instantiated?
493 if (this.paramEncoder == null)
494 {
495 // use the id attribute to get the unique identifier
496 this.paramEncoder = new ParamEncoder(this.tableModel.getId());
497 }
498
499 return this.paramEncoder.encodeParameterName(parameterName);
500 }
501
502 /**
503 * Generates table footer with links for export commands.
504 */
505 public void writeNavigationAndExportLinks()
506 {
507 // Put the page stuff there if it needs to be there...
508 if (this.properties.getAddPagingBannerBottom())
509 {
510 writeSearchResultAndNavigation();
511 }
512
513 // add export links (only if the table is not empty)
514 if (this.export && this.tableModel.getRowListPage().size() != 0)
515 {
516 writeExportLinks();
517 }
518 }
519
520 /**
521 * generates the search result and navigation bar.
522 */
523 public void writeSearchResultAndNavigation()
524 {
525 if ((this.paginatedList == null && this.pagesize != 0 && this.listHelper != null)
526 || (this.paginatedList != null))
527 {
528 // create a new href
529 Href navigationHref = (Href) this.baseHref.clone();
530
531 write(this.listHelper.getSearchResultsSummary());
532
533 String pageParameter;
534 if (paginatedList == null)
535 {
536 pageParameter = encodeParameter(TableTagParameters.PARAMETER_PAGE);
537 }
538 else
539 {
540 pageParameter = properties.getPaginationPageNumberParam();
541 if ((paginatedList.getSearchId() != null)
542 && (!navigationHref.getParameterMap().containsKey(properties.getPaginationSearchIdParam())))
543 {
544 navigationHref.addParameter(properties.getPaginationSearchIdParam(), paginatedList.getSearchId());
545 }
546 }
547 write(this.listHelper.getPageNavigationBar(navigationHref, pageParameter));
548 }
549 }
550
551 /**
552 * Writes the formatted export links section.
553 */
554 private void writeExportLinks()
555 {
556 // Figure out what formats they want to export, make up a little string
557 Href exportHref = (Href) this.baseHref.clone();
558
559 StringBuffer buffer = new StringBuffer(200);
560 Iterator iterator = MediaTypeEnum.iterator();
561
562 while (iterator.hasNext())
563 {
564 MediaTypeEnum currentExportType = (MediaTypeEnum) iterator.next();
565
566 if (this.properties.getAddExport(currentExportType))
567 {
568
569 if (buffer.length() > 0)
570 {
571 buffer.append(this.properties.getExportBannerSeparator());
572 }
573
574 exportHref.addParameter(encodeParameter(TableTagParameters.PARAMETER_EXPORTTYPE), currentExportType
575 .getCode());
576
577 // export marker
578 exportHref.addParameter(TableTagParameters.PARAMETER_EXPORTING, "1");
579
580 Anchor anchor = new Anchor(exportHref, this.properties.getExportLabel(currentExportType));
581 buffer.append(anchor.toString());
582 }
583 }
584
585 String[] exportOptions = {buffer.toString()};
586 write(MessageFormat.format(this.properties.getExportBanner(), exportOptions));
587 }
588
589 /**
590 * create the open tag containing all the attributes.
591 * @return open tag string: <code>%lt;table attribute="value" ... ></code>
592 */
593 public String getOpenTag()
594 {
595
596 if (this.uid != null && attributeMap.get(TagConstants.ATTRIBUTE_ID) == null)
597 {
598 // we need to clone the attribute map in order to "fix" the html id when using only the "uid" attribute
599 Map localAttributeMap = (Map) attributeMap.clone();
600 localAttributeMap.put(TagConstants.ATTRIBUTE_ID, this.uid);
601
602 StringBuffer buffer = new StringBuffer();
603 buffer.append(TagConstants.TAG_OPEN).append(TagConstants.TABLE_TAG_NAME);
604 buffer.append(localAttributeMap);
605 buffer.append(TagConstants.TAG_CLOSE);
606
607 return buffer.toString();
608
609 }
610
611 // fast, no clone
612 StringBuffer buffer = new StringBuffer();
613
614 buffer.append(TagConstants.TAG_OPEN).append(TagConstants.TABLE_TAG_NAME);
615 buffer.append(attributeMap);
616 buffer.append(TagConstants.TAG_CLOSE);
617
618 return buffer.toString();
619 }
620
621 /**
622 * Utility method.
623 * @param string String
624 */
625 public void write(String string)
626 {
627 if (string != null)
628 {
629 try
630 {
631 out.write(string);
632 }
633 catch (IOException e)
634 {
635 throw new WrappedRuntimeException(getClass(), e);
636 }
637 }
638
639 }
640
641 public void writeTable(TableModel model, String id) throws JspException
642 {
643 super.writeTable(model, id);
644 }
645
646 /**
647 * Utility method.
648 * @param string String
649 */
650 public void write(Object string)
651 {
652 if (string != null)
653 {
654 try
655 {
656 out.write(string.toString());
657 }
658 catch (IOException e)
659 {
660 throw new WrappedRuntimeException(getClass(), e);
661 }
662 }
663
664 }
665
666 }