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  
20  package org.apache.myfaces.renderkit.html.ext;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.myfaces.component.NewspaperTable;
25  import org.apache.myfaces.component.html.ext.HtmlDataTable;
26  import org.apache.myfaces.custom.column.HtmlColumn;
27  import org.apache.myfaces.custom.column.HtmlSimpleColumn;
28  import org.apache.myfaces.custom.crosstable.UIColumns;
29  import org.apache.myfaces.renderkit.html.util.ColumnInfo;
30  import org.apache.myfaces.renderkit.html.util.RowInfo;
31  import org.apache.myfaces.renderkit.html.util.TableContext;
32  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
33  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
34  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
35  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
36  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase;
37  import org.apache.myfaces.shared_tomahawk.util.ArrayUtils;
38  
39  import javax.faces.component.UIComponent;
40  import javax.faces.component.UIData;
41  import javax.faces.component.ValueHolder;
42  import javax.faces.context.FacesContext;
43  import javax.faces.context.ResponseWriter;
44  import java.io.IOException;
45  import java.util.ArrayList;
46  import java.util.HashMap;
47  import java.util.Iterator;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.Set;
51  
52  /**
53   * Renderer for the Tomahawk extended HtmlDataTable component.
54   *
55   * @JSFRenderer
56   *   renderKitId = "HTML_BASIC"
57   *   family = "javax.faces.Data"
58   *   type = "org.apache.myfaces.Table" 
59   *   
60   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
61   * @version $Revision: 691079 $ $Date: 2008-09-01 17:55:44 -0500 (Mon, 01 Sep 2008) $
62   */
63  public class HtmlTableRenderer extends HtmlTableRendererBase {
64      private static final Log log = LogFactory.getLog(HtmlTableRenderer.class);
65  
66      /**
67       * DetailStamp facet name.
68       */
69      public static final String DETAIL_STAMP_FACET_NAME = "detailStamp";
70      private static final String BODY_STYLE_CLASS = "bodyStyleClass";
71      private static final String BODY_STYLE = "bodyStyle";
72  
73      /**
74       * @param component dataTable
75       * @return number of layout columns
76       */
77      protected int getNewspaperColumns(UIComponent component) {
78          if (component instanceof NewspaperTable) {
79              // the number of slices to break the table up into */
80              NewspaperTable newspaperTable = (NewspaperTable) component;
81              return newspaperTable.getNewspaperColumns();
82          }
83          return super.getNewspaperColumns(component);
84      }
85  
86      /**
87       * @param component dataTable
88       * @return component to display between layout columns
89       */
90      protected UIComponent getNewspaperTableSpacer(UIComponent component) {
91          if (component instanceof NewspaperTable) {
92              // the number of slices to break the table up into */
93              NewspaperTable newspaperTable = (NewspaperTable) component;
94              return newspaperTable.getSpacer();
95          }
96          return super.getNewspaperTableSpacer(component);
97      }
98  
99      /**
100      * @param component dataTable
101      * @return whether dataTable has component to display between layout columns
102      */
103     protected boolean hasNewspaperTableSpacer(UIComponent component) {
104         if (null != getNewspaperTableSpacer(component)) {
105             return true;
106         }
107         return super.hasNewspaperTableSpacer(component);
108     }
109 
110     /**
111      * @param component dataTable
112      * @return if the orientation of the has newspaper columns is horizontal
113      */
114     protected boolean isNewspaperHorizontalOrientation(UIComponent component) {
115         if (component instanceof NewspaperTable) {
116             // get the value of the newspaperOrientation attribute, any value besides horizontal
117             // means vertical, the default
118             NewspaperTable newspaperTable = (NewspaperTable) component;
119             return NewspaperTable.NEWSPAPER_HORIZONTAL_ORIENTATION.equals(newspaperTable.getNewspaperOrientation());
120         }
121         return super.isNewspaperHorizontalOrientation(component);
122     }
123 
124     protected void startTable(FacesContext facesContext, UIComponent uiComponent) throws IOException {
125         boolean embedded = isEmbeddedTable(uiComponent);
126 
127         if (!embedded) {
128             super.startTable(facesContext, uiComponent);
129         }
130     }
131 
132     protected String determineHeaderFooterTag(FacesContext facesContext, UIComponent component, boolean header) {
133         if (isEmbeddedTable(component)) {
134             // we are embedded, so do not render the tfoot/thead stuff
135             return null;
136         }
137 
138         return super.determineHeaderFooterTag(facesContext, component, header);
139     }
140 
141     protected String determineHeaderCellTag(FacesContext facesContext, UIComponent component) {
142         if (isEmbeddedTable(component)) {
143             return HTML.TD_ELEM;
144         }
145 
146         return HTML.TH_ELEM;
147     }
148 
149     protected void renderTableHeaderOrFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component, UIComponent facet, String styleClass, String colElementName, int colspan, boolean isHeader) throws IOException {
150         if (isEmbeddedTable(component)) {
151             // embedded tables render the header/footer stuff using TD only
152             colElementName = HTML.TD_ELEM;
153         }
154 
155         super.renderTableHeaderOrFooterRow(facesContext, writer, component, facet, styleClass, colElementName, colspan, isHeader);
156     }
157 
158     protected boolean isEmbeddedTable(UIComponent uiComponent) {
159         boolean embedded = false;
160         if (uiComponent instanceof HtmlDataTable) {
161             HtmlDataTable table = (HtmlDataTable) uiComponent;
162             embedded = table.isEmbedded();
163         }
164         return embedded;
165     }
166 
167     protected boolean isDetailStampAfterRow(UIComponent uiComponent) {
168         if (uiComponent instanceof HtmlDataTable) {
169             return "after".equals(((HtmlDataTable) uiComponent).getDetailStampLocation());
170         }
171 
172         return true;
173     }
174 
175     protected void endTable(FacesContext facesContext, UIComponent uiComponent) throws IOException {
176         boolean embedded = isEmbeddedTable(uiComponent);
177 
178         if (!embedded) {
179             super.endTable(facesContext, uiComponent);
180         }
181     }
182 
183     protected void beforeRow(FacesContext facesContext, UIData uiData) throws IOException {
184         super.beforeRow(facesContext, uiData);
185 
186         if (!isDetailStampAfterRow(uiData)) {
187             renderDetailRow(facesContext, uiData);
188         }
189     }
190 
191     protected void afterRow(FacesContext facesContext, UIData uiData) throws IOException {
192         super.afterRow(facesContext, uiData);
193 
194         if (isDetailStampAfterRow(uiData)) {
195             renderDetailRow(facesContext, uiData);
196         }
197     }
198 
199     /**
200      * @param facesContext
201      * @param uiData
202      * @throws IOException
203      */
204     private void renderDetailRow(FacesContext facesContext, UIData uiData) throws IOException {
205         UIComponent detailStampFacet = uiData.getFacet(DETAIL_STAMP_FACET_NAME);
206 
207         if (uiData instanceof HtmlDataTable) {
208             HtmlDataTable htmlDataTable = (HtmlDataTable) uiData;
209 
210             if (htmlDataTable.isCurrentDetailExpanded()) {
211 
212                 boolean embedded = false;
213                 if (detailStampFacet != null) {
214                     embedded = isEmbeddedTable(detailStampFacet);
215                 }
216 
217                 ResponseWriter writer = facesContext.getResponseWriter();
218 
219                 if (!embedded) {
220                     writer.startElement(HTML.TR_ELEM, uiData);
221                     writer.startElement(HTML.TD_ELEM, uiData);
222                     //TOMAHAWK-1087 datatable dont renders a detail correct 
223                     //if a UIColumns is used we have to count UIColumns 
224                     //elements as component.getRowCount()
225                     //instead of just get the number of children available,
226                     //so the colspan could be assigned correctly.
227                     int childCount = 0;
228                     for (Iterator childIter = uiData.getChildren().iterator();
229                         childIter.hasNext();)
230                     {
231                         UIComponent childComp = (UIComponent) childIter.next();
232                         if (childComp instanceof UIColumns)
233                         {
234                             UIColumns v = (UIColumns) childComp;
235                             childCount += v.getRowCount();
236                         }
237                         else
238                         {
239                             childCount++;
240                         }
241                     }
242                     writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(
243                             childCount), null);
244                 }
245 
246                 if (detailStampFacet != null) {
247                     RendererUtils.renderChild(facesContext, detailStampFacet);
248                 }
249 
250                 if (!embedded) {
251                     writer.endElement(HTML.TD_ELEM);
252                     writer.endElement(HTML.TR_ELEM);
253                 }
254             }
255         }
256     }
257 
258     /**
259      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#encodeBegin(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
260      */
261     public void encodeBegin(FacesContext facesContext, UIComponent uiComponent) throws IOException {
262         if (uiComponent instanceof HtmlDataTable) {
263             HtmlDataTable htmlDataTable = (HtmlDataTable) uiComponent;
264             if (htmlDataTable.isRenderedIfEmpty() || htmlDataTable.getRowCount() > 0) {
265                 super.encodeBegin(facesContext, uiComponent);
266             }
267         }
268         else {
269             super.encodeBegin(facesContext, uiComponent);
270         }
271     }
272 
273     /**
274      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#encodeChildren(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
275      */
276     public void encodeChildren(FacesContext facesContext, UIComponent component) throws IOException {
277         if (component instanceof HtmlDataTable) {
278             HtmlDataTable htmlDataTable = (HtmlDataTable) component;
279             if (htmlDataTable.isRenderedIfEmpty() || htmlDataTable.getRowCount() > 0) {
280                 super.encodeChildren(facesContext, component);
281             }
282         }
283         else {
284             super.encodeChildren(facesContext, component);
285         }
286     }
287 
288     private boolean isGroupedTable(UIData uiData) {
289         if (uiData instanceof HtmlDataTable) {
290             List children = getChildren(uiData);
291             for (int j = 0, size = getChildCount(uiData); j < size; j++) {
292                 UIComponent child = (UIComponent) children.get(j);
293                 if (child instanceof HtmlSimpleColumn) {
294                     HtmlSimpleColumn column = (HtmlSimpleColumn) child;
295                     if (column.isGroupBy()) {
296                         return true;
297                     }
298                 }
299             }
300         }
301 
302         return false;  //To change body of created methods use File | Settings | File Templates.
303     }
304 
305     protected void beforeBody(FacesContext facesContext, UIData uiData) throws IOException {
306         if (isGroupedTable(uiData)) {
307             createColumnInfos((HtmlDataTable) uiData, facesContext);
308         }
309         super.beforeBody(facesContext, uiData);
310     }
311 
312     private void createColumnInfos(HtmlDataTable htmlDataTable, FacesContext facesContext)
313         throws IOException {
314         int first = htmlDataTable.getFirst();
315         int rows = htmlDataTable.getRows();
316         int last;
317         int currentRowSpan = -1;
318         int currentRowInfoIndex = -1;
319 
320         TableContext tableContext = htmlDataTable.getTableContext();
321         RowInfo rowInfo = null;
322         ColumnInfo columnInfo = null;
323         HtmlSimpleColumn currentColumn = null;
324         Map groupHashTable = new HashMap();
325 
326         if (rows <= 0) {
327             last = htmlDataTable.getRowCount();
328         }
329         else {
330             last = first + rows;
331         }
332 
333         //Loop over the Children Columns to find the Columns with groupBy Attribute true
334         List children = getChildren(htmlDataTable);
335         int nChildren = getChildCount(htmlDataTable);
336 
337         for (int j = 0, size = nChildren; j < size; j++) {
338             UIComponent child = (UIComponent) children.get(j);
339             if (child instanceof HtmlSimpleColumn) {
340                 currentColumn = (HtmlSimpleColumn) child;
341                 if (currentColumn.isGroupBy()) {
342                     groupHashTable.put(new Integer(j), null);
343                 }
344             }
345         }
346 
347         boolean groupEndReached = false;
348 
349         for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++) {
350             htmlDataTable.setRowIndex(rowIndex);
351             rowInfo = new RowInfo();
352             //scrolled past the last row
353             if (!htmlDataTable.isRowAvailable()) {
354                 break;
355             }
356 
357             Set groupIndexList = groupHashTable.keySet();
358             List currentColumnContent = null;
359             for (Iterator it = groupIndexList.iterator(); it.hasNext();) {
360                 currentColumnContent = new ArrayList();
361                 Integer currentIndex = (Integer) it.next();
362                 currentColumn = (HtmlSimpleColumn) children.get(currentIndex.intValue());
363 
364                 if (currentColumn.isGroupByValueSet()) {
365                     currentColumnContent.add(currentColumn.getGroupByValue());
366                 }
367                 else {
368                     // iterate the children - this avoids to add the column facet too
369                     List currentColumnChildren = currentColumn.getChildren();
370                     if (currentColumnChildren != null) {
371                         collectChildrenValues(currentColumnContent, currentColumnChildren.iterator());
372                     }
373                 }
374 
375                 if (!isListEqual(currentColumnContent, (List) groupHashTable.get(currentIndex)) &&
376                     currentRowInfoIndex > -1) {
377                     groupEndReached = true;
378                     groupHashTable.put(currentIndex, currentColumnContent);
379                 }
380                 else if (currentRowInfoIndex == -1) {
381                     groupHashTable.put(currentIndex, currentColumnContent);
382                 }
383             }
384             currentRowSpan++;
385 
386 
387             for (int j = 0, size = nChildren; j < size; j++) {
388                 columnInfo = new ColumnInfo();
389                 if (groupHashTable.containsKey(new Integer(j)))  // Column is groupBy
390                 {
391                     if (currentRowSpan > 0) {
392                         if (groupEndReached) {
393                             ((ColumnInfo)
394                                 ((RowInfo)
395                                     tableContext.getRowInfos().get(currentRowInfoIndex - currentRowSpan + 1)).
396                                     getColumnInfos().get(j)).
397                                 setRowSpan(currentRowSpan);
398                             columnInfo.setStyle(htmlDataTable.getRowGroupStyle());
399                             columnInfo.setStyleClass(htmlDataTable.getRowGroupStyleClass());
400                         }
401                         else {
402                             columnInfo.setRendered(false);
403                         }
404                     }
405                     else {
406                         columnInfo.setStyle(htmlDataTable.getRowGroupStyle());
407                         columnInfo.setStyleClass(htmlDataTable.getRowGroupStyleClass());
408                     }
409 
410                 }
411                 else    // Column  is not group by
412                 {
413                     if (groupEndReached) {
414                         ((ColumnInfo)
415                             ((RowInfo)
416                                 tableContext.getRowInfos().get(currentRowInfoIndex)).
417                                 getColumnInfos().get(j)).
418                             setStyle(htmlDataTable.getRowGroupStyle());
419                         ((ColumnInfo)
420                             ((RowInfo)
421                                 tableContext.getRowInfos().get(currentRowInfoIndex)).
422                                 getColumnInfos().get(j)).
423                             setStyleClass(htmlDataTable.getRowGroupStyleClass());
424                     }
425                 }
426                 rowInfo.getColumnInfos().add(columnInfo);
427             }
428             if (groupEndReached) {
429                 currentRowSpan = 0;
430                 groupEndReached = false;
431             }
432             tableContext.getRowInfos().add(rowInfo);
433             currentRowInfoIndex++;
434         }
435 
436         // do further processing if we've found at least one row
437         if (currentRowInfoIndex > -1) {
438             for (int j = 0, size = nChildren; j < size; j++) {
439                 if (groupHashTable.containsKey(new Integer(j)))  // Column is groupBy
440                 {
441                     ((ColumnInfo)
442                         ((RowInfo)
443                             tableContext.getRowInfos().get(currentRowInfoIndex - currentRowSpan)).
444                             getColumnInfos().get(j)).
445                         setRowSpan(currentRowSpan + 1);
446                 }
447                 else    // Column  is not group by
448                 {
449                     ((ColumnInfo)
450                         ((RowInfo)
451                             tableContext.getRowInfos().get(currentRowInfoIndex)).
452                             getColumnInfos().get(j)).
453                         setStyle(htmlDataTable.getRowGroupStyle());
454                     ((ColumnInfo)
455                         ((RowInfo)
456                             tableContext.getRowInfos().get(currentRowInfoIndex)).
457                             getColumnInfos().get(j)).
458                         setStyleClass(htmlDataTable.getRowGroupStyleClass());
459                 }
460             }
461         }
462 
463         htmlDataTable.setRowIndex(-1);
464     }
465 
466     protected void collectChildrenValues(List container, Iterator iterChildren) {
467         while (iterChildren.hasNext()) {
468             UIComponent child = (UIComponent) iterChildren.next();
469             if (child.isRendered()) {
470                 if (child instanceof ValueHolder) {
471                     Object value = ((ValueHolder) child).getValue();
472                     container.add(value);
473                 }
474 
475                 // we lave the column ... now iterate the facets too
476                 collectChildrenValues(container, child.getFacetsAndChildren());
477             }
478         }
479     }
480 
481     /**
482      * checks if the contenta of both lists are the same.<br />
483      * <b>Notice:</b> In case both lists are null or empty they are
484      * considered NOT being equal
485      */
486     protected boolean isListEqual(List list1, List list2) {
487         if (list1 == list2) {
488             return list1 != null && list1.size() > 0;
489         }
490         if (list1 == null || list2 == null || list1.size() != list2.size()) {
491             return false;
492         }
493 
494         for (int i = 0; i < list1.size(); i++) {
495             Object o1 = list1.get(i);
496             Object o2 = list2.get(i);
497 
498             if (o1 != null && !o1.equals(o2)) {
499                 return false;
500             }
501             if (o2 != null && !o2.equals(o1)) {
502                 return false;
503             }
504         }
505 
506         return true;
507     }
508 
509 
510     /**
511      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
512      */
513     public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
514         if (uiComponent instanceof HtmlDataTable) {
515             HtmlDataTable htmlDataTable = (HtmlDataTable) uiComponent;
516             if (htmlDataTable.isRenderedIfEmpty() || htmlDataTable.getRowCount() > 0) {
517                 super.encodeEnd(facesContext, uiComponent);
518             }
519         }
520         else {
521             super.encodeEnd(facesContext, uiComponent);
522         }
523     }
524 
525     protected void renderRowStart(FacesContext facesContext,
526                                   ResponseWriter writer, UIData uiData, Styles styles, int rowStyleIndex)
527         throws IOException {
528         super.renderRowStart(facesContext, writer, uiData, styles, rowStyleIndex);
529 
530         // get event handlers from component
531         HtmlDataTable table = (HtmlDataTable) uiData;
532 
533         renderRowAttribute(writer, HTML.ONCLICK_ATTR, table.getRowOnClick());
534         renderRowAttribute(writer, HTML.ONDBLCLICK_ATTR, table.getRowOnDblClick());
535         renderRowAttribute(writer, HTML.ONKEYDOWN_ATTR, table.getRowOnKeyDown());
536         renderRowAttribute(writer, HTML.ONKEYPRESS_ATTR, table.getRowOnKeyPress());
537         renderRowAttribute(writer, HTML.ONKEYUP_ATTR, table.getRowOnKeyUp());
538         renderRowAttribute(writer, HTML.ONMOUSEDOWN_ATTR, table.getRowOnMouseDown());
539         renderRowAttribute(writer, HTML.ONMOUSEMOVE_ATTR, table.getRowOnMouseMove());
540         renderRowAttribute(writer, HTML.ONMOUSEOUT_ATTR, table.getRowOnMouseOut());
541         renderRowAttribute(writer, HTML.ONMOUSEOVER_ATTR, table.getRowOnMouseOver());
542         renderRowAttribute(writer, HTML.ONMOUSEUP_ATTR, table.getRowOnMouseUp());
543     }
544 
545     /**
546      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderRowStyle(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIData, org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase.Styles, int)
547      */
548     protected void renderRowStyle(FacesContext facesContext, ResponseWriter writer, UIData uiData, Styles styles, int rowStyleIndex) throws IOException {
549         String rowStyleClass;
550         String rowStyle;
551         if (uiData instanceof HtmlDataTable) {
552             HtmlDataTable datatable = (HtmlDataTable) uiData;
553             rowStyleClass = datatable.getRowStyleClass();
554             rowStyle = datatable.getRowStyle();
555         }
556         else {
557             rowStyleClass = (String) uiData.getAttributes().get(JSFAttr.ROW_STYLECLASS_ATTR);
558             rowStyle = (String) uiData.getAttributes().get(JSFAttr.ROW_STYLE_ATTR);
559         }
560         if (rowStyleClass == null) {
561             super.renderRowStyle(facesContext, writer, uiData, styles, rowStyleIndex);
562         }
563         else {
564             writer.writeAttribute(HTML.CLASS_ATTR, rowStyleClass, null);
565         }
566         if (rowStyle != null) {
567             writer.writeAttribute(HTML.STYLE_ATTR, rowStyle, null);
568         }
569     }
570 
571     protected void renderRowAttribute(ResponseWriter writer,
572                                       String htmlAttribute, Object value) throws IOException {
573         if (value != null) {
574             writer.writeAttribute(htmlAttribute, value, null);
575         }
576     }
577 
578     /**
579      * Render the specified column object using the current row data.
580      * <p/>
581      * When the component is a UIColumn object, the inherited method is
582      * invoked to render a single table cell.
583      * <p/>
584      * In addition to the inherited functionality, support is implemented
585      * here for UIColumns children. When a UIColumns child is encountered:
586      * <pre>
587      * For each dynamic column in that UIColumns child:
588      *   * Select the column (which sets variable named by the var attribute
589      *     to refer to the current column object)
590      *   * Call this.renderColumnBody passing the UIColumns object.
591      * </pre>
592      * The renderColumnBody method eventually:
593      * <ul>
594      * <li>emits TD
595      * <li>calls encodeBegin on the UIColumns (which does nothing)
596      * <li>calls rendering methods on all children of the UIColumns
597      * <li>calls encodeEnd on the UIColumns (which does nothing)
598      * <li> emits /TD
599      * </ul>
600      * If the children of the UIColumns access the variable named by the var
601      * attribute on the UIColumns object, then they end up rendering content
602      * that is extracted from the current column object.
603      *
604      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#encodeColumnChild(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIData, javax.faces.component.UIComponent, org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase.Styles, int)
605      */
606     protected void encodeColumnChild(FacesContext facesContext,
607                                      ResponseWriter writer, UIData uiData,
608                                      UIComponent component, Styles styles, int columnStyleIndex)
609         throws IOException {        
610         //columnStyleIndex param does not takes into
611         //consideration UIColumns, since it is called from HtmlTableRendererBase.
612         //So we need to recalculate its index taking into account that
613         //UIColumns component count as ((UIColumns) child).getRowCount() instead 1 
614         columnStyleIndex = getColumnStyleIndex(uiData, columnStyleIndex);
615         super.encodeColumnChild(facesContext, writer, uiData, component,
616             styles, columnStyleIndex);
617         if (component instanceof UIColumns)
618         {
619             UIColumns columns = (UIColumns) component;
620             for (int k = 0, colSize = columns.getRowCount(); k < colSize; k++)
621             {
622                 columns.setRowIndex(k);
623                 renderColumnBody(facesContext, writer, uiData, component,
624                     styles, columnStyleIndex + k);
625             }
626             columns.setRowIndex(-1);
627         }
628     }
629     
630     /**
631       * This method calculates the correct columnStyleIndex taking the dynamic columns
632       * of <b>UIColumns</b> into consideration
633       * 
634       * @param uiData
635       * @param columnStyleIndex
636       * @return the correct columnStyleIndex 
637       */
638     private int getColumnStyleIndex(UIData uiData, int columnStyleIndex)
639     {
640         int colStyleIndex = 0;
641         for (int i = 0; i < columnStyleIndex; i++)
642         {
643             UIComponent child = (UIComponent) uiData.getChildren().get(i % uiData.getChildCount());
644             if (child instanceof UIColumns)
645             {
646                 colStyleIndex += ((UIColumns) child).getRowCount();
647                 continue;
648             }
649             colStyleIndex++;
650         }
651         return colStyleIndex;
652     }
653     
654 
655     /**
656      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderColumnBody(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIData, javax.faces.component.UIComponent, org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase.Styles, int)
657      */
658     protected void renderColumnBody(FacesContext facesContext,
659                                     ResponseWriter writer, UIData uiData,
660                                     UIComponent component, Styles styles, int columnStyleIndex)
661         throws IOException {
662         if (isGroupedTable(uiData)) {
663 
664             HtmlDataTable htmlDataTable = (HtmlDataTable) uiData;
665             List tableChildren = htmlDataTable.getChildren();
666 
667             int first = htmlDataTable.getFirst();
668             int rowInfoIndex = htmlDataTable.getRowIndex() - first;
669             int columnInfoIndex = tableChildren.indexOf(component);
670 
671             RowInfo rowInfo = (RowInfo) htmlDataTable.getTableContext().getRowInfos().get(rowInfoIndex);
672             ColumnInfo columnInfo = (ColumnInfo) rowInfo.getColumnInfos().get(columnInfoIndex);
673 
674             if (!columnInfo.isRendered()) {
675                 return;
676             }
677 
678             if (component instanceof HtmlColumn && amISpannedOver(null, component)) {
679                 return;
680             }
681             writer.startElement(HTML.TD_ELEM, uiData);
682             String styleClass = null;
683             if (component instanceof HtmlColumn) {
684                 styleClass = ((HtmlColumn) component).getStyleClass();
685             }
686             if (columnInfo.getStyleClass() != null) {
687                 styleClass = columnInfo.getStyleClass();
688             }
689 
690             if (styles.hasColumnStyle()) {
691                 if (styleClass == null) {
692                     styleClass = styles.getColumnStyle(columnStyleIndex);
693                 }
694             }
695             if (styleClass != null) {
696                 writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
697             }
698 
699             if (columnInfo.getStyle() != null) {
700                 writer.writeAttribute(HTML.STYLE_ATTR, columnInfo.getStyle(), null);
701             }
702 
703             if (columnInfo.getRowSpan() > 1) {
704                 writer.writeAttribute("rowspan", new Integer(columnInfo.getRowSpan()).toString(), null);
705                 if (columnInfo.getStyle() == null) {
706                     writer.writeAttribute(HTML.STYLE_ATTR, "vertical-align:top", null);
707                 }
708             }
709 
710             renderHtmlColumnAttributes(writer, component, null);
711 
712             RendererUtils.renderChild(facesContext, component);
713             writer.endElement(HTML.TD_ELEM);
714         }
715         else if (component instanceof HtmlColumn) {
716             if (amISpannedOver(null, component)) {
717                 return;
718             }
719             writer.startElement(HTML.TD_ELEM, uiData);
720             String styleClass = ((HtmlColumn) component).getStyleClass();
721             if (styles.hasColumnStyle()) {
722                 if (styleClass == null) {
723                     styleClass = styles.getColumnStyle(columnStyleIndex);
724                 }
725             }
726             if (styleClass != null) {
727                 writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
728             }
729             renderHtmlColumnAttributes(writer, component, null);
730 
731             RendererUtils.renderChild(facesContext, component);
732             writer.endElement(HTML.TD_ELEM);
733         }
734         else {
735             super.renderColumnBody(facesContext, writer, uiData, component,
736                 styles, columnStyleIndex);
737         }
738     }
739 
740     /**
741      * Render the header or footer of the specified column object.
742      * <p/>
743      * When the component is a UIColumn object, the inherited method is
744      * invoked to render a single header cell.
745      * <p/>
746      * In addition to the inherited functionality, support is implemented
747      * here for UIColumns children. When a UIColumns child is encountered:
748      * <pre>
749      * For each dynamic column in that UIColumns child:
750      *   * Select the column (which sets variable named by the var attribute
751      *     to refer to the current column object)
752      *   * Call this.renderColumnHeaderCell or this.renderColumnFooterCell
753      *     passing the header or footer facet of the UIColumns object.
754      * </pre>
755      * If the facet of the UIColumns accesses the variable named by the var
756      * attribute on the UIColumns object, then it ends up rendering content
757      * that is extracted from the current column object.
758      *
759      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderColumnChildHeaderOrFooterRow(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIComponent, java.lang.String, boolean)
760      */
761     protected void renderColumnChildHeaderOrFooterRow(
762         FacesContext facesContext, ResponseWriter writer,
763         UIComponent uiComponent, String styleClass, boolean header)
764         throws IOException {
765         super.renderColumnChildHeaderOrFooterRow(facesContext, writer,
766             uiComponent, styleClass, header);
767         if (uiComponent instanceof UIColumns) {
768             UIColumns columns = (UIColumns) uiComponent;
769             for (int i = 0, size = columns.getRowCount(); i < size; i++) {
770                 columns.setRowIndex(i);
771                 if (header) {
772                     renderColumnHeaderCell(facesContext, writer, columns,
773                         columns.getHeader(), styleClass, 0);
774                 }
775                 else {
776                     renderColumnFooterCell(facesContext, writer, columns,
777                         columns.getFooter(), styleClass, 0);
778                 }
779             }
780             columns.setRowIndex(-1);
781         }
782     }
783 
784     /**
785      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderColumnHeaderCell(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIComponent, javax.faces.component.UIComponent, java.lang.String, int)
786      */
787     protected void renderColumnHeaderCell(FacesContext facesContext,
788                                           ResponseWriter writer, UIComponent uiComponent,
789                                           UIComponent facet, String headerStyleClass, int colspan)
790         throws IOException {
791         if (uiComponent instanceof HtmlColumn) {
792 
793             HtmlColumn column = (HtmlColumn) uiComponent;
794 
795             if (amISpannedOver("header", uiComponent)) {
796                 return;
797             }
798 
799             writer.startElement(determineHeaderCellTag(facesContext, uiComponent.getParent()), uiComponent);
800 
801             String columnId = column.getColumnId();
802             if (columnId != null)
803             {
804                 writer.writeAttribute(HTML.ID_ATTR, columnId, null);
805             }
806 
807             if (colspan > 1) {
808                 writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(colspan),
809                     null);
810             }
811             String styleClass = ((HtmlColumn) uiComponent)
812                 .getHeaderstyleClass();
813             if (styleClass == null) {
814                 styleClass = headerStyleClass;
815             }
816             if (styleClass != null) {
817                 writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
818             }
819             renderHtmlColumnAttributes(writer, uiComponent, "header");
820             if (facet != null) {
821                 RendererUtils.renderChild(facesContext, facet);
822             }
823             writer.endElement(determineHeaderCellTag(facesContext, uiComponent.getParent()));
824         }
825         else {
826             super.renderColumnHeaderCell(facesContext, writer, uiComponent,
827                 facet, headerStyleClass, colspan);
828         }
829     }
830 
831     /**
832      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderColumnFooterCell(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIComponent, javax.faces.component.UIComponent, java.lang.String, int)
833      */
834     protected void renderColumnFooterCell(FacesContext facesContext,
835                                           ResponseWriter writer, UIComponent uiComponent,
836                                           UIComponent facet, String footerStyleClass, int colspan)
837         throws IOException {
838         if (uiComponent instanceof HtmlColumn) {
839             if (amISpannedOver("footer", uiComponent)) {
840                 return;
841             }
842             writer.startElement(HTML.TD_ELEM, uiComponent);
843             if (colspan > 1) {
844                 writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(colspan),
845                     null);
846             }
847             String styleClass = ((HtmlColumn) uiComponent)
848                 .getFooterstyleClass();
849             if (styleClass == null) {
850                 styleClass = footerStyleClass;
851             }
852             if (styleClass != null) {
853                 writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
854             }
855             renderHtmlColumnAttributes(writer, uiComponent, "footer");
856             if (facet != null) {
857                 RendererUtils.renderChild(facesContext, facet);
858             }
859             writer.endElement(HTML.TD_ELEM);
860         }
861         else {
862             super.renderColumnFooterCell(facesContext, writer, uiComponent,
863                 facet, footerStyleClass, colspan);
864         }
865     }
866 
867     protected void renderHtmlColumnAttributes(ResponseWriter writer,
868                                               UIComponent uiComponent, String prefix) throws IOException {
869         String[] attrs = (String[]) ArrayUtils.concat(HTML.COMMON_PASSTROUGH_ATTRIBUTES_WITHOUT_STYLE, new String[]{HTML.COLSPAN_ATTR});
870         for (int i = 0, size = attrs.length; i < size; i++) {
871             String attributeName = attrs[i];
872             String compAttrName = prefix != null ? prefix + attributeName : attributeName;
873             HtmlRendererUtils.renderHTMLAttribute(writer, uiComponent,
874                 compAttrName, attributeName);
875         }
876         String compAttrName = prefix != null ? prefix + HTML.STYLE_ATTR : HTML.STYLE_ATTR;
877         HtmlRendererUtils.renderHTMLAttribute(writer, uiComponent,
878             compAttrName, HTML.STYLE_ATTR);
879 
880         HtmlRendererUtils.renderHTMLAttribute(writer, uiComponent,
881             HTML.WIDTH_ATTR, HTML.WIDTH_ATTR);
882     }
883 
884     /**
885      * Return the number of columns spanned by the specified component.
886      * <p/>
887      * For normal components, use the inherited implementation.
888      * For UIColumns children, return the number of dynamic columns rendered
889      * by that child.
890      *
891      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase
892      *      #determineChildColSpan(javax.faces.component.UIComponent)
893      */
894     protected int determineChildColSpan(UIComponent uiComponent) {
895         int result = super.determineChildColSpan(uiComponent);
896         if (uiComponent instanceof UIColumns) {
897             result += ((UIColumns) uiComponent).getRowCount();
898         }
899         return result;
900     }
901 
902     /**
903      * Return true if the specified component has a facet that needs to be
904      * rendered in a THEAD or TFOOT section.
905      *
906      * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#hasFacet(boolean, javax.faces.component.UIComponent)
907      */
908     protected boolean hasFacet(boolean header, UIComponent uiComponent) {
909         boolean result = super.hasFacet(header, uiComponent);
910         if (!result && uiComponent instanceof UIColumns) {
911             // Why is this necessary? It seems to me that the inherited
912             // implementation will work fine with a UIColumns component...
913             UIColumns columns = (UIColumns) uiComponent;
914             result = header ? columns.getHeader() != null : columns.getFooter() != null;
915         }
916         return result;
917     }
918 
919     /**
920      * Renders the column footer.
921      * Rendering will be supressed if all of the facets have rendered="false"
922      */
923     protected void renderColumnFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component, String footerStyleClass) throws IOException {
924         if (determineRenderFacet(component, false)) {
925             super.renderColumnFooterRow(facesContext, writer, component, footerStyleClass);
926         }
927     }
928 
929     /**
930      * Renders the column header.
931      * Rendering will be supressed if all of the facets have rendered="false"
932      */
933     protected void renderColumnHeaderRow(FacesContext facesContext, ResponseWriter writer, UIComponent component, String headerStyleClass) throws IOException {
934         if (determineRenderFacet(component, true)) {
935             super.renderColumnHeaderRow(facesContext, writer, component, headerStyleClass);
936         }
937     }
938 
939     /**
940      * determine if the header or footer should be rendered.
941      */
942     protected boolean determineRenderFacet(UIComponent component, boolean header) {
943         for (Iterator it = getChildren(component).iterator(); it.hasNext();) {
944             UIComponent uiComponent = (UIComponent) it.next();
945             if (uiComponent.isRendered() && determineChildColSpan(uiComponent) > 0) {
946                 UIComponent facet = header ? (UIComponent) uiComponent.getFacets().get(HEADER_FACET_NAME)
947                     : (UIComponent) uiComponent.getFacets().get(FOOTER_FACET_NAME);
948 
949                 if (facet != null && facet.isRendered()) {
950                     return true;
951                 }
952             }
953         }
954 
955         return false;
956     }
957 
958     protected void beforeColumn(FacesContext facesContext, UIData uiData, int columnIndex) throws IOException {
959         super.beforeColumn(facesContext, uiData, columnIndex);
960 
961         if (uiData instanceof HtmlDataTable) {
962             HtmlDataTable dataTable = (HtmlDataTable) uiData;
963 
964             putSortedReqScopeParam(facesContext, dataTable, columnIndex);
965         }
966     }
967 
968     protected void beforeColumnHeaderOrFooter(FacesContext facesContext, UIData uiData, boolean header, int columnIndex) throws IOException {
969         super.beforeColumnHeaderOrFooter(facesContext, uiData, header, columnIndex);
970 
971         if (header && uiData instanceof HtmlDataTable) {
972             HtmlDataTable dataTable = (HtmlDataTable) uiData;
973 
974             putSortedReqScopeParam(facesContext, dataTable, columnIndex);
975         }
976     }
977 
978     protected void putSortedReqScopeParam(FacesContext facesContext, HtmlDataTable dataTable, int columnIndex) {
979         String sortedColumnVar = dataTable.getSortedColumnVar();
980         Map requestMap = facesContext.getExternalContext().getRequestMap();
981 
982         if (columnIndex == dataTable.getSortColumnIndex()) {
983             if (sortedColumnVar != null) {
984                 requestMap.put(sortedColumnVar, Boolean.TRUE);
985             }
986         }
987         else if (sortedColumnVar != null) {
988             requestMap.remove(sortedColumnVar);
989         }
990     }
991 
992     /**
993      * specify if the header, footer or <td> is spanned over (not shown) because
994      * of a colspan in a cell in a previous column
995      *
996      * @param prefix    header, footer or null
997      * @param component
998      */
999     protected boolean amISpannedOver(String prefix, UIComponent component) {
1000         UIComponent table = component.getParent();
1001         int span = 0;
1002         for (Iterator it = table.getChildren().iterator(); it.hasNext();) {
1003             UIComponent columnComponent = (UIComponent) it.next();
1004             if (!(columnComponent instanceof HtmlColumn)) {
1005                 continue;
1006             }
1007             if (span > 0) {
1008                 span--;
1009             }
1010             if (columnComponent == component) {
1011                 return span > 0;
1012             }
1013             if (span == 0) {
1014                 try {
1015                     if (prefix == null && ((HtmlColumn) columnComponent).getColspan() != null) {
1016                         span = Integer.parseInt(((HtmlColumn) columnComponent).getColspan());
1017                     }
1018                     if ("header".equals(prefix) && ((HtmlColumn) columnComponent).getHeadercolspan() != null) {
1019                         span = Integer.parseInt(((HtmlColumn) columnComponent).getHeadercolspan());
1020                     }
1021                     if ("footer".equals(prefix) && ((HtmlColumn) columnComponent).getFootercolspan() != null) {
1022                         span = Integer.parseInt(((HtmlColumn) columnComponent).getFootercolspan());
1023                     }
1024                 }
1025                 catch (NumberFormatException ex) {
1026                     log.warn("Invalid " + ((prefix == null) ? "" : prefix) + "colspan attribute ignored");
1027                 }
1028             }
1029         }
1030         return false;
1031     }
1032 
1033 
1034     /**
1035      * Perform any operations necessary in the TBODY start tag.
1036      *
1037      * @param facesContext the <code>FacesContext</code>.
1038      * @param uiData       the <code>UIData</code> being rendered.
1039      */
1040     protected void inBodyStart(FacesContext facesContext, UIData uiData) throws IOException {
1041         String bodyStyleClass;
1042         String bodyStyle;
1043 
1044         if (uiData instanceof HtmlDataTable) {
1045             bodyStyleClass = ((HtmlDataTable) uiData).getBodyStyleClass();
1046             bodyStyle = ((HtmlDataTable) uiData).getBodyStyle();
1047         }
1048         else {
1049             bodyStyleClass = (String) uiData.getAttributes().get(BODY_STYLE_CLASS);
1050             bodyStyle = (String) uiData.getAttributes().get(BODY_STYLE);
1051         }
1052 
1053         ResponseWriter writer = facesContext.getResponseWriter();
1054         if (bodyStyleClass != null) {
1055             writer.writeAttribute(HTML.CLASS_ATTR, bodyStyleClass,
1056                 BODY_STYLE_CLASS);
1057         }
1058         if (bodyStyle != null) {
1059             writer.writeAttribute(HTML.STYLE_ATTR, bodyStyle, BODY_STYLE);
1060         }
1061     }
1062 
1063 
1064 }