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