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