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