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