View Javadoc

1   /***
2    * Copyright (C) 2005 The Java Community
3    *
4    * This program is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU General Public License as published by the Free Software
6    * Foundation; either version 2 of the License, or (at your option) any later
7    * version.
8    *
9    * This program is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12   * details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16   * Place, Suite 330, Boston, MA 02111-1307 USA.
17   *
18   */
19  package org.bejug.javacareers.common.view.jsf.comment;
20  
21  
22  import java.io.IOException;
23  import java.text.Format;
24  import java.text.SimpleDateFormat;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import javax.faces.component.UIComponent;
32  import javax.faces.component.UIInput;
33  import javax.faces.context.ExternalContext;
34  import javax.faces.context.FacesContext;
35  import javax.faces.context.ResponseWriter;
36  import javax.servlet.http.HttpSession;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.bejug.javacareers.common.view.jsf.constants.JSFConstants;
41  import org.bejug.javacareers.common.view.jsf.utils.HtmlRenderer;
42  import org.bejug.javacareers.common.view.jsf.utils.RendererHelper;
43  import org.bejug.javacareers.jobs.model.Comment;
44  import org.bejug.javacareers.jobs.model.Offer;
45  import org.bejug.javacareers.jobs.model.Item;
46  import org.bejug.javacareers.jobs.view.jsf.action.CommentAction;
47  import org.bejug.javacareers.jobs.view.jsf.util.Utils;
48  import org.springframework.util.StringUtils;
49  
50  import uk.ltd.getahead.dwr.lang.StringEscapeUtils;
51  
52  
53  /***
54   * HTML renderer for the comments component.
55   *
56   * @author : Peter Symoens (Last modified by $Author: shally $)
57   * @version $Revision: 1.35 $ - $Date: 2005/12/08 14:53:46 $:
58   */
59  public class CommentRenderer extends HtmlRenderer {
60  
61      /***
62       * The logger.
63       */
64      private static final Log LOG = LogFactory.getLog(CommentRenderer.class);
65  
66      /***
67       * The initial action
68       */
69      private String action = "init";
70  
71      /***
72       *The styleClass for the comment header
73       */
74      private String styleClass;
75  
76      /***
77       * the bordercolor used.
78       */
79      private String borderColor = "#A0A0A0";
80  
81      /***
82       * the borderstyle.
83       */
84      private String borderStyle = "border: 1px solid " + borderColor + ";";
85  
86      /***
87       * the cellstyle for the replytable.
88       */
89      private String replyTableCellStyle =
90              "border: 1px solid " + borderColor + "; background-color: #FFFFCC;";
91  
92      /***
93       * the css-class for errors.
94       */
95      private String errorStyleClass;
96  
97      /***
98       * the tablewidth.
99       */
100     private int tableWidth;
101 
102     /***
103      * the list of comments.
104      */
105     private List commentList = new ArrayList();
106 
107     /***
108      * the id of the reply on a comment.
109      */
110     private Integer replyCommentId;
111 
112     /***
113      * boolean indicating of initialisation has to be done.
114      */
115     private boolean init = false;
116 
117     /***
118      * boolean indicating this renders a childreply or not.
119      */
120     private boolean childReply;
121 
122     /***
123      * boolean indicating whether the content is null or not.
124      */
125     private boolean contentIsNull;
126 
127     /***
128      * the responsewriter.
129      */
130     private ResponseWriter writer;
131 
132     /***
133      * The action to process the requests
134      */
135     private CommentAction commentAction;
136 
137 
138     /***
139      * Overrides the parent's encodeBegin method. Renders the component to HTML.
140      *
141      * @param context   the FacesContext.
142      * @param component the UIComponent.
143      * @throws java.io.IOException thrown when something goes wrong will encoding the collapsed comments.
144      */
145     public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
146     	ExternalContext externalContext = context.getExternalContext();
147     	HttpSession httpSession = (HttpSession) externalContext.getSession(false);
148         commentAction = (CommentAction) component.getAttributes().get("actionListener");
149 
150         if (Boolean.TRUE.equals(httpSession.getAttribute(JSFConstants.COLLAPSE_COMMENTS))
151         		&& !"expandComments".equals(action) && !"addComments".equals(action)) {
152         	action = "collapseComments";
153         }else if ("collapseComments".equals(action)) {
154             httpSession.setAttribute( JSFConstants.COLLAPSE_COMMENTS, Boolean.TRUE);
155         }
156         
157         writer = context.getResponseWriter();
158         super.setComponent(component);
159         super.setWriter(writer);
160         if (!init) {
161             styleClass = (String) component.getAttributes().get("styleClass");
162             errorStyleClass = (String) component.getAttributes().get("errorStyleClass");
163             tableWidth = ((Integer) component.getAttributes().get("tableWidth")).intValue();
164             init = true;
165         }
166         if ("init".equals(action) || "expandComments".equals(action) || "addComments".equals(action)) {
167             encodeExpandedComments(context, component);
168             httpSession.setAttribute( JSFConstants.COLLAPSE_COMMENTS, Boolean.FALSE);
169         }
170         
171         if (action.equals("collapseComments")) {
172             encodeCollapsedComments(context, component);
173         }
174         // reset all class variables upon each request
175         reset();
176     }
177 
178     /***
179      * Overrides the parent's decode method, processes the client's requests.
180      *
181      * @param context   the FacesContext.
182      * @param component the UIComponent.
183      */
184     public void decode(FacesContext context, UIComponent component) {
185         Offer offer = (Offer) ((UIInput) component).getValue();
186         Map requestMap = context.getExternalContext().getRequestParameterMap();
187         Set keys = requestMap.keySet();
188         String replyValue = null;
189         for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
190             String key = (String) iterator.next();
191             String value = (String) requestMap.get(key);
192             if (value.startsWith("replyTo_")) {
193                 replyValue = value.substring(8);
194             }
195         }
196         if (requestMap.containsKey("collapse") && ((String) requestMap.get("collapse")).equals("collapse")) {
197             action = "collapseComments";
198         }
199         if (requestMap.containsKey("expandComments") && ((String) requestMap.get("expandComments")).equals("expandComments")) {
200             action = "expandComments";
201         }
202         if (requestMap.containsKey("addComments") && ((String) requestMap.get("addComments")).equals("addComments")) {
203             action = "addComments";
204         }
205         if (replyValue != null) {
206             try {
207                 replyCommentId = Integer.decode(replyValue);
208             } catch (NumberFormatException e) {
209                 LOG.error("Could not cast " + replyValue + " to an Integer");
210             }
211             action = "init";
212         }
213 
214         if (requestMap.containsKey("saveComment")) {
215             String content = (String) requestMap.get("content");
216             if (validateContent(content)) {
217                 contentIsNull = true;
218             } else {
219                 commentAction.processPostAction(offer, content);
220             }
221             action = "init";
222         }
223 
224         if (requestMap.containsKey("cancel")) {
225             action = "init";
226             replyCommentId = null;
227         }
228         if (requestMap.containsKey("saveReply")) {
229             //LOG.info("Debug: processing save reply");
230             String content = (String) requestMap.get("content");
231             if (validateContent(content)) {
232                 contentIsNull = true;
233                 action = "init";
234             } else {
235                 commentAction.processPostResponse(offer, replyCommentId, content);
236                 replyCommentId = null;
237             }
238         }
239     }
240 
241     /***
242      * Encode's the add comment form.
243      *
244      * @param component UIComponent
245      * @param reply     boolean
246      * @throws IOException e
247      */
248     private void encodeAddCommentForm(UIComponent component, boolean reply) throws IOException {
249         openTableWidth();
250         encodeContentField();
251         encodePostCommentButton(component, reply);
252         closeTable();
253     }
254 
255     /***
256      * Encode's the subject field
257      *
258      * @param component UIComponent.
259      * @param context   FacesContext.
260      * @param size the size.
261      * @throws IOException e
262      */
263     private void encodeCommentHeader(FacesContext context, UIComponent component, 
264     		int size)
265             throws IOException {
266         openTableRow();
267         writer.write("<td colspan='2'>");
268         String addCommentLabel = Utils.getMessage("label_comments");
269 
270         StringBuffer buf = new StringBuffer(100);
271         buf.append("<span class ='");
272         buf.append(styleClass);
273         buf.append("'>");
274         buf.append(addCommentLabel);
275         buf.append("</span>");
276         writer.write(buf.toString());
277 
278         if((size > 0 && !commentAction.isUserAuthenticated()) || (commentAction.isUserAuthenticated()) ){
279         writer.write("&nbsp;");
280         //TODO get this as style (psong09)
281         String style = "font-size: 12px; color:" + borderColor + ";";
282         encodeCommandLink(context, component, "collapse",
283         Utils.getMessage("label_hide_comments"), style);
284         }
285         closeTableCell();
286         closeTableRow();
287     }
288 
289     /***
290      * @param context   The FacesContext.
291      * @param component The UIComponent.
292      * @param command   The action to executed when the link is clicked
293      * @param link      The message for the html link.
294      * @param style     The CSS style.
295      * @throws IOException e
296      */
297     private void encodeCommandLink(FacesContext context, UIComponent component,
298                                    String command, String link, String style)
299             throws IOException {
300 
301         String formId = RendererHelper.getFormId(context, component);
302 
303         StringBuffer buf = new StringBuffer(200);
304         buf.append("document.forms['");
305         buf.append(formId);
306         buf.append("']['");
307         buf.append(command);
308         buf.append("'].value='");
309         buf.append(command);
310         buf.append("';document.forms['");
311         buf.append(formId);
312         buf.append("'].submit();");
313 
314         writer.startElement("a", component);
315         writer.writeAttribute("style", style, null);
316         writer.writeAttribute("href", "#", "href");
317         writer.writeAttribute("onclick", buf.toString(), null);
318         writer.write(link);
319         writer.endElement("a");
320         writer.startElement("input", component);
321         writer.writeAttribute("type", "hidden", null);
322         writer.writeAttribute("name", command, null);
323         writer.endElement("input");
324     }
325 
326     /***
327      * Encodes the content field.
328      *
329      * @throws IOException e
330      */
331     private void encodeContentField() throws IOException {
332 
333         openTableCell();
334         //TODO make sure text in textarea inserts linebreaks after threshold  (psong09 01/06)
335         //TODO use javascript to do so
336         writer.write("<textarea name='content' style='width: 80%;' rows='5'>");
337         writer.write("</textarea>");
338         if (contentIsNull) {
339             writer.write("<br/>");
340             encodeContentValidatorMessage(writer);
341         }
342         closeTableCell();
343         closeTableRow();
344 
345     }
346 
347     /***
348      * Encode's the post comment and cancel buttons.
349      *
350      * @param component the UIComponent.
351      * @param reply     a boolean indicating it should be possible or not to give a reply.
352      * @throws IOException e
353      */
354     private void encodePostCommentButton(UIComponent component, boolean reply)
355             throws IOException {
356         openTableRow();
357         writer.write("<td align='center'>");
358         /*-- submit button --*/
359         writer.startElement("input", component);
360         writer.writeAttribute("type", "submit", null);
361         if (!reply) {
362             writer.writeAttribute("name", "saveComment", null);
363         } else {
364             writer.writeAttribute("name", "saveReply", null);
365         }
366         String post = Utils.getMessage("button_post_comment");
367         writer.writeAttribute("value", post, "post");
368         writer.endElement("input");
369         /*-- cancel button --*/
370         if (reply) {
371             writer.startElement("input", component);
372             writer.writeAttribute("type", "submit", null);
373             writer.writeAttribute("name", "cancel", null);
374             String cancel = Utils.getMessage("button_cancel");
375             writer.writeAttribute("value", cancel, null);
376             writer.endElement("input");
377         }
378         closeTableCell();
379         closeTableRow();
380     }
381 
382     /***
383      * Prints the number of comments related to the offer, and an add comment link.
384      *
385      * @param context   the FacesContext.
386      * @param component UIComponent.
387      * @throws IOException e
388      */
389     private void encodeCollapsedComments(FacesContext context, UIComponent component) throws IOException {
390         Item offer = (Item) ((UIInput) component).getValue();
391         CommentAction commentaction = (CommentAction) component.getAttributes().get("actionListener");
392 
393         openTableWidth();
394         openTableRow();
395 
396         StringBuffer buf = new StringBuffer(100);
397         buf.append("<td style='");
398         buf.append(borderStyle);
399         buf.append("'>");
400         writer.write(buf.toString());
401         encodeCommentImage();
402         String link = Utils.getMessage("label_comments").concat("(");
403         String commentCount = Integer.toString(commentaction.getCommentCount(offer));
404         link = link.concat(commentCount);
405         link = link.concat(")");
406         String style = "font-family : verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 12";
407         encodeCommandLink(context, component, "expandComments", link, style);
408         if(commentaction.isUserAuthenticated()){
409         writer.write(" | ");
410         encodeCommentImage();
411         encodeCommandLink(context, component, "addComments", Utils.getMessage("label_add_comment"), style);
412         }
413         closeTableCell();
414         closeTableRow();
415         closeTable();
416     }
417 
418 
419     /***
420      * Lists the comments related to the current offer.
421      *
422      * @param context   the FacesContext.
423      * @param component UIComponent.
424      * @throws IOException e
425      */
426     private void encodeExpandedComments(FacesContext context, UIComponent component) throws IOException {
427         Offer offer = (Offer) ((UIInput) component).getValue();
428         CommentAction commentaction = (CommentAction) component.getAttributes().get("actionListener");
429         commentList = commentaction.getComments(offer);
430         boolean reply = false;
431         childReply = false;
432         openTableWidth();
433         encodeCommentHeader(context, component, commentList.size());
434         for (Iterator iterator = commentList.iterator(); iterator.hasNext();) {
435             Comment comment = (Comment) iterator.next();
436             reply = false;
437             openTableRow();
438             openTableCell();
439             openTableWidth();
440             /*-- Parent comment table row --*/
441             openTableRow();
442             String style = borderStyle;
443 
444             if (replyCommentId != null && comment.getId().intValue() == replyCommentId.intValue()) {
445                 reply = true;
446                 style = replyTableCellStyle;
447             }
448 
449             StringBuffer buf = new StringBuffer(100);
450             buf.append("<td colspan='2' style='");
451             buf.append(style);
452             buf.append("'>");
453             writer.write(buf.toString());
454 
455             newLine();
456             String filteredContent = StringEscapeUtils.escapeHtml(comment.getContent());
457             filteredContent = filteredContent.replaceAll("\n", "<br>");
458             encodeCommentContent(filteredContent);
459             encodeCommentFooter(context, component, comment, reply);
460             if (reply) {
461                 encodeAddCommentForm(component, true);
462             }
463             closeTableCell();
464             closeTableRow();
465             closeTable();
466             encodeCommentTree(comment, context, component);
467         }
468         closeTable();
469         if (!reply && !childReply && commentaction.isUserAuthenticated()) {
470             encodeAddCommentForm(component, false);
471         }
472     }
473 
474     /***
475      * Prints recursively all child comments of the given comment.
476      *
477      * @param comment   The child comment.
478      * @param context   The FacesContext.
479      * @param component The UIComponent.
480      * @throws IOException thrown when the encoding of the comments failes.
481      */
482     private void encodeCommentTree(Comment comment, FacesContext context,
483                                    UIComponent component) throws IOException {
484 
485         CommentAction commentaction = (CommentAction) component.getAttributes().get("actionListener");
486         childReply = false;
487         List childComments = commentaction.getChildComments(comment);
488         if (!childComments.isEmpty()) {
489             Comment childComment;
490             /*-- Childcomment table rows --*/
491             for (Iterator it = childComments.iterator(); it.hasNext();) {
492                 childComment = (Comment) it.next();
493                 childReply = false;
494                 if (replyCommentId != null &&
495                         childComment.getId().intValue() == replyCommentId.intValue()) {
496                     childReply = true;
497                 }
498                 encodeChildComment(childComment, context, component);
499                 encodeCommentTree(childComment, context, component);
500             }
501         }
502     }
503 
504     /***
505      * Prints the given child comment.
506      *
507      * @param childComment The child comment.
508      * @param context      The FacesContext.
509      * @param component    The UIComponent.
510      * @throws IOException e
511      */
512     private void encodeChildComment(Comment childComment, FacesContext context,
513                                     UIComponent component) throws IOException {
514 
515         int level = getIndentationLevel(childComment);
516         //the indentation in pixels
517         int indentation = (20 * level);
518         openTableRow();
519         openTableCell();
520         openTableWidth();
521         openTableRow();
522         writer.write("<td width='" + indentation + "'>");
523         writer.write("&nbsp");
524         closeTableCell();
525         int width = tableWidth - indentation;
526         String style = borderStyle;
527         if (childReply) {
528             style = replyTableCellStyle;
529         }
530 
531         StringBuffer buf = new StringBuffer(100);
532         buf.append("<td width='");
533         buf.append(width);
534         buf.append("' style='");
535         buf.append(style);
536         buf.append("'>");
537         writer.write(buf.toString());
538 
539         String content = childComment.getContent().replaceAll("\n", "<br>");
540         encodeCommentContent(content);
541         encodeCommentFooter(context, component, childComment, childReply);
542         if (childReply) {
543             encodeAddCommentForm(component, true);
544         }
545         style = borderStyle;
546         closeTableCell();
547         closeTableRow();
548         closeTable();
549         closeTableCell();
550         closeTableRow();
551     }
552 
553     /***
554      * returns the identationlevel of the comment.
555      *
556      * @param comment The child comment.
557      * @return level Returns the indentation level.
558      */
559     private int getIndentationLevel(Comment comment) {
560         Comment c = comment.getParentComment();
561         int level = 0;
562         do {
563             level++;
564             c = c.getParentComment();
565         } while (c != null);
566         return level;
567 
568     }
569 
570     /***
571      * Prints the comment.
572      *
573      * @param content The content of the comment.
574      * @throws IOException thrown whenever the writer failes to write.
575      */
576     private void encodeCommentContent(String content) throws IOException {
577         writer.write("<div><p>");
578         writer.write(content);
579         writer.write("</p></div>");
580         newLine();
581     }
582 
583     /***
584      * Prints author and creation date of the comment as a table header.
585      *
586      * @param context   the FacexContext.
587      * @param comment   Comment.
588      * @param component the UIComponent.
589      * @param reply     a boolean indicating to include the reply or not.
590      * @throws IOException e
591      */
592     private void encodeCommentFooter(FacesContext context,
593                                      UIComponent component,
594                                      Comment comment,
595                                      boolean reply)
596             throws IOException {
597         Format formatter = new SimpleDateFormat("MMM d yyyy ' " + Utils.getMessage("label_at_dateformat") + " ' hh:mm a");
598         newLine();
599         StringBuffer buf = new StringBuffer(100);
600         buf.append("<div style='color:");
601         buf.append(borderColor);
602         buf.append("'>");
603         writer.write(buf.toString());
604         encodeCommentImage();
605         writer.write(Utils.getMessage("label_posted_by") + "&nbsp;<i>" + comment.getUser().getUserName() + "</i>");
606         writer.write("&nbsp;");
607         writer.write(Utils.getMessage("label_posted_on") + "&nbsp;" + formatter.format(comment.getCreationDate()));
608         if (!reply && commentAction.isUserAuthenticated()) {
609             writer.write(" | ");
610             String command = "replyTo_".concat(comment.getId().toString());
611             encodeCommandLink(context, component, command,
612                     Utils.getMessage("link_reply"), "");
613         }
614         writer.write("</div>");
615         newLine();
616     }
617 
618     /***
619      * Encodes the comment image gif.
620      *
621      * @throws IOException e
622      */
623     private void encodeCommentImage() throws IOException {
624         newLine();
625         StringBuffer buf = new StringBuffer(100);
626         buf.append("<img src='");
627         buf.append(Utils.getServletContextRoot());
628         buf.append("/images/comment_16.gif'>");
629         writer.write(buf.toString());
630         newLine();
631     }
632 
633     /***
634      * Encode's the html open table tag.
635      *
636      * @throws IOException e
637      */
638     private void openTableWidth() throws IOException {
639         newLine();
640         StringBuffer buf = new StringBuffer(100);
641         buf.append("<table width='");
642         buf.append(tableWidth);
643         buf.append("'>");
644         writer.write(buf.toString());
645         newLine();
646     }
647 
648     /***
649      * Reset class variables.
650      */
651     private void reset() {
652         childReply = false;
653         contentIsNull = false;
654         //replyCommentId = null;
655         action = "init";
656     }
657 
658 
659     /***
660      * Validator for the content field of the comment.
661      *
662      * @param content a String containing the content to validate.
663      * @return a boolean indicating wether the content is valid or not.
664      */
665     private boolean validateContent(String content) {
666         //filter out the carriage returns
667         int count = StringUtils.countOccurrencesOf(content, "\n");
668         int contentLength = content.length() - (count * 2);
669         if (contentLength < 1) {
670             return true;
671         }
672         return false;
673     }
674 
675     /***
676      * Encode content validator message.
677      *
678      * @param respwriter ResponseWriter
679      * @throws IOException e
680      */
681     private void encodeContentValidatorMessage(ResponseWriter respwriter)
682     
683             throws IOException {
684 
685         StringBuffer buf = new StringBuffer(100);
686         buf.append("<span class='");
687         buf.append(errorStyleClass);
688         buf.append("'>");
689         respwriter.write(buf.toString());
690 
691         respwriter.write(Utils.getMessage("error_invalid_content"));
692         respwriter.write("</span>");
693     }
694 }
695 
696 /***
697  * $Log: CommentRenderer.java,v $
698  * Revision 1.35  2005/12/08 14:53:46  shally
699  * Opkuis voor checkstyle.
700  *
701  * Revision 1.34  2005/09/30 14:38:07  bavo_jcs
702  * Fixed URL
703  *
704  * Revision 1.33  2005/09/19 15:53:31  bavo_jcs
705  * Refactored comments to use Items
706  *
707  * Revision 1.32  2005/09/06 13:22:50  schauwvliege
708  * Intro of interview
709  *
710  * Revision 1.31  2005/08/31 08:56:12  psong09
711  * I18zed labels
712  *
713  * Revision 1.30  2005/08/30 14:55:17  psong09
714  * cleanup
715  *
716  * Revision 1.29  2005/08/30 14:40:34  psong09
717  * added javadoc
718  *
719  * Revision 1.28  2005/08/30 13:07:46  psong09
720  * renamed author: psong09
721  *
722  * Revision 1.27  2005/08/24 13:04:26  psong09
723  * moved isAuthenticated method to CommentAction
724  *
725  * Revision 1.26  2005/08/24 12:32:11  psong09
726  * Added spinner component and renamed commentAction
727  *
728  * Revision 1.25  2005/08/24 11:56:44  bavo_jcs
729  * Fixed <BR> switch
730  *
731  * Revision 1.24  2005/08/19 13:27:01  bme_jcs
732  * checkstyle errors resolved
733  *
734  * Revision 1.23  2005/08/19 13:19:52  psong09
735  * renamed VariableConstants to JSFConstants
736  *
737  * Revision 1.22  2005/08/18 10:04:32  schauwvliege
738  * hide comments in session
739  *
740  * Revision 1.21  2005/08/17 16:38:30  psong09
741  * Refactored so that post- and reply functionality is rendered only when the user is logged in.
742  *
743  * Revision 1.20  2005/08/16 08:48:47  psong09
744  * Corrected inline css syntax
745  *
746  * Revision 1.19  2005/08/15 14:27:25  stephan_janssen
747  * Added StringBuffer's and change date format.
748  *
749  * Revision 1.18  2005/08/12 13:48:57  schauwvliege
750  * intercepted html in output
751  *
752  * Revision 1.17  2005/08/10 09:04:46  bavo_jcs
753  * Optimized imports according to checkstyle
754  *
755  * Revision 1.16  2005/08/09 12:59:53  bavo_jcs
756  * Optimized imports
757  *
758  * Revision 1.15  2005/08/08 12:31:38  bme_jcs
759  * resolved PMD errors
760  *
761  * Revision 1.14  2005/08/05 08:27:55  bme_jcs
762  * resolved checkstyle errors
763  *
764  * Revision 1.13  2005/07/13 08:02:06  psong09
765  * to have childcomments correctly sorted, now load childComments from database<br> instead of getting them from the parentcomment
766  *
767  * Revision 1.12  2005/07/12 16:01:22  psong09
768  * modified comments component, removed some css styles
769  *
770  * Revision 1.11  2005/07/11 15:35:27  psong09
771  * modified encodeExpandedComments method
772  *
773  * Revision 1.10  2005/07/08 15:47:18  psong09
774  * modified comment component, removed subject field
775  *
776  * Revision 1.9  2005/07/05 15:28:53  psong09
777  * added styleClasses, modified validation
778  *
779  * Revision 1.8  2005/07/04 12:51:51  psong09
780  * added validation and placed addCommentForm in HTML table
781  *
782  * Revision 1.7  2005/07/04 07:53:54  psong09
783  * update comment component: added count(*) query, user login check, javascript
784  *
785  * Revision 1.6  2005/06/30 15:20:54  psong09
786  * added dateformat
787  *
788  * Revision 1.5  2005/06/30 12:16:36  psong09
789  * update comment component
790  *
791  * Revision 1.4  2005/06/29 09:00:15  psong09
792  * comment component updated
793  *
794  * Revision 1.3  2005/06/28 07:38:22  psong09
795  * comment component updated
796  *
797  * Revision 1.2  2005/06/24 16:28:52  psong09
798  * comment component updated
799  *
800  * Revision 1.1  2005/06/23 15:12:40  psong09
801  * comment component updated
802  *
803  */