View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.component;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.ColumnEvent;
24  import org.apache.myfaces.tobago.component.ComponentTypes;
25  import org.apache.myfaces.tobago.component.Facets;
26  import org.apache.myfaces.tobago.component.OnComponentPopulated;
27  import org.apache.myfaces.tobago.component.RendererTypes;
28  import org.apache.myfaces.tobago.component.Sorter;
29  import org.apache.myfaces.tobago.component.SupportsRenderedPartially;
30  import org.apache.myfaces.tobago.event.PageActionEvent;
31  import org.apache.myfaces.tobago.event.SheetStateChangeEvent;
32  import org.apache.myfaces.tobago.event.SheetStateChangeListener;
33  import org.apache.myfaces.tobago.event.SheetStateChangeSource2;
34  import org.apache.myfaces.tobago.event.SortActionEvent;
35  import org.apache.myfaces.tobago.event.SortActionSource2;
36  import org.apache.myfaces.tobago.internal.layout.Grid;
37  import org.apache.myfaces.tobago.layout.LayoutComponent;
38  import org.apache.myfaces.tobago.layout.LayoutContainer;
39  import org.apache.myfaces.tobago.layout.LayoutManager;
40  import org.apache.myfaces.tobago.layout.LayoutTokens;
41  import org.apache.myfaces.tobago.model.ExpandedState;
42  import org.apache.myfaces.tobago.model.SelectedState;
43  import org.apache.myfaces.tobago.model.SheetState;
44  import org.apache.myfaces.tobago.renderkit.LayoutComponentRenderer;
45  import org.apache.myfaces.tobago.util.ComponentUtils;
46  import org.apache.myfaces.tobago.util.CreateComponentUtils;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  import javax.el.ELContext;
51  import javax.el.MethodExpression;
52  import javax.el.ValueExpression;
53  import javax.faces.component.UIColumn;
54  import javax.faces.component.UIComponent;
55  import javax.faces.component.UINamingContainer;
56  import javax.faces.context.FacesContext;
57  import javax.faces.event.AbortProcessingException;
58  import javax.faces.event.FacesEvent;
59  import javax.faces.event.PhaseId;
60  import java.io.IOException;
61  import java.util.ArrayList;
62  import java.util.Collections;
63  import java.util.List;
64  import java.util.Map;
65  
66  public abstract class AbstractUISheet extends AbstractUIData
67      implements SheetStateChangeSource2, SortActionSource2, OnComponentPopulated,
68      LayoutContainer, LayoutComponent, SupportsRenderedPartially {
69  
70    private static final Logger LOG = LoggerFactory.getLogger(AbstractUISheet.class);
71  
72    public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.Data";
73  
74    /**
75     * @see org.apache.myfaces.tobago.component.Facets
76     * @deprecated Please use Facets instead. Will be removed after Tobago 1.5.0 */
77    @Deprecated
78    public static final String FACET_SORTER = "sorter";
79    public static final String SORTER_ID = "sorter";
80    /**
81     * @see org.apache.myfaces.tobago.component.Attributes
82     * @deprecated Please use Attributes instead. Will be removed after Tobago 1.5.0 */
83    @Deprecated
84    public static final String ATTR_SCROLL_POSITION = "attrScrollPosition";
85  
86    public static final String NONE = "none";
87    public static final String SINGLE = "single";
88    public static final String MULTI = "multi";
89  
90    private SheetState state;
91    private List<Integer> widthList;
92    private transient LayoutTokens columnLayout;
93  
94    private transient int ajaxResponseCode;
95  
96    private transient List<LayoutComponent> layoutComponents;
97  
98    private transient Boolean needVerticalScrollbar;
99  
100   private transient Grid headerGrid;
101 
102   public LayoutComponentRenderer getLayoutComponentRenderer(FacesContext context) {
103     return (LayoutComponentRenderer) getRenderer(context);
104   }
105 
106   @Override
107   public void encodeBegin(FacesContext facesContext) throws IOException {
108     SheetState state = getSheetState(facesContext);
109     final int first = state.getFirst();
110     if (first > -1 && (!hasRowCount() || first < getRowCount())) {
111       final ValueExpression expression = getValueExpression(Attributes.FIRST);
112       if (expression != null) {
113         expression.setValue(facesContext.getELContext(), first);
114       } else {
115         setFirst(first);
116       }
117     }
118     super.encodeBegin(facesContext);
119   }
120 
121   public void setState(SheetState state) {
122     this.state = state;
123   }
124 
125   public SheetState getState() {
126     return getSheetState(FacesContext.getCurrentInstance());
127   }
128 
129   public SheetState getSheetState(FacesContext facesContext) {
130     if (state != null) {
131       return state;
132     }
133 
134     final ValueExpression expression = getValueExpression(Attributes.STATE);
135     if (expression != null) {
136       final ELContext elContext = facesContext.getELContext();
137       SheetState sheetState = (SheetState) expression.getValue(elContext);
138       if (sheetState == null) {
139         sheetState = new SheetState();
140         expression.setValue(elContext, sheetState);
141       }
142       return sheetState;
143     }
144 
145     state = new SheetState();
146     return state;
147   }
148 
149   public abstract String getColumns();
150 
151   public LayoutTokens getColumnLayout() {
152     if (columnLayout == null) {
153       String columns = getColumns();
154       if (columns != null) {
155         columnLayout = LayoutTokens.parse(columns);
156       }
157     }
158     return columnLayout;
159   }
160 
161   /**
162    * Remove the (by user) resized column widths. An application may provide a button to access it.
163    * Since 1.0.26.
164    */
165   public void resetColumnWidths() {
166     SheetState state = getState();
167     if (state != null) {
168       state.setColumnWidths(null);
169     }
170     getAttributes().remove(Attributes.WIDTH_LIST_STRING);
171   }
172 
173   /**
174    * @deprecated The name of this method is ambiguous.
175    * You may use {@link #getLastRowIndexOfCurrentPage()}. Deprecated since 1.5.5.
176    */
177   public int getLast() {
178     int last = getFirst() + getRows();
179     return last < getRowCount() ? last : getRowCount();
180   }
181 
182   /**
183    * The rowIndex of the last row on the current page plus one (because of zero based iterating).
184    * @throws IllegalArgumentException If the number of rows in the model returned
185    * by {@link #getRowCount()} is -1 (undefined).
186    */
187   public int getLastRowIndexOfCurrentPage() {
188     if (!hasRowCount()) {
189       throw new IllegalArgumentException(
190           "Can't determine the last row, because the row count of the model is unknown.");
191     }
192     if (isRowsUnlimited()) {
193       return getRowCount();
194     }
195     int last = getFirst() + getRows();
196     return last < getRowCount() ? last : getRowCount();
197   }
198 
199   /**
200    * @return returns the current page (based by 0).
201    */
202   public int getCurrentPage() {
203     int rows = getRows();
204     if (rows == 0) {
205       // if the rows are unlimited, there is only one page
206       return 0;
207     }
208     int first = getFirst();
209     if (hasRowCount() && first >= getRowCount()) {
210       return getPages() - 1; // last page
211     } else {
212       return (first / rows);
213     }
214   }
215   
216   /**
217    * @return returns the current page (based by 1).
218    * @deprecated Please use {@link #getCurrentPage()} which returns the value zero-based. Deprecated since 1.5.5.
219    */
220   @Deprecated
221   public int getPage() {
222     return getCurrentPage() + 1;
223   }
224 
225   /**
226    * The number of pages to render.
227    * @throws IllegalArgumentException If the number of rows in the model returned
228    * by {@link #getRowCount()} is -1 (undefined).
229    */
230   public int getPages() {
231     if (isRowsUnlimited()) {
232       return 1;
233     }
234     if (!hasRowCount()) {
235       throw new IllegalArgumentException(
236           "Can't determine the number of pages, because the row count of the model is unknown.");
237     }
238     return (getRowCount() - 1) / getRows() + 1;
239   }
240 
241   public List<UIComponent> getRenderedChildrenOf(UIColumn column) {
242     List<UIComponent> children = new ArrayList<UIComponent>();
243     for (UIComponent kid : column.getChildren()) {
244       if (kid.isRendered()) {
245         children.add(kid);
246       }
247     }
248     return children;
249   }
250 
251   /**
252    * @return Is the interval to display starting with the first row?
253    */
254   public boolean isAtBeginning() {
255     return getFirst() == 0;
256   }
257 
258   /**
259    * @return Does the data model knows the number of rows?
260    */
261   public boolean hasRowCount() {
262     return getRowCount() != -1;
263   }
264 
265   /**
266    * @return Should the paging controls be rendered? Either because of the need of paging or because
267    * the show is enforced by {@link #isShowPagingAlways()}
268    */
269   public boolean isPagingVisible() {
270     return isShowPagingAlways() || needMoreThanOnePage();
271   }
272 
273   /**
274    * @return Is panging needed to display all rows? If the number of rows is unknown this method returns true.
275    */
276   public boolean needMoreThanOnePage() {
277     if (isRowsUnlimited()) {
278       return false;
279     } else if (!hasRowCount()) {
280       return true;
281     } else {
282       return getRowCount() > getRows();
283     }
284   }
285 
286   public abstract boolean isShowPagingAlways();
287 
288   public boolean isAtEnd() {
289     if (!hasRowCount()) {
290       final int old = getRowIndex();
291       setRowIndex(getFirst() + getRows() + 1);
292       final boolean atEnd = !isRowAvailable();
293       setRowIndex(old);
294       return atEnd;
295     } else {
296       return getFirst() >= getFirstRowIndexOfLastPage();
297     }
298   }
299 
300   /**
301    * @deprecated The name of this method is ambiguous.
302    * You may use {@link #getFirstRowIndexOfLastPage()}. Deprecated since 1.5.5.
303    */
304   @Deprecated
305   public int getLastPageIndex() {
306     if (hasRowCount()) {
307       return getFirstRowIndexOfLastPage();
308     } else {
309       return 0;
310     }
311   }
312 
313   /**
314    * Determines the beginning of the last page in the model.
315    * If the number of rows to display on one page is unlimited, the value is 0 (there is only one page).
316    * @return The index of the first row of the last paging page.
317    * @throws IllegalArgumentException If the number of rows in the model returned
318    * by {@link #getRowCount()} is -1 (undefined).
319    */
320   public int getFirstRowIndexOfLastPage() {
321     if (isRowsUnlimited()) {
322       return 0;
323     } else if (!hasRowCount()) {
324       throw new IllegalArgumentException(
325           "Can't determine the last page, because the row count of the model is unknown.");
326     } else {
327       int rows = getRows();
328       int rowCount = getRowCount();
329       int tail = rowCount % rows;
330       return rowCount - (tail != 0 ? tail : rows);
331     }
332   }
333 
334   @Override
335   public void processUpdates(FacesContext context) {
336     super.processUpdates(context);
337     updateSheetState(context);
338   }
339 
340   private void updateSheetState(FacesContext facesContext) {
341     SheetState state = getSheetState(facesContext);
342     if (state != null) {
343       // ensure sortActionListener
344 //      getSortActionListener();
345 //      state.setSortedColumn(sortActionListener != null ? sortActionListener.getColumn() : -1);
346 //      state.setAscending(sortActionListener != null && sortActionListener.isAscending());
347       Map attributes = getAttributes();
348       //noinspection unchecked
349       final List<Integer> list = (List<Integer>) attributes.get(Attributes.SELECTED_LIST_STRING);
350       state.setSelectedRows(list != null ? list : Collections.<Integer>emptyList());
351       state.setColumnWidths((String) attributes.get(Attributes.WIDTH_LIST_STRING));
352       state.setScrollPosition((Integer[]) attributes.get(Attributes.SCROLL_POSITION));
353       attributes.remove(Attributes.SELECTED_LIST_STRING);
354       attributes.remove(Attributes.SCROLL_POSITION);
355     }
356   }
357 
358 
359   @Override
360   public Object saveState(FacesContext context) {
361     Object[] saveState = new Object[2];
362     saveState[0] = super.saveState(context);
363     saveState[1] = state;
364     return saveState;
365   }
366 
367   @Override
368   public void restoreState(FacesContext context, Object savedState) {
369     Object[] values = (Object[]) savedState;
370     super.restoreState(context, values[0]);
371     state = (SheetState) values[1];
372   }
373 
374   public List<AbstractUIColumn> getAllColumns() {
375     List<AbstractUIColumn> columns = new ArrayList<AbstractUIColumn>();
376     for (AbstractUIColumn kid : ComponentUtils.findDescendantList(this, AbstractUIColumn.class)) {
377       columns.add(kid);
378     }
379     return columns;
380   }
381 
382   public List<AbstractUIColumn> getRenderedColumns() {
383     List<AbstractUIColumn> columns = new ArrayList<AbstractUIColumn>();
384     for (AbstractUIColumn kid : ComponentUtils.findDescendantList(this, AbstractUIColumn.class)) {
385       if (kid.isRendered()) {
386         columns.add(kid);
387       }
388     }
389     return columns;
390   }
391 
392 /*  public MethodBinding getSortActionListener() {
393     if (sortActionListener != null) {
394       return sortActionListener;
395     } else {
396       return new Sorter();
397     }
398   }*/
399 
400   @Override
401   public void queueEvent(FacesEvent facesEvent) {
402     UIComponent parent = getParent();
403     if (parent == null) {
404       throw new IllegalStateException("Component is not a descendant of a UIViewRoot");
405     }
406 
407     if (facesEvent.getComponent() == this
408         && (facesEvent instanceof SheetStateChangeEvent
409         || facesEvent instanceof PageActionEvent)) {
410       facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
411       parent.queueEvent(facesEvent);
412     } else {
413       UIComponent source = facesEvent.getComponent();
414       UIComponent sourceParent = source.getParent();
415       if (sourceParent.getParent() == this
416           && source.getId() != null && source.getId().endsWith(SORTER_ID)) {
417         facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
418         parent.queueEvent(new SortActionEvent(this, (UIColumn) sourceParent));
419       } else {
420         super.queueEvent(facesEvent);
421       }
422     }
423   }
424 
425   @Override
426   public void broadcast(FacesEvent facesEvent) throws AbortProcessingException {
427     super.broadcast(facesEvent);
428     if (facesEvent instanceof SheetStateChangeEvent) {
429       final MethodExpression listener = getStateChangeListenerExpression();
430       listener.invoke(getFacesContext().getELContext(), new Object[]{facesEvent});
431     } else if (facesEvent instanceof PageActionEvent) {
432       if (facesEvent.getComponent() == this) {
433         final MethodExpression listener = getStateChangeListenerExpression();
434         if (listener != null) {
435           listener.invoke(getFacesContext().getELContext(), new Object[]{facesEvent});
436         }
437         performPaging((PageActionEvent) facesEvent);
438       }
439     } else if (facesEvent instanceof SortActionEvent) {
440       MethodExpression expression = getSortActionListenerExpression();
441       if (expression!= null) {
442         // TODO should be first invokeMethodBinding and the update state
443         getSheetState(getFacesContext()).updateSortState((SortActionEvent) facesEvent);
444         expression.invoke(getFacesContext().getELContext(), new Object[]{facesEvent});
445       } else {
446         getSheetState(getFacesContext()).updateSortState((SortActionEvent) facesEvent);
447         new Sorter().perform((SortActionEvent) facesEvent);
448       }
449     }
450   }
451 
452   public void addStateChangeListener(SheetStateChangeListener listener) {
453     addFacesListener(listener);
454   }
455 
456   public SheetStateChangeListener[] getStateChangeListeners() {
457     return (SheetStateChangeListener[]) getFacesListeners(SheetStateChangeListener.class);
458   }
459 
460   public void removeStateChangeListener(SheetStateChangeListener listener) {
461     removeFacesListener(listener);
462   }
463 
464   public List<Integer> getWidthList() {
465     return widthList;
466   }
467 
468   public void setWidthList(List<Integer> widthList) {
469     this.widthList = widthList;
470   }
471 
472 
473 
474   public Integer[] getScrollPosition() {
475     Integer[] scrollPosition = (Integer[]) getAttributes().get(Attributes.SCROLL_POSITION);
476     if (scrollPosition == null) {
477       scrollPosition = getSheetState(FacesContext.getCurrentInstance()).getScrollPosition();
478     }
479     return scrollPosition;
480   }
481 
482 
483   @Override
484   public UIComponent findComponent(String searchId) {
485     return super.findComponent(stripRowIndex(searchId));
486   }
487 
488   public String stripRowIndex(String searchId) {
489     if (searchId.length() > 0 && Character.isDigit(searchId.charAt(0))) {
490       for (int i = 1; i < searchId.length(); ++i) {
491         char c = searchId.charAt(i);
492         if (c == UINamingContainer.getSeparatorChar(getFacesContext())) {
493           searchId = searchId.substring(i + 1);
494           break;
495         }
496         if (!Character.isDigit(c)) {
497           break;
498         }
499       }
500     }
501     return searchId;
502   }
503 
504   public void performPaging(PageActionEvent pageEvent) {
505 
506     int first;
507 
508     if (LOG.isDebugEnabled()) {
509       LOG.debug("action = '" + pageEvent.getAction().name() + "'");
510     }
511 
512     switch (pageEvent.getAction()) {
513       case FIRST:
514         first = 0;
515         break;
516       case PREV:
517         first = getFirst() - getRows();
518         first = first < 0 ? 0 : first;
519         break;
520       case NEXT:
521         if (hasRowCount()) {
522           first = getFirst() + getRows();
523           first = first > getRowCount() ? getFirstRowIndexOfLastPage() : first;
524         } else {
525           if (isAtEnd()) {
526             first = getFirst();
527           } else {
528             first = getFirst() + getRows();
529           }
530         }
531         break;
532       case LAST:
533         first = getFirstRowIndexOfLastPage();
534         break;
535       case TO_ROW:
536         first = pageEvent.getValue() - 1;
537         if (hasRowCount() && first > getFirstRowIndexOfLastPage()) {
538           first = getFirstRowIndexOfLastPage();
539         } else if (first < 0) {
540           first = 0;
541         }
542         break;
543       case TO_PAGE:
544         int pageIndex = pageEvent.getValue() - 1;
545         first = pageIndex * getRows();
546         if (hasRowCount() && first > getFirstRowIndexOfLastPage()) {
547           first = getFirstRowIndexOfLastPage();
548         } else if (first < 0) {
549           first = 0;
550         }
551         break;
552       default:
553         // may not happen
554         first = -1;
555     }
556 
557     final ValueExpression expression = getValueExpression(Attributes.FIRST);
558     if (expression != null) {
559       expression.setValue(getFacesContext().getELContext(), first);
560     } else {
561       setFirst(first);
562     }
563 
564     getState().setFirst(first);
565 //      sheet.queueEvent(new SheetStateChangeEvent(sheet));
566   }
567 
568   public List<LayoutComponent> getComponents() {
569     if (layoutComponents != null) {
570       return layoutComponents;
571     }
572     layoutComponents = new ArrayList<LayoutComponent>();
573     for (UIComponent column : getChildren()) {
574       if (column instanceof AbstractUIColumnSelector) {
575         layoutComponents.add(null); // XXX UIColumnSelector is currently not an instance of LayoutComponent
576       } else if (column instanceof ColumnEvent) {
577         // ignore
578       } else if (column instanceof AbstractUIColumnNode) {
579         layoutComponents.add((AbstractUIColumnNode) column);
580       } else if (column instanceof UIColumn) {
581         LayoutComponent layoutComponent = null;
582         for (UIComponent component : column.getChildren()) {
583           if (component instanceof LayoutComponent) {
584             if (layoutComponent == null) {
585               layoutComponent = (LayoutComponent) component;
586             } else {
587               LOG.warn(
588                   "Found more than one layout components inside of a UIColumn: column id='{}' renderer-type='{}'",
589                   column.getClientId(FacesContext.getCurrentInstance()),
590                   component.getRendererType());
591             }
592           }
593         }
594         if (layoutComponent != null) {
595           layoutComponents.add(layoutComponent);
596         } else {
597           final FacesContext facesContext = FacesContext.getCurrentInstance();
598           final AbstractUIOut dummy = (AbstractUIOut) CreateComponentUtils.createComponent(
599               facesContext, ComponentTypes.OUT, RendererTypes.OUT, facesContext.getViewRoot().createUniqueId());
600           dummy.setTransient(true);
601           column.getChildren().add(dummy);
602           layoutComponents.add(dummy);
603           LOG.warn(
604               "Found no component inside of a UIColumn: column id='{}'. Creating a dummy with id='{}'!",
605               column.getClientId(facesContext), dummy.getClientId(facesContext));
606         }
607       }
608     }
609     return layoutComponents;
610   }
611 
612   public void onComponentPopulated(FacesContext facesContext, UIComponent parent) {
613     if (getLayoutManager() == null) {
614       setLayoutManager(CreateComponentUtils.createAndInitLayout(
615           facesContext, ComponentTypes.SHEET_LAYOUT, RendererTypes.SHEET_LAYOUT, parent));
616     }
617   }
618 
619   public LayoutManager getLayoutManager() {
620     return (LayoutManager) getFacet(Facets.LAYOUT);
621   }
622 
623   public void setLayoutManager(LayoutManager layoutManager) {
624     getFacets().put(Facets.LAYOUT, (AbstractUILayoutBase) layoutManager);
625   }
626 
627   public boolean isLayoutChildren() {
628     return isRendered();
629   }
630 
631   public boolean isRendersRowContainer() {
632     return true;
633   }
634 
635   public abstract boolean isShowHeader();
636 
637   public Boolean getNeedVerticalScrollbar() {
638     return needVerticalScrollbar;
639   }
640 
641   public void setNeedVerticalScrollbar(Boolean needVerticalScrollbar) {
642     this.needVerticalScrollbar = needVerticalScrollbar;
643   }
644 
645   @Override
646   public ExpandedState getExpandedState() {
647     return getState().getExpandedState();
648   }
649 
650   @Override
651   public SelectedState getSelectedState() {
652     return getState().getSelectedState();
653   }
654 
655   public Grid getHeaderGrid() {
656     return headerGrid;
657   }
658 
659   public void setHeaderGrid(Grid headerGrid) {
660     this.headerGrid = headerGrid;
661   }
662 }