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 
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 }