1 // WARNING: This file was automatically generated. Do not edit it directly,
2 // or you will lose your changes.
3
4 /*
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 */
22 package org.apache.myfaces.trinidad.component;
23
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.IdentityHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import javax.el.MethodExpression;
32 import javax.faces.component.UIComponent;
33 import javax.faces.component.visit.VisitCallback;
34 import javax.faces.component.visit.VisitContext;
35 import javax.faces.context.FacesContext;
36 import javax.faces.el.MethodBinding;
37 import javax.faces.event.AbortProcessingException;
38 import javax.faces.event.FacesEvent;
39 import javax.faces.event.PhaseId;
40 import org.apache.myfaces.trinidad.bean.FacesBean;
41 import org.apache.myfaces.trinidad.bean.PropertyKey;
42 import org.apache.myfaces.trinidad.event.FocusEvent;
43 import org.apache.myfaces.trinidad.event.RangeChangeEvent;
44 import org.apache.myfaces.trinidad.event.RangeChangeListener;
45 import org.apache.myfaces.trinidad.model.RowKeySet;
46 import org.apache.myfaces.trinidad.util.ComponentUtils;
47
48 /**
49 *
50 * <html:p>
51 * The Apache Trinidad TreeTable is used to display data that is structured in a hierarchical format.
52 * This component displays a hierarchy
53 * in a UI similar to a Trinidad Table, and is more elaborate than the Tree component.
54 * TreeTable supports displaying columns of data per element in the hierarchy.
55 * Unlike the Tree component, TreeTable
56 * only supports single rooted hierarchies. The features of the TreeTable
57 * component include mechanisms for focusing in on subtrees (within the main
58 * tree), as well as expanding and collapsing elements in the hierarchy.
59 * </html:p>
60 *
61 * <html:p>
62 * Like the Table, the TreeTable's children must be Trinidad
63 * Column components. Like the Tree, the TreeTable has a "nodeStamp"
64 * facet which renders the "Object Name" Column.
65 *
66 * The "Object Name" Column contains the primary identifier of an element
67 * in the hierarchy. For example, in an organization chart of employees, the "Object Name"
68 * Column might be the employee name.
69 * </html:p>
70 *
71 * <h4>Events:</h4>
72 * <table border="1" width="100%" cellpadding="3" summary="">
73 * <tr bgcolor="#CCCCFF" class="TableHeadingColor">
74 * <th align="left">Type</th>
75 * <th align="left">Phases</th>
76 * <th align="left">Description</th>
77 * </tr>
78 * <tr class="TableRowColor">
79 * <td valign="top"><code>org.apache.myfaces.trinidad.event.RowDisclosureEvent</code></td>
80 * <td valign="top" nowrap>Apply<br>Request<br>Values<br>Invoke<br>Application</td>
81 * <td valign="top">The expansion event is generated for a table when the detail facet of a row is expanded or collapsed. For tree or a treeTable, the expansion
82 event is generated when tree nodes are expanded or collapsed.</td>
83 * </tr>
84 * <tr class="TableRowColor">
85 * <td valign="top"><code>org.apache.myfaces.trinidad.event.SelectionEvent</code></td>
86 * <td valign="top" nowrap>Apply<br>Request<br>Values<br>Invoke<br>Application</td>
87 * <td valign="top">The selection event is delivered when the table selection
88 changes.</td>
89 * </tr>
90 * <tr class="TableRowColor">
91 * <td valign="top"><code>org.apache.myfaces.trinidad.event.FocusEvent</code></td>
92 * <td valign="top" nowrap>Apply<br>Request<br>Values<br>Invoke<br>Application</td>
93 * <td valign="top">Event delivered when user clicks to focus on (or zoom into) a particular element's subtree of children.
94 The TreeTable responds to this event by modifying the "focusPath" property appropriately.
95 Subsequently, any registered FocusListener instances are called.</td>
96 * </tr>
97 * <tr class="TableRowColor">
98 * <td valign="top"><code>org.apache.myfaces.trinidad.event.AttributeChangeEvent</code></td>
99 * <td valign="top" nowrap>Invoke<br>Application<br>Apply<br>Request<br>Values</td>
100 * <td valign="top">Event delivered to describe an attribute change. Attribute change events are not delivered for any programmatic change to a property. They are only delivered when a renderer changes a property without the application's specific request. An example of an attribute change event might include the width of a column that supported client-side resizing.</td>
101 * </tr>
102 * </table>
103 */
104 public class UIXTreeTable extends UIXTree
105 {
106 static public final FacesBean.Type TYPE = new FacesBean.Type(
107 UIXTree.TYPE);
108 static public final PropertyKey ROOT_NODE_RENDERED_KEY =
109 TYPE.registerKey("rootNodeRendered", Boolean.class, Boolean.TRUE);
110 static public final PropertyKey ROWS_BY_DEPTH_KEY =
111 TYPE.registerKey("rowsByDepth", int[].class, null, 0, PropertyKey.Mutable.SOMETIMES);
112 static public final PropertyKey RANGE_CHANGE_LISTENER_KEY =
113 TYPE.registerKey("rangeChangeListener", MethodExpression.class);
114
115 static public final String COMPONENT_FAMILY =
116 "org.apache.myfaces.trinidad.TreeTable";
117 static public final String COMPONENT_TYPE =
118 "org.apache.myfaces.trinidad.TreeTable";
119
120 /**
121 * Construct an instance of the UIXTreeTable.
122 */
123 public UIXTreeTable()
124 {
125 super("org.apache.myfaces.trinidad.BaseTreeTable");
126 }
127
128 /**
129 * Override to update the container client id cache before decode
130 */
131 @Override
132 public void decode(FacesContext context)
133 {
134 _resetContainerClientIdCache();
135 super.decode(context);
136 }
137
138 /**
139 * Override to update the container client id cache before validations
140 */
141 @Override
142 public void processValidators(FacesContext context)
143 {
144 _resetContainerClientIdCache();
145 super.processValidators(context);
146 }
147
148 /**
149 * Override to update the container client id cache before updates
150 */
151 @Override
152 public void processUpdates(FacesContext context)
153 {
154 _resetContainerClientIdCache();
155 super.processUpdates(context);
156 }
157
158 /**
159 * Override to update the container client id cache before encode
160 */
161 @Override
162 protected void __encodeBegin(FacesContext context) throws IOException
163 {
164 _resetContainerClientIdCache();
165 super.__encodeBegin(context);
166 }
167
168
169 /**
170 * Override to return clientd ids with no currency for items in header/footer facets
171 */
172 @Override
173 public String getContainerClientId(FacesContext context, UIComponent child)
174 {
175 String id;
176 if (_containerClientIdCache == null || _isStampedChild(child))
177 {
178 // call the UIXCollection getContainerClientId, which attaches currency string to the client id
179 id = getContainerClientId(context);
180 }
181 else
182 {
183 // The target is not a stamped child, so return a client id with no currency string
184 id = getClientId(context);
185 }
186
187 return id;
188 }
189
190 @Deprecated
191 public void setRangeChangeListener(MethodBinding binding)
192 {
193 setRangeChangeListener(adaptMethodBinding(binding));
194 }
195
196 /**
197 * Gets the maximum number of rows to show.
198 * This changes depending on the depth of the current row in the tree
199 * hierarchy.
200 * The rows per depth is obtained from
201 * {@link #getRowsByDepth}.
202 * @return 0 if all rows must be shown at this level.
203 */
204 @Override
205 public final int getRows()
206 {
207 int depth = getTreeModel().getDepth();
208 assert depth >= 0;
209
210 // the root element is selected when depth is zero:
211 if (depth==0)
212 return 1; // the treeTable only shows the first root node.
213
214 int[] rows = getRowsByDepth();
215 if ((rows == null) || (rows.length == 0))
216 return 0;
217
218 depth--;
219
220 // in a treeTable, the the first "rows" property affects how many
221 // children of the root element to show.
222 return (depth >= rows.length) ? rows[rows.length - 1] : rows[depth];
223 }
224
225 /**
226 * Gets the range start index for the current collection.
227 * The current collection is the children of the parent of the
228 * current rowData. ie: the current collection is the collection of
229 * siblings of the current rowData.
230 * @return zero based index of the row that must be displayed first.
231 * @see #getRowData()
232 */
233 @Override
234 public final int getFirst()
235 {
236 // "first" does not change per path. It changes per parent path.
237 // this is because "first", "rows" and "rowCount" applies to the container
238 // element and not the current element:
239 Object container = _getContainerPath();
240 Integer first = _firstMap.get(container);
241 return (first != null) ? first.intValue() : 0;
242 }
243
244 /**
245 * Sets the range start index for the current collection.
246 * The current collection is the children of the parent of the
247 * current rowData. ie: the current collection is the collection of
248 * siblings of the current rowData.
249 * @param index zero based index of the row that must be displayed first.
250 * @see #getRowData()
251 */
252 public void setFirst(int index)
253 {
254 // "first" does not change per path. It changes per parent path.
255 // this is because "first", "rows" and "rowCount" applies to the container
256 // element and not the current element:
257 Object container = _getContainerPath();
258 Map<Object, Integer> comparant = Collections.emptyMap();
259 if (_firstMap == comparant)
260 _firstMap = new HashMap<Object, Integer>(3);
261
262 if (index <= 0)
263 _firstMap.remove(container);
264 else
265 _firstMap.put(container, Integer.valueOf(index));
266 }
267
268 /**
269 * Adds a RangeChangeListener.
270 */
271 public void addRangeChangeListener(RangeChangeListener listener)
272 {
273 addFacesListener(listener);
274 }
275
276 /**
277 * Removes a RangeChangeListener.
278 */
279 public void removeRangeChangeListener(RangeChangeListener listener)
280 {
281 removeFacesListener(listener);
282 }
283
284
285 /**
286 * Retrieves all RangeChangeListeners
287 */
288 public RangeChangeListener[] getRangeChangeListeners()
289 {
290 return (RangeChangeListener[]) getFacesListeners(RangeChangeListener.class);
291 }
292
293 @Override
294 public Object saveState(FacesContext context)
295 {
296 Object[] array = new Object[2];
297 array[0] = super.saveState(context);
298 array[1] = (_firstMap.isEmpty()) ? null : _firstMap;
299
300 if (array[0] == null && array[1] == null)
301 return null;
302
303 return array;
304 }
305
306 @Override
307 @SuppressWarnings("unchecked")
308 public void restoreState(FacesContext context, Object state)
309 {
310 Object[] array = (Object[]) state;
311 super.restoreState(context, array[0]);
312 _firstMap = (Map<Object, Integer>) array[1];
313 if (_firstMap == null)
314 _firstMap = Collections.emptyMap();
315 }
316
317 @Override
318 public void broadcast(FacesEvent event) throws AbortProcessingException
319 {
320 // Notify the specified disclosure listener method (if any)
321 if (event instanceof FocusEvent)
322 {
323 setFocusRowKey(getRowKey());
324 //pu: Implicitly record a Change for 'focusPath' attribute
325 addAttributeChange("focusPath",
326 getFocusRowKey());
327 // it is nice to expand the focused item:
328 getDisclosedRowKeys().add();
329
330 broadcastToMethodExpression(event, getFocusListener());
331 }
332 else if (event instanceof RangeChangeEvent)
333 {
334 RangeChangeEvent rce = (RangeChangeEvent) event;
335 setFirst(rce.getNewStart());
336 broadcastToMethodExpression(event, getRangeChangeListener());
337 }
338
339 // Perform standard superclass processing
340 super.broadcast(event);
341 }
342
343
344 /**
345 * Gets the stamps. This returns the children of this component plus
346 * the nodeStamp stamp (if any).
347 */
348 // TODO cache the result.
349 @Override
350 protected final List<UIComponent> getStamps()
351 {
352
353 List<UIComponent> children = getChildren();
354 List<UIComponent> stamps;
355
356 if (children.isEmpty())
357 {
358 // no children, so use Node stamps as the stamp
359 stamps = super.getStamps();
360 }
361 else
362 {
363 UIComponent nodeStamp = getNodeStamp();
364
365 if (nodeStamp == null)
366 {
367 // no node stamp, so stamp, is only the children
368 stamps = children;
369 }
370 else
371 {
372 // stamps are the children plus the node stamp
373 stamps = new ArrayList<UIComponent>(children.size() + 1);
374 stamps.addAll(children);
375 stamps.add(nodeStamp);
376 }
377 }
378
379 return stamps;
380 }
381
382 /**
383 * Restores the state for the given stamp.
384 * This method avoids changing the state of facets on columns.
385 */
386 @Override
387 protected final void restoreStampState(FacesContext context, UIComponent stamp,
388 Object stampState)
389 {
390 if (stamp instanceof UIXColumn)
391 {
392 // if it is a column, we don't want the facets processed.
393 // Only the children:
394 StampState.restoreChildStampState(context, stamp, this, stampState);
395 }
396 else
397 super.restoreStampState(context, stamp, stampState);
398 }
399
400 /**
401 * Saves the state for the given stamp.
402 * This method avoids changing the state of facets on columns.
403 */
404 @Override
405 protected final Object saveStampState(FacesContext context, UIComponent stamp)
406 {
407 if (stamp instanceof UIXColumn)
408 {
409 // if it is a column, we don't want the facets processed.
410 // Only the children:
411 return StampState.saveChildStampState(context, stamp, this);
412 }
413 else
414 return super.saveStampState(context, stamp);
415 }
416
417 @SuppressWarnings("unchecked")
418 @Override
419 protected void processFacetsAndChildren(
420 FacesContext context,
421 PhaseId phaseId)
422 {
423 // process all the facets of this hgrid just once
424 // (except for the "nodeStamp" facet which must be processed once
425 // per row):
426 TableUtils.processFacets(context, this, this, phaseId,
427 UIXTreeTable.NODE_STAMP_FACET);
428
429 UIComponent nodeStamp = getNodeStamp();
430 // process any facets of the nodeStamp column:
431 TableUtils.processFacets(context, this, nodeStamp, phaseId, null);
432
433 // process all the facets of this table's column children:
434 TableUtils.processColumnFacets(context, this, this, phaseId);
435
436 // recursively process any grandchild columns of the nodeStamp column:
437 TableUtils.processColumnFacets(context, this, nodeStamp, phaseId);
438
439 Object oldPath = getRowKey();
440 RowKeySet state = getDisclosedRowKeys();
441 try
442 {
443 Object path = getFocusRowKey();
444 setRowKey(path);
445 if (path == null)
446 {
447 HierarchyUtils.__iterateOverTree(context,
448 phaseId,
449 this,
450 state,
451 true);
452
453 }
454 else
455 {
456 TableUtils.processStampedChildren(context, this, phaseId);
457 processComponent(context, nodeStamp, phaseId); // bug 4688568
458
459 if (state.isContained())
460 {
461 enterContainer();
462 HierarchyUtils.__iterateOverTree(context,
463 phaseId,
464 this,
465 state,
466 true);
467 }
468 }
469 }
470 finally
471 {
472 setRowKey(oldPath);
473 }
474 }
475
476 @Override
477 protected boolean visitChildren(
478 VisitContext visitContext,
479 VisitCallback callback)
480 {
481 // need to override to do the default since our superclass
482 // UIXTree does stuff here we don't want
483 return defaultVisitChildren(visitContext, callback);
484 }
485
486 @Override
487 protected boolean visitUnstampedFacets(
488 VisitContext visitContext,
489 VisitCallback callback)
490 {
491 // Visit the facets except for the node stamp
492 int facetCount = getFacetCount();
493
494 if (facetCount > 0)
495 {
496 UIComponent nodeStamp = getNodeStamp();
497
498 // if our only facet is the node stamp, we don't need to do this
499 if ((facetCount > 1) || (nodeStamp == null))
500 {
501 for (UIComponent facet : getFacets().values())
502 {
503 // ignore the nodeStamp facet, since it is stamped
504 if (facet != nodeStamp)
505 {
506 if (UIXComponent.visitTree(visitContext, facet, callback))
507 {
508 return true;
509 }
510 }
511 }
512 }
513 }
514
515 return false;
516 }
517
518 @Override
519 protected boolean visitData(
520 VisitContext visitContext,
521 VisitCallback callback)
522 {
523 Object focusedPath = getFocusRowKey();
524 Object oldRowKey = null;
525
526 // start from the focused area
527 if (focusedPath != null)
528 {
529 oldRowKey = getRowKey();
530 setRowKey(focusedPath);
531 }
532
533 boolean done;
534
535 try
536 {
537 done = super.visitData(new NoColumnFacetsVisitContext(visitContext), callback);
538 }
539 finally
540 {
541 if (focusedPath != null)
542 {
543 setRowKey(oldRowKey);
544 }
545 }
546
547 return done;
548 }
549
550 /**
551 * Gets the path of the parent
552 */
553 private Object _getContainerPath()
554 {
555 Object parentKey = getTreeModel().getContainerRowKey();
556 return parentKey;
557 }
558
559 /**
560 * Is target a stamped child UIComponent in the treeTable body
561 */
562 private boolean _isStampedChild(UIComponent target)
563 {
564 assert _containerClientIdCache != null;
565 return !_containerClientIdCache.containsKey(target);
566 }
567
568 /**
569 * Reset the cache of child components used in getContainerClientId
570 */
571 private void _resetContainerClientIdCache()
572 {
573 if(_containerClientIdCache == null)
574 _containerClientIdCache = new IdentityHashMap<UIComponent, Boolean>();
575 else
576 _containerClientIdCache.clear();
577
578 // cache treeTable header/footer items
579 TableUtils.cacheHeaderFooterFacets(this, _containerClientIdCache);
580 // cache child column header/footer items, including nested columns
581 TableUtils.cacheColumnHeaderFooterFacets(this, _containerClientIdCache);
582
583 UIComponent nodeStamp = getNodeStamp();
584 if(nodeStamp != null)
585 {
586 // cache nodeStamp header/footer items
587 TableUtils.cacheHeaderFooterFacets(nodeStamp, _containerClientIdCache);
588 // cache any nested columns in nodeStamp facet
589 TableUtils.cacheColumnHeaderFooterFacets(nodeStamp, _containerClientIdCache);
590 }
591 }
592
593 /**
594 * Gets the internal state of this component.
595 */
596 @Override
597 Object __getMyStampState()
598 {
599 Object[] state = new Object[2];
600 state[0] = super.__getMyStampState();
601 state[1] = (_firstMap.isEmpty()) ? null : _firstMap;
602
603 return state;
604 }
605
606 /**
607 * Sets the internal state of this component.
608 * @param stampState the internal state is obtained from this object.
609 */
610 @Override
611 @SuppressWarnings("unchecked")
612 void __setMyStampState(Object stampState)
613 {
614 Object[] state = (Object[]) stampState;
615 super.__setMyStampState(state[0]);
616 _firstMap = (Map<Object, Integer>) state[1];
617 if (_firstMap == null)
618 _firstMap = Collections.emptyMap();
619 }
620
621 @Override
622 void __resetMyStampState()
623 {
624 super.__resetMyStampState();
625 _firstMap = Collections.emptyMap();
626 }
627
628 private Map<Object, Integer> _firstMap = Collections.emptyMap();
629 // cache of child components inside this treeTable header/footer facets and column header/footer
630 // facets
631 transient private IdentityHashMap<UIComponent, Boolean> _containerClientIdCache = null;
632
633 /**
634 * Gets If the root node should be rendered or not. Defaults to true.
635 *
636 * @return the new rootNodeRendered value
637 */
638 final public boolean isRootNodeRendered()
639 {
640 return ComponentUtils.resolveBoolean(getProperty(ROOT_NODE_RENDERED_KEY), true);
641 }
642
643 /**
644 * Sets If the root node should be rendered or not. Defaults to true.
645 *
646 * @param rootNodeRendered the new rootNodeRendered value
647 */
648 final public void setRootNodeRendered(boolean rootNodeRendered)
649 {
650 setProperty(ROOT_NODE_RENDERED_KEY, rootNodeRendered ? Boolean.TRUE : Boolean.FALSE);
651 }
652
653 /**
654 * Gets the maximum number of records that can be displayed at
655 * one time (range size).
656 * Each level of depth in the tree can have a different range size.
657 * The first number in the array sets the range size for the root
658 * collection. Each subsequent number sets the range size for the
659 * corresponding depth. The last number becomes the default for
660 * each subsequent level of depth.
661 *
662 * If a node has more children than
663 * the range size, navigation rows will be rendered above and
664 * below the child nodes.
665 *
666 * @return the new rowsByDepth value
667 */
668 final public int[] getRowsByDepth()
669 {
670 return (int[])getProperty(ROWS_BY_DEPTH_KEY);
671 }
672
673 /**
674 * Sets the maximum number of records that can be displayed at
675 * one time (range size).
676 * Each level of depth in the tree can have a different range size.
677 * The first number in the array sets the range size for the root
678 * collection. Each subsequent number sets the range size for the
679 * corresponding depth. The last number becomes the default for
680 * each subsequent level of depth.
681 *
682 * If a node has more children than
683 * the range size, navigation rows will be rendered above and
684 * below the child nodes.
685 *
686 * @param rowsByDepth the new rowsByDepth value
687 */
688 final public void setRowsByDepth(int[] rowsByDepth)
689 {
690 setProperty(ROWS_BY_DEPTH_KEY, (rowsByDepth));
691 }
692
693 /**
694 * Gets a method reference to a rangeChange listener that
695 * will be called when a new range is selected.
696 *
697 * @return the new rangeChangeListener value
698 */
699 final public MethodExpression getRangeChangeListener()
700 {
701 return (MethodExpression)getProperty(RANGE_CHANGE_LISTENER_KEY);
702 }
703
704 /**
705 * Sets a method reference to a rangeChange listener that
706 * will be called when a new range is selected.
707 *
708 * @param rangeChangeListener the new rangeChangeListener value
709 */
710 final public void setRangeChangeListener(MethodExpression rangeChangeListener)
711 {
712 setProperty(RANGE_CHANGE_LISTENER_KEY, (rangeChangeListener));
713 }
714
715 @Override
716 public String getFamily()
717 {
718 return COMPONENT_FAMILY;
719 }
720
721 @Override
722 protected FacesBean.Type getBeanType()
723 {
724 return TYPE;
725 }
726
727 /**
728 * Construct an instance of the UIXTreeTable.
729 */
730 protected UIXTreeTable(
731 String rendererType
732 )
733 {
734 super(rendererType);
735 }
736
737 static
738 {
739 TYPE.lockAndRegister("org.apache.myfaces.trinidad.TreeTable","org.apache.myfaces.trinidad.BaseTreeTable");
740 }
741 }