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