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.tobago.renderkit.html.scarborough.standard.tag;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.ComponentTypes;
24  import org.apache.myfaces.tobago.component.Facets;
25  import org.apache.myfaces.tobago.component.RendererTypes;
26  import org.apache.myfaces.tobago.component.SupportsMarkup;
27  import org.apache.myfaces.tobago.component.UIColumnSelector;
28  import org.apache.myfaces.tobago.component.UICommand;
29  import org.apache.myfaces.tobago.component.UILink;
30  import org.apache.myfaces.tobago.component.UIMenu;
31  import org.apache.myfaces.tobago.component.UIMenuCommand;
32  import org.apache.myfaces.tobago.component.UIOut;
33  import org.apache.myfaces.tobago.component.UIReload;
34  import org.apache.myfaces.tobago.component.UISheet;
35  import org.apache.myfaces.tobago.component.UIToolBar;
36  import org.apache.myfaces.tobago.config.Configurable;
37  import org.apache.myfaces.tobago.context.ClientProperties;
38  import org.apache.myfaces.tobago.context.Markup;
39  import org.apache.myfaces.tobago.context.ResourceManager;
40  import org.apache.myfaces.tobago.context.ResourceManagerUtils;
41  import org.apache.myfaces.tobago.event.PageAction;
42  import org.apache.myfaces.tobago.internal.component.AbstractUIColumn;
43  import org.apache.myfaces.tobago.internal.component.AbstractUIColumnNode;
44  import org.apache.myfaces.tobago.internal.component.AbstractUIData;
45  import org.apache.myfaces.tobago.internal.component.AbstractUIMenu;
46  import org.apache.myfaces.tobago.internal.component.AbstractUIOut;
47  import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
48  import org.apache.myfaces.tobago.internal.component.AbstractUISheetLayout;
49  import org.apache.myfaces.tobago.internal.context.ResourceManagerFactory;
50  import org.apache.myfaces.tobago.internal.layout.Cell;
51  import org.apache.myfaces.tobago.internal.layout.Grid;
52  import org.apache.myfaces.tobago.internal.layout.OriginCell;
53  import org.apache.myfaces.tobago.internal.util.StringUtils;
54  import org.apache.myfaces.tobago.layout.Display;
55  import org.apache.myfaces.tobago.layout.LayoutBase;
56  import org.apache.myfaces.tobago.layout.Measure;
57  import org.apache.myfaces.tobago.layout.TextAlign;
58  import org.apache.myfaces.tobago.model.ExpandedState;
59  import org.apache.myfaces.tobago.model.SheetState;
60  import org.apache.myfaces.tobago.model.TreePath;
61  import org.apache.myfaces.tobago.renderkit.LayoutComponentRendererBase;
62  import org.apache.myfaces.tobago.renderkit.css.Classes;
63  import org.apache.myfaces.tobago.renderkit.css.Style;
64  import org.apache.myfaces.tobago.renderkit.html.Command;
65  import org.apache.myfaces.tobago.renderkit.html.CommandMap;
66  import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
67  import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
68  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
69  import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
70  import org.apache.myfaces.tobago.renderkit.html.JsonUtils;
71  import org.apache.myfaces.tobago.renderkit.html.util.HtmlRendererUtils;
72  import org.apache.myfaces.tobago.renderkit.util.RenderUtils;
73  import org.apache.myfaces.tobago.util.ComponentUtils;
74  import org.apache.myfaces.tobago.util.CreateComponentUtils;
75  import org.apache.myfaces.tobago.util.FacetUtils;
76  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
77  import org.slf4j.Logger;
78  import org.slf4j.LoggerFactory;
79  
80  import javax.el.ValueExpression;
81  import javax.faces.application.Application;
82  import javax.faces.component.UIColumn;
83  import javax.faces.component.UIComponent;
84  import javax.faces.component.UISelectOne;
85  import javax.faces.context.FacesContext;
86  import java.io.IOException;
87  import java.text.MessageFormat;
88  import java.util.ArrayList;
89  import java.util.Collections;
90  import java.util.List;
91  import java.util.Locale;
92  import java.util.Map;
93  
94  public class SheetRenderer extends LayoutComponentRendererBase {
95  
96    private static final Logger LOG = LoggerFactory.getLogger(SheetRenderer.class);
97  
98    public static final String WIDTHS_POSTFIX = ComponentUtils.SUB_SEPARATOR + "widths";
99    public static final String SELECTED_POSTFIX = ComponentUtils.SUB_SEPARATOR + "selected";
100 
101   private static final Integer HEIGHT_0 = 0;
102 
103   @Override
104   public void prepareRender(FacesContext facesContext, UIComponent component) throws IOException {
105     super.prepareRender(facesContext, component);
106     ensureHeader(facesContext, (UISheet) component);
107   }
108 
109   private void ensureHeader(FacesContext facesContext, UISheet sheet) {
110     UIComponent header = sheet.getHeader();
111     if (header == null) {
112       header = CreateComponentUtils.createComponent(facesContext, ComponentTypes.PANEL, null, "_header");
113       header.setTransient(true);
114       final List<AbstractUIColumn> columns = ComponentUtils.findDescendantList(sheet, AbstractUIColumn.class);
115       int i = 0;
116       for (AbstractUIColumn column : columns) {
117         final AbstractUIOut out = (AbstractUIOut) CreateComponentUtils.createComponent(
118             facesContext, ComponentTypes.OUT, RendererTypes.OUT, "_col" + i);
119         out.setTransient(true);
120 //        out.setValue(column.getLabel());
121         ValueExpression valueExpression = column.getValueExpression(Attributes.LABEL);
122         if (valueExpression != null) {
123           out.setValueExpression(Attributes.VALUE, valueExpression);
124         } else {
125           out.setValue(column.getAttributes().get(Attributes.LABEL));
126         }
127         valueExpression = column.getValueExpression(Attributes.RENDERED);
128         if (valueExpression != null) {
129           out.setValueExpression(Attributes.RENDERED, valueExpression);
130         } else {
131           out.setRendered((Boolean) column.getAttributes().get(Attributes.RENDERED));
132         }
133         header.getChildren().add(out);
134         i++;
135       }
136       sheet.setHeader(header);
137     }
138   }
139 
140   @Override
141   public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
142 
143     UISheet sheet = (UISheet) uiComponent;
144 
145     Style style = new Style(facesContext, sheet);
146 
147     final String sheetId = sheet.getClientId(facesContext);
148 
149     TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
150 
151     // Outer sheet div
152     writer.startElement(HtmlElements.DIV, sheet);
153     writer.writeIdAttribute(sheetId);
154     HtmlRendererUtils.writeDataAttributes(facesContext, writer, sheet);
155     writer.writeClassAttribute(Classes.create(sheet));
156     writer.writeStyleAttribute(style);
157     UIComponent facetReload = sheet.getFacet(Facets.RELOAD);
158     if (facetReload != null && facetReload instanceof UIReload && facetReload.isRendered()) {
159       UIReload update = (UIReload) facetReload;
160       writer.writeAttribute(DataAttributes.RELOAD, update.getFrequency());
161     }
162 
163     writer.writeAttribute(DataAttributes.PARTIALLY,
164         HtmlRendererUtils.getRenderedPartiallyJavascriptArray(facesContext, sheet, sheet), false);
165     writer.writeAttribute(DataAttributes.SELECTION_MODE, sheet.getSelectable(), false);
166     writer.writeAttribute(DataAttributes.FIRST, Integer.toString(sheet.getFirst()), false);
167 
168     boolean rowAction = HtmlRendererUtils.renderSheetCommands(sheet, facesContext, writer);
169 
170     renderSheet(facesContext, sheet, rowAction, style);
171 
172     writer.endElement(HtmlElements.DIV);
173   }
174 
175   private void renderSheet(FacesContext facesContext, UISheet sheet, boolean hasClickAction, Style style)
176       throws IOException {
177     final TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
178     final ResourceManager resourceManager = ResourceManagerFactory.getResourceManager(facesContext);
179     final String contextPath = facesContext.getExternalContext().getRequestContextPath();
180     final String sheetId = sheet.getClientId(facesContext);
181 
182     Measure sheetHeight;
183     if (style.getHeight() == null) {
184       // FIXME: nullpointer if height not defined
185       LOG.error("no height in parent container, setting to 100");
186       sheetHeight = Measure.valueOf(100);
187     } else {
188       sheetHeight = style.getHeight();
189     }
190     final Measure footerHeight = getFooterHeight(facesContext, sheet);
191     final Measure headerHeight = getHeaderHeight(facesContext, sheet);
192     final String selectable = sheet.getSelectable();
193 
194     final Application application = facesContext.getApplication();
195     final SheetState state = sheet.getSheetState(facesContext);
196     final List<Integer> columnWidths = sheet.getWidthList();
197 
198     final List<Integer> selectedRows = getSelectedRows(sheet, state);
199     final List<AbstractUIColumn> renderedColumnList = sheet.getRenderedColumns();
200 
201     writer.startElement(HtmlElements.INPUT, null);
202     writer.writeIdAttribute(sheetId + WIDTHS_POSTFIX);
203     writer.writeNameAttribute(sheetId + WIDTHS_POSTFIX);
204     writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN, false);
205     writer.writeAttribute(HtmlAttributes.VALUE, StringUtils.joinWithSurroundingSeparator(columnWidths), false);
206     writer.endElement(HtmlElements.INPUT);
207 
208     RenderUtils.writeScrollPosition(facesContext, writer, sheet, sheet.getScrollPosition());
209 
210     if (!UISheet.NONE.equals(selectable)) {
211       writer.startElement(HtmlElements.INPUT, null);
212       writer.writeIdAttribute(sheetId + SELECTED_POSTFIX);
213       writer.writeNameAttribute(sheetId + SELECTED_POSTFIX);
214       writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN, false);
215       writer.writeAttribute(
216           HtmlAttributes.VALUE, StringUtils.joinWithSurroundingSeparator(selectedRows), true);
217       writer.endElement(HtmlElements.INPUT);
218     }
219 
220     ExpandedState expandedState = null;
221     StringBuilder expandedValue = null;
222     if (sheet.isTreeModel()) {
223       expandedState = sheet.getExpandedState();
224       expandedValue = new StringBuilder(",");
225     }
226 
227     final boolean showHeader = sheet.isShowHeader();
228     final boolean ie6SelectOneFix = showHeader
229         && ClientProperties.getInstance(facesContext).getUserAgent().isMsie6()
230             && ComponentUtils.findDescendant(sheet, UISelectOne.class) != null;
231 
232 // BEGIN RENDER BODY CONTENT
233     Style bodyStyle = new Style();
234 /*
235     bodyStyle.setPosition(Position.RELATIVE);
236 */
237     Measure tableBodyWidth = sheet.getCurrentWidth().subtractNotNegative(getContentBorder(facesContext, sheet));
238     bodyStyle.setWidth(tableBodyWidth);
239     if (sheet.isPagingVisible()) {
240       sheetHeight = sheetHeight.subtract(footerHeight);
241     }
242     if (ie6SelectOneFix) {
243       bodyStyle.setTop(headerHeight);
244     }
245     if (showHeader) {
246       sheetHeight = sheetHeight.subtract(headerHeight);
247     }
248     bodyStyle.setHeight(sheetHeight);
249 
250     if (showHeader) {
251       renderColumnHeaders(
252           facesContext, sheet, writer, resourceManager, contextPath, sheetId, renderedColumnList, tableBodyWidth);
253     }
254 
255     writer.startElement(HtmlElements.DIV, null);
256     writer.writeIdAttribute(sheetId + ComponentUtils.SUB_SEPARATOR + "data_div");
257     writer.writeClassAttribute(Classes.create(sheet, "body"));
258 
259 /*
260     bodyStyle.setPaddingTop(ie6SelectOneFix ? Measure.ZERO : headerHeight);
261 */
262 
263     writer.writeStyleAttribute(bodyStyle);
264     bodyStyle.setHeight(null);
265     bodyStyle.setTop(null);
266     Style sheetBodyStyle = new Style(bodyStyle);
267     // is null, in AJAX case.
268     if (sheet.getNeedVerticalScrollbar() == Boolean.TRUE) {
269       tableBodyWidth = tableBodyWidth.subtractNotNegative(getVerticalScrollbarWeight(facesContext, sheet));
270     }
271     sheetBodyStyle.setWidth(tableBodyWidth);
272 
273     writer.startElement(HtmlElements.TABLE, null);
274     writer.writeAttribute(HtmlAttributes.CELLSPACING, "0", false);
275     writer.writeAttribute(HtmlAttributes.CELLPADDING, "0", false);
276     writer.writeAttribute(HtmlAttributes.SUMMARY, "", false);
277     writer.writeClassAttribute(Classes.create(sheet, "bodyTable"));
278     writer.writeStyleAttribute(sheetBodyStyle);
279 
280     if (columnWidths != null) {
281       writer.startElement(HtmlElements.COLGROUP, null);
282       for (Integer columnWidth : columnWidths) {
283         writer.startElement(HtmlElements.COL, null);
284         writer.writeAttribute(HtmlAttributes.WIDTH, columnWidth);
285         writer.endElement(HtmlElements.COL);
286       }
287       writer.endElement(HtmlElements.COLGROUP);
288     }
289 
290     // Print the Content
291 
292     if (LOG.isDebugEnabled()) {
293       LOG.debug("first = " + sheet.getFirst() + "   rows = " + sheet.getRows());
294     }
295 
296     final String var = sheet.getVar();
297 
298     boolean odd = false;
299     boolean emptySheet = true;
300     // rows = 0 means: show all
301     final int last = sheet.isRowsUnlimited() ? Integer.MAX_VALUE : sheet.getFirst() + sheet.getRows();
302     for (int rowIndex = sheet.getFirst(); rowIndex < last; rowIndex++) {
303       sheet.setRowIndex(rowIndex);
304       if (!sheet.isRowAvailable()) {
305         break;
306       }
307 
308       Object rowRendered = sheet.getAttributes().get("rowRendered");
309       if (rowRendered instanceof  Boolean && !((Boolean) rowRendered)) {
310         continue;
311       }
312 
313       emptySheet = false;
314       odd = !odd;
315 
316       if (LOG.isDebugEnabled()) {
317         LOG.debug("var       " + var);
318         LOG.debug("list      " + sheet.getValue());
319       }
320 
321       if (sheet.isTreeModel()) {
322         final TreePath path = sheet.getPath();
323         if (sheet.isFolder() && expandedState.isExpanded(path)) {
324           expandedValue.append(rowIndex);
325           expandedValue.append(",");
326         }
327       }
328 
329       writer.startElement(HtmlElements.TR, null);
330       if (rowRendered instanceof  Boolean) {
331         // if rowRendered attribute is set we need the rowIndex on the client
332         writer.writeAttribute(DataAttributes.ROW_INDEX, rowIndex);
333       }
334       Markup rowMarkup = odd ? Markup.ODD : Markup.EVEN;
335       final boolean selected = selectedRows.contains(rowIndex);
336       if (selected) {
337         rowMarkup = rowMarkup.add(Markup.SELECTED);
338       }
339       String[] rowMarkups = (String[]) sheet.getAttributes().get("rowMarkup");
340       if (rowMarkups != null) {
341         rowMarkup = rowMarkup.add(Markup.valueOf(rowMarkups));
342       }
343       writer.writeClassAttribute(Classes.create(sheet, "row", rowMarkup));
344       if (!sheet.isRowVisible()) {
345         Style rowStyle = new Style();
346         rowStyle.setDisplay(Display.NONE);
347         writer.writeStyleAttribute(rowStyle);
348       }
349       final String parentId = sheet.getRowParentClientId();
350       if (parentId != null) {
351         writer.writeAttribute(DataAttributes.TREE_PARENT, parentId, false);
352       }
353 
354       int columnIndex = -1;
355       for (UIColumn column : renderedColumnList) {
356         columnIndex++;
357 
358         writer.startElement(HtmlElements.TD, column);
359 
360         Markup markup = column instanceof SupportsMarkup ? ((SupportsMarkup) column).getMarkup() : Markup.NULL;
361         if (markup == null) {
362           markup = Markup.NULL;
363         }
364         if (columnIndex == 0) {
365           markup = markup.add(Markup.FIRST);
366         }
367         if (hasClickAction) {
368           markup = markup.add(Markup.CLICKABLE);
369         }
370         if (isPure(column)) {
371           markup = markup.add(Markup.PURE);
372         }
373         writer.writeClassAttribute(Classes.create(sheet, "cell", markup));
374         final TextAlign align = TextAlign.parse((String) column.getAttributes().get(Attributes.ALIGN));
375         if (align != null) {
376           Style alignStyle = new Style();
377           alignStyle.setTextAlign(align);
378           writer.writeStyleAttribute(alignStyle);
379         }
380 
381         if (column instanceof UIColumnSelector) {
382           final boolean disabled = ComponentUtils.getBooleanAttribute(column, Attributes.DISABLED);
383           writer.startElement(HtmlElements.INPUT, null);
384           writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.CHECKBOX, false);
385           writer.writeAttribute(HtmlAttributes.CHECKED, selected);
386           writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
387           writer.writeIdAttribute(sheetId + "_data_row_selector_" + rowIndex);
388           writer.writeClassAttribute(Classes.create(sheet, "columnSelector"));
389           writer.endElement(HtmlElements.INPUT);
390         } else if (column instanceof AbstractUIColumnNode) {
391           RenderUtils.prepareRendererAll(facesContext, column);
392           RenderUtils.encode(facesContext, column);
393         } else {
394           List<UIComponent> children = sheet.getRenderedChildrenOf(column);
395           for (UIComponent grandKid : children) {
396             // set height to 0 to prevent use of layoutheight from parent
397             grandKid.getAttributes().put(Attributes.LAYOUT_HEIGHT, HEIGHT_0);
398             // XXX hotfix
399             if (grandKid instanceof LayoutBase) {
400               LayoutBase base = (LayoutBase) grandKid;
401               if (base.getLeft() != null) {
402                 base.setLeft(null);
403               }
404               if (base.getTop() != null) {
405                 base.setTop(null);
406               }
407             }
408             RenderUtils.prepareRendererAll(facesContext, grandKid);
409             RenderUtils.encode(facesContext, grandKid);
410           }
411         }
412 
413         writer.endElement(HtmlElements.TD);
414       }
415 
416       writer.startElement(HtmlElements.TD, null);
417       writer.writeClassAttribute(Classes.create(sheet, "cell", Markup.FILLER));
418 //      writer.write("&nbsp;");
419       writer.startElement(HtmlElements.DIV, null);
420       writer.endElement(HtmlElements.DIV);
421       writer.endElement(HtmlElements.TD);
422 
423       writer.endElement(HtmlElements.TR);
424     }
425 
426     sheet.setRowIndex(-1);
427 
428     if (emptySheet && showHeader) {
429       writer.startElement(HtmlElements.TR, null);
430       int columnIndex = -1;
431       for (UIColumn ignored : renderedColumnList) {
432         columnIndex++;
433         writer.startElement(HtmlElements.TD, null);
434         writer.startElement(HtmlElements.DIV, null);
435         Integer divWidth = sheet.getWidthList().get(columnIndex);
436         Style divStyle = new Style();
437         divStyle.setWidth(Measure.valueOf(divWidth));
438         writer.writeStyleAttribute(divStyle);
439         writer.endElement(HtmlElements.DIV);
440         writer.endElement(HtmlElements.TD);
441       }
442       writer.startElement(HtmlElements.TD, null);
443       writer.writeClassAttribute(Classes.create(sheet, "cell", Markup.FILLER));
444 //      writer.write("&nbsp;");
445       writer.startElement(HtmlElements.DIV, null);
446       writer.endElement(HtmlElements.DIV);
447       writer.endElement(HtmlElements.TD);
448       writer.endElement(HtmlElements.TR);
449     }
450 
451     writer.endElement(HtmlElements.TABLE);
452     writer.endElement(HtmlElements.DIV);
453 
454 // END RENDER BODY CONTENT
455 
456     if (sheet.isPagingVisible()) {
457       Style footerStyle = new Style();
458       footerStyle.setWidth(sheet.getCurrentWidth());
459       if (ie6SelectOneFix) {
460         footerStyle.setTop(headerHeight);
461       }
462       writer.startElement(HtmlElements.DIV, sheet);
463       writer.writeClassAttribute(Classes.create(sheet, "footer"));
464       writer.writeStyleAttribute(footerStyle);
465 
466       // show row range
467       final Markup showRowRange = markupForLeftCenterRight(sheet.getShowRowRange());
468       if (showRowRange != Markup.NULL) {
469         UICommand pagerCommand = (UICommand) sheet.getFacet(Facets.PAGER_ROW);
470         if (pagerCommand == null) {
471           pagerCommand = createPagingCommand(application, PageAction.TO_ROW, false);
472           sheet.getFacets().put(Facets.PAGER_ROW, pagerCommand);
473         }
474         final String pagerCommandId = pagerCommand.getClientId(facesContext);
475 
476         writer.startElement(HtmlElements.SPAN, null);
477         writer.writeClassAttribute(Classes.create(sheet, "pagingOuter", showRowRange));
478         writer.writeAttribute(HtmlAttributes.TITLE,
479             ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago", "sheetPagingInfoRowPagingTip"), true);
480         writer.flush(); // is needed in some cases, e. g. TOBAGO-1094
481         writer.write(createSheetPagingInfo(sheet, facesContext, pagerCommandId, true));
482         writer.endElement(HtmlElements.SPAN);
483       }
484 
485       // show direct links
486       final Markup showDirectLinks = markupForLeftCenterRight(sheet.getShowDirectLinks());
487       if (showDirectLinks != Markup.NULL) {
488         writer.startElement(HtmlElements.SPAN, null);
489         writer.writeClassAttribute(Classes.create(sheet, "pagingOuter", showDirectLinks));
490         writer.writeIdAttribute(sheetId + ComponentUtils.SUB_SEPARATOR + "pagingLinks");
491         writeDirectPagingLinks(writer, facesContext, application, sheet);
492         writer.endElement(HtmlElements.SPAN);
493       }
494 
495       // show page range
496       final Markup showPageRange = markupForLeftCenterRight(sheet.getShowPageRange());
497       if (showPageRange != Markup.NULL) {
498         UICommand pagerCommand = (UICommand) sheet.getFacet(Facets.PAGER_PAGE);
499         if (pagerCommand == null) {
500           pagerCommand = createPagingCommand(application, PageAction.TO_PAGE, false);
501           sheet.getFacets().put(Facets.PAGER_PAGE, pagerCommand);
502         }
503         final String pagerCommandId = pagerCommand.getClientId(facesContext);
504 
505         writer.startElement(HtmlElements.SPAN, null);
506         writer.writeClassAttribute(Classes.create(sheet, "pagingOuter", showPageRange));
507         writer.writeIdAttribute(sheetId + ComponentUtils.SUB_SEPARATOR + "pagingPages");
508         writer.writeText("");
509 
510         boolean atBeginning = sheet.isAtBeginning();
511         link(facesContext, application, atBeginning, PageAction.FIRST, sheet);
512         link(facesContext, application, atBeginning, PageAction.PREV, sheet);
513         writer.startElement(HtmlElements.SPAN, null);
514         writer.writeClassAttribute(Classes.create(sheet, "pagingText"));
515         writer.writeAttribute(HtmlAttributes.TITLE,
516             ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago", "sheetPagingInfoPagePagingTip"), true);
517         writer.flush(); // is needed in some cases, e. g. TOBAGO-1094
518         writer.write(createSheetPagingInfo(sheet, facesContext, pagerCommandId, false));
519         writer.endElement(HtmlElements.SPAN);
520         boolean atEnd = sheet.isAtEnd();
521         link(facesContext, application, atEnd, PageAction.NEXT, sheet);
522         link(facesContext, application, atEnd || !sheet.hasRowCount(), PageAction.LAST, sheet);
523         writer.endElement(HtmlElements.SPAN);
524       }
525 
526       writer.endElement(HtmlElements.DIV);
527     }
528 
529     if (sheet.isTreeModel()) {
530       writer.startElement(HtmlElements.INPUT, sheet);
531       writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN, false);
532       final String expandedId = sheetId + ComponentUtils.SUB_SEPARATOR + AbstractUIData.SUFFIX_EXPANDED;
533       writer.writeNameAttribute(expandedId);
534       writer.writeIdAttribute(expandedId);
535       writer.writeClassAttribute(Classes.create(sheet, AbstractUIData.SUFFIX_EXPANDED));
536       writer.writeAttribute(HtmlAttributes.VALUE, expandedValue.toString(), false);
537       writer.endElement(HtmlElements.INPUT);
538     }
539   }
540 
541   /**
542    * Differ between simple content and complex content.
543    * Decide if the content of a cell needs usually the whole possible space or
544    * is the character of the content like flowing text.
545    * In the second case, the style usually sets a padding.<br/>
546    * Pure is needed for &lt;tc:panel>,  &lt;tc:in>, etc.<br/>
547    * Pure is not needed for  &lt;tc:out> and &lt;tc:link>
548    */
549   private boolean isPure(UIColumn column) {
550     for (UIComponent child : column.getChildren()) {
551       if (!(child instanceof UIOut) && !(child instanceof UILink)) {
552         return true;
553       }
554     }
555     return false;
556   }
557 
558   private String createSheetPagingInfo(UISheet sheet, FacesContext facesContext, String pagerCommandId, boolean row) {
559     String sheetPagingInfo;
560     if (sheet.getRowCount() != 0) {
561       Locale locale = facesContext.getViewRoot().getLocale();
562       int first = row ? sheet.getFirst() + 1 : sheet.getCurrentPage() + 1;
563       int last = sheet.hasRowCount()
564           ? row ? sheet.getLastRowIndexOfCurrentPage() : sheet.getPages()
565           : -1;
566       final boolean unknown = !sheet.hasRowCount();
567       final String key = "sheetPagingInfo"
568           + (unknown ? "Undefined" : "")
569           + (first == last ? "Single" : "")
570           + (row ? "Row" : "Page")
571           + (first == last ? "" : "s"); // plural
572       final String message = ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago", key);
573       final MessageFormat detail = new MessageFormat(message, locale);
574       Object[] args = {
575           first,
576           last == -1 ? "?" : last,
577           unknown ? "" : sheet.getRowCount(),
578           pagerCommandId + ComponentUtils.SUB_SEPARATOR + "text"
579       };
580       sheetPagingInfo = detail.format(args);
581     } else {
582       sheetPagingInfo =
583           ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago",
584               "sheetPagingInfoEmpty" + (row ? "Row" : "Page"));
585     }
586     return sheetPagingInfo;
587   }
588 
589   @Override
590   public void decode(FacesContext facesContext, UIComponent component) {
591     super.decode(facesContext, component);
592 
593     UISheet sheet = (UISheet) component;
594 
595     String key = sheet.getClientId(facesContext) + WIDTHS_POSTFIX;
596 
597     Map requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
598     if (requestParameterMap.containsKey(key)) {
599       String widths = (String) requestParameterMap.get(key);
600       if (widths.trim().length() > 0) {
601         sheet.getAttributes().put(Attributes.WIDTH_LIST_STRING, widths);
602       }
603     }
604 
605     key = sheet.getClientId(facesContext) + SELECTED_POSTFIX;
606     if (requestParameterMap.containsKey(key)) {
607       String selected = (String) requestParameterMap.get(key);
608       if (LOG.isDebugEnabled()) {
609         LOG.debug("selected = " + selected);
610       }
611       List<Integer> selectedRows;
612       try {
613         selectedRows = StringUtils.parseIntegerList(selected);
614       } catch (NumberFormatException e) {
615         LOG.warn(selected, e);
616         selectedRows = Collections.emptyList();
617       }
618 
619       sheet.getAttributes().put(Attributes.SELECTED_LIST_STRING, selectedRows);
620     }
621 
622     RenderUtils.decodeScrollPosition(facesContext, sheet);
623     RenderUtils.decodedStateOfTreeData(facesContext, sheet);
624   }
625 
626   private Measure getHeaderHeight(FacesContext facesContext, UISheet sheet) {
627     int rows = sheet.getHeaderGrid() != null ? sheet.getHeaderGrid().getRowCount() : 0;
628     return sheet.isShowHeader()
629         ? getResourceManager().getThemeMeasure(facesContext, sheet, "headerHeight").multiply(rows)
630         : Measure.ZERO;
631   }
632 
633   private Measure getRowHeight(FacesContext facesContext, UISheet sheet) {
634     return getResourceManager().getThemeMeasure(facesContext, sheet, "rowHeight");
635   }
636 
637   private Measure getFooterHeight(FacesContext facesContext, UISheet sheet) {
638     return sheet.isPagingVisible()
639         ? getResourceManager().getThemeMeasure(facesContext, sheet, "footerHeight")
640         : Measure.ZERO;
641   }
642 
643   private Markup markupForLeftCenterRight(String name) {
644     if ("left".equals(name)) {
645       return Markup.LEFT;
646     }
647     if ("center".equals(name)) {
648       return Markup.CENTER;
649     }
650     if ("right".equals(name)) {
651       return Markup.RIGHT;
652     }
653     return Markup.NULL;
654   }
655 
656   private String checkPagingAttribute(String name) {
657     if (isNotNone(name)) {
658       return name;
659     } else {
660       if (!"none".equals(name)) {
661         LOG.warn("Illegal value in sheets paging attribute: '" + name + "'");
662       }
663       return "none";
664     }
665   }
666 
667   private boolean isNotNone(String value) {
668     // todo: use enum type instead of string
669     return "left".equals(value) || "center".equals(value) || "right".equals(value);
670   }
671 
672   @Override
673   public boolean getRendersChildren() {
674     return true;
675   }
676 
677   private List<Integer> getSelectedRows(UISheet data, SheetState state) {
678     List<Integer> selected = (List<Integer>)
679         data.getAttributes().get(Attributes.SELECTED_LIST_STRING);
680     if (selected == null && state != null) {
681       selected = state.getSelectedRows();
682     }
683     if (selected == null) {
684       selected = Collections.emptyList();
685     }
686     return selected;
687   }
688 
689   private void link(FacesContext facesContext, Application application,
690                     boolean disabled, PageAction command, UISheet data)
691       throws IOException {
692     UICommand link = createPagingCommand(application, command, disabled);
693 
694     data.getFacets().put(command.getToken(), link);
695 
696 
697     String tip = ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago",
698         "sheet" + command.getToken());
699     String image = ResourceManagerUtils.getImageWithPath(facesContext,
700         "image/sheet" + command.getToken() + (disabled ? "Disabled" : "") + ".gif");
701 
702     TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
703     writer.startElement(HtmlElements.IMG, null);
704     writer.writeIdAttribute(data.getClientId(facesContext)
705         + ComponentUtils.SUB_SEPARATOR + "pagingPages" + ComponentUtils.SUB_SEPARATOR + command.getToken());
706     Classes pagerClasses = Classes.create(data, "footerPagerButton", disabled ? Markup.DISABLED : null);
707     writer.writeClassAttribute(pagerClasses);
708     writer.writeAttribute(HtmlAttributes.SRC, image, false);
709     writer.writeAttribute(HtmlAttributes.TITLE, tip, true);
710     writer.writeAttribute(HtmlAttributes.ALT, "", false);
711     writer.writeAttribute(DataAttributes.DISABLED, disabled);
712     writer.endElement(HtmlElements.IMG);
713   }
714 
715   // TODO sheet.getColumnLayout() liefert ggf. eine falsche Anzahl von Spalten
716   // TODO
717   // TODO
718 
719   private void renderColumnHeaders(
720       FacesContext facesContext, UISheet sheet, TobagoResponseWriter writer, ResourceManager resourceManager,
721       String contextPath, String sheetId, List<AbstractUIColumn> renderedColumnList, Measure headerWidth)
722       throws IOException {
723 
724     final Grid grid = sheet.getHeaderGrid();
725     final List<Integer> columnWidths = sheet.getWidthList();
726 
727     if (LOG.isDebugEnabled()) {
728       LOG.debug("*****************************************************");
729       LOG.debug("" + grid);
730       LOG.debug("*****************************************************");
731     }
732 
733     boolean needVerticalScrollbar = needVerticalScrollbar(facesContext, sheet);
734     int verticalScrollbarWidth = 0;
735 
736     writer.startElement(HtmlElements.DIV, sheet);
737     writer.writeClassAttribute(Classes.create(sheet, "headerDiv"));
738     writer.startElement(HtmlElements.TABLE, sheet);
739     writer.writeAttribute(HtmlAttributes.CELLSPACING, "0", false);
740     writer.writeAttribute(HtmlAttributes.CELLPADDING, "0", false);
741     writer.writeAttribute(HtmlAttributes.SUMMARY, "", false);
742     writer.writeClassAttribute(Classes.create(sheet, "headerTable"));
743     if (needVerticalScrollbar) {
744       verticalScrollbarWidth = getVerticalScrollbarWeight(facesContext, sheet).getPixel();
745       writer.writeAttribute("data-tobago-sheet-verticalscrollbarwidth", String.valueOf(verticalScrollbarWidth), false);
746     }
747 
748     if (columnWidths != null) {
749       writer.startElement(HtmlElements.COLGROUP, null);
750       for (int i = 0; i < columnWidths.size(); i++) {
751         writer.startElement(HtmlElements.COL, null);
752         if (i == columnWidths.size() - 2 && needVerticalScrollbar) {
753           // if scrollbar is needed the coll for column in header must have own width + scrollbarWidth
754           writer.writeAttribute(
755               HtmlAttributes.WIDTH, columnWidths.get(i) + verticalScrollbarWidth);
756         } else {
757           writer.writeAttribute(HtmlAttributes.WIDTH, columnWidths.get(i));
758         }
759         writer.endElement(HtmlElements.COL);
760       }
761       writer.endElement(HtmlElements.COLGROUP);
762     }
763 
764     writer.startElement(HtmlElements.TBODY, sheet);
765     for (int i = 0; i < grid.getRowCount(); i++) {
766       writer.startElement(HtmlElements.TR, null);
767       for (int j = 0; j < grid.getColumnCount(); j++) {
768         final Cell cell = grid.getCell(j, i);
769         if (cell instanceof OriginCell) {
770           writer.startElement(HtmlElements.TD, null);
771           if (cell.getColumnSpan() > 1) {
772             writer.writeAttribute(HtmlAttributes.COLSPAN, cell.getColumnSpan());
773           }
774           if (cell.getRowSpan() > 1) {
775             writer.writeAttribute(HtmlAttributes.ROWSPAN, cell.getRowSpan());
776           }
777 
778           final UIComponent cellComponent = (UIComponent) cell.getComponent();
779           final boolean pure = !(cellComponent instanceof UIOut);
780 
781           writer.startElement(HtmlElements.DIV, null);
782           writer.writeClassAttribute(Classes.create(sheet, "headerCell"));
783           writer.startElement(HtmlElements.SPAN, null);
784           Style headerStyle = new Style();
785           Measure headerHeight = Measure.valueOf(20).multiply(cell.getRowSpan());
786           if (!pure) {
787             headerHeight = headerHeight.subtract(6); // XXX todo
788           }
789           headerStyle.setHeight(headerHeight);
790           writer.writeStyleAttribute(headerStyle);
791           final AbstractUIColumn column = renderedColumnList.get(j);
792           String sorterImage = null;
793           Markup markup = Markup.NULL;
794           String tip = (String) column.getAttributes().get(Attributes.TIP);
795           if (cell.getColumnSpan() == 1) {
796             final boolean sortable = ComponentUtils.getBooleanAttribute(column, Attributes.SORTABLE);
797             if (sortable) {
798               UICommand sortCommand = (UICommand) column.getFacet(Facets.SORTER);
799               if (sortCommand == null) {
800                 final String columnId = column.getClientId(facesContext);
801                 final String sorterId = columnId.substring(columnId.lastIndexOf(":") + 1) + "_" + UISheet.SORTER_ID;
802                 sortCommand = (UICommand) CreateComponentUtils.createComponent(
803                     facesContext, UICommand.COMPONENT_TYPE, RendererTypes.LINK, sorterId);
804                 column.getFacets().put(Facets.SORTER, sortCommand);
805               }
806               final CommandMap map = new CommandMap();
807               final Command click = new Command(sortCommand.getClientId(facesContext),
808                   null, null, null, new String[]{sheet.getClientId(facesContext)}, null, null, null, null, null);
809               map.setClick(click);
810               writer.writeAttribute(DataAttributes.COMMANDS, JsonUtils.encode(map), true);
811 
812               if (tip == null) {
813                 tip = "";
814               } else {
815                 tip += " - ";
816               }
817               tip += ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago", "sheetTipSorting");
818 
819               markup = markup.add(Markup.SORTABLE);
820 
821               SheetState sheetState = sheet.getSheetState(facesContext);
822               if (column.getId().equals(sheetState.getSortedColumnId())) {
823                 String sortTitle;
824                 if (sheetState.isAscending()) {
825                   sorterImage = contextPath + resourceManager.getImage(facesContext, "image/ascending.gif");
826                   sortTitle = ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago", "sheetAscending");
827                   markup = markup.add(Markup.ASCENDING);
828                 } else {
829                   sorterImage = contextPath + resourceManager.getImage(facesContext, "image/descending.gif");
830                   sortTitle = ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago", "sheetDescending");
831                   markup = markup.add(Markup.DESCENDING);
832                 }
833                 if (sortTitle != null) {
834                   tip += " - " + sortTitle;
835                 }
836               }
837             }
838           }
839 
840           if (j == 0) {
841             markup = markup.add(Markup.FIRST);
842           }
843           if (pure) {
844              markup = markup.add(Markup.PURE);
845           }
846           writer.writeClassAttribute(Classes.create(sheet, "header", markup));
847           writer.writeAttribute(HtmlAttributes.TITLE, tip, true);
848 
849           if (column instanceof UIColumnSelector) {
850             renderColumnSelectorHeader(facesContext, writer, sheet);
851           } else {
852             RenderUtils.encode(facesContext, cellComponent);
853 
854             AbstractUIMenu dropDownMenu = FacetUtils.getDropDownMenu(column);
855             // render sub menu popup button
856             if (dropDownMenu != null && dropDownMenu.isRendered()) {
857 
858               writer.startElement(HtmlElements.SPAN, column);
859               writer.writeClassAttribute(Classes.create(column, "menu"));
860 
861               writer.startElement(HtmlElements.IMG, column);
862               String menuImage = ResourceManagerUtils.getImageWithPath(facesContext, "image/sheetSelectorMenu.gif");
863               writer.writeAttribute(HtmlAttributes.TITLE, "", false);
864               writer.writeAttribute(HtmlAttributes.SRC, menuImage, false);
865               writer.endElement(HtmlElements.IMG);
866               ToolBarRendererBase.renderDropDownMenu(facesContext, writer, dropDownMenu);
867 
868               writer.endElement(HtmlElements.SPAN);
869             }
870 
871           }
872 
873           if (sorterImage != null) {
874             writer.startElement(HtmlElements.IMG, null);
875             writer.writeAttribute(HtmlAttributes.SRC, sorterImage, false);
876             writer.writeAttribute(HtmlAttributes.ALT, "", false);
877             writer.endElement(HtmlElements.IMG);
878           }
879 
880           writer.endElement(HtmlElements.SPAN);
881           if (renderedColumnList.get(j).isResizable()) {
882             encodeResizing(writer, sheet, j + cell.getColumnSpan() - 1);
883           }
884           writer.endElement(HtmlElements.DIV);
885 
886           writer.endElement(HtmlElements.TD);
887         }
888       }
889       // add a filler column
890       writer.startElement(HtmlElements.TD, null);
891       writer.startElement(HtmlElements.DIV, null);
892       // todo: is the filler class needed here?
893       writer.writeClassAttribute(Classes.create(sheet, "headerCell", Markup.FILLER));
894       writer.endElement(HtmlElements.DIV);
895       writer.endElement(HtmlElements.TD);
896 
897       writer.endElement(HtmlElements.TR);
898     }
899     writer.endElement(HtmlElements.TBODY);
900     writer.endElement(HtmlElements.TABLE);
901     writer.endElement(HtmlElements.DIV);
902   }
903 
904   private boolean needVerticalScrollbar(FacesContext facesContext, UISheet sheet) {
905     return ((AbstractUISheetLayout) sheet.getLayoutManager()).needVerticalScrollbar(facesContext, sheet);
906   }
907 
908   private void encodeResizing(TobagoResponseWriter writer, AbstractUISheet sheet, int columnIndex) throws IOException {
909     writer.startElement(HtmlElements.SPAN, null);
910     writer.writeClassAttribute(Classes.create(sheet, "headerResize"));
911     writer.writeAttribute(DataAttributes.COLUMN_INDEX, Integer.toString(columnIndex), false);
912     writer.write("&nbsp;&nbsp;"); // is needed for IE
913     writer.endElement(HtmlElements.SPAN);
914   }
915 
916   protected void renderColumnSelectorHeader(
917       FacesContext facesContext, TobagoResponseWriter writer, UISheet sheet)
918       throws IOException {
919 
920     final UIToolBar toolBar = createToolBar(facesContext, sheet);
921     writer.startElement(HtmlElements.DIV, null);
922     writer.writeClassAttribute(Classes.create(sheet, "toolBar"));
923 
924     if (UISheet.MULTI.equals(sheet.getSelectable())) {
925       RenderUtils.prepareRendererAll(facesContext, toolBar);
926       RenderUtils.encode(facesContext, toolBar);
927     }
928 
929     writer.endElement(HtmlElements.DIV);
930   }
931 
932   private UIToolBar createToolBar(FacesContext facesContext, UISheet sheet) {
933     final Application application = facesContext.getApplication();
934     final UICommand dropDown = (UICommand) CreateComponentUtils.createComponent(
935         facesContext, UICommand.COMPONENT_TYPE, null, "dropDown");
936     final UIMenu menu = (UIMenu) CreateComponentUtils.createComponent(
937         facesContext, UIMenu.COMPONENT_TYPE, RendererTypes.MENU, "menu");
938     FacetUtils.setDropDownMenu(dropDown, menu);
939     final String sheetId = sheet.getClientId(facesContext);
940 
941     createMenuItem(facesContext, menu, "sheetMenuSelect", Markup.SHEET_SELECT_ALL, sheetId);
942     createMenuItem(facesContext, menu, "sheetMenuUnselect", Markup.SHEET_DESELECT_ALL, sheetId);
943     createMenuItem(facesContext, menu, "sheetMenuToggleselect", Markup.SHEET_TOGGLE_ALL, sheetId);
944 
945     final UIToolBar toolBar = (UIToolBar) application.createComponent(UIToolBar.COMPONENT_TYPE);
946     toolBar.setId(facesContext.getViewRoot().createUniqueId());
947     toolBar.setRendererType("TabGroupToolBar");
948     toolBar.setTransient(true);
949     toolBar.getChildren().add(dropDown);
950     sheet.getFacets().put(Facets.TOOL_BAR, toolBar);
951     return toolBar;
952   }
953 
954   private void createMenuItem(
955       final FacesContext facesContext, UIMenu menu, String label, Markup markup, String sheetId) {
956     final String id = markup.toString();
957     final UIMenuCommand menuItem = (UIMenuCommand) CreateComponentUtils.createComponent(
958         facesContext, UIMenuCommand.COMPONENT_TYPE, RendererTypes.MENU_COMMAND, id);
959     menuItem.setLabel(ResourceManagerUtils.getPropertyNotNull(facesContext, "tobago", label));
960     menuItem.setMarkup(markup);
961     menuItem.setOnclick("/**/"); // XXX avoid submit
962     ComponentUtils.putDataAttributeWithPrefix(menuItem, DataAttributes.SHEET_ID, sheetId);
963     menu.getChildren().add(menuItem);
964   }
965 
966   private void writeDirectPagingLinks(
967       TobagoResponseWriter writer, FacesContext facesContext, Application application, UISheet sheet)
968       throws IOException {
969     UICommand pagerCommand = (UICommand) sheet.getFacet(Facets.PAGER_PAGE);
970     if (pagerCommand == null) {
971       pagerCommand = createPagingCommand(application, PageAction.TO_PAGE, false);
972       sheet.getFacets().put(Facets.PAGER_PAGE, pagerCommand);
973     }
974     String pagerCommandId = pagerCommand.getClientId(facesContext);
975     int linkCount = ComponentUtils.getIntAttribute(sheet, Attributes.DIRECT_LINK_COUNT);
976     linkCount--;  // current page needs no link
977     ArrayList<Integer> prevs = new ArrayList<Integer>(linkCount);
978     int page = sheet.getCurrentPage() + 1;
979     for (int i = 0; i < linkCount && page > 1; i++) {
980       page--;
981       if (page > 0) {
982         prevs.add(0, page);
983       }
984     }
985 
986     ArrayList<Integer> nexts = new ArrayList<Integer>(linkCount);
987     page = sheet.getCurrentPage() + 1;
988     final int pages = sheet.hasRowCount() || sheet.isRowsUnlimited() ? sheet.getPages() : Integer.MAX_VALUE;
989     for (int i = 0; i < linkCount && page < pages; i++) {
990       page++;
991       if (page > 1) {
992         nexts.add(page);
993       }
994     }
995 
996     if (prevs.size() > (linkCount / 2)
997         && nexts.size() > (linkCount - (linkCount / 2))) {
998       while (prevs.size() > (linkCount / 2)) {
999         prevs.remove(0);
1000       }
1001       while (nexts.size() > (linkCount - (linkCount / 2))) {
1002         nexts.remove(nexts.size() - 1);
1003       }
1004     } else if (prevs.size() <= (linkCount / 2)) {
1005       while (prevs.size() + nexts.size() > linkCount) {
1006         nexts.remove(nexts.size() - 1);
1007       }
1008     } else {
1009       while (prevs.size() + nexts.size() > linkCount) {
1010         prevs.remove(0);
1011       }
1012     }
1013 
1014     String name;
1015     int skip = prevs.size() > 0 ? prevs.get(0) : 1;
1016     if (skip > 1) {
1017       skip -= (linkCount - (linkCount / 2));
1018       skip--;
1019       name = "...";
1020       if (skip < 1) {
1021         skip = 1;
1022         if (prevs.get(0) == 2) {
1023           name = "1";
1024         }
1025       }
1026       writeLinkElement(writer, sheet, name, Integer.toString(skip), pagerCommandId, true);
1027     }
1028     for (Integer prev : prevs) {
1029       name = prev.toString();
1030       writeLinkElement(writer, sheet, name, name, pagerCommandId, true);
1031     }
1032     name = Integer.toString(sheet.getCurrentPage() + 1);
1033     writeLinkElement(writer, sheet, name, name, pagerCommandId, false);
1034 
1035     for (Integer next : nexts) {
1036       name = next.toString();
1037       writeLinkElement(writer, sheet, name, name, pagerCommandId, true);
1038     }
1039 
1040     skip = nexts.size() > 0 ? nexts.get(nexts.size() - 1) : pages;
1041     if (skip < pages) {
1042       skip += linkCount / 2;
1043       skip++;
1044       name = "...";
1045       if (skip > pages) {
1046         skip = pages;
1047         if ((nexts.get(nexts.size() - 1)) == skip - 1) {
1048           name = Integer.toString(skip);
1049         }
1050       }
1051       writeLinkElement(writer, sheet, name, Integer.toString(skip), pagerCommandId, true);
1052     }
1053   }
1054 
1055   private UICommand createPagingCommand(Application application, PageAction command, boolean disabled) {
1056     UICommand link;
1057     link = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
1058     link.setRendererType(RendererTypes.SHEET_PAGE_COMMAND);
1059     link.setRendered(true);
1060     link.setId(command.getToken());
1061     link.getAttributes().put(Attributes.INLINE, Boolean.TRUE);
1062     link.getAttributes().put(Attributes.DISABLED, disabled);
1063     return link;
1064   }
1065 
1066   private void writeLinkElement(
1067       TobagoResponseWriter writer, UISheet sheet, String str, String skip, String id, boolean makeLink)
1068       throws IOException {
1069     String type = makeLink ? HtmlElements.A : HtmlElements.SPAN;
1070     writer.startElement(type, null);
1071     writer.writeClassAttribute(Classes.create(sheet, "pagingLink"));
1072     if (makeLink) {
1073       writer.writeIdAttribute(id + ComponentUtils.SUB_SEPARATOR + "link_" + skip);
1074       writer.writeAttribute(HtmlAttributes.HREF, "#", true);
1075     }
1076     writer.flush();
1077     writer.write(str);
1078     writer.endElement(type);
1079   }
1080 
1081   private Measure getContentBorder(FacesContext facesContext, UISheet data) {
1082     return getBorderLeft(facesContext, data).add(getBorderRight(facesContext, data));
1083   }
1084 
1085   @Override
1086   public Measure getPreferredHeight(FacesContext facesContext, Configurable component) {
1087     final UISheet sheet = (UISheet) component;
1088     final Measure headerHeight = getHeaderHeight(facesContext, sheet);
1089     final Measure rowHeight = getRowHeight(facesContext, sheet);
1090     final Measure footerHeight = getFooterHeight(facesContext, sheet);
1091     int rows = sheet.getRows();
1092     if (rows == 0) {
1093       rows = sheet.getRowCount();
1094     }
1095     if (rows == -1) {
1096       rows = 10; // estimating something to get a valid value...
1097     }
1098 
1099     if (LOG.isDebugEnabled()) {
1100       LOG.debug(headerHeight + " " + footerHeight + " " + rowHeight + " " + rows);
1101     }
1102 
1103     return headerHeight.add(rowHeight.multiply(rows)).add(footerHeight);
1104   }
1105 
1106   @Override
1107   public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
1108     // DO Nothing
1109   }
1110 
1111   @Override
1112   public boolean getPrepareRendersChildren() {
1113     return true;
1114   }
1115 
1116   @Override
1117   public void prepareRendersChildren(FacesContext facesContext, UIComponent component) throws IOException {
1118     UISheet sheet = (UISheet) component;
1119     for (UIColumn column : sheet.getRenderedColumns()) {
1120       if (column instanceof AbstractUIColumnNode) {
1121         if (LOG.isDebugEnabled()) {
1122           LOG.debug("TODO: AbstractUIColumnNode are not prepared.");
1123         }
1124         // TBD: when the column should be prepared for rendering, I think we need to
1125         // TBD: iterate over each row to prepare it.
1126         // TBD: in the moment this method TreeNodeRendererBase.prepareRender() will not be called in sheets
1127       } else {
1128         RenderUtils.prepareRendererAll(facesContext, column);
1129       }
1130     }
1131   }
1132 }