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