001    package org.apache.myfaces.tobago.component;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.commons.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    import org.apache.myfaces.tobago.ajax.api.AjaxComponent;
023    import org.apache.myfaces.tobago.ajax.api.AjaxPhaseListener;
024    import org.apache.myfaces.tobago.ajax.api.AjaxUtils;
025    import org.apache.myfaces.tobago.event.PageActionEvent;
026    import org.apache.myfaces.tobago.event.SheetStateChangeEvent;
027    import org.apache.myfaces.tobago.event.SheetStateChangeListener;
028    import org.apache.myfaces.tobago.event.SheetStateChangeSource;
029    import org.apache.myfaces.tobago.event.SortActionEvent;
030    import org.apache.myfaces.tobago.event.SortActionSource;
031    import org.apache.myfaces.tobago.model.SheetState;
032    import org.apache.myfaces.tobago.renderkit.LayoutInformationProvider;
033    import org.apache.myfaces.tobago.renderkit.LayoutableRendererBase;
034    import org.apache.myfaces.tobago.renderkit.SheetRendererWorkaround;
035    import org.apache.myfaces.tobago.util.LayoutInfo;
036    import org.apache.myfaces.tobago.util.LayoutUtil;
037    import org.apache.myfaces.tobago.util.StringUtils;
038    
039    import javax.faces.component.UIColumn;
040    import javax.faces.component.UIComponent;
041    import javax.faces.context.FacesContext;
042    import javax.faces.el.EvaluationException;
043    import javax.faces.el.MethodBinding;
044    import javax.faces.el.ValueBinding;
045    import javax.faces.event.AbortProcessingException;
046    import javax.faces.event.FacesEvent;
047    import javax.faces.event.PhaseId;
048    import javax.servlet.http.HttpServletResponse;
049    import java.io.IOException;
050    import java.util.ArrayList;
051    import java.util.List;
052    import java.util.Map;
053    
054    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_COLUMNS;
055    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_DIRECT_LINK_COUNT;
056    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_FIRST;
057    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_WIDTH;
058    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_WIDTH;
059    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ROWS;
060    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SELECTABLE;
061    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SELECTED_LIST_STRING;
062    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_DIRECT_LINKS;
063    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_HEADER;
064    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_PAGE_RANGE;
065    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_ROW_RANGE;
066    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STATE;
067    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_WIDTH_LIST_STRING;
068    import static org.apache.myfaces.tobago.TobagoConstants.FACET_RELOAD;
069    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
070    
071    public class UIData extends javax.faces.component.UIData
072        implements SheetStateChangeSource, SortActionSource, AjaxComponent {
073    
074      private static final Log LOG = LogFactory.getLog(UIData.class);
075    
076      public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.Data";
077    
078      public static final String FACET_SORTER = "sorter";
079      public static final String SORTER_ID = "sorter";
080      public static final String ATTR_SCROLL_POSITION = "attrScrollPosition";
081    
082      public static final String NONE = "none";
083      public static final String SINGLE = "single";
084      public static final String SINGLE_OR_NONE = "singleOrNone";
085      public static final String MULTI = "multi";
086      public static final int DEFAULT_DIRECT_LINK_COUNT = 9;
087      public static final int DEFAULT_ROW_COUNT = 100;
088      private static final String DEFAULT_SELECTABLE = MULTI;
089    
090      private MethodBinding stateChangeListener;
091      private List<Integer> widthList;
092      private MethodBinding sortActionListener;
093      private SheetState sheetState;
094      private Boolean showHeader;
095      private String showRowRange;
096      private String showPageRange;
097      private String showDirectLinks;
098      private String columns;
099      private Integer directLinkCount;
100      private Integer rows;
101    
102      private String selectable;
103    
104      private transient LayoutTokens columnLayout;
105    
106      /**
107       * Remove the (by user) resized column widths. An application may provide a button to access it.
108       * Since 1.0.26.
109       */
110      public void resetColumnWidths() {
111        SheetState state = getSheetState(FacesContext.getCurrentInstance());
112        if (state != null) {
113          state.setColumnWidths(null);
114        }
115        getAttributes().remove(ATTR_WIDTH_LIST_STRING);
116      }
117    
118      public void encodeBegin(FacesContext facesContext) throws IOException {
119        UILayout.prepareDimension(facesContext, this);
120        SheetState state = getSheetState(facesContext);
121        if (state.getFirst() > -1 && state.getFirst() < getRowCount()) {
122          ValueBinding valueBinding = getValueBinding(ATTR_FIRST);
123          if (valueBinding != null) {
124            valueBinding.setValue(facesContext, state.getFirst());
125          } else {
126            setFirst(state.getFirst());
127          }
128        }
129        super.encodeBegin(facesContext);
130      }
131    
132      public void encodeEnd(FacesContext facesContext) throws IOException {
133        setupState(facesContext);
134        prepareDimensions(facesContext);
135        super.encodeEnd(facesContext);
136      }
137    
138      public void processDecodes(FacesContext context) {
139        final String ajaxId = (String) context.getExternalContext()
140            .getRequestParameterMap().get(AjaxPhaseListener.AJAX_COMPONENT_ID);
141        if (ajaxId !=null && ajaxId.equals(getClientId(context))) {
142          if (getFacet(FACET_RELOAD) != null && getFacet(FACET_RELOAD) instanceof UIReload
143              && getFacet(FACET_RELOAD).isRendered()
144              && ((UIReload) getFacet(FACET_RELOAD)).isImmediate()
145              && ajaxId.equals(ComponentUtil.findPage(context, this).getActionId())) {
146            UIReload reload = (UIReload) getFacet(FACET_RELOAD);
147            if (!reload.getUpdate()) {
148              if (context.getExternalContext().getResponse() instanceof HttpServletResponse) {
149                 ((HttpServletResponse) context.getExternalContext().getResponse())
150                     .setStatus(HttpServletResponse.SC_NOT_MODIFIED);
151              }
152              context.responseComplete();
153              return;
154            }
155          }
156        }
157        super.processDecodes(context);
158      }
159    
160      public String getShowRowRange() {
161        if (showRowRange != null) {
162          return showRowRange;
163        }
164        ValueBinding vb = getValueBinding(ATTR_SHOW_ROW_RANGE);
165        if (vb != null) {
166          return (String) vb.getValue(getFacesContext());
167        } else {
168          return NONE;
169        }
170      }
171    
172      public void setShowRowRange(String showRowRange) {
173        this.showRowRange = showRowRange;
174      }
175    
176      public String getShowPageRange() {
177        if (showPageRange != null) {
178          return showPageRange;
179        }
180        ValueBinding vb = getValueBinding(ATTR_SHOW_PAGE_RANGE);
181        if (vb != null) {
182          return (String) vb.getValue(getFacesContext());
183        } else {
184          return NONE;
185        }
186      }
187    
188      public void setShowPageRange(String showPageRange) {
189        this.showPageRange = showPageRange;
190      }
191    
192      public String getColumns() {
193        if (columns != null) {
194          return columns;
195        }
196        ValueBinding vb = getValueBinding(ATTR_COLUMNS);
197        if (vb != null) {
198          return (String) vb.getValue(getFacesContext());
199        } else {
200          return null;
201        }
202      }
203    
204      public void setColumns(String columns) {
205        this.columns = columns;
206      }
207    
208      public String getShowDirectLinks() {
209        if (showDirectLinks != null) {
210          return showDirectLinks;
211        }
212        ValueBinding vb = getValueBinding(ATTR_SHOW_DIRECT_LINKS);
213        if (vb != null) {
214          return (String) vb.getValue(getFacesContext());
215        } else {
216          return NONE;
217        }
218      }
219    
220      public void setShowDirectLinks(String showDirectLinks) {
221        this.showDirectLinks = showDirectLinks;
222      }
223    
224      public String getSelectable() {
225        if (selectable != null) {
226          return selectable;
227        }
228        ValueBinding vb = getValueBinding(ATTR_SELECTABLE);
229        if (vb != null) {
230          return (String) vb.getValue(getFacesContext());
231        } else {
232          return DEFAULT_SELECTABLE;
233        }
234      }
235    
236      public void setSelectable(String selectable) {
237        this.selectable = selectable;
238      }
239    
240      public Integer getDirectLinkCount() {
241        if (directLinkCount != null) {
242          return directLinkCount;
243        }
244        ValueBinding vb = getValueBinding(ATTR_DIRECT_LINK_COUNT);
245        if (vb != null) {
246          return (Integer) vb.getValue(getFacesContext());
247        } else {
248          return DEFAULT_DIRECT_LINK_COUNT;
249        }
250      }
251    
252      public void setDirectLinkCount(Integer directLinkCount) {
253        this.directLinkCount = directLinkCount;
254      }
255    
256      private void setupState(FacesContext facesContext) {
257        SheetState state = getSheetState(facesContext);
258        ensureColumnWidthList(facesContext, state);
259      }
260    
261      public void setState(SheetState state) {
262        this.sheetState = state;
263      }
264    
265      public SheetState getSheetState(FacesContext facesContext) {
266        if (sheetState != null) {
267          return sheetState;
268        } else {
269          ValueBinding stateBinding = getValueBinding(ATTR_STATE);
270          if (stateBinding != null) {
271            SheetState state = (SheetState) stateBinding.getValue(facesContext);
272            if (state == null) {
273              state = new SheetState();
274              stateBinding.setValue(facesContext, state);
275            }
276            return state;
277          } else {
278            sheetState = new SheetState();
279            return sheetState;
280          }
281        }
282      }
283    
284      public LayoutTokens getColumnLayout() {
285        if (columnLayout == null) {
286          String columns = getColumns();
287          if (columns != null) {
288            columnLayout = LayoutTokens.parse(columns);
289          }
290        }
291        return columnLayout;
292      }
293    
294      private void ensureColumnWidthList(FacesContext facesContext, SheetState state) {
295        List<Integer> currentWidthList = null;
296        List<UIColumn> rendererdColumns = getRenderedColumns();
297    
298        final Map attributes = getAttributes();
299        String widthListString = null;
300    
301        if (state != null) {
302          widthListString = state.getColumnWidths();
303        }
304        if (widthListString == null) {
305          widthListString = (String) attributes.get(ATTR_WIDTH_LIST_STRING);
306        }
307    
308        if (widthListString != null) {
309          currentWidthList = StringUtils.parseIntegerList(widthListString);
310        }
311        if (currentWidthList != null && currentWidthList.size() != rendererdColumns.size()) {
312          currentWidthList = null;
313        }
314    
315    
316        if (currentWidthList == null) {
317          LayoutTokens tokens = getColumnLayout();
318          List<UIColumn> allColumns = getAllColumns();
319          LayoutTokens newTokens = new LayoutTokens();
320          if (allColumns.size() > 0) {
321            for (int i = 0; i < allColumns.size(); i++) {
322              UIColumn column = allColumns.get(i);
323              if (column.isRendered()) {
324                if (tokens == null) {
325                  if (column instanceof org.apache.myfaces.tobago.component.UIColumn) {
326                    newTokens.addToken(
327                        LayoutTokens.parseToken(((org.apache.myfaces.tobago.component.UIColumn) column).getWidth()));
328                  } else {
329                    newTokens.addToken(RelativeLayoutToken.DEFAULT_INSTANCE);
330                  }
331                } else {
332                  if (i < tokens.getSize()) {
333                    newTokens.addToken(tokens.get(i));
334                  } else {
335                    newTokens.addToken(RelativeLayoutToken.DEFAULT_INSTANCE);
336                  }
337                }
338              }
339            }
340          }
341    
342    
343          int space = LayoutUtil.getInnerSpace(facesContext, this, true);
344          SheetRendererWorkaround renderer
345              = (SheetRendererWorkaround) ComponentUtil.getRenderer(facesContext, this);
346          space -= renderer.getContentBorder(facesContext, this);
347          if (renderer.needVerticalScrollbar(facesContext, this)) {
348            space -= renderer.getScrollbarWidth(facesContext, this);
349          }
350          LayoutInfo layoutInfo = new LayoutInfo(newTokens.getSize(), space, newTokens, getClientId(facesContext), false);
351          parseFixedWidth(facesContext, layoutInfo, rendererdColumns);
352          layoutInfo.parseColumnLayout(space);
353          currentWidthList = layoutInfo.getSpaceList();
354        }
355    
356        if (currentWidthList != null) {
357          if (rendererdColumns.size() != currentWidthList.size()) {
358            LOG.warn("widthList.size() = " + currentWidthList.size()
359                + " != columns.size() = " + rendererdColumns.size() + "  widthList : "
360                + LayoutInfo.listToTokenString(currentWidthList));
361          } else {
362            this.widthList = currentWidthList;
363          }
364        }
365      }
366    
367      private void parseFixedWidth(FacesContext facesContext, LayoutInfo layoutInfo, List<UIColumn> rendereredColumns) {
368        LayoutTokens tokens = layoutInfo.getLayoutTokens();
369        for (int i = 0; i < tokens.getSize(); i++) {
370          LayoutToken token = tokens.get(i);
371          if (token instanceof FixedLayoutToken) {
372            int width = 0;
373            if (!rendereredColumns.isEmpty()) {
374              if (i < rendereredColumns.size()) {
375                UIColumn column = rendereredColumns.get(i);
376                if (column instanceof UIColumnSelector) {
377                  LayoutInformationProvider renderer
378                      = ComponentUtil.getRenderer(facesContext, column);
379                  if (renderer == null) {
380                    LOG.warn("can't find renderer for " + column.getClass().getName());
381                    renderer = ComponentUtil.getRenderer(facesContext, UIPanel.COMPONENT_FAMILY, RENDERER_TYPE_OUT);
382                  }
383                  width = renderer.getFixedWidth(facesContext, column);
384    
385                } else {
386                  for (UIComponent component : (List<UIComponent>) column.getChildren()) {
387                    LayoutInformationProvider renderer
388                        = ComponentUtil.getRenderer(facesContext, component);
389                    width += renderer.getFixedWidth(facesContext, component);
390                  }
391                }
392                layoutInfo.update(width, i);
393              } else {
394                layoutInfo.update(0, i);
395                if (LOG.isWarnEnabled()) {
396                  LOG.warn("More LayoutTokens found than rows! skipping!");
397                }
398              }
399            }
400            if (LOG.isDebugEnabled()) {
401              LOG.debug("set column " + i + " from fixed to with " + width);
402            }
403          }
404        }
405      }
406    
407    
408      private void prepareDimensions(FacesContext facesContext) {
409        // prepare width's in column's children components
410    
411        List<Integer> columnWidths = getWidthList();
412        int i = 0;
413        for (UIColumn column : getRenderedColumns()) {
414          if (i < columnWidths.size()) {
415            Integer width = columnWidths.get(i);
416            if (!(column instanceof UIColumnSelector)) {
417              if (column.getChildCount() == 1) {
418                UIComponent child = (UIComponent) column.getChildren().get(0);
419                int cellPaddingWidth = ((LayoutableRendererBase) getRenderer(facesContext))
420                    .getConfiguredValue(facesContext, this, "cellPaddingWidth");
421                child.getAttributes().put(
422                    ATTR_LAYOUT_WIDTH, width - cellPaddingWidth);
423                child.getAttributes().remove(ATTR_INNER_WIDTH);
424              } else {
425                LOG.warn("More or less than 1 child in column! "
426                    + "Can't set width for column " + i + " to " + width);
427              }
428            }
429          } else {
430            LOG.warn("More columns than columnSizes! "
431                + "Can't set width for column " + i);
432          }
433          i++;
434        }
435      }
436    
437      public int getLast() {
438        int last = getFirst() + getRows();
439        return last < getRowCount() ? last : getRowCount();
440      }
441    
442      public int getPage() {
443        int first = getFirst() + 1;
444        int rows = getRows();
445        if (rows == 0) {
446          // avoid division by zero
447          return 0;
448        }
449        if ((first % rows) > 0) {
450          return (first / rows) + 1;
451        } else {
452          return (first / rows);
453        }
454      }
455    
456      public int getPages() {
457        int rows = getRows();
458        if (rows == 0) {
459          return 0;
460        }
461        return getRowCount() / rows + (getRowCount() % rows == 0 ? 0 : 1);
462      }
463    
464      public List<UIComponent> getRenderedChildrenOf(UIColumn column) {
465        List<UIComponent> children = new ArrayList<UIComponent>();
466        for (Object o : column.getChildren()) {
467          UIComponent kid = (UIComponent) o;
468          if (kid.isRendered()) {
469            children.add(kid);
470          }
471        }
472        return children;
473      }
474    
475      public boolean isAtBeginning() {
476        return getFirst() == 0;
477      }
478    
479      public boolean hasRowCount() {
480        return getRowCount() != -1;
481      }
482    
483      public boolean isAtEnd() {
484        if (!hasRowCount()) {
485          setRowIndex(getFirst() + getRows() + 1);
486          return !isRowAvailable();
487        } else {
488          return getFirst() >= getLastPageIndex();
489        }
490      }
491    
492      public int getLastPageIndex() {
493        int rows = getRows();
494        if (rows == 0) {
495          // avoid division by zero
496          return 0;
497        }
498        int rowCount = getRowCount();
499        int tail = rowCount % rows;
500        return rowCount - (tail != 0 ? tail : rows);
501      }
502    
503      public void processUpdates(FacesContext context) {
504        super.processUpdates(context);
505        updateSheetState(context);
506      }
507    
508      private void updateSheetState(FacesContext facesContext) {
509        SheetState state = getSheetState(facesContext);
510        if (state != null) {
511          // ensure sortActionListener
512    //      getSortActionListener();
513    //      state.setSortedColumn(sortActionListener != null ? sortActionListener.getColumn() : -1);
514    //      state.setAscending(sortActionListener != null && sortActionListener.isAscending());
515          Map attributes = getAttributes();
516          //noinspection unchecked
517          state.setSelectedRows((List<Integer>) attributes.get(ATTR_SELECTED_LIST_STRING));
518          state.setColumnWidths((String) attributes.get(ATTR_WIDTH_LIST_STRING));
519          state.setScrollPosition((Integer[]) attributes.get(ATTR_SCROLL_POSITION));
520          attributes.remove(ATTR_SELECTED_LIST_STRING);
521          attributes.remove(ATTR_SCROLL_POSITION);
522        }
523      }
524    
525    
526      public Object saveState(FacesContext context) {
527        Object[] saveState = new Object[12];
528        saveState[0] = super.saveState(context);
529        saveState[1] = sheetState;
530        saveState[2] = saveAttachedState(context, sortActionListener);
531        saveState[3] = saveAttachedState(context, stateChangeListener);
532        saveState[4] = showHeader;
533        saveState[5] = showRowRange;
534        saveState[6] = showPageRange;
535        saveState[7] = showDirectLinks;
536        saveState[8] = directLinkCount;
537        saveState[9] = selectable;
538        saveState[10] = columns;
539        saveState[11] = rows;
540        return saveState;
541      }
542    
543      public void restoreState(FacesContext context, Object savedState) {
544        Object[] values = (Object[]) savedState;
545        super.restoreState(context, values[0]);
546        sheetState = (SheetState) values[1];
547        sortActionListener = (MethodBinding) restoreAttachedState(context, values[2]);
548        stateChangeListener = (MethodBinding) restoreAttachedState(context, values[3]);
549        showHeader = (Boolean) values[4];
550        showRowRange = (String) values[5];
551        showPageRange = (String) values[6];
552        showDirectLinks = (String) values[7];
553        directLinkCount = (Integer) values[8];
554        selectable = (String) values[9];
555        columns = (String) values[10];
556        rows = (Integer) values[11];
557      }
558    
559    
560      public List<UIColumn> getAllColumns() {
561        List<UIColumn> columns = new ArrayList<UIColumn>();
562        for (UIComponent kid : (List<UIComponent>) getChildren()) {
563          if (kid instanceof UIColumn && !(kid instanceof UIColumnEvent)) {
564            columns.add((UIColumn) kid);
565          }
566        }
567        return columns;
568      }
569    
570      public List<UIColumn> getRenderedColumns() {
571        List<UIColumn> columns = new ArrayList<UIColumn>();
572        for (UIComponent kid : (List<UIComponent>) getChildren()) {
573          if (kid instanceof UIColumn && kid.isRendered() && !(kid instanceof UIColumnEvent)) {
574            columns.add((UIColumn) kid);
575          }
576        }
577        return columns;
578      }
579    
580      public MethodBinding getSortActionListener() {
581        if (sortActionListener != null) {
582          return sortActionListener;
583        } else {
584          return new Sorter();
585        }
586      }
587    
588      public void setSortActionListener(MethodBinding sortActionListener) {
589        this.sortActionListener = sortActionListener;
590      }
591    
592      public void queueEvent(FacesEvent facesEvent) {
593        UIComponent parent = getParent();
594        if (parent == null) {
595          throw new IllegalStateException(
596              "component is not a descendant of a UIViewRoot");
597        }
598    
599        if (facesEvent.getComponent() == this
600            && (facesEvent instanceof SheetStateChangeEvent
601            || facesEvent instanceof PageActionEvent)) {
602          facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
603          if (LOG.isInfoEnabled()) {
604            LOG.info("queueEvent = \"" + facesEvent + "\"");
605          }
606          parent.queueEvent(facesEvent);
607        } else {
608          UIComponent source = facesEvent.getComponent();
609          UIComponent sourceParent = source.getParent();
610          if (sourceParent.getParent() == this
611              && source.getId() != null && source.getId().endsWith(SORTER_ID)) {
612            facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
613            parent.queueEvent(new SortActionEvent(this, (UIColumn) sourceParent));
614          } else {
615            super.queueEvent(facesEvent);
616          }
617        }
618      }
619    
620      public void broadcast(FacesEvent facesEvent) throws AbortProcessingException {
621        super.broadcast(facesEvent);
622        if (facesEvent instanceof SheetStateChangeEvent) {
623          invokeMethodBinding(getStateChangeListener(), facesEvent);
624        } else if (facesEvent instanceof PageActionEvent) {
625          invokeMethodBinding(new Pager(), facesEvent);
626          invokeMethodBinding(getStateChangeListener(), new SheetStateChangeEvent(this));
627        } else if (facesEvent instanceof SortActionEvent) {
628          getSheetState(getFacesContext()).updateSortState((SortActionEvent) facesEvent);
629          invokeMethodBinding(getSortActionListener(), facesEvent);
630        }
631      }
632    
633      private void invokeMethodBinding(MethodBinding methodBinding, FacesEvent event) {
634        if (methodBinding != null && event != null) {
635          try {
636            Object[] objects = new Object[]{event};
637            methodBinding.invoke(getFacesContext(), objects);
638          } catch (EvaluationException e) {
639            Throwable cause = e.getCause();
640            if (cause instanceof AbortProcessingException) {
641              throw (AbortProcessingException) cause;
642            } else {
643              throw e;
644            }
645          }
646        }
647      }
648    
649      public void addStateChangeListener(SheetStateChangeListener listener) {
650        addFacesListener(listener);
651      }
652    
653      public SheetStateChangeListener[] getStateChangeListeners() {
654        return (SheetStateChangeListener[]) getFacesListeners(SheetStateChangeListener.class);
655      }
656    
657      public void removeStateChangeListener(SheetStateChangeListener listener) {
658        removeFacesListener(listener);
659      }
660    
661      public MethodBinding getStateChangeListener() {
662        return stateChangeListener;
663      }
664    
665      public void setStateChangeListener(MethodBinding stateChangeListener) {
666        this.stateChangeListener = stateChangeListener;
667      }
668    
669      public List<Integer> getWidthList() {
670        return widthList;
671      }
672    
673      public int getRows() {
674        if (rows != null) {
675          return rows;
676        }
677        ValueBinding vb = getValueBinding(ATTR_ROWS);
678        if (vb != null) {
679          return (Integer) vb.getValue(getFacesContext());
680        } else {
681          return DEFAULT_ROW_COUNT;
682        }
683      }
684    
685      public void setRows(int rows) {
686        this.rows = rows;
687      }
688    
689      public boolean isShowHeader() {
690        if (showHeader != null) {
691          return showHeader;
692        }
693        ValueBinding vb = getValueBinding(ATTR_SHOW_HEADER);
694        if (vb != null) {
695          return (!Boolean.FALSE.equals(vb.getValue(getFacesContext())));
696        } else {
697          return true;
698        }
699      }
700    
701      public void setShowHeader(boolean showHeader) {
702        this.showHeader = showHeader;
703      }
704    
705      public void encodeAjax(FacesContext facesContext) throws IOException {
706        setupState(facesContext);
707        prepareDimensions(facesContext);
708        // TODO neets more testing!!!
709        //if (!facesContext.getRenderResponse() && !ComponentUtil.hasErrorMessages(facesContext)) {
710        // in encodeBegin of superclass is some logic which clears the DataModel
711        // this must here also done.
712        // in RI and myfaces this could done via setValue(null)
713        ValueBinding binding = getValueBinding("value");
714        if (binding != null) {
715          setValue(null);
716        } else {
717          setValue(getValue());
718        }
719        //}
720        AjaxUtils.encodeAjaxComponent(facesContext, this);
721      }
722    
723      public void processAjax(FacesContext facesContext) throws IOException {
724        final String ajaxId = (String) facesContext.getExternalContext()
725            .getRequestParameterMap().get(AjaxPhaseListener.AJAX_COMPONENT_ID);
726        if (ajaxId.equals(getClientId(facesContext))) {
727          AjaxUtils.processActiveAjaxComponent(facesContext, this);
728        } else {
729          AjaxUtils.processAjaxOnChildren(facesContext, this);
730        }
731      }
732    
733      public Integer[] getScrollPosition() {
734        Integer[] scrollPosition = (Integer[]) getAttributes().get(ATTR_SCROLL_POSITION);
735        if (scrollPosition == null) {
736          scrollPosition = getSheetState(FacesContext.getCurrentInstance()).getScrollPosition();
737        }
738        return scrollPosition;
739      }
740    
741      public UIComponent findComponent(String searchId) {
742        return super.findComponent(stripRowIndex(searchId));
743      }
744    
745      String stripRowIndex(String searchId) {
746        if (searchId.length() > 0 && Character.isDigit(searchId.charAt(0))) {
747          for (int i = 1; i < searchId.length(); ++i) {
748            char c = searchId.charAt(i);
749            if (c == SEPARATOR_CHAR) {
750              searchId = searchId.substring(i + 1);
751              break;
752            }
753            if (!Character.isDigit(c)) {
754              break;
755            }
756          }
757        }
758        return searchId;
759      }
760    }