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.component.Attributes;
23 import org.apache.myfaces.tobago.component.Visual;
24 import org.apache.myfaces.tobago.event.PageActionEvent;
25 import org.apache.myfaces.tobago.event.SheetStateChangeEvent;
26 import org.apache.myfaces.tobago.event.SheetStateChangeListener;
27 import org.apache.myfaces.tobago.event.SheetStateChangeSource;
28 import org.apache.myfaces.tobago.event.SortActionEvent;
29 import org.apache.myfaces.tobago.event.SortActionSource;
30 import org.apache.myfaces.tobago.internal.layout.Grid;
31 import org.apache.myfaces.tobago.internal.layout.OriginCell;
32 import org.apache.myfaces.tobago.internal.util.SortingUtils;
33 import org.apache.myfaces.tobago.layout.Measure;
34 import org.apache.myfaces.tobago.layout.MeasureList;
35 import org.apache.myfaces.tobago.layout.ShowPosition;
36 import org.apache.myfaces.tobago.model.ExpandedState;
37 import org.apache.myfaces.tobago.model.ScrollPosition;
38 import org.apache.myfaces.tobago.model.SelectedState;
39 import org.apache.myfaces.tobago.model.SheetState;
40 import org.apache.myfaces.tobago.renderkit.RendererBase;
41 import org.apache.myfaces.tobago.util.AjaxUtils;
42 import org.apache.myfaces.tobago.util.ComponentUtils;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import javax.el.ELContext;
47 import javax.el.MethodExpression;
48 import javax.el.ValueExpression;
49 import javax.faces.component.UIColumn;
50 import javax.faces.component.UIComponent;
51 import javax.faces.component.UINamingContainer;
52 import javax.faces.component.behavior.ClientBehaviorHolder;
53 import javax.faces.context.FacesContext;
54 import javax.faces.event.AbortProcessingException;
55 import javax.faces.event.ComponentSystemEvent;
56 import javax.faces.event.ComponentSystemEventListener;
57 import javax.faces.event.FacesEvent;
58 import javax.faces.event.ListenerFor;
59 import javax.faces.event.PhaseId;
60 import javax.faces.event.PreRenderComponentEvent;
61 import javax.faces.render.Renderer;
62 import java.io.IOException;
63 import java.lang.invoke.MethodHandles;
64 import java.util.ArrayList;
65 import java.util.Collections;
66 import java.util.List;
67
68
69
70
71 @ListenerFor(systemEventClass = PreRenderComponentEvent.class)
72 public abstract class AbstractUISheet extends AbstractUIData
73 implements SheetStateChangeSource, SortActionSource, ClientBehaviorHolder, Visual,
74 ComponentSystemEventListener {
75
76 private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
77
78
79
80
81 @Deprecated
82 public static final String COMPONENT_TYPE = "org.apache.myfaces.tobago.Data";
83
84 public static final String SORTER_ID = "sorter";
85 public static final String NOT_SORTABLE_COL_MESSAGE_ID = "org.apache.myfaces.tobago.UISheet.SORTING_COL";
86 public static final String NOT_SORTABLE_MESSAGE_ID = "org.apache.myfaces.tobago.UISheet.SORTING";
87
88 private SheetState state;
89 private transient MeasureList columnLayout;
90 private transient boolean autoLayout;
91
92 private transient Grid headerGrid;
93
94 @Override
95 public void encodeAll(FacesContext facesContext) throws IOException {
96
97 if (isLazy()) {
98 if (getRows() == 0) {
99 LOG.warn("Sheet id={} has lazy=true set, but not set the rows attribute!", getClientId(facesContext));
100 }
101 if (getShowRowRange() != ShowPosition.none) {
102 LOG.warn("Sheet id={} has lazy=true set, but also set showRowRange!=none!", getClientId(facesContext));
103 }
104 if (getShowPageRange() != ShowPosition.none) {
105 LOG.warn("Sheet id={} has lazy=true set, but also set showPageRange!=none!", getClientId(facesContext));
106 }
107 if (getShowDirectLinks() != ShowPosition.none) {
108 LOG.warn("Sheet id={} has lazy=true set, but also set showDirectLinks!=none!", getClientId(facesContext));
109 }
110 }
111
112 final AbstractUIReload reload = ComponentUtils.getReloadFacet(this);
113
114 if (reload != null && AjaxUtils.isAjaxRequest(facesContext) && reload.isRendered() && !reload.isUpdate()) {
115
116 final Renderer renderer = getRenderer(facesContext);
117 if (renderer instanceof RendererBase) {
118 ((RendererBase) renderer).encodeReload(facesContext, reload);
119 } else {
120 LOG.warn("Found reload facet but no renderer support for it id='{}'!", getClientId(facesContext));
121 }
122 } else {
123 super.encodeAll(facesContext);
124 }
125 }
126
127 @Override
128 public void encodeBegin(final FacesContext facesContext) throws IOException {
129 final SheetState theState = getSheetState(facesContext);
130 final int first = theState.getFirst();
131 if (first > -1 && (!hasRowCount() || first < getRowCount())) {
132 final ValueExpression expression = getValueExpression(Attributes.first.getName());
133 if (expression != null) {
134 expression.setValue(facesContext.getELContext(), first);
135 } else {
136 setFirst(first);
137 }
138 }
139
140 super.encodeBegin(facesContext);
141 }
142
143 public void setState(final SheetState state) {
144 this.state = state;
145 }
146
147 public SheetState getState() {
148 return getSheetState(FacesContext.getCurrentInstance());
149 }
150
151 public SheetState getSheetState(final FacesContext facesContext) {
152 if (state != null) {
153 return state;
154 }
155
156 final ValueExpression expression = getValueExpression(Attributes.state.getName());
157 if (expression != null) {
158 final ELContext elContext = facesContext.getELContext();
159 SheetState sheetState = (SheetState) expression.getValue(elContext);
160 if (sheetState == null) {
161 sheetState = new SheetState();
162 expression.setValue(elContext, sheetState);
163 }
164 return sheetState;
165 }
166
167 state = new SheetState();
168 return state;
169 }
170
171 public abstract String getColumns();
172
173 @Override
174 public void processEvent(final ComponentSystemEvent event) throws AbortProcessingException {
175
176 super.processEvent(event);
177
178 if (event instanceof PreRenderComponentEvent) {
179 final String columns = getColumns();
180 if (columns != null) {
181 columnLayout = MeasureList.parse(columns);
182 }
183
184 autoLayout = true;
185 if (columnLayout != null) {
186 for (final Measure token : columnLayout) {
187 if (token != Measure.AUTO) {
188 autoLayout = false;
189 break;
190 }
191 }
192 }
193
194 LOG.debug("autoLayout={}", autoLayout);
195 }
196 }
197
198 public MeasureList getColumnLayout() {
199 return columnLayout;
200 }
201
202 public boolean isAutoLayout() {
203 return autoLayout;
204 }
205
206
207
208
209
210
211
212 public int getLastRowIndexOfCurrentPage() {
213 if (!hasRowCount()) {
214 throw new IllegalArgumentException(
215 "Can't determine the last row, because the row count of the model is unknown.");
216 }
217 if (isRowsUnlimited()) {
218 return getRowCount();
219 }
220 final int last = getFirst() + getRows();
221 return Math.min(last, getRowCount());
222 }
223
224
225
226
227 public int getCurrentPage() {
228 final int rows = getRows();
229 if (rows == 0) {
230
231 return 0;
232 }
233 final int first = getFirst();
234 if (hasRowCount() && first >= getRowCount()) {
235 return getPages() - 1;
236 } else {
237 return first / rows;
238 }
239 }
240
241
242
243
244
245
246
247 public int getPages() {
248 if (isRowsUnlimited()) {
249 return 1;
250 }
251 if (!hasRowCount()) {
252 throw new IllegalArgumentException(
253 "Can't determine the number of pages, because the row count of the model is unknown.");
254 }
255 return (getRowCount() - 1) / getRows() + 1;
256 }
257
258 public List<UIComponent> getRenderedChildrenOf(final UIColumn column) {
259 final List<UIComponent> children = new ArrayList<>();
260 for (final UIComponent kid : column.getChildren()) {
261 if (kid.isRendered()) {
262 children.add(kid);
263 }
264 }
265 return children;
266 }
267
268
269
270
271 public boolean isAtBeginning() {
272 return getFirst() == 0;
273 }
274
275
276
277
278 public boolean hasRowCount() {
279 return getRowCount() != -1;
280 }
281
282
283
284
285
286 public boolean isPagingVisible() {
287 return isShowPagingAlways() || needMoreThanOnePage();
288 }
289
290
291
292
293 public boolean needMoreThanOnePage() {
294 if (isRowsUnlimited()) {
295 return false;
296 } else if (!hasRowCount()) {
297 return true;
298 } else {
299 return getRowCount() > getRows();
300 }
301 }
302
303 public abstract boolean isShowPagingAlways();
304
305 public boolean isAtEnd() {
306 if (!hasRowCount()) {
307 final int old = getRowIndex();
308 setRowIndex(getFirst() + getRows() + 1);
309 final boolean atEnd = !isRowAvailable();
310 setRowIndex(old);
311 return atEnd;
312 } else {
313 return getFirst() >= getFirstRowIndexOfLastPage();
314 }
315 }
316
317
318
319
320
321
322
323
324
325 public int getFirstRowIndexOfLastPage() {
326 if (isRowsUnlimited()) {
327 return 0;
328 } else if (!hasRowCount()) {
329 throw new IllegalArgumentException(
330 "Can't determine the last page, because the row count of the model is unknown.");
331 } else {
332 final int rows = getRows();
333 final int rowCount = getRowCount();
334 final int tail = rowCount % rows;
335 return rowCount - (tail != 0 ? tail : rows);
336 }
337 }
338
339 @Override
340 public void processUpdates(final FacesContext context) {
341 super.processUpdates(context);
342
343 final SheetState sheetState = getSheetState(context);
344 if (sheetState != null) {
345 final List<Integer> list = (List<Integer>) ComponentUtils.getAttribute(this, Attributes.selectedListString);
346 sheetState.setSelectedRows(list != null ? list : Collections.emptyList());
347 ComponentUtils.removeAttribute(this, Attributes.selectedListString);
348 ComponentUtils.removeAttribute(this, Attributes.scrollPosition);
349 }
350 }
351
352 @Override
353 public Object saveState(final FacesContext context) {
354 final Object[] saveState = new Object[2];
355 saveState[0] = super.saveState(context);
356 saveState[1] = state;
357 return saveState;
358 }
359
360 @Override
361 public void restoreState(final FacesContext context, final Object savedState) {
362 final Object[] values = (Object[]) savedState;
363 super.restoreState(context, values[0]);
364 state = (SheetState) values[1];
365 }
366
367 public List<AbstractUIColumnBase> getAllColumns() {
368 final ArrayList<AbstractUIColumnBase> result = new ArrayList<>();
369 findColumns(this, result, true);
370 return result;
371 }
372
373 private void findColumns(final UIComponent component, final List<AbstractUIColumnBase> result, final boolean all) {
374 for (final UIComponent child : component.getChildren()) {
375 if (all || child.isRendered()) {
376 if (child instanceof AbstractUIColumnBase) {
377 result.add((AbstractUIColumnBase) child);
378 } else if (child instanceof AbstractUIData) {
379
380 } else {
381 findColumns(child, result, all);
382 }
383 }
384 }
385 }
386
387 @Override
388 public void queueEvent(final FacesEvent facesEvent) {
389 final UIComponent parent = getParent();
390 if (parent == null) {
391 throw new IllegalStateException("Component is not a descendant of a UIViewRoot");
392 }
393
394 if (facesEvent.getComponent() == this
395 && (facesEvent instanceof SheetStateChangeEvent
396 || facesEvent instanceof PageActionEvent)) {
397 facesEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
398 parent.queueEvent(facesEvent);
399 } else {
400 super.queueEvent(facesEvent);
401 }
402 }
403
404 @Override
405 public void broadcast(final FacesEvent facesEvent) throws AbortProcessingException {
406 super.broadcast(facesEvent);
407 if (facesEvent instanceof SheetStateChangeEvent) {
408 final MethodExpression listener = getStateChangeListenerExpression();
409 listener.invoke(getFacesContext().getELContext(), new Object[]{facesEvent});
410 } else if (facesEvent instanceof PageActionEvent) {
411 if (facesEvent.getComponent() == this) {
412 final MethodExpression listener = getStateChangeListenerExpression();
413 if (listener != null) {
414 listener.invoke(getFacesContext().getELContext(), new Object[]{facesEvent});
415 }
416 performPaging((PageActionEvent) facesEvent);
417 }
418 } else if (facesEvent instanceof SortActionEvent) {
419 getSheetState(getFacesContext()).updateSortState(((SortActionEvent) facesEvent).getColumn().getId());
420 sort(getFacesContext(), (SortActionEvent) facesEvent);
421 }
422 }
423
424 public void init(final FacesContext facesContext) {
425 sort(facesContext, null);
426 layoutHeader();
427 }
428
429 private void layoutHeader() {
430 final UIComponent header = getHeader();
431 if (header == null) {
432 LOG.warn("This should not happen. Please file a bug in the issue tracker to reproduce this case.");
433 return;
434 }
435 final MeasureList tokens = new MeasureList();
436 final List<AbstractUIColumnBase> columns = getAllColumns();
437 for (final UIColumn column : columns) {
438 if (!(column instanceof AbstractUIRow)) {
439 tokens.add(Measure.FRACTION1);
440 }
441 }
442 final MeasureList rows = new MeasureList();
443 rows.add(Measure.AUTO);
444 final Grid grid = new Grid(tokens, rows);
445
446 for (final UIComponent child : header.getChildren()) {
447 if (child.isRendered()) {
448 final int columnSpan = ComponentUtils.getIntAttribute(child, Attributes.columnSpan, 1);
449 final int rowSpan = ComponentUtils.getIntAttribute(child, Attributes.rowSpan, 1);
450 grid.add(new OriginCell(child), columnSpan, rowSpan);
451 }
452 }
453 setHeaderGrid(grid);
454 }
455
456 protected void sort(final FacesContext facesContext, final SortActionEvent event) {
457 final SheetState sheetState = getSheetState(getFacesContext());
458 if (sheetState.isToBeSorted()) {
459 final MethodExpression expression = getSortActionListenerExpression();
460 if (expression != null) {
461 try {
462 expression.invoke(facesContext.getELContext(),
463 new Object[]{
464 event != null
465 ? event
466 : new SortActionEvent(this,
467 (UIColumn) findComponent(getSheetState(facesContext).getSortedColumnId()))});
468 } catch (final Exception e) {
469 LOG.warn("Sorting not possible!", e);
470 }
471 } else {
472 SortingUtils.sort(this, null);
473 }
474 sheetState.setToBeSorted(false);
475 }
476 }
477
478 @Override
479 public void addStateChangeListener(final SheetStateChangeListener listener) {
480 addFacesListener(listener);
481 }
482
483 @Override
484 public SheetStateChangeListener[] getStateChangeListeners() {
485 return (SheetStateChangeListener[]) getFacesListeners(SheetStateChangeListener.class);
486 }
487
488 @Override
489 public void removeStateChangeListener(final SheetStateChangeListener listener) {
490 removeFacesListener(listener);
491 }
492
493 @Override
494 public UIComponent findComponent(final String searchId) {
495 return super.findComponent(stripRowIndex(searchId));
496 }
497
498 public String stripRowIndex(final String initialSearchId) {
499 String searchId = initialSearchId;
500 if (searchId.length() > 0 && Character.isDigit(searchId.charAt(0))) {
501 for (int i = 1; i < searchId.length(); ++i) {
502 final char c = searchId.charAt(i);
503 if (c == UINamingContainer.getSeparatorChar(getFacesContext())) {
504 searchId = searchId.substring(i + 1);
505 break;
506 }
507 if (!Character.isDigit(c)) {
508 break;
509 }
510 }
511 }
512 return searchId;
513 }
514
515 public void performPaging(final PageActionEvent pageEvent) {
516
517 int first;
518
519 if (LOG.isDebugEnabled()) {
520 LOG.debug("action = '" + pageEvent.getAction().name() + "'");
521 }
522
523 ScrollPosition scrollPosition = getState().getScrollPosition();
524 scrollPosition.setTop(0);
525 switch (pageEvent.getAction()) {
526 case first:
527 first = 0;
528 break;
529 case prev:
530 first = getFirst() - getRows();
531 first = Math.max(first, 0);
532 scrollPosition.setTop(Integer.MAX_VALUE);
533 break;
534 case next:
535 if (hasRowCount()) {
536 first = getFirst() + getRows();
537 first = first > getRowCount() ? getFirstRowIndexOfLastPage() : first;
538 } else {
539 if (isAtEnd()) {
540 first = getFirst();
541 } else {
542 first = getFirst() + getRows();
543 }
544 }
545 break;
546 case last:
547 first = getFirstRowIndexOfLastPage();
548 break;
549 case toRow:
550 case lazy:
551 first = pageEvent.getValue() - 1;
552 if (hasRowCount() && first > getFirstRowIndexOfLastPage()) {
553 first = getFirstRowIndexOfLastPage();
554 } else if (first < 0) {
555 first = 0;
556 }
557 break;
558 case toPage:
559 final int pageIndex = pageEvent.getValue() - 1;
560 first = pageIndex * getRows();
561 if (hasRowCount() && first > getFirstRowIndexOfLastPage()) {
562 first = getFirstRowIndexOfLastPage();
563 } else if (first < 0) {
564 first = 0;
565 }
566 break;
567 default:
568
569 first = -1;
570 }
571
572 final ValueExpression expression = getValueExpression(Attributes.first.getName());
573 if (expression != null) {
574 expression.setValue(getFacesContext().getELContext(), first);
575 } else {
576 setFirst(first);
577 }
578
579 getState().setFirst(first);
580 }
581
582 @Override
583 public boolean isRendersRowContainer() {
584 return true;
585 }
586
587 public abstract boolean isShowHeader();
588
589 @Override
590 public ExpandedState getExpandedState() {
591 return getState().getExpandedState();
592 }
593
594 @Override
595 public SelectedState getSelectedState() {
596 return getState().getSelectedState();
597 }
598
599 public Grid getHeaderGrid() {
600 return headerGrid;
601 }
602
603 public void setHeaderGrid(final Grid headerGrid) {
604 this.headerGrid = headerGrid;
605 }
606
607 public abstract boolean isShowDirectLinksArrows();
608
609 public abstract boolean isShowPageRangeArrows();
610
611 public abstract ShowPosition getShowRowRange();
612
613 public abstract ShowPosition getShowPageRange();
614
615 public abstract ShowPosition getShowDirectLinks();
616
617 public abstract boolean isLazy();
618 }