1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.internal.component;
21
22 import org.apache.myfaces.tobago.compat.FacesUtils;
23 import org.apache.myfaces.tobago.component.Attributes;
24 import org.apache.myfaces.tobago.component.ColumnEvent;
25 import org.apache.myfaces.tobago.component.ComponentTypes;
26 import org.apache.myfaces.tobago.component.Facets;
27 import org.apache.myfaces.tobago.component.OnComponentPopulated;
28 import org.apache.myfaces.tobago.component.RendererTypes;
29 import org.apache.myfaces.tobago.component.Sorter;
30 import org.apache.myfaces.tobago.component.SupportsRenderedPartially;
31 import org.apache.myfaces.tobago.event.PageActionEvent;
32 import org.apache.myfaces.tobago.event.SheetStateChangeEvent;
33 import org.apache.myfaces.tobago.event.SheetStateChangeListener;
34 import org.apache.myfaces.tobago.event.SheetStateChangeSource;
35 import org.apache.myfaces.tobago.event.SortActionEvent;
36 import org.apache.myfaces.tobago.event.SortActionSource;
37 import org.apache.myfaces.tobago.internal.layout.Grid;
38 import org.apache.myfaces.tobago.layout.LayoutComponent;
39 import org.apache.myfaces.tobago.layout.LayoutContainer;
40 import org.apache.myfaces.tobago.layout.LayoutManager;
41 import org.apache.myfaces.tobago.layout.LayoutTokens;
42 import org.apache.myfaces.tobago.model.ExpandedState;
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.ValueExpression;
52 import javax.faces.component.UIColumn;
53 import javax.faces.component.UIComponent;
54 import javax.faces.context.FacesContext;
55 import javax.faces.el.MethodBinding;
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 SheetStateChangeSource, SortActionSource, 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
75
76 @Deprecated
77 public static final String FACET_SORTER = "sorter";
78 public static final String SORTER_ID = "sorter";
79
80
81
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 int ajaxResponseCode;
94
95 private transient List<LayoutComponent> layoutComponents;
96
97 private transient Boolean needVerticalScrollbar;
98
99 private transient Grid headerGrid;
100
101 public LayoutComponentRenderer getLayoutComponentRenderer(FacesContext context) {
102 return (LayoutComponentRenderer) getRenderer(context);
103 }
104
105 @Override
106 public void encodeBegin(FacesContext facesContext) throws IOException {
107 SheetState state = getSheetState(facesContext);
108 final int first = state.getFirst();
109 if (first > -1 && (!hasRowCount() || first < getRowCount())) {
110 final ValueExpression expression = getValueExpression(Attributes.FIRST);
111 if (expression != null) {
112 expression.setValue(facesContext.getELContext(), first);
113 } else {
114 setFirst(first);
115 }
116 }
117 super.encodeBegin(facesContext);
118 }
119
120 public void setState(SheetState state) {
121 this.state = state;
122 }
123
124 public SheetState getState() {
125 return getSheetState(FacesContext.getCurrentInstance());
126 }
127
128 public SheetState getSheetState(FacesContext facesContext) {
129 if (state != null) {
130 return state;
131 }
132
133 final ValueExpression expression = getValueExpression(Attributes.STATE);
134 if (expression != null) {
135 final ELContext elContext = facesContext.getELContext();
136 SheetState sheetState = (SheetState) expression.getValue(elContext);
137 if (sheetState == null) {
138 sheetState = new SheetState();
139 expression.setValue(elContext, sheetState);
140 }
141 return sheetState;
142 }
143
144 state = new SheetState();
145 return state;
146 }
147
148 public abstract String getColumns();
149
150 public LayoutTokens getColumnLayout() {
151 if (columnLayout == null) {
152 String columns = getColumns();
153 if (columns != null) {
154 columnLayout = LayoutTokens.parse(columns);
155 }
156 }
157 return columnLayout;
158 }
159
160
161
162
163
164 public void resetColumnWidths() {
165 SheetState state = getState();
166 if (state != null) {
167 state.setColumnWidths(null);
168 }
169 getAttributes().remove(Attributes.WIDTH_LIST_STRING);
170 }
171
172
173
174
175
176 public int getLast() {
177 int last = getFirst() + getRows();
178 return last < getRowCount() ? last : getRowCount();
179 }
180
181
182
183
184
185
186 public int getLastRowIndexOfCurrentPage() {
187 if (!hasRowCount()) {
188 throw new IllegalArgumentException(
189 "Can't determine the last row, because the row count of the model is unknown.");
190 }
191 if (isRowsUnlimited()) {
192 return getRowCount();
193 }
194 int last = getFirst() + getRows();
195 return last < getRowCount() ? last : getRowCount();
196 }
197
198
199
200
201 public int getCurrentPage() {
202 int rows = getRows();
203 if (rows == 0) {
204
205 return 0;
206 }
207 int first = getFirst();
208 if (hasRowCount() && first >= getRowCount()) {
209 return getPages() - 1;
210 } else {
211 return (first / rows);
212 }
213 }
214
215
216
217
218
219 @Deprecated
220 public int getPage() {
221 return getCurrentPage() + 1;
222 }
223
224
225
226
227
228
229 public int getPages() {
230 if (isRowsUnlimited()) {
231 return 1;
232 }
233 if (!hasRowCount()) {
234 throw new IllegalArgumentException(
235 "Can't determine the number of pages, because the row count of the model is unknown.");
236 }
237 return (getRowCount() - 1) / getRows() + 1;
238 }
239
240 public List<UIComponent> getRenderedChildrenOf(UIColumn column) {
241 List<UIComponent> children = new ArrayList<UIComponent>();
242 for (UIComponent kid : column.getChildren()) {
243 if (kid.isRendered()) {
244 children.add(kid);
245 }
246 }
247 return children;
248 }
249
250
251
252
253 public boolean isAtBeginning() {
254 return getFirst() == 0;
255 }
256
257
258
259
260 public boolean hasRowCount() {
261 return getRowCount() != -1;
262 }
263
264
265
266
267
268 public boolean isPagingVisible() {
269 return isShowPagingAlways() || needMoreThanOnePage();
270 }
271
272
273
274
275 public boolean needMoreThanOnePage() {
276 if (isRowsUnlimited()) {
277 return false;
278 } else if (!hasRowCount()) {
279 return true;
280 } else {
281 return getRowCount() > getRows();
282 }
283 }
284
285 public abstract boolean isShowPagingAlways();
286
287 public boolean isAtEnd() {
288 if (!hasRowCount()) {
289 final int old = getRowIndex();
290 setRowIndex(getFirst() + getRows() + 1);
291 final boolean atEnd = !isRowAvailable();
292 setRowIndex(old);
293 return atEnd;
294 } else {
295 return getFirst() >= getFirstRowIndexOfLastPage();
296 }
297 }
298
299
300
301
302
303 @Deprecated
304 public int getLastPageIndex() {
305 if (hasRowCount()) {
306 return getFirstRowIndexOfLastPage();
307 } else {
308 return 0;
309 }
310 }
311
312
313
314
315
316
317
318
319 public int getFirstRowIndexOfLastPage() {
320 if (isRowsUnlimited()) {
321 return 0;
322 } else if (!hasRowCount()) {
323 throw new IllegalArgumentException(
324 "Can't determine the last page, because the row count of the model is unknown.");
325 } else {
326 int rows = getRows();
327 int rowCount = getRowCount();
328 int tail = rowCount % rows;
329 return rowCount - (tail != 0 ? tail : rows);
330 }
331 }
332
333 @Override
334 public void processUpdates(FacesContext context) {
335 super.processUpdates(context);
336 updateSheetState(context);
337 }
338
339 private void updateSheetState(FacesContext facesContext) {
340 SheetState state = getSheetState(facesContext);
341 if (state != null) {
342
343
344
345
346 Map attributes = getAttributes();
347
348 final List<Integer> list = (List<Integer>) attributes.get(Attributes.SELECTED_LIST_STRING);
349 state.setSelectedRows(list != null ? list : Collections.<Integer>emptyList());
350 state.setColumnWidths((String) attributes.get(Attributes.WIDTH_LIST_STRING));
351 state.setScrollPosition((Integer[]) attributes.get(Attributes.SCROLL_POSITION));
352 attributes.remove(Attributes.SELECTED_LIST_STRING);
353 attributes.remove(Attributes.SCROLL_POSITION);
354 }
355 }
356
357
358 @Override
359 public Object saveState(FacesContext context) {
360 Object[] saveState = new Object[2];
361 saveState[0] = super.saveState(context);
362 saveState[1] = state;
363 return saveState;
364 }
365
366 @Override
367 public void restoreState(FacesContext context, Object savedState) {
368 Object[] values = (Object[]) savedState;
369 super.restoreState(context, values[0]);
370 state = (SheetState) values[1];
371 }
372
373 public List<AbstractUIColumn> getAllColumns() {
374 List<AbstractUIColumn> columns = new ArrayList<AbstractUIColumn>();
375 for (AbstractUIColumn kid : ComponentUtils.findDescendantList(this, AbstractUIColumn.class)) {
376 columns.add(kid);
377 }
378 return columns;
379 }
380
381 public List<AbstractUIColumn> getRenderedColumns() {
382 List<AbstractUIColumn> columns = new ArrayList<AbstractUIColumn>();
383 for (AbstractUIColumn kid : ComponentUtils.findDescendantList(this, AbstractUIColumn.class)) {
384 if (kid.isRendered()) {
385 columns.add(kid);
386 }
387 }
388 return columns;
389 }
390
391
392
393
394
395
396
397
398
399 @Override
400 public void queueEvent(FacesEvent facesEvent) {
401 UIComponent parent = getParent();
402 if (parent == null) {
403 throw new IllegalStateException("Component is not a descendant of a UIViewRoot");
404 }
405
406 if (facesEvent.getComponent() == this
407 && (facesEvent instanceof SheetStateChangeEvent
408 || facesEvent instanceof PageActionEvent)) {
409 facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
410 parent.queueEvent(facesEvent);
411 } else {
412 UIComponent source = facesEvent.getComponent();
413 UIComponent sourceParent = source.getParent();
414 if (sourceParent.getParent() == this
415 && source.getId() != null && source.getId().endsWith(SORTER_ID)) {
416 facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
417 parent.queueEvent(new SortActionEvent(this, (UIColumn) sourceParent));
418 } else {
419 super.queueEvent(facesEvent);
420 }
421 }
422 }
423
424 @Override
425 public void broadcast(FacesEvent facesEvent) throws AbortProcessingException {
426 super.broadcast(facesEvent);
427 if (facesEvent instanceof SheetStateChangeEvent) {
428 FacesUtils.invokeMethodBinding(getFacesContext(), getStateChangeListener(), facesEvent);
429 } else if (facesEvent instanceof PageActionEvent) {
430 if (facesEvent.getComponent() == this) {
431 FacesUtils.invokeMethodBinding(
432 getFacesContext(), getStateChangeListener(), new SheetStateChangeEvent(this));
433 performPaging((PageActionEvent) facesEvent);
434 }
435 } else if (facesEvent instanceof SortActionEvent) {
436 MethodBinding methodBinding = getSortActionListener();
437 if (methodBinding!= null) {
438
439 getSheetState(getFacesContext()).updateSortState((SortActionEvent) facesEvent);
440 FacesUtils.invokeMethodBinding(getFacesContext(), methodBinding, facesEvent);
441 } else {
442 getSheetState(getFacesContext()).updateSortState((SortActionEvent) facesEvent);
443 new Sorter().perform((SortActionEvent) facesEvent);
444 }
445 }
446 }
447
448 public void addStateChangeListener(SheetStateChangeListener listener) {
449 addFacesListener(listener);
450 }
451
452 public SheetStateChangeListener[] getStateChangeListeners() {
453 return (SheetStateChangeListener[]) getFacesListeners(SheetStateChangeListener.class);
454 }
455
456 public void removeStateChangeListener(SheetStateChangeListener listener) {
457 removeFacesListener(listener);
458 }
459
460 public List<Integer> getWidthList() {
461 return widthList;
462 }
463
464 public void setWidthList(List<Integer> widthList) {
465 this.widthList = widthList;
466 }
467
468
469
470 public Integer[] getScrollPosition() {
471 Integer[] scrollPosition = (Integer[]) getAttributes().get(Attributes.SCROLL_POSITION);
472 if (scrollPosition == null) {
473 scrollPosition = getSheetState(FacesContext.getCurrentInstance()).getScrollPosition();
474 }
475 return scrollPosition;
476 }
477
478
479 @Override
480 public UIComponent findComponent(String searchId) {
481 return super.findComponent(stripRowIndex(searchId));
482 }
483
484 public String stripRowIndex(String searchId) {
485 if (searchId.length() > 0 && Character.isDigit(searchId.charAt(0))) {
486 for (int i = 1; i < searchId.length(); ++i) {
487 char c = searchId.charAt(i);
488 if (c == SEPARATOR_CHAR) {
489 searchId = searchId.substring(i + 1);
490 break;
491 }
492 if (!Character.isDigit(c)) {
493 break;
494 }
495 }
496 }
497 return searchId;
498 }
499
500 public void performPaging(PageActionEvent pageEvent) {
501
502 int first;
503
504 if (LOG.isDebugEnabled()) {
505 LOG.debug("action = '" + pageEvent.getAction().name() + "'");
506 }
507
508 switch (pageEvent.getAction()) {
509 case FIRST:
510 first = 0;
511 break;
512 case PREV:
513 first = getFirst() - getRows();
514 first = first < 0 ? 0 : first;
515 break;
516 case NEXT:
517 if (hasRowCount()) {
518 first = getFirst() + getRows();
519 first = first > getRowCount() ? getFirstRowIndexOfLastPage() : first;
520 } else {
521 if (isAtEnd()) {
522 first = getFirst();
523 } else {
524 first = getFirst() + getRows();
525 }
526 }
527 break;
528 case LAST:
529 first = getFirstRowIndexOfLastPage();
530 break;
531 case TO_ROW:
532 first = pageEvent.getValue() - 1;
533 if (hasRowCount() && first > getFirstRowIndexOfLastPage()) {
534 first = getFirstRowIndexOfLastPage();
535 } else if (first < 0) {
536 first = 0;
537 }
538 break;
539 case TO_PAGE:
540 int pageIndex = pageEvent.getValue() - 1;
541 first = pageIndex * getRows();
542 if (hasRowCount() && first > getFirstRowIndexOfLastPage()) {
543 first = getFirstRowIndexOfLastPage();
544 } else if (first < 0) {
545 first = 0;
546 }
547 break;
548 default:
549
550 first = -1;
551 }
552
553 final ValueExpression expression = getValueExpression(Attributes.FIRST);
554 if (expression != null) {
555 expression.setValue(getFacesContext().getELContext(), first);
556 } else {
557 setFirst(first);
558 }
559
560 getState().setFirst(first);
561
562 }
563
564 public List<LayoutComponent> getComponents() {
565 if (layoutComponents != null) {
566 return layoutComponents;
567 }
568 layoutComponents = new ArrayList<LayoutComponent>();
569 for (UIComponent column : getChildren()) {
570 if (column instanceof AbstractUIColumnSelector) {
571 layoutComponents.add(null);
572 } else if (column instanceof ColumnEvent) {
573
574 } else if (column instanceof AbstractUIColumnNode) {
575 layoutComponents.add((AbstractUIColumnNode) column);
576 } else if (column instanceof UIColumn) {
577 LayoutComponent layoutComponent = null;
578 for (UIComponent component : column.getChildren()) {
579 if (component instanceof LayoutComponent) {
580 if (layoutComponent == null) {
581 layoutComponent = (LayoutComponent) component;
582 } else {
583 LOG.warn(
584 "Found more than one layout components inside of a UIColumn: column id='{}' renderer-type='{}'",
585 column.getClientId(FacesContext.getCurrentInstance()),
586 component.getRendererType());
587 }
588 }
589 }
590 if (layoutComponent != null) {
591 layoutComponents.add(layoutComponent);
592 } else {
593 final FacesContext facesContext = FacesContext.getCurrentInstance();
594 final AbstractUIOut dummy = (AbstractUIOut) CreateComponentUtils.createComponent(
595 facesContext, ComponentTypes.OUT, RendererTypes.OUT, facesContext.getViewRoot().createUniqueId());
596 dummy.setTransient(true);
597 column.getChildren().add(dummy);
598 layoutComponents.add(dummy);
599 LOG.warn(
600 "Found no component inside of a UIColumn: column id='{}'. Creating a dummy with id='{}'!",
601 column.getClientId(facesContext), dummy.getClientId(facesContext));
602 }
603 }
604 }
605 return layoutComponents;
606 }
607
608 public void onComponentPopulated(FacesContext facesContext, UIComponent parent) {
609 if (getLayoutManager() == null) {
610 setLayoutManager(CreateComponentUtils.createAndInitLayout(
611 facesContext, ComponentTypes.SHEET_LAYOUT, RendererTypes.SHEET_LAYOUT, parent));
612 }
613 }
614
615 public LayoutManager getLayoutManager() {
616 return (LayoutManager) getFacet(Facets.LAYOUT);
617 }
618
619 public void setLayoutManager(LayoutManager layoutManager) {
620 getFacets().put(Facets.LAYOUT, (AbstractUILayoutBase) layoutManager);
621 }
622
623 public boolean isLayoutChildren() {
624 return isRendered();
625 }
626
627 public boolean isRendersRowContainer() {
628 return true;
629 }
630
631 public abstract boolean isShowHeader();
632
633 public Boolean getNeedVerticalScrollbar() {
634 return needVerticalScrollbar;
635 }
636
637 public void setNeedVerticalScrollbar(Boolean needVerticalScrollbar) {
638 this.needVerticalScrollbar = needVerticalScrollbar;
639 }
640
641 @Override
642 public ExpandedState getExpandedState() {
643 return getState().getExpandedState();
644 }
645
646 public Grid getHeaderGrid() {
647 return headerGrid;
648 }
649
650 public void setHeaderGrid(Grid headerGrid) {
651 this.headerGrid = headerGrid;
652 }
653 }