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