View Javadoc

1   package net.sf.tacos.components.tree;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.Collection;
6   import java.util.Collections;
7   import java.util.Comparator;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  import java.util.Stack;
13  import net.sf.tacos.model.IKeyProvider;
14  import net.sf.tacos.model.ITreeContentProvider;
15  import net.sf.tacos.model.IdentityKeyProvider;
16  import org.apache.commons.lang.StringEscapeUtils;
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  import org.apache.tapestry.BaseComponent;
20  import org.apache.tapestry.IActionListener;
21  import org.apache.tapestry.IDirect;
22  import org.apache.tapestry.IMarkupWriter;
23  import org.apache.tapestry.IRequestCycle;
24  import org.apache.tapestry.IScript;
25  import org.apache.tapestry.PageRenderSupport;
26  import org.apache.tapestry.TapestryUtils;
27  import org.apache.tapestry.bean.EvenOdd;
28  import org.apache.tapestry.components.Any;
29  import org.apache.tapestry.engine.DirectServiceParameter;
30  import org.apache.tapestry.engine.IEngineService;
31  import org.apache.tapestry.listener.ListenerInvoker;
32  
33  /**
34   * Base component for providing Tree-like semantics for displaying data.
35   * 
36   * @author phraktle
37   */
38  public abstract class Tree extends BaseComponent implements /*PartialRenderBlock, */IDirect {
39  
40      /** Logger */
41      private static final Log log = LogFactory.getLog(Tree.class);
42      /** Default key provider */
43      private static final IKeyProvider identityKeyProvider = new IdentityKeyProvider();
44      /** Default iterator */
45      private TreeIterator treeIterator;
46      /** Parent nodes */
47      private Stack parts;
48      
49      /**
50       * The default {@link IdentityKeyProvider}.
51       * 
52       * @return The default key provider.
53       */
54      public IKeyProvider getIdentityKeyProvider()
55      {
56          return identityKeyProvider;
57      }
58      
59      /**
60       * The default {@link TreeIterator} provided if no other is specified.
61       * 
62       * @return Instance of {@link TreeIterator}
63       */
64      public TreeIterator getTreeIterator()
65      {
66          return treeIterator;
67      }
68      
69      /**
70       * {@inheritDoc}
71       */
72      protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
73      {
74          boolean dynamic = cycle.getResponseBuilder().isDynamic();
75          //Only if this is a window load and delayed loading is needed
76          if (!cycle.isRewinding() 
77                  && isDelayedLoad()
78                  && !dynamic/*getAjaxWebRequest().isValidRequest()*/) {            
79              renderDynamicLoad(writer, cycle);
80              return;
81          }        
82          treeIterator = new TreeIter();
83          parts = new Stack();
84          try {
85              super.renderComponent(writer, cycle);
86          } finally {
87              try {
88                  while(!parts.isEmpty()) {
89                      String partId = parts.pop().toString();
90                      /*if (getAjaxWebRequest().isValidRequest()
91                              && cycle.getResponseBuilder().componentWriterExists(partId))
92                          getAjaxWebRequest().getResponseBuilder().getComponentWriter(partId).end();
93                      else
94                          writer.end();*/
95                  }
96              } catch (Throwable t) {
97                  t.printStackTrace();
98              }
99              treeIterator = null;
100             parts = null;
101         }
102     }
103     
104     private void renderDynamicLoad(IMarkupWriter writer, IRequestCycle cycle) {
105         //Render our node manually instead of using Any
106         //we still have to have a node to replace on the client
107         Any anyContainer = (Any)getComponent("treeDiv");
108         writer.begin(anyContainer.getElement());
109         super.renderInformalParameters(writer, cycle);
110         writer.end();
111 
112         String treeId = getClientId();
113 
114         /*AjaxDirectServiceParameter dsp = 
115             new AjaxDirectServiceParameter(this, new Object[0], 
116                     new String[] {treeId}, false);*/
117 
118         DirectServiceParameter dsp = new DirectServiceParameter(this);
119 
120         Map parms = new HashMap();
121         parms.put("treeId", treeId);
122         parms.put("url", StringEscapeUtils.escapeJavaScript(
123                         getDirectService().getLink(true, dsp).getAbsoluteURL()));
124         if (getLoadElement() != null)
125             parms.put("loadElement", getLoadElement());
126 
127         PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, this);
128         getScript().execute(this, cycle, pageRenderSupport, parms);        
129     }
130 
131     /**
132      * Called to request the expansion of a tree node.
133      * 
134      * @param cycle
135      */
136     public void expansion(IRequestCycle cycle)
137     {
138         Object[] params = cycle.getListenerParameters();
139         Serializable key = (Serializable)params[0];
140         boolean expanded = ((Boolean)params[1]).booleanValue();        
141         
142         getManager().setExpandedKey(key, expanded);
143         setState(getState());  
144     }
145 
146     /**
147      * Invoked by contentLinkToggle component, will invoke
148      * {@link #expansion(IRequestCycle)} first before checking if the invoking
149      * component specified an {@link IActionListener} and any optional
150      * parameters it may need.
151      * 
152      * @param cycle
153      */
154     public void contentExpansion(IRequestCycle cycle)
155     {
156         // Call expansion first
157         expansion(cycle);
158         
159         // If they specified a listener we will now invoke it
160         if (getLinkListener() == null) {
161             log.warn("contentExpansion() called but no linkListener was specified on the tree. "
162                             + "Use the linkListener parameter if you want your page/component to be invoked "
163                             + "as well.");
164             return;
165         }
166         
167         getListenerInvoker().invokeListener(getLinkListener(), this, cycle);
168     }
169 
170     /**
171      * @param element
172      *            to test expanded of
173      * @return True if element should be displayed/expanded.
174      */
175     public boolean isExpanded(Object element)
176     {
177         return getManager().isExpanded(element);
178     }
179 
180     /*
181      * Default {@link TreeIterator} implementation.
182      */
183     private final class TreeIter extends TreeIterator {
184 
185         /**
186          * Creates new iterator
187          *
188          */
189         private TreeIter()
190         {
191             super(sorted(getContentProvider().getElements()));
192         }
193 
194         /**
195          * 
196          * {@inheritDoc}
197          */
198         protected Collection getChildren(Object node)
199         {
200             if (!isExpanded(node)) { return Collections.EMPTY_LIST; }
201             Collection coll = getContentProvider().getChildren(node);
202             return sorted(coll);
203         }
204         
205     }
206     
207     /**
208      * Default comparator/sorter for tree elements.
209      * 
210      * @param elements
211      * @return
212      */
213     private Collection sorted(Collection elements)
214     {
215         Comparator comp = getSorter();
216         if (comp == null) { return elements; }
217         List l = new ArrayList(elements);
218         Collections.sort(l, comp);
219         return l;
220     }
221 
222     /**
223      * @return The {@link ITreeManager} for this tree.
224      */
225     public ITreeManager getManager()
226     {
227         return new TreeManager(getState(), getContentProvider(),
228                 getKeyProvider());
229     }
230     
231     /**
232      * {@inheritDoc}
233      */
234     public void beforeRenderBody(IMarkupWriter writer, IRequestCycle cycle,
235             Serializable partId, boolean isRendering)
236     {
237         //If we need to close a parent
238         if (treeIterator.getDepth() <= treeIterator.getPreviousDepth()
239                 && !parts.isEmpty()) {
240             
241             int pops = 1;
242             if (treeIterator.getDepth() < treeIterator.getPreviousDepth()) {
243                 pops = treeIterator.getPreviousDepth()
244                         - treeIterator.getDepth();
245                 pops++; // because iterator depth is 0-based
246             }
247             while(pops > 0 && !parts.isEmpty()) {
248                 Serializable id = (Serializable)parts.pop();
249                 if (isRendering) {
250                     
251                     /*if (getAjaxWebRequest().isValidRequest()
252                             && getAjaxWebRequest().containsComponentId(id.toString()))
253                         getAjaxWebRequest().getResponseBuilder().getComponentWriter(id.toString()).end();
254                     else*/ {
255                         IMarkupWriter pwriter = findParentPart();
256                         if (pwriter != null) pwriter.end();
257                         else
258                             writer.end();
259                     }
260                 }
261                 pops--;
262             }
263         }
264         
265         if (isRendering) {
266             writer.begin("div");
267             writer.attribute("id", partId.toString());
268             if (isRowStyleInOuterDiv() && getRowStyle() != null)
269             {
270                 writer.attribute("class", getRowStyle().getNext());
271             }
272             else if (getPartialBlockClass() != null)
273             {
274                 writer.attribute("class", getPartialBlockClass());
275             }
276             parts.push(partId.toString());
277         }
278     }
279     
280     /**
281      * {@inheritDoc}
282      */
283     public void incrementNonRenderBlock()
284     {
285         if (getRowStyle() != null) getRowStyle().getNext();
286         beforeRenderBody(null, null, getKeyProvider().getKey(getValue()), false);
287     }
288     
289     /**
290      * {@inheritDoc}
291      */
292     public IMarkupWriter getPartWriter(Serializable partId)
293     {
294         String partStr = partId.toString();
295         // It may be specified directly
296         IMarkupWriter ret = null;
297         
298         // If we are not a root node we'll try
299         // and find a matching parent
300         if (treeIterator.getDepth() < treeIterator.getPreviousDepth()
301                 && !parts.isEmpty()
302                 && ((parts.size() - 1) > (treeIterator.getPreviousDepth() - treeIterator
303                         .getDepth()))) {
304             ret = findParentPart();
305         } else if (treeIterator.getDepth() > treeIterator.getPreviousDepth()
306                 && !parts.isEmpty()) {
307             ret = findParentPart();
308         } else if (treeIterator.getDepth() == treeIterator.getPreviousDepth()
309                 && parts.size() > 1) {
310             ret = findParentPart();
311         }
312         
313         return ret;
314     }
315     
316     /**
317      * Loops through the current part stack looking for a part that returns a
318      * valid {@link IMarkupWriter}.
319      * 
320      * @return Valid writer, or null.
321      */
322     private IMarkupWriter findParentPart()
323     {
324         //if (!getAjaxWebRequest().isValidRequest())        
325             //return null;
326         
327         IMarkupWriter ret = null;
328         Serializable[] partIds = (Serializable[])parts
329                 .toArray(new Serializable[parts.size()]);
330         /*for(int i = 0; i < partIds.length; i++) {
331             if (getAjaxWebRequest().containsComponentId(partIds[i].toString()))
332                 return getAjaxWebRequest().getResponseBuilder().getComponentWriter(partIds[i].toString());
333         }*/
334         
335         return ret;
336     }
337     
338     /**
339      * Used to pass self into {@link PartialForeach}.
340      * 
341      * @return
342      *//*
343     public PartialRenderBlock getPartialTreeBlock()
344     {
345         return this;
346     }*/
347     
348     public boolean getCurrentHasChildren() {
349         return getContentProvider().hasChildren(getValue());
350     }
351     
352     public Serializable getCurrentKey() {
353         return getKeyProvider().getKey(getValue());
354     }
355     
356     public boolean getCurrentNotExpanded() {
357         return !isExpanded(getValue());
358     }
359     
360     public String getCurrentOffset() {
361         int off = (getContentProvider().hasChildren(getValue())) ? 0 : 1;
362         return "" + ( off + getTreeIterator().getDepth() ) * getOffset();
363     }
364     
365     /**
366      * {@inheritDoc}
367      */
368     public boolean isStateful()
369     {
370         return true;
371     }
372     
373     /**
374      * {@inheritDoc}
375      */
376     public void trigger(IRequestCycle cycle)
377     {
378         //Does nothing, only used in delayed loads
379     }
380 
381     /** Injected script */
382     public abstract IScript getScript();
383     /** Injected ajax engine */
384     public abstract IEngineService getDirectService();
385     /** Current value being rendered */
386     public abstract Object getValue();
387     /** Gets default/specified state */
388     public abstract Set getState();
389     /** Saves tree state */
390     public abstract void setState(Set state);
391     /** Injected content provider */
392     public abstract ITreeContentProvider getContentProvider();
393     /** Injected key provider */
394     public abstract IKeyProvider getKeyProvider();
395     /** Injected sort {@link Comparator} */
396     public abstract Comparator getSorter();
397     /** Optional EvenOdd component */
398     public abstract EvenOdd getRowStyle();
399     /** The offset in pixels for depth-indentation */
400     public abstract int getOffset();     
401     /** Where to apply the row style */
402     public abstract boolean isRowStyleInOuterDiv();    
403     /** Partial block class */
404     public abstract String getPartialBlockClass();
405     /** For invoking linkToggleListener, if it exists */
406     public abstract ListenerInvoker getListenerInvoker();
407     /** Listener to invoke */
408     public abstract IActionListener getLinkListener();
409     /** Delayed loading parameter */
410     public abstract boolean isDelayedLoad();    
411     /** Delayed loading display element id */
412     public abstract String getLoadElement();
413 }