001 package net.sf.tacos.components.tree;
002
003 import java.io.Serializable;
004 import java.util.ArrayList;
005 import java.util.Collection;
006 import java.util.Collections;
007 import java.util.Comparator;
008 import java.util.HashMap;
009 import java.util.List;
010 import java.util.Map;
011 import java.util.Set;
012 import java.util.Stack;
013 import net.sf.tacos.model.IKeyProvider;
014 import net.sf.tacos.model.ITreeContentProvider;
015 import net.sf.tacos.model.IdentityKeyProvider;
016 import org.apache.commons.lang.StringEscapeUtils;
017 import org.apache.commons.logging.Log;
018 import org.apache.commons.logging.LogFactory;
019 import org.apache.tapestry.BaseComponent;
020 import org.apache.tapestry.IActionListener;
021 import org.apache.tapestry.IDirect;
022 import org.apache.tapestry.IMarkupWriter;
023 import org.apache.tapestry.IRequestCycle;
024 import org.apache.tapestry.IScript;
025 import org.apache.tapestry.PageRenderSupport;
026 import org.apache.tapestry.TapestryUtils;
027 import org.apache.tapestry.bean.EvenOdd;
028 import org.apache.tapestry.components.Any;
029 import org.apache.tapestry.engine.DirectServiceParameter;
030 import org.apache.tapestry.engine.IEngineService;
031 import org.apache.tapestry.listener.ListenerInvoker;
032
033 /**
034 * Base component for providing Tree-like semantics for displaying data.
035 *
036 * @author phraktle
037 */
038 public abstract class Tree extends BaseComponent implements /*PartialRenderBlock, */IDirect {
039
040 /** Logger */
041 private static final Log log = LogFactory.getLog(Tree.class);
042 /** Default key provider */
043 private static final IKeyProvider identityKeyProvider = new IdentityKeyProvider();
044 /** Default iterator */
045 private TreeIterator treeIterator;
046 /** Parent nodes */
047 private Stack parts;
048
049 /**
050 * The default {@link IdentityKeyProvider}.
051 *
052 * @return The default key provider.
053 */
054 public IKeyProvider getIdentityKeyProvider()
055 {
056 return identityKeyProvider;
057 }
058
059 /**
060 * The default {@link TreeIterator} provided if no other is specified.
061 *
062 * @return Instance of {@link TreeIterator}
063 */
064 public TreeIterator getTreeIterator()
065 {
066 return treeIterator;
067 }
068
069 /**
070 * {@inheritDoc}
071 */
072 protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
073 {
074 boolean dynamic = cycle.getResponseBuilder().isDynamic();
075 //Only if this is a window load and delayed loading is needed
076 if (!cycle.isRewinding()
077 && isDelayedLoad()
078 && !dynamic/*getAjaxWebRequest().isValidRequest()*/) {
079 renderDynamicLoad(writer, cycle);
080 return;
081 }
082 treeIterator = new TreeIter();
083 parts = new Stack();
084 try {
085 super.renderComponent(writer, cycle);
086 } finally {
087 try {
088 while(!parts.isEmpty()) {
089 String partId = parts.pop().toString();
090 /*if (getAjaxWebRequest().isValidRequest()
091 && cycle.getResponseBuilder().componentWriterExists(partId))
092 getAjaxWebRequest().getResponseBuilder().getComponentWriter(partId).end();
093 else
094 writer.end();*/
095 }
096 } catch (Throwable t) {
097 t.printStackTrace();
098 }
099 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 /**
349 * {@inheritDoc}
350 */
351 public boolean isStateful()
352 {
353 return true;
354 }
355
356 /**
357 * {@inheritDoc}
358 */
359 public void trigger(IRequestCycle cycle)
360 {
361 //Does nothing, only used in delayed loads
362 }
363
364 /** Injected script */
365 public abstract IScript getScript();
366 /** Injected ajax engine */
367 public abstract IEngineService getDirectService();
368 /** Current value being rendered */
369 public abstract Object getValue();
370 /** Gets default/specified state */
371 public abstract Set getState();
372 /** Saves tree state */
373 public abstract void setState(Set state);
374 /** Injected content provider */
375 public abstract ITreeContentProvider getContentProvider();
376 /** Injected key provider */
377 public abstract IKeyProvider getKeyProvider();
378 /** Injected sort {@link Comparator} */
379 public abstract Comparator getSorter();
380 /** Optional EvenOdd component */
381 public abstract EvenOdd getRowStyle();
382 /** Where to apply the row style */
383 public abstract boolean isRowStyleInOuterDiv();
384 /** Partial block class */
385 public abstract String getPartialBlockClass();
386 /** For invoking linkToggleListener, if it exists */
387 public abstract ListenerInvoker getListenerInvoker();
388 /** Listener to invoke */
389 public abstract IActionListener getLinkListener();
390 /** Delayed loading parameter */
391 public abstract boolean isDelayedLoad();
392 /** Delayed loading display element id */
393 public abstract String getLoadElement();
394 }