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