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 }