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.custom.newspaper;
20  
21  import javax.faces.component.UIComponent;
22  import javax.faces.context.FacesContext;
23  import javax.faces.component.html.HtmlDataTable;
24  import javax.faces.context.ResponseWriter;
25  import javax.faces.component.UIColumn;
26  import javax.faces.component.UIData;
27  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
28  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
29  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
30  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
31  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase;
32  import org.apache.myfaces.shared_tomahawk.util.ArrayUtils;
33  import org.apache.myfaces.shared_tomahawk.util.StringUtils;
34  
35  import java.io.IOException;
36  import java.util.Iterator;
37  import java.util.List;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  
41  /**
42   * Renderer for a table in multiple balanced columns.
43   *
44   * @JSFRenderer
45   *   renderKitId = "HTML_BASIC" 
46   *   family = "javax.faces.Data"
47   *   type = "org.apache.myfaces.HtmlNewspaperTable"
48   *
49   * @author <a href="mailto:jesse@odel.on.ca">Jesse Wilson</a>
50   */
51  public class HtmlNewspaperTableRenderer
52          extends HtmlTableRendererBase
53  {
54      private static final Log log = LogFactory.getLog(HtmlNewspaperTableRenderer.class);
55  
56      public void encodeBegin(FacesContext facesContext, UIComponent uiComponent) throws IOException {
57          RendererUtils.checkParamValidity(facesContext, uiComponent, UIData.class);
58          ResponseWriter writer = facesContext.getResponseWriter();
59          HtmlNewspaperTable newspaperTable = (HtmlNewspaperTable)uiComponent;
60          
61          // write out the table start tag
62          HtmlRendererUtils.writePrettyLineSeparator(facesContext);
63          writer.startElement(HTML.TABLE_ELEM, newspaperTable);
64          writer.writeAttribute(HTML.ID_ATTR, newspaperTable.getClientId(facesContext), null);
65          HtmlRendererUtils.renderHTMLAttributes(writer, newspaperTable, HTML.TABLE_PASSTHROUGH_ATTRIBUTES);
66  
67          // render the header
68          renderFacet(facesContext, writer, newspaperTable, true);
69      }
70      
71      public void encodeChildren(FacesContext facesContext, UIComponent uiComponent) throws IOException {
72          RendererUtils.checkParamValidity(facesContext, uiComponent, UIData.class);
73          ResponseWriter writer = facesContext.getResponseWriter();
74          HtmlNewspaperTable newspaperTable = (HtmlNewspaperTable)uiComponent;
75          
76          // begin the table
77          HtmlRendererUtils.writePrettyLineSeparator(facesContext);
78          writer.startElement(HTML.TBODY_ELEM, newspaperTable);
79          
80          // get the CSS styles
81          Styles styles = getStyles(newspaperTable);
82  
83          // count the number of actual rows
84          int first = newspaperTable.getFirst();
85          int rows = newspaperTable.getRows();
86          int rowCount = newspaperTable.getRowCount();
87          if(rows <= 0) {
88              rows = rowCount - first;
89          }
90          int last = first + rows;
91          if(last > rowCount) last = rowCount;
92          
93          // the number of slices to break the table up into */
94          int newspaperColumns = newspaperTable.getNewspaperColumns();
95          int newspaperRows;
96          if((last - first) % newspaperColumns == 0) newspaperRows = (last - first) / newspaperColumns;
97          else newspaperRows = ((last - first) / newspaperColumns) + 1;
98  
99          // walk through the newspaper rows
100         for(int nr = 0; nr < newspaperRows; nr++) {
101 
102             // write the row start
103             HtmlRendererUtils.writePrettyLineSeparator(facesContext);
104             writer.startElement(HTML.TR_ELEM, newspaperTable);
105             if(styles.hasRowStyle()) {
106                 String rowStyle = styles.getRowStyle(nr);
107                 writer.writeAttribute(HTML.CLASS_ATTR, rowStyle, null);
108             }
109 
110             // walk through the newspaper columns
111             for(int nc = 0; nc < newspaperColumns; nc++) {
112 
113                 // the current row in the 'real' table
114                 int currentRow = nc * newspaperRows + nr + first;
115                 
116                 // if this row is not to be rendered
117                 if(currentRow >= last) continue;
118 
119                 // bail if any row does not exist
120                 newspaperTable.setRowIndex(currentRow);
121                 if(!newspaperTable.isRowAvailable()) {
122                     log.error("Row is not available. Rowindex = " + currentRow);
123                     return;
124                 }
125     
126                 // write each cell
127                 List children = newspaperTable.getChildren();
128                 for(int j = 0; j < newspaperTable.getChildCount(); j++) {
129                     // skip this child if its not a rendered column 
130                     UIComponent child = (UIComponent)children.get(j);
131                     if(!(child instanceof UIColumn)) continue;
132                     if(!child.isRendered()) continue;
133                     // draw the element's cell
134                     writer.startElement(HTML.TD_ELEM, newspaperTable);
135                     if(styles.hasColumnStyle()) writer.writeAttribute(HTML.CLASS_ATTR, styles.getColumnStyle(nc * newspaperTable.getChildCount() + j), null);
136                     RendererUtils.renderChild(facesContext, child);
137                     writer.endElement(HTML.TD_ELEM);
138                 }
139 
140                 // draw the spacer facet
141                 if(nc < newspaperColumns - 1) renderSpacerCell(facesContext, writer, newspaperTable);
142             }
143             // write the row end
144             writer.endElement(HTML.TR_ELEM);
145         }
146         
147         // write the end of the table's body
148         writer.endElement(HTML.TBODY_ELEM);
149     }
150     
151     /** 
152      * Fetch the number of columns to divide the table into.
153      */
154     private int getNewspaperColumns() {
155         return 3;
156     }
157 
158     public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
159         RendererUtils.checkParamValidity(facesContext, uiComponent, UIData.class);
160         ResponseWriter writer = facesContext.getResponseWriter();
161         HtmlNewspaperTable newspaperTable = (HtmlNewspaperTable)uiComponent;
162         
163         // render the footer
164         renderFacet(facesContext, writer, newspaperTable, false);
165         
166         // write out the table end tag
167         writer.endElement(HTML.TABLE_ELEM);
168         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
169     }
170 
171     
172     /**
173      * Count the number of columns in the speicifed Newspaper table..
174      */
175     private int countColumns(HtmlNewspaperTable newspaperTable) {
176         int columnCount = 0;
177         for(Iterator it = newspaperTable.getChildren().iterator(); it.hasNext(); ) {
178             UIComponent uiComponent = (UIComponent)it.next();
179             if (uiComponent instanceof UIColumn && ((UIColumn)uiComponent).isRendered()) {
180                 columnCount++;
181             }
182         }
183         return columnCount;
184     }
185     
186     /**
187      * Tests if the specified facet exists for the specified newspaper table.
188      */
189     private boolean hasFacet(HtmlNewspaperTable newspaperTable, boolean header) {
190         for(Iterator it = newspaperTable.getChildren().iterator(); it.hasNext(); ) {
191             // get the column
192             UIComponent uiComponent = (UIComponent)it.next();
193             if(!(uiComponent instanceof UIColumn)) continue;
194             UIColumn column = (UIColumn)uiComponent;
195             if(!column.isRendered()) continue;
196             
197             // test the facet
198             if(header && ((UIColumn)uiComponent).getHeader() != null) return true;
199             if(!header && ((UIColumn)uiComponent).getFooter() != null) return true;
200         }
201         return false;
202     }
203 
204     /**
205      * Render table headers and footers.
206      */
207     private void renderFacet(FacesContext facesContext, ResponseWriter writer, HtmlNewspaperTable newspaperTable, boolean header) throws IOException {
208         int columnCount = countColumns(newspaperTable);
209         boolean hasColumnFacet = hasFacet(newspaperTable, header);
210         UIComponent facet = header ? newspaperTable.getHeader() : newspaperTable.getFooter();
211         
212         // quit if there's nothing to draw
213         if(facet == null && !hasColumnFacet) return;
214         
215         // start the row block
216         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
217         String elemName = header ? HTML.THEAD_ELEM : HTML.TFOOT_ELEM;
218         writer.startElement(elemName, newspaperTable);
219         
220         // fetch the style
221         String styleClass;
222         if(header) styleClass = getHeaderClass(newspaperTable);
223         else styleClass = getFooterClass(newspaperTable);
224         
225         // write the header row and column headers
226         if(header) {
227             if (facet != null) renderTableHeaderOrFooterRow(facesContext, writer, newspaperTable, facet, styleClass, HTML.TD_ELEM, columnCount);
228             if (hasColumnFacet) renderColumnHeaderOrFooterRow(facesContext, writer, newspaperTable, styleClass, header);
229         // write the footer row and column footers
230         } else {
231             if (hasColumnFacet) renderColumnHeaderOrFooterRow(facesContext, writer, newspaperTable, styleClass, header);
232             if (facet != null) renderTableHeaderOrFooterRow(facesContext, writer, newspaperTable, facet, styleClass, HTML.TD_ELEM, columnCount);
233         }
234         
235         // end the row block
236         writer.endElement(elemName);
237     }
238     
239     /**
240      * Renders the table header or footer row. This is one giant cell that spans
241      * the entire table header or footer.
242      */
243     private void renderTableHeaderOrFooterRow(FacesContext facesContext, ResponseWriter writer,
244         HtmlNewspaperTable newspaperTable, UIComponent facet, String styleClass, String colElementName, int tableColumns)
245         throws IOException {
246 
247         // start the row
248         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
249         writer.startElement(HTML.TR_ELEM, newspaperTable);
250         writer.startElement(colElementName, newspaperTable);
251         if(styleClass != null) writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
252         if(colElementName.equals(HTML.TH_ELEM)) writer.writeAttribute(HTML.SCOPE_ATTR, HTML.SCOPE_COLGROUP_VALUE, null);
253 
254         // span all the table's columns
255         int totalColumns = newspaperTable.getNewspaperColumns() * tableColumns;
256         if(newspaperTable.getSpacer() != null) totalColumns = totalColumns + getNewspaperColumns() - 1;
257         writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(totalColumns), null);
258 
259         // write the actual cell contents
260         if(facet != null) RendererUtils.renderChild(facesContext, facet);
261         
262         // finish
263         writer.endElement(colElementName);
264         writer.endElement(HTML.TR_ELEM);
265     }
266 
267 
268     /**
269      * Renders the column header or footer row.
270      */
271     private void renderColumnHeaderOrFooterRow(FacesContext facesContext,
272         ResponseWriter writer, HtmlNewspaperTable newspaperTable, String styleClass, boolean header)
273         throws IOException {
274 
275         HtmlRendererUtils.writePrettyLineSeparator(facesContext);
276         writer.startElement(HTML.TR_ELEM, newspaperTable);
277         int newspaperColumns = newspaperTable.getNewspaperColumns();
278         for(int nc = 0; nc < newspaperColumns; nc++) {
279             for(Iterator it = newspaperTable.getChildren().iterator(); it.hasNext(); ) {
280                 UIComponent uiComponent = (UIComponent)it.next();
281                 if(!(uiComponent instanceof UIColumn)) continue;
282                 UIColumn column = (UIColumn)uiComponent;
283                 if(!column.isRendered()) continue;
284                 // get the component to render
285                 UIComponent facet = null;
286                 if(header) facet = column.getHeader();
287                 else facet = column.getFooter();
288                 // do the rendering of the cells
289                 renderColumnHeaderOrFooterCell(facesContext, writer, column, styleClass, facet);
290             }
291 
292             // draw the spacer facet
293             if(nc < newspaperColumns - 1) renderSpacerCell(facesContext, writer, newspaperTable);
294         }
295         writer.endElement(HTML.TR_ELEM);
296     }
297 
298     /**
299      * Renders a cell in the column header or footer.
300      */
301     private void renderColumnHeaderOrFooterCell(FacesContext facesContext, ResponseWriter writer,
302         UIColumn uiColumn, String styleClass, UIComponent facet) throws IOException {
303 
304         writer.startElement(HTML.TH_ELEM, uiColumn);
305         if(styleClass != null) writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
306         if(facet != null) RendererUtils.renderChild(facesContext, facet);
307         writer.endElement(HTML.TH_ELEM);
308     }
309     
310     /**
311      * Renders a spacer between adjacent newspaper columns.
312      */
313     private void renderSpacerCell(FacesContext facesContext, ResponseWriter writer,
314         HtmlNewspaperTable newspaperTable) throws IOException {
315         if(newspaperTable.getSpacer() == null) return;
316         
317         writer.startElement(HTML.TD_ELEM, newspaperTable);
318         RendererUtils.renderChild(facesContext, newspaperTable.getSpacer());
319         writer.endElement(HTML.TD_ELEM);
320     }
321 
322     /**
323      * Gets the style class for the table header.
324      */
325     private static String getHeaderClass(HtmlNewspaperTable newspaperTable) {
326         if(newspaperTable instanceof HtmlDataTable) {
327             return ((HtmlDataTable)newspaperTable).getHeaderClass();
328         } else {
329             return (String)newspaperTable.getAttributes().get(JSFAttr.HEADER_CLASS_ATTR);
330         }
331     }
332     /**
333      * Gets the style class for the table footer.
334      */
335     private static String getFooterClass(HtmlNewspaperTable newspaperTable) {
336         if(newspaperTable instanceof HtmlDataTable) {
337             return ((HtmlDataTable)newspaperTable).getFooterClass();
338         } else {
339             return (String)newspaperTable.getAttributes().get(JSFAttr.FOOTER_CLASS_ATTR);
340         }
341     }
342 
343     /**
344      * Gets styles for the specified component.
345      */
346     public static Styles getStyles(HtmlNewspaperTable newspaperTable) {
347         String rowClasses;
348         String columnClasses;
349         if(newspaperTable instanceof HtmlDataTable) {
350             rowClasses = ((HtmlDataTable)newspaperTable).getRowClasses();
351             columnClasses = ((HtmlDataTable)newspaperTable).getColumnClasses();
352         } else {
353             rowClasses = (String)newspaperTable.getAttributes().get(JSFAttr.ROW_CLASSES_ATTR);
354             columnClasses = (String)newspaperTable.getAttributes().get(JSFAttr.COLUMN_CLASSES_ATTR);
355         }
356         return new Styles(rowClasses, columnClasses);
357     }
358 
359     /**
360      * Class manages the styles from String lists.
361      */
362     private static class Styles {
363 
364         private String[] _columnStyle;
365         private String[] _rowStyle;
366 
367         Styles(String rowStyles, String columnStyles) {
368             _rowStyle = (rowStyles == null)
369                 ? ArrayUtils.EMPTY_STRING_ARRAY
370                 : StringUtils.trim(
371                     StringUtils.splitShortString(rowStyles, ','));
372             _columnStyle = (columnStyles == null)
373                 ? ArrayUtils.EMPTY_STRING_ARRAY
374                 : StringUtils.trim(
375                     StringUtils.splitShortString(columnStyles, ','));
376         }
377 
378         public String getRowStyle(int idx) {
379             if(!hasRowStyle()) {
380                 return null;
381             }
382             return _rowStyle[idx % _rowStyle.length];
383         }
384 
385         public String getColumnStyle(int idx) {
386             if(!hasColumnStyle()) {
387                 return null;
388             }
389             return _columnStyle[idx % _columnStyle.length];
390         }
391 
392         public boolean hasRowStyle() {
393             return _rowStyle.length > 0;
394         }
395 
396         public boolean hasColumnStyle() {
397             return _columnStyle.length > 0;
398         }
399     }
400 }