View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.shared_orchestra.renderkit.html;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.myfaces.shared_orchestra.renderkit.JSFAttr;
24  import org.apache.myfaces.shared_orchestra.renderkit.RendererUtils;
25  import org.apache.myfaces.shared_orchestra.util.ArrayUtils;
26  import org.apache.myfaces.shared_orchestra.util.StringUtils;
27  
28  import javax.faces.component.UIColumn;
29  import javax.faces.component.UIComponent;
30  import javax.faces.component.UIData;
31  import javax.faces.component.html.HtmlDataTable;
32  import javax.faces.context.FacesContext;
33  import javax.faces.context.ResponseWriter;
34  import java.io.IOException;
35  import java.util.Iterator;
36  import java.util.List;
37  
38  /**
39   * Common methods for renderers for components that subclass the standard
40   * JSF HtmlDataTable component.
41   *
42   * @author Thomas Spiegl (latest modification by $Author: lu4242 $)
43   * @version $Revision: 1307197 $ $Date: 2012-03-29 20:12:16 -0500 (Thu, 29 Mar 2012) $
44   */
45  public class HtmlTableRendererBase extends HtmlRenderer
46  {
47      /** Header facet name. */
48      protected static final String HEADER_FACET_NAME = "header";
49  
50      /** Footer facet name. */
51      protected static final String FOOTER_FACET_NAME = "footer";
52  
53      /** The logger. */
54      private static final Log log = LogFactory.getLog(HtmlTableRendererBase.class);
55  
56      /**
57       * @param component dataTable
58       * @return number of layout columns
59       */
60      protected int getNewspaperColumns(UIComponent component) {
61          return 1;
62      }
63  
64      /**
65       * @param component dataTable
66       * @return component to display between layout columns
67       */
68      protected UIComponent getNewspaperTableSpacer(UIComponent component) {
69          return null;
70      }
71  
72      /**
73       * @param component dataTable
74       * @return whether dataTable has component to display between layout columns
75       */
76      protected boolean hasNewspaperTableSpacer(UIComponent component) {
77          return false;
78      }
79  
80      /**
81       * @param component dataTable
82       * @return whether dataTable has newspaper columns layed out horizontally
83       */
84      protected boolean isNewspaperHorizontalOrientation(UIComponent component) {
85          return false;
86      }
87  
88      /**
89       * @see javax.faces.render.Renderer#getRendersChildren()
90       */
91      public boolean getRendersChildren()
92      {
93          return true;
94      }
95  
96      /**
97       * Render the necessary bits that come before any actual <i>rows</i> in the table.
98       *
99       * @see javax.faces.render.Renderer#encodeBegin(FacesContext, UIComponent)
100      */
101     public void encodeBegin(FacesContext facesContext, UIComponent uiComponent) throws IOException
102     {
103         RendererUtils.checkParamValidity(facesContext, uiComponent, UIData.class);
104 
105         beforeTable(facesContext, (UIData) uiComponent);
106 
107         startTable(facesContext, uiComponent);
108     }
109 
110     /**
111      * actually render the start of the table
112      */
113     protected void startTable(FacesContext facesContext, UIComponent uiComponent) throws IOException
114     {
115         ResponseWriter writer = facesContext.getResponseWriter();
116         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
117         writer.startElement(HTML.TABLE_ELEM, uiComponent);
118         HtmlRendererUtils.writeIdIfNecessary(writer, uiComponent, facesContext);
119         HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.TABLE_PASSTHROUGH_ATTRIBUTES);
120     }
121 
122     /**
123      * Render the TBODY section of the html table. See also method encodeInnerHtml.
124      *
125      * @see javax.faces.render.Renderer#encodeChildren(FacesContext, UIComponent)
126      */
127     public void encodeChildren(FacesContext facesContext, UIComponent component) throws IOException
128     {
129         RendererUtils.checkParamValidity(facesContext, component, UIData.class);
130 
131         ResponseWriter writer = facesContext.getResponseWriter();
132 
133         beforeBody(facesContext, (UIData) component);
134 
135         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
136         writer.startElement(HTML.TBODY_ELEM, component);
137         writer.writeAttribute(HTML.ID_ATTR, component.getClientId(facesContext)+":tbody_element", null);
138         inBodyStart(facesContext, (UIData) component);
139 
140         encodeInnerHtml(facesContext, component);
141 
142         writer.endElement(HTML.TBODY_ELEM);
143 
144         afterBody(facesContext, (UIData) component);
145     }
146 
147     /**
148      * Gets styles for the specified component.
149      */
150     protected static Styles getStyles(UIData uiData) {
151         String rowClasses;
152         String columnClasses;
153         if(uiData instanceof HtmlDataTable) {
154             rowClasses = ((HtmlDataTable)uiData).getRowClasses();
155             columnClasses = ((HtmlDataTable)uiData).getColumnClasses();
156         } else {
157             rowClasses = (String)uiData.getAttributes().get(JSFAttr.ROW_CLASSES_ATTR);
158             columnClasses = (String)uiData.getAttributes().get(JSFAttr.COLUMN_CLASSES_ATTR);
159         }
160         return new Styles(rowClasses, columnClasses);
161     }
162 
163     /**
164      * Class manages the styles from String lists.
165      */
166     protected static class Styles {
167 
168         private String[] _columnStyle;
169         private String[] _rowStyle;
170 
171         Styles(String rowStyles, String columnStyles) {
172             _rowStyle = (rowStyles == null)
173                 ? ArrayUtils.EMPTY_STRING_ARRAY
174                 : StringUtils.trim(
175                     StringUtils.splitShortString(rowStyles, ','));
176             _columnStyle = (columnStyles == null)
177                 ? ArrayUtils.EMPTY_STRING_ARRAY
178                 : StringUtils.trim(
179                     StringUtils.splitShortString(columnStyles, ','));
180         }
181 
182         public String getRowStyle(int idx) {
183             if(!hasRowStyle()) {
184                 return null;
185             }
186             return _rowStyle[idx % _rowStyle.length];
187         }
188 
189         public String getColumnStyle(int idx) {
190             if(!hasColumnStyle()) {
191                 return null;
192             }
193             return _columnStyle[idx % _columnStyle.length];
194         }
195 
196         public boolean hasRowStyle() {
197             return _rowStyle.length > 0;
198         }
199 
200         public boolean hasColumnStyle() {
201             return _columnStyle.length > 0;
202         }
203     }
204 
205 
206     /**
207      * Renders everything inside the TBODY tag by iterating over the row objects
208      * between offsets first and first+rows and applying the UIColumn components
209      * to those objects. 
210      * <p>
211      * This method is separated from the encodeChildren so that it can be overridden by
212      * subclasses. One class that uses this functionality is autoUpdateDataTable.
213      */
214      public void encodeInnerHtml(FacesContext facesContext, UIComponent component)throws IOException{
215 
216         UIData uiData = (UIData) component;
217         ResponseWriter writer = facesContext.getResponseWriter();
218 
219         int rowCount = uiData.getRowCount();
220 
221         int newspaperColumns = getNewspaperColumns(component);
222 
223         if (rowCount == -1 && newspaperColumns == 1)
224         {
225             encodeInnerHtmlUnknownRowCount(facesContext, component);
226             return;
227         }
228         
229         if (rowCount == 0)
230         {
231             //nothing to render, to get valid xhtml we render an empty dummy row
232             writer.startElement(HTML.TR_ELEM, uiData);
233             writer.startElement(HTML.TD_ELEM, uiData);
234             writer.endElement(HTML.TD_ELEM);
235             writer.endElement(HTML.TR_ELEM);
236             return;
237         }
238 
239         // begin the table
240         // get the CSS styles
241         Styles styles = getStyles(uiData);
242 
243         int first = uiData.getFirst();
244         int rows = uiData.getRows();
245         int last;
246 
247         if (rows <= 0)
248         {
249            last = rowCount;
250         }
251         else
252         {
253            last = first + rows;
254            if (last > rowCount)
255            {
256                last = rowCount;
257            }
258         }
259 
260         int newspaperRows;
261         if((last - first) % newspaperColumns == 0)
262         {
263             newspaperRows = (last - first) / newspaperColumns;
264         }
265         else
266         {
267             newspaperRows = ((last - first) / newspaperColumns) + 1;
268         }
269         boolean newspaperHorizontalOrientation = isNewspaperHorizontalOrientation(component);
270 
271         // walk through the newspaper rows
272         for(int nr = 0; nr < newspaperRows; nr++)
273         {
274             boolean rowStartRendered = false;
275             // walk through the newspaper columns
276             for(int nc = 0; nc < newspaperColumns; nc++)
277             {
278 
279                 // the current row in the 'real' table
280                 int currentRow;
281                 if (newspaperHorizontalOrientation)
282                 {
283                     currentRow = nr * newspaperColumns + nc + first;
284                 }
285                 else
286                 {
287                     currentRow = nc * newspaperRows + nr + first;
288                 }
289                 
290                 // if this row is not to be rendered
291                 if(currentRow >= last)
292                 {
293                     continue;
294                 }
295 
296                 // bail if any row does not exist
297                 uiData.setRowIndex(currentRow);
298                 if(!uiData.isRowAvailable())
299                 {
300                     log.error("Row is not available. Rowindex = " + currentRow);
301                     break;
302                 }
303     
304                 if (nc == 0)
305                 {
306                     // first column in table, start new row
307                     beforeRow(facesContext, uiData);
308 
309                     HtmlRendererUtils.writePrettyLineSeparator(facesContext);
310                     renderRowStart(facesContext, writer, uiData, styles, nr);
311                     rowStartRendered = true;
312                 }
313 
314                 List children = null;
315                 for (int j = 0, size = getChildCount(component); j < size; j++)
316                 {
317                     if (children == null)
318                     {
319                         children = getChildren(component);
320                     }
321                     UIComponent child = (UIComponent) children.get(j);
322                     if (child.isRendered())
323                     {
324                         boolean columnRendering = child instanceof UIColumn;
325                         
326                         if (columnRendering)
327                         {
328                             beforeColumn(facesContext, uiData, j);
329                         }
330                            
331                         encodeColumnChild(facesContext, writer, uiData, child, 
332                                 styles, nc * uiData.getChildCount() + j);                    
333                        
334                         if (columnRendering)
335                         {
336                             afterColumn(facesContext, uiData, j);
337                         }
338                     }
339                 }
340 
341                 if (hasNewspaperTableSpacer(uiData))
342                 {
343                     // draw the spacer facet
344                     if(nc < newspaperColumns - 1)
345                     {
346                         renderSpacerCell(facesContext, writer, uiData);
347                     }
348                 }
349             }
350             if (rowStartRendered)
351             {
352                 renderRowEnd(facesContext, writer, uiData);
353                 afterRow(facesContext, uiData);
354             }
355         }
356     }
357 
358     private void encodeInnerHtmlUnknownRowCount(FacesContext facesContext, UIComponent component)throws IOException
359     {
360         UIData uiData = (UIData) component;
361         ResponseWriter writer = facesContext.getResponseWriter();
362 
363         Styles styles = getStyles(uiData);
364         
365         int first = uiData.getFirst();
366         int rows = uiData.getRows();
367         int currentRow = first;
368         
369         while(true)
370         {
371             uiData.setRowIndex(currentRow);
372             if (!uiData.isRowAvailable())
373             {
374                 break;
375             }
376             
377             // first column in table, start new row
378             beforeRow(facesContext, uiData);
379 
380             HtmlRendererUtils.writePrettyLineSeparator(facesContext);
381             renderRowStart(facesContext, writer, uiData, styles, currentRow);
382             
383             List children = null;
384             for (int j = 0, size = getChildCount(component); j < size; j++)
385             {
386                 if (children == null)
387                 {
388                     children = getChildren(component);
389                 }
390                 UIComponent child = (UIComponent) children.get(j);
391                 if (child.isRendered())
392                 {
393                     boolean columnRendering = child instanceof UIColumn;
394                     
395                     if (columnRendering)
396                     {
397                         beforeColumn(facesContext, uiData, j);
398                     }
399                        
400                     encodeColumnChild(facesContext, writer, uiData, child, 
401                             styles, j);                    
402                    
403                     if (columnRendering)
404                     {
405                         afterColumn(facesContext, uiData, j);
406                     }
407                 }
408             }
409 
410             renderRowEnd(facesContext, writer, uiData);
411             afterRow(facesContext, uiData);
412             
413             currentRow++;
414 
415             if (rows > 0 && currentRow-first > rows )
416             {
417                 break;
418             }
419         }
420     }
421 
422     protected void encodeColumnChild(FacesContext facesContext, ResponseWriter writer,
423         UIData uiData, UIComponent component, Styles styles, int columnStyleIndex) throws IOException
424     {
425         if (component instanceof UIColumn)
426         {
427             renderColumnBody(facesContext, writer, uiData, component, styles, columnStyleIndex);
428         }
429     }
430 
431     /**
432      * Renders the body of a given <code>UIColumn</code> (everything but
433      * the header and footer facets). This emits a TD cell, whose contents
434      * are the result of calling encodeBegin, encodeChildren and
435      * encodeEnd methods on the component (or its associated renderer).
436      *
437      * @param facesContext the <code>FacesContext</code>.
438      * @param writer the <code>ResponseWriter</code>.
439      * @param uiData the <code>UIData</code> being rendered.
440      * @param component the <code>UIComponent</code> to render.
441      * @param styles the styleClasses of rows and columns
442      * @param columnStyleIndex the index of the column
443      * @throws IOException if an exception occurs.
444      */
445     protected void renderColumnBody(
446             FacesContext facesContext,
447             ResponseWriter writer,
448             UIData uiData,
449             UIComponent component,
450             Styles styles, int columnStyleIndex) throws IOException
451     {
452         writer.startElement(HTML.TD_ELEM, uiData);
453         if (styles.hasColumnStyle())
454         {
455             writer.writeAttribute(HTML.CLASS_ATTR, styles.getColumnStyle(columnStyleIndex), null);
456         }
457 
458         RendererUtils.renderChild(facesContext, component);
459         writer.endElement(HTML.TD_ELEM);
460     }
461 
462     /**
463      * Renders the start of a new row of body content.
464      * @param facesContext the <code>FacesContext</code>.
465      * @param writer the <code>ResponseWriter</code>.
466      * @param uiData the <code>UIData</code> being rendered.
467      * @param styles the styleClasses of rows and columns
468      * @param rowStyleIndex the index of the row
469      * @throws IOException if an exceptoin occurs.
470      */
471     protected void renderRowStart(
472         FacesContext facesContext,
473         ResponseWriter writer,
474         UIData uiData,
475         Styles styles, int rowStyleIndex) throws IOException
476     {
477         writer.startElement(HTML.TR_ELEM, uiData);
478 
479         renderRowStyle(facesContext, writer, uiData, styles, rowStyleIndex);
480 
481         Object rowId = uiData.getAttributes().get(org.apache.myfaces.shared_orchestra.renderkit.JSFAttr.ROW_ID);
482 
483         if (rowId != null)
484         {
485             writer.writeAttribute(HTML.ID_ATTR, rowId.toString(), null);
486         }
487     }
488 
489     protected void renderRowStyle(FacesContext facesContext, ResponseWriter writer, UIData uiData, Styles styles, int rowStyleIndex) throws IOException
490     {
491         if(styles.hasRowStyle()) {
492             String rowStyle = styles.getRowStyle(rowStyleIndex);
493             writer.writeAttribute(HTML.CLASS_ATTR, rowStyle, null);
494         }
495     }
496 
497     /**
498      * Renders the end of a row of body content.
499      * @param facesContext the <code>FacesContext</code>.
500      * @param writer the <code>ResponseWriter</code>.
501      * @param uiData the <code>UIData</code> being rendered.
502      * @throws IOException if an exceptoin occurs.
503      */
504     protected void renderRowEnd(
505         FacesContext facesContext,
506         ResponseWriter writer,
507         UIData uiData) throws IOException
508     {
509         writer.endElement(HTML.TR_ELEM);
510     }
511 
512     /**
513      * Perform any operations necessary immediately before the TABLE start tag
514      * is output.
515      *
516      * @param facesContext the <code>FacesContext</code>.
517      * @param uiData the <code>UIData</code> being rendered.
518      * @throws java.io.IOException
519      */
520     protected void beforeTable(FacesContext facesContext, UIData uiData) throws IOException
521     {
522     }
523 
524     /**
525      * Perform any operations necessary after TABLE start tag is output
526      * but before the TBODY start tag.
527      * <p>
528      * This method generates the THEAD/TFOOT sections of a table if there
529      * are any header or footer facets defined on the table or on any child
530      * UIColumn component.
531      *
532      * @param facesContext the <code>FacesContext</code>.
533      * @param uiData the <code>UIData</code> being rendered.
534      */
535     protected void beforeBody(FacesContext facesContext, UIData uiData) throws IOException
536     {
537         ResponseWriter writer = facesContext.getResponseWriter();
538         renderFacet(facesContext, writer, uiData, true);
539         renderFacet(facesContext, writer, uiData, false);
540     }
541 
542     /**
543      * Perform any operations necessary immediately before each TR start tag
544      * is output.
545      *
546      * @param facesContext the <code>FacesContext</code>.
547      * @param uiData the <code>UIData</code> being rendered.
548      */
549     protected void beforeRow(FacesContext facesContext, UIData uiData) throws IOException
550     {
551     }
552 
553     /**
554      * Perform any operations necessary immediately after each TR end tag
555      * is output.
556      *
557      * @param facesContext the <code>FacesContext</code>.
558      * @param uiData the <code>UIData</code> being rendered.
559      */
560     protected void afterRow(FacesContext facesContext, UIData uiData) throws IOException
561     {
562     }
563     /**
564      * Perform any operations necessary immediately before each column child is rendered
565      *
566      * @param facesContext the <code>FacesContext</code>.
567      * @param uiData the <code>UIData</code> being rendered.
568      * @param columnIndex the index of the currenly rendered column
569      */
570     protected void beforeColumn(FacesContext facesContext, UIData uiData, int columnIndex) throws IOException
571     {
572     }
573     /**
574      * Perform any operations necessary immediately after each column child is rendered
575      *
576      * @param facesContext the <code>FacesContext</code>.
577      * @param uiData the <code>UIData</code> being rendered.
578      * @param columnIndex the index of the currenly rendered column
579      */
580     protected void afterColumn(FacesContext facesContext, UIData uiData, int columnIndex) throws IOException
581     {
582     }
583     /**
584      *Perform any operations necessary immediately before each column child's header or footer is rendered
585      *
586      * @param facesContext the <code>FacesContext</code>.
587      * @param uiData the <code>UIData</code> being rendered.
588      * @param header true if the header of the column child is rendered
589      * @param columnIndex the index of the currenly rendered column
590      */
591     protected void beforeColumnHeaderOrFooter(FacesContext facesContext, UIData uiData, boolean header, int columnIndex) throws IOException
592     {
593     }
594     /**
595      * Perform any operations necessary immediately after each column child's header of footer is rendered
596      *
597      * @param facesContext the <code>FacesContext</code>.
598      * @param uiData the <code>UIData</code> being rendered.
599      * @param header true if the header of the column child is rendered
600      * @param columnIndex the index of the currenly rendered column
601      */
602     protected void afterColumnHeaderOrFooter(FacesContext facesContext, UIData uiData, boolean header, int columnIndex) throws IOException
603     {
604     }
605 
606     /**
607      * Perform any operations necessary in the TBODY start tag.
608      *
609      * @param facesContext the <code>FacesContext</code>.
610      * @param uiData the <code>UIData</code> being rendered.
611      */
612     protected void inBodyStart(FacesContext facesContext, UIData uiData) throws IOException
613     {
614     }
615 
616     /**
617      * Perform any operations necessary immediately after the TBODY end tag
618      * is output.
619      *
620      * @param facesContext the <code>FacesContext</code>.
621      * @param uiData the <code>UIData</code> being rendered.
622      */
623     protected void afterBody(FacesContext facesContext, UIData uiData) throws IOException
624     {
625     }
626 
627     /**
628      * Perform any operations necessary immediately after the TABLE end tag
629      * is output.
630      *
631      * @param facesContext the <code>FacesContext</code>.
632      * @param uiData the <code>UIData</code> being rendered.
633      */
634     protected void afterTable(FacesContext facesContext, UIData uiData) throws IOException
635     {
636     }
637 
638     /**
639      * @see javax.faces.render.Renderer#encodeEnd(FacesContext, UIComponent)
640      */
641     public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException
642     {
643         RendererUtils.checkParamValidity(facesContext, uiComponent, UIData.class);
644 
645         endTable(facesContext, uiComponent);
646 
647         afterTable(facesContext, (UIData) uiComponent);
648     }
649 
650     /**
651      * actually render the end of the table
652      */
653     protected void endTable(FacesContext facesContext, UIComponent uiComponent) throws IOException
654     {
655         ResponseWriter writer = facesContext.getResponseWriter();
656         writer.endElement(HTML.TABLE_ELEM);
657         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
658     }
659 
660     /**
661      * Renders either the header or the footer facets for the UIData component
662      * and all the child UIColumn components, as a THEAD or TFOOT element
663      * containing TR (row) elements.
664      * <p>
665      * If there is a header or footer attached to the UIData then that is
666      * rendered as a TR element whose COLSPAN is the sum of all rendered
667      * columns in the table. This allows that header/footer to take up the
668      * entire width of the table.
669      * <p>
670      * If any child column has a header or footer then a TR is rendered
671      * with a TH cell for each column child.
672      *
673      * @param facesContext the <code>FacesContext</code>.
674      * @param writer the <code>ResponseWriter</code>.
675      * @param component the UIData component
676      * @param header whether this is the header facet (if not, then the footer facet).
677      * @throws IOException if an exception occurs.
678      */
679     protected void renderFacet(FacesContext facesContext, ResponseWriter writer,
680             UIComponent component, boolean header)
681             throws IOException
682     {
683         int colspan = 0;
684         boolean hasColumnFacet = false;
685         for (Iterator it = getChildren(component).iterator(); it.hasNext();)
686         {
687             UIComponent uiComponent = (UIComponent) it.next();
688             if(uiComponent.isRendered())
689             {
690                 // a UIColumn has a span of 1, anything else has a span of 0
691                 colspan += determineChildColSpan(uiComponent);
692 
693                 // hasColumnFacet is true if *any* child column has a facet of
694                 // the specified type.
695                 if (!hasColumnFacet)
696                 {
697                      hasColumnFacet = hasFacet(header, uiComponent);
698                 }
699             }
700         }
701 
702         UIComponent facet = header ? (UIComponent) component.getFacets().get(HEADER_FACET_NAME)
703                 : (UIComponent) component.getFacets().get(FOOTER_FACET_NAME);
704         if (facet != null || hasColumnFacet)
705         {
706             // Header or Footer present on either the UIData or a column, so we
707             // definitely need to render the THEAD or TFOOT section.
708             String elemName = determineHeaderFooterTag(facesContext, component, header);
709 
710             HtmlRendererUtils.writePrettyLineSeparator(facesContext);
711             if (elemName != null)
712             {
713                 writer.startElement(elemName, component);
714             }
715             if (header)
716             {
717                 String headerStyleClass = getHeaderClass(component);
718                 if (facet != null)
719                     renderTableHeaderRow(facesContext, writer, component, facet, headerStyleClass, colspan);
720                 if (hasColumnFacet)
721                     renderColumnHeaderRow(facesContext, writer, component, headerStyleClass);
722             }
723             else
724             {
725                 String footerStyleClass = getFooterClass(component);
726                 if (hasColumnFacet)
727                     renderColumnFooterRow(facesContext, writer, component, footerStyleClass);
728                 if (facet != null)
729                     renderTableFooterRow(facesContext, writer, component, facet, footerStyleClass, colspan);
730             }
731             if (elemName != null)
732             {
733                 writer.endElement(elemName);
734             }
735         }
736     }
737 
738     protected String determineHeaderFooterTag(FacesContext facesContext, UIComponent component, boolean header)
739     {
740         return header ? HTML.THEAD_ELEM : HTML.TFOOT_ELEM;
741     }
742 
743     /**
744      * @param header
745      * @param uiComponent
746      * @return boolean
747      */
748     protected boolean hasFacet(boolean header, UIComponent uiComponent)
749     {
750         if (uiComponent instanceof UIColumn)
751         {
752             UIColumn uiColumn = (UIColumn) uiComponent;
753             return header ? uiColumn.getHeader() != null : uiColumn.getFooter() != null;
754         }
755         return false;
756     }
757 
758     /**
759      * Calculate the number of columns the specified child component will span
760      * when rendered.
761      * <p>
762      * Normally, this is a fairly simple calculation: a UIColumn component
763      * is rendered as one column, every other child type is not rendered
764      * (ie spans zero columns). However custom subclasses of this renderer may
765      * override this method to handle cases where a single component renders
766      * as multiple columns.
767      */
768     protected int determineChildColSpan(UIComponent uiComponent)
769     {
770         if (uiComponent instanceof UIColumn)
771         {
772             return 1;
773         }
774         return 0;
775     }
776 
777     /**
778      * Renders the header row of the table being rendered.
779      * @param facesContext the <code>FacesContext</code>.
780      * @param writer the <code>ResponseWriter</code>.
781      * @param component the <code>UIComponent</code> for whom a table is being rendered.
782      * @param headerFacet the facet for the header.
783      * @param headerStyleClass the styleClass of the header.
784      * @param colspan the number of columns the header should span.  Typically, this is
785      * the number of columns in the table.
786      * @throws IOException if an exception occurs.
787      */
788     protected void renderTableHeaderRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
789             UIComponent headerFacet, String headerStyleClass, int colspan) throws IOException
790     {
791         renderTableHeaderOrFooterRow(facesContext, writer, component, headerFacet, headerStyleClass, determineHeaderCellTag(facesContext, component),
792                 colspan);
793     }
794 
795     /**
796      * Renders the footer row of the table being rendered.
797      * @param facesContext the <code>FacesContext</code>.
798      * @param writer the <code>ResponseWriter</code>.
799      * @param component the <code>UIComponent</code> for whom a table is being rendered.
800      * @param footerFacet the facet for the footer.
801      * @param footerStyleClass the styleClass of the footer.
802      * @param colspan the number of columns the header should span.  Typically, this is
803      * the number of columns in the table.
804      * @throws IOException if an exception occurs.
805      */
806     protected void renderTableFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
807             UIComponent footerFacet, String footerStyleClass, int colspan) throws IOException
808     {
809         renderTableHeaderOrFooterRow(facesContext, writer, component, footerFacet, footerStyleClass, HTML.TD_ELEM,
810                 colspan);
811     }
812 
813     /**
814      * Renders the header row for the columns, which is a separate row from the header row for the
815      * <code>UIData</code> header facet.
816      *
817      * @param facesContext the <code>FacesContext</code>.
818      * @param writer the <code>ResponseWriter</code>.
819      * @param component the UIData component for whom a table is being rendered.
820      * @param headerStyleClass the styleClass of the header
821      * @throws IOException if an exception occurs.
822      */
823     protected void renderColumnHeaderRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
824             String headerStyleClass) throws IOException
825     {
826         renderColumnHeaderOrFooterRow(facesContext, writer, component, headerStyleClass, true);
827     }
828 
829     /**
830      * Renders the footer row for the columns, which is a separate row from the footer row for the
831      * <code>UIData</code> footer facet.
832      * @param facesContext the <code>FacesContext</code>.
833      * @param writer the <code>ResponseWriter</code>.
834      * @param component the <code>UIComponent</code> for whom a table is being rendered.
835      * @param footerStyleClass the styleClass of the footerStyleClass
836      * @throws IOException if an exception occurs.
837      */
838     protected void renderColumnFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
839             String footerStyleClass) throws IOException
840     {
841         renderColumnHeaderOrFooterRow(facesContext, writer, component, footerStyleClass, false);
842     }
843 
844     protected void renderTableHeaderOrFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component,
845             UIComponent facet, String styleClass, String colElementName, int colspan) throws IOException
846     {
847         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
848         writer.startElement(HTML.TR_ELEM, component);
849         writer.startElement(colElementName, component);
850         if (colElementName.equals(determineHeaderCellTag(facesContext, component)))
851         {
852             writer.writeAttribute(HTML.SCOPE_ATTR, HTML.SCOPE_COLGROUP_VALUE, null);
853         }
854 
855         // span all the table's columns
856         int newsPaperColumns = getNewspaperColumns(component);
857         int totalColumns = colspan * newsPaperColumns;
858         if(hasNewspaperTableSpacer(component))
859         {
860             totalColumns = totalColumns + newsPaperColumns - 1;
861         }
862         writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(totalColumns), null);
863         if (styleClass != null)
864         {
865             writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
866         }
867         if (facet != null)
868         {
869             RendererUtils.renderChild(facesContext, facet);
870         }
871         writer.endElement(colElementName);
872         writer.endElement(HTML.TR_ELEM);
873     }
874 
875     /**
876      * @param component the UIData component for whom a table is being rendered.
877      */
878     private void renderColumnHeaderOrFooterRow(FacesContext facesContext, ResponseWriter writer,
879             UIComponent component, String styleClass, boolean header) throws IOException
880     {
881         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
882 
883         writer.startElement(HTML.TR_ELEM, component);
884         int columnIndex = 0;
885         int newspaperColumns = getNewspaperColumns(component);
886         for(int nc = 0; nc < newspaperColumns; nc++)
887         {
888             for (Iterator it = getChildren(component).iterator(); it.hasNext();)
889             {
890                 UIComponent uiComponent = (UIComponent) it.next();
891                 if (uiComponent.isRendered())
892                 {
893                     if (component instanceof UIData && uiComponent instanceof UIColumn)
894                         beforeColumnHeaderOrFooter(facesContext, (UIData)component, header, columnIndex);
895 
896                     renderColumnChildHeaderOrFooterRow(facesContext, writer, uiComponent, styleClass, header);
897 
898                     if (component instanceof UIData && uiComponent instanceof UIColumn)
899                         afterColumnHeaderOrFooter(facesContext, (UIData)component, header, columnIndex);
900                 }
901                 columnIndex += 1;
902             }
903 
904             if (hasNewspaperTableSpacer(component))
905             {
906                 // draw the spacer facet
907                 if(nc < newspaperColumns - 1) renderSpacerCell(facesContext, writer, component);
908             }
909         }
910         writer.endElement(HTML.TR_ELEM);
911     }
912 
913       /**
914       * Renders a spacer between adjacent newspaper columns.
915       */
916     protected void renderSpacerCell(FacesContext facesContext, ResponseWriter writer, UIComponent component) throws IOException {
917         UIComponent spacer = getNewspaperTableSpacer(component);
918         if(spacer == null) return;
919 
920          writer.startElement(HTML.TD_ELEM, component);
921          RendererUtils.renderChild(facesContext, spacer);
922          writer.endElement(HTML.TD_ELEM);
923      }
924 
925     protected void renderColumnChildHeaderOrFooterRow(FacesContext facesContext,
926         ResponseWriter writer, UIComponent uiComponent, String styleClass, boolean header) throws IOException
927     {
928         if (uiComponent instanceof UIColumn)
929         {
930             if (header)
931             {
932                 renderColumnHeaderCell(facesContext, writer, uiComponent,
933                     ((UIColumn) uiComponent).getHeader(), styleClass, 0);
934             }
935             else
936             {
937                 renderColumnFooterCell(facesContext, writer, uiComponent,
938                     ((UIColumn) uiComponent).getFooter(), styleClass, 0);
939             }
940         }
941     }
942 
943     /**
944      * Renders the header facet for the given <code>UIColumn</code>.
945      * @param facesContext the <code>FacesContext</code>.
946      * @param writer the <code>ResponseWriter</code>.
947      * @param uiColumn the <code>UIColumn</code>.
948      * @param headerStyleClass the styleClass of the header facet.
949      * @param colspan the colspan for the tableData element in which the header facet
950      * will be wrapped.
951      * @throws IOException
952      */
953     protected void renderColumnHeaderCell(FacesContext facesContext, ResponseWriter writer, UIColumn uiColumn,
954         String headerStyleClass, int colspan) throws IOException
955     {
956         renderColumnHeaderCell(facesContext, writer, uiColumn, uiColumn.getHeader(), headerStyleClass, colspan);
957     }
958 
959     /**
960      * Renders a TH cell within a TR within a THEAD section. If the specified
961      * UIColumn object does have a header facet, then that facet is rendered
962      * within the cell, otherwise the cell is left blank (though any specified
963      * style class is still applied to empty cells).
964      *
965      * @param facesContext the <code>FacesContext</code>.
966      * @param writer the <code>ResponseWriter</code>.
967      * @param uiComponent the <code>UIComponent</code> to render the facet for.
968      * @param facet the <code>UIComponent</code> to render as facet.
969      * @param headerStyleClass the styleClass of the header facet.
970      * @param colspan the colspan for the tableData element in which the header facet
971      * will be wrapped.
972      * @throws IOException
973      */
974     protected void renderColumnHeaderCell(FacesContext facesContext, ResponseWriter writer, UIComponent uiComponent,
975             UIComponent facet, String headerStyleClass, int colspan) throws IOException
976     {
977         writer.startElement(determineHeaderCellTag(facesContext, uiComponent.getParent()), uiComponent);
978         if (colspan > 1)
979         {
980             writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(colspan), null);
981         }
982         if (headerStyleClass != null)
983         {
984             writer.writeAttribute(HTML.CLASS_ATTR, headerStyleClass, null);
985         }
986         if (facet != null)
987         {
988             RendererUtils.renderChild(facesContext, facet);
989         }
990         writer.endElement(determineHeaderCellTag(facesContext, uiComponent.getParent()));
991     }
992 
993     protected String determineHeaderCellTag(FacesContext facesContext, UIComponent uiComponent)
994     {
995         return HTML.TH_ELEM;
996     }
997 
998     /**
999      * Renders the footer facet for the given <code>UIColumn</code>.
1000      * @param facesContext the <code>FacesContext</code>.
1001      * @param writer the <code>ResponseWriter</code>.
1002      * @param uiColumn the <code>UIComponent</code>.
1003      * @param footerStyleClass the styleClass of the footer facet.
1004      * @param colspan the colspan for the tableData element in which the footer facet
1005      * will be wrapped.
1006      * @throws IOException
1007      */
1008     protected void renderColumnFooterCell(FacesContext facesContext, ResponseWriter writer, UIColumn uiColumn,
1009         String footerStyleClass, int colspan) throws IOException
1010     {
1011       renderColumnFooterCell(facesContext, writer, uiColumn, uiColumn.getFooter(), footerStyleClass, colspan);
1012     }
1013 
1014     /**
1015      * Renders the footer facet for the given <code>UIColumn</code>.
1016      * @param facesContext the <code>FacesContext</code>.
1017      * @param writer the <code>ResponseWriter</code>.
1018      * @param uiComponent the <code>UIComponent</code> to render the facet for.
1019      * @param facet the <code>UIComponent</code> to render as facet.
1020      * @param footerStyleClass the styleClass of the footer facet.
1021      * @param colspan the colspan for the tableData element in which the footer facet
1022      * will be wrapped.
1023      * @throws IOException
1024      */
1025     protected void renderColumnFooterCell(FacesContext facesContext, ResponseWriter writer, UIComponent uiComponent,
1026         UIComponent facet, String footerStyleClass, int colspan) throws IOException
1027     {
1028         writer.startElement(HTML.TD_ELEM, uiComponent);
1029         if (colspan > 1)
1030         {
1031             writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(colspan), null);
1032         }
1033         if (footerStyleClass != null)
1034         {
1035             writer.writeAttribute(HTML.CLASS_ATTR, footerStyleClass, null);
1036         }
1037         if (facet != null)
1038         {
1039             RendererUtils.renderChild(facesContext, facet);
1040         }
1041         writer.endElement(HTML.TD_ELEM);
1042     }
1043 
1044     /**
1045      * Gets the headerClass attribute of the given <code>UIComponent</code>.
1046      * @param component the <code>UIComponent</code>.
1047      * @return the headerClass attribute of the given <code>UIComponent</code>.
1048      */
1049     protected static String getHeaderClass(UIComponent component)
1050     {
1051         if (component instanceof HtmlDataTable)
1052         {
1053             return ((HtmlDataTable) component).getHeaderClass();
1054         }
1055         else
1056         {
1057             return (String) component.getAttributes().get(org.apache.myfaces.shared_orchestra.renderkit.JSFAttr.HEADER_CLASS_ATTR);
1058         }
1059     }
1060 
1061     /**
1062      * Gets the footerClass attribute of the given <code>UIComponent</code>.
1063      * @param component the <code>UIComponent</code>.
1064      * @return the footerClass attribute of the given <code>UIComponent</code>.
1065      */
1066     protected static String getFooterClass(UIComponent component)
1067     {
1068         if (component instanceof HtmlDataTable)
1069         {
1070             return ((HtmlDataTable) component).getFooterClass();
1071         }
1072         else
1073         {
1074             return (String) component.getAttributes().get(org.apache.myfaces.shared_orchestra.renderkit.JSFAttr.FOOTER_CLASS_ATTR);
1075         }
1076     }
1077 
1078     public void decode(FacesContext context, UIComponent component)
1079     {
1080         super.decode(context, component);
1081     }
1082 
1083 }