View Javadoc

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 &quot;nodeStamp&quot;
64   *           facet which renders the &quot;Object Name&quot; Column.
65   * 
66   *           The &quot;Object Name&quot; Column contains the primary identifier of an element
67   *           in the hierarchy. For example, in an organization chart of employees, the &quot;Object Name&quot;
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   static public final PropertyKey HEIGHT_KEY =
115     TYPE.registerKey("height", String.class);
116 
117   static public final String COMPONENT_FAMILY =
118     "org.apache.myfaces.trinidad.TreeTable";
119   static public final String COMPONENT_TYPE =
120     "org.apache.myfaces.trinidad.TreeTable";
121 
122   /**
123    * Construct an instance of the UIXTreeTable.
124    */
125   public UIXTreeTable()
126   {
127     super("org.apache.myfaces.trinidad.BaseTreeTable");
128   }
129   
130   /**
131    * Override to update the container client id cache before decode
132    */
133   @Override
134   public void decode(FacesContext context)
135   {
136     _resetContainerClientIdCache();
137     super.decode(context);    
138   }
139   
140   /**
141    * Override to update the container client id cache before validations
142    */
143   @Override
144   public void processValidators(FacesContext context)
145   {
146     _resetContainerClientIdCache();
147     super.processValidators(context);
148   }
149   
150   /**
151    * Override to update the container client id cache before updates
152    */  
153   @Override
154   public void processUpdates(FacesContext context)
155   {
156     _resetContainerClientIdCache();
157     super.processUpdates(context);
158   }  
159 
160   /**
161    * Override to update the container client id cache before encode
162    */
163   @Override
164   protected void __encodeBegin(FacesContext context) throws IOException
165   {
166     _resetContainerClientIdCache();
167     super.__encodeBegin(context);
168   }
169 
170 
171   /**
172    * Override to return clientd ids with no currency for items in header/footer facets
173    */
174   @Override
175   public String getContainerClientId(FacesContext context, UIComponent child)
176   {
177     String id;
178     if (_containerClientIdCache == null || _isStampedChild(child))
179     {   
180       // call the UIXCollection getContainerClientId, which attaches currency string to the client id
181       id = getContainerClientId(context);
182     }
183     else
184     {
185       // The target is not a stamped child, so return a client id with no currency string
186       id = getClientId(context);
187     }
188 
189     return id;
190   }
191 
192   @Deprecated
193   public void setRangeChangeListener(MethodBinding binding)
194   {
195     setRangeChangeListener(adaptMethodBinding(binding));
196   }
197 
198   /**
199    * Gets the maximum number of rows to show.
200    * This changes depending on the depth of the current row in the tree
201    * hierarchy.
202    * The rows per depth is obtained from
203    * {@link #getRowsByDepth}.
204    * @return 0 if all rows must be shown at this level.
205    */
206   @Override
207   public final int getRows()
208   {
209     int depth = getTreeModel().getDepth();
210     assert depth >= 0;
211 
212     // the root element is selected when depth is zero:
213     if (depth==0)
214       return 1; // the treeTable only shows the first root node.
215 
216     int[] rows = getRowsByDepth();
217     if ((rows == null) || (rows.length == 0))
218       return 0;
219 
220     depth--;
221 
222     // in a treeTable, the the first "rows" property affects how many
223     // children of the root element to show.
224     return (depth >= rows.length) ? rows[rows.length - 1] : rows[depth];
225   }
226 
227   /**
228    * Gets the range start index for the current collection.
229    * The current collection is the children of the parent of the
230    * current rowData. ie: the current collection is the collection of
231    * siblings of the current rowData.
232    * @return zero based index of the row that must be displayed first.
233    * @see #getRowData()
234    */
235   @Override
236   public final int getFirst()
237   {
238     // "first" does not change per path. It changes per parent path.
239     // this is because "first", "rows" and "rowCount" applies to the container
240     // element and not the current element:
241     Object container = _getContainerPath();
242     Integer first = _firstMap.get(container);
243     return (first != null) ? first.intValue() : 0;
244   }
245 
246   /**
247    * Sets the range start index for the current collection.
248    * The current collection is the children of the parent of the
249    * current rowData. ie: the current collection is the collection of
250    * siblings of the current rowData.
251    * @param index zero based index of the row that must be displayed first.
252    * @see #getRowData()
253    */
254   public void setFirst(int index)
255   {
256     // "first" does not change per path. It changes per parent path.
257     // this is because "first", "rows" and "rowCount" applies to the container
258     // element and not the current element:
259     Object container = _getContainerPath();
260     Map<Object, Integer> comparant = Collections.emptyMap();
261     if (_firstMap == comparant)
262       _firstMap = new HashMap<Object, Integer>(3);
263 
264     if (index <= 0)
265       _firstMap.remove(container);
266     else
267       _firstMap.put(container, Integer.valueOf(index));
268   }
269 
270   /**
271    * Adds a RangeChangeListener.
272    */
273   public void addRangeChangeListener(RangeChangeListener listener)
274   {
275     addFacesListener(listener);
276   }
277 
278   /**
279    * Removes a RangeChangeListener.
280    */
281   public void removeRangeChangeListener(RangeChangeListener listener)
282   {
283     removeFacesListener(listener);
284   }
285 
286 
287   /**
288    * Retrieves all RangeChangeListeners
289    */
290   public RangeChangeListener[] getRangeChangeListeners()
291   {
292     return (RangeChangeListener[]) getFacesListeners(RangeChangeListener.class);
293   }
294 
295   @Override
296   public Object saveState(FacesContext context)
297   {
298     Object[] array = new Object[2];
299     array[0] = super.saveState(context);
300     array[1] = (_firstMap.isEmpty()) ? null : _firstMap;
301 
302     if (array[0] == null && array[1] == null)
303       return null;
304 
305     return array;
306   }
307 
308   @Override
309   @SuppressWarnings("unchecked")
310   public void restoreState(FacesContext context, Object state)
311   {
312     Object[] array = (Object[]) state;
313     super.restoreState(context, array[0]);
314     _firstMap = (Map<Object, Integer>) array[1];
315     if (_firstMap == null)
316       _firstMap = Collections.emptyMap();
317   }
318 
319   @Override
320   public void broadcast(FacesEvent event) throws AbortProcessingException
321   {
322     // Notify the specified disclosure listener method (if any)
323     if (event instanceof FocusEvent)
324     {
325       setFocusRowKey(getRowKey());
326       //pu: Implicitly record a Change for 'focusPath' attribute
327       addAttributeChange("focusPath",
328                          getFocusRowKey());
329       // it is nice to expand the focused item:
330       getDisclosedRowKeys().add();
331 
332       broadcastToMethodExpression(event, getFocusListener());
333     }
334     else if (event instanceof RangeChangeEvent)
335     {
336       RangeChangeEvent rce = (RangeChangeEvent) event;
337       setFirst(rce.getNewStart());
338       broadcastToMethodExpression(event, getRangeChangeListener());
339     }
340 
341     // Perform standard superclass processing
342     super.broadcast(event);
343   }
344 
345 
346   /**
347    * Gets the stamps. This returns the children of this component plus
348    * the nodeStamp stamp (if any).
349    */
350   // TODO cache the result.
351   @Override
352   protected final List<UIComponent> getStamps()
353   {
354     
355     List<UIComponent> children = getChildren();
356     List<UIComponent> stamps;
357     
358     if (children.isEmpty())
359     {
360       // no children, so use Node stamps as the stamp
361       stamps = super.getStamps();
362     }
363     else
364     {
365       UIComponent nodeStamp = getNodeStamp();
366       
367       if (nodeStamp == null)
368       {
369         // no node stamp, so stamp, is only the children
370         stamps = children;
371       }
372       else
373       {
374         // stamps are the children plus the node stamp
375         stamps = new ArrayList<UIComponent>(children.size() + 1);
376         stamps.addAll(children);
377         stamps.add(nodeStamp);
378       }
379     }
380     
381     return stamps;
382   }
383 
384   /**
385    * Restores the state for the given stamp.
386    * This method avoids changing the state of facets on columns.
387    */
388   @Override
389   protected final void restoreStampState(FacesContext context, UIComponent stamp,
390                                          Object stampState)
391   {
392     if (stamp instanceof UIXColumn)
393     {
394       // if it is a column, we don't want the facets processed.
395       // Only the children:
396       StampState.restoreChildStampState(context, stamp, this, stampState);
397     }
398     else
399       super.restoreStampState(context, stamp, stampState);
400   }
401 
402   /**
403    * Saves the state for the given stamp.
404    * This method avoids changing the state of facets on columns.
405    */
406   @Override
407   protected final Object saveStampState(FacesContext context, UIComponent stamp)
408   {
409     if (stamp instanceof UIXColumn)
410     {
411       // if it is a column, we don't want the facets processed.
412       // Only the children:
413       return StampState.saveChildStampState(context, stamp, this);
414     }
415     else
416       return super.saveStampState(context, stamp);
417   }
418 
419   @SuppressWarnings("unchecked")
420   @Override
421   protected void processFacetsAndChildren(
422     FacesContext context,
423     PhaseId phaseId)
424   {
425     // process all the facets of this hgrid just once
426     // (except for the "nodeStamp" facet which must be processed once
427     // per row):
428     TableUtils.processFacets(context, this, this, phaseId,
429       UIXTreeTable.NODE_STAMP_FACET);
430 
431     UIComponent nodeStamp = getNodeStamp();
432     // process any facets of the nodeStamp column:
433     TableUtils.processFacets(context, this, nodeStamp, phaseId, null);
434 
435     // process all the facets of this table's column children:
436     TableUtils.processColumnFacets(context, this, this, phaseId);
437 
438     // recursively process any grandchild columns of the nodeStamp column:
439     TableUtils.processColumnFacets(context, this, nodeStamp, phaseId);
440 
441     Object oldPath = getRowKey();
442     RowKeySet state = getDisclosedRowKeys();
443     try
444     {
445       Object path = getFocusRowKey();
446       setRowKey(path);
447       if (path == null)
448       {
449         HierarchyUtils.__iterateOverTree(context,
450                                          phaseId,
451                                          this,
452                                          state,
453                                          true);        
454 
455       }
456       else
457       {
458         TableUtils.processStampedChildren(context, this, phaseId);
459         processComponent(context, nodeStamp, phaseId); // bug 4688568
460   
461         if (state.isContained())
462         {
463           enterContainer();
464           HierarchyUtils.__iterateOverTree(context,
465                                            phaseId,
466                                            this,
467                                            state,
468                                            true);
469         }
470       }
471     }
472     finally
473     {
474       setRowKey(oldPath);
475     }
476   }
477 
478   @Override
479   protected boolean visitChildren(
480     VisitContext  visitContext,
481     VisitCallback callback)
482   {
483     // need to override to do the default since our superclass
484     // UIXTree does stuff here we don't want
485     return defaultVisitChildren(visitContext, callback);
486   }
487 
488   @Override
489   protected boolean visitUnstampedFacets(
490     VisitContext  visitContext,
491     VisitCallback callback)
492   {
493     // Visit the facets except for the node stamp
494     int facetCount = getFacetCount();
495     
496     if (facetCount > 0)
497     {
498       UIComponent nodeStamp = getNodeStamp();
499       
500       // if our only facet is the node stamp, we don't need to do this
501       if ((facetCount > 1) || (nodeStamp == null))
502       {
503         for (UIComponent facet : getFacets().values())
504         {
505           // ignore the nodeStamp facet, since it is stamped
506           if (facet != nodeStamp)
507           {
508             if (UIXComponent.visitTree(visitContext, facet, callback))
509             {
510               return true;
511             }
512           }
513         }
514       }
515     }
516 
517     return false;
518   }
519 
520   @Override
521   protected boolean visitData(
522     VisitContext  visitContext,
523     VisitCallback callback)
524   {
525     Object focusedPath = getFocusRowKey();
526     Object oldRowKey = null;
527     
528     // start from the focused area
529     if (focusedPath != null)
530     {
531       oldRowKey = getRowKey();
532       setRowKey(focusedPath);
533     }
534     
535     boolean done;
536     
537     try
538     {
539       done = super.visitData(new NoColumnFacetsVisitContext(visitContext), callback);
540     }
541     finally
542     {
543       if (focusedPath != null)
544       {
545         setRowKey(oldRowKey);
546       }
547     }
548     
549     return done;
550   }
551 
552   /**
553    * Gets the path of the parent
554    */
555   private Object _getContainerPath()
556   {
557     Object parentKey = getTreeModel().getContainerRowKey();
558     return parentKey;
559   }
560 
561   /**
562    * Is target a stamped child UIComponent in the treeTable body
563    */
564   private boolean _isStampedChild(UIComponent target)
565   {
566     assert _containerClientIdCache != null;
567     return !_containerClientIdCache.containsKey(target);
568   }
569 
570   /**
571    * Reset the cache of child components used in getContainerClientId
572    */
573   private void _resetContainerClientIdCache()
574   {
575     if(_containerClientIdCache == null)
576       _containerClientIdCache = new IdentityHashMap<UIComponent, Boolean>();
577     else
578       _containerClientIdCache.clear();
579 
580     // cache treeTable header/footer items
581     TableUtils.cacheHeaderFooterFacets(this, _containerClientIdCache);
582     // cache child column header/footer items, including nested columns
583     TableUtils.cacheColumnHeaderFooterFacets(this, _containerClientIdCache);
584 
585     UIComponent nodeStamp = getNodeStamp();
586     if(nodeStamp != null)
587     {
588       // cache nodeStamp header/footer items
589       TableUtils.cacheHeaderFooterFacets(nodeStamp, _containerClientIdCache);
590       // cache any nested columns in nodeStamp facet
591       TableUtils.cacheColumnHeaderFooterFacets(nodeStamp, _containerClientIdCache);      
592     }
593   }
594 
595   /**
596    * Gets the internal state of this component.
597    */
598   @Override
599   Object __getMyStampState()
600   {
601     Object[] state = new Object[2];
602     state[0] = super.__getMyStampState();
603     state[1] = (_firstMap.isEmpty()) ? null : _firstMap;
604 
605     return state;
606   }
607   
608   /**
609    * Sets the internal state of this component.
610    * @param stampState the internal state is obtained from this object.
611    */
612   @Override
613   @SuppressWarnings("unchecked")
614   void __setMyStampState(Object stampState)
615   {
616     Object[] state = (Object[]) stampState;
617     super.__setMyStampState(state[0]);
618     _firstMap = (Map<Object, Integer>) state[1];
619     if (_firstMap == null)
620       _firstMap = Collections.emptyMap();
621   }
622 
623   @Override
624   void __resetMyStampState()
625   {
626     super.__resetMyStampState();
627     _firstMap = Collections.emptyMap();
628   }
629   
630   private Map<Object, Integer> _firstMap = Collections.emptyMap();
631   // cache of child components inside this treeTable header/footer facets and column header/footer
632   // facets
633   transient private IdentityHashMap<UIComponent, Boolean> _containerClientIdCache = null;
634 
635   /**
636    * Gets If the root node should be rendered or not. Defaults to true.
637    *
638    * @return  the new rootNodeRendered value
639    */
640   final public boolean isRootNodeRendered()
641   {
642     return ComponentUtils.resolveBoolean(getProperty(ROOT_NODE_RENDERED_KEY), true);
643   }
644 
645   /**
646    * Sets If the root node should be rendered or not. Defaults to true.
647    * 
648    * @param rootNodeRendered  the new rootNodeRendered value
649    */
650   final public void setRootNodeRendered(boolean rootNodeRendered)
651   {
652     setProperty(ROOT_NODE_RENDERED_KEY, rootNodeRendered ? Boolean.TRUE : Boolean.FALSE);
653   }
654 
655   /**
656    * Gets the maximum number of records that can be displayed at
657    *               one time (range size).
658    *               Each level of depth in the tree can have a different range size.
659    *               The first number in the array sets the range size for the root
660    *               collection. Each subsequent number sets the range size for the
661    *               corresponding depth. The last number becomes the default for
662    *               each subsequent level of depth.
663    * 
664    *               If a node has more children than
665    *               the range size, navigation rows will be rendered above and
666    *               below the child nodes.
667    *
668    * @return  the new rowsByDepth value
669    */
670   final public int[] getRowsByDepth()
671   {
672     return (int[])getProperty(ROWS_BY_DEPTH_KEY);
673   }
674 
675   /**
676    * Sets the maximum number of records that can be displayed at
677    *               one time (range size).
678    *               Each level of depth in the tree can have a different range size.
679    *               The first number in the array sets the range size for the root
680    *               collection. Each subsequent number sets the range size for the
681    *               corresponding depth. The last number becomes the default for
682    *               each subsequent level of depth.
683    * 
684    *               If a node has more children than
685    *               the range size, navigation rows will be rendered above and
686    *               below the child nodes.
687    * 
688    * @param rowsByDepth  the new rowsByDepth value
689    */
690   final public void setRowsByDepth(int[] rowsByDepth)
691   {
692     setProperty(ROWS_BY_DEPTH_KEY, (rowsByDepth));
693   }
694 
695   /**
696    * Gets a method reference to a rangeChange listener that
697    *          will be called when a new range is selected.
698    *
699    * @return  the new rangeChangeListener value
700    */
701   final public MethodExpression getRangeChangeListener()
702   {
703     return (MethodExpression)getProperty(RANGE_CHANGE_LISTENER_KEY);
704   }
705 
706   /**
707    * Sets a method reference to a rangeChange listener that
708    *          will be called when a new range is selected.
709    * 
710    * @param rangeChangeListener  the new rangeChangeListener value
711    */
712   final public void setRangeChangeListener(MethodExpression rangeChangeListener)
713   {
714     setProperty(RANGE_CHANGE_LISTENER_KEY, (rangeChangeListener));
715   }
716 
717   /**
718    * Gets Sets a height to the content of the table. 
719    *         This generates a scrollbar to access all rows in the current range.
720    *
721    * @return  the new height value
722    */
723   final public String getHeight()
724   {
725     return ComponentUtils.resolveString(getProperty(HEIGHT_KEY));
726   }
727 
728   /**
729    * Sets Sets a height to the content of the table. 
730    *         This generates a scrollbar to access all rows in the current range.
731    * 
732    * @param height  the new height value
733    */
734   final public void setHeight(String height)
735   {
736     setProperty(HEIGHT_KEY, (height));
737   }
738 
739   @Override
740   public String getFamily()
741   {
742     return COMPONENT_FAMILY;
743   }
744 
745   @Override
746   protected FacesBean.Type getBeanType()
747   {
748     return TYPE;
749   }
750 
751   /**
752    * Construct an instance of the UIXTreeTable.
753    */
754   protected UIXTreeTable(
755     String rendererType
756     )
757   {
758     super(rendererType);
759   }
760 
761   static
762   {
763     TYPE.lockAndRegister("org.apache.myfaces.trinidad.TreeTable","org.apache.myfaces.trinidad.BaseTreeTable");
764   }
765 }