Tree
Works similar to a Foreach: the body is rendered for every element. The current element can be accessed via the outgoing binding value. This component is partial request compatible, meaning that if you invoke AjaxDirectLink's with updateComponents containing unique tree node id's it will render them correctly.
See also: Live Demo
Parameters
Name | Type | Direction | Required | Default | Description |
---|---|---|---|---|---|
contentProvider | IContentProvider | in | yes | Provides elements for tree to display. | |
keyProvider | IKeyProvider | in | no | IdentityKeyProvider | Converter from elements to keys. |
converter | IPrimaryKeyConverter | in | no | Provides the ability to specify a tapestry IPrimaryKeyConverter to the tree. | |
value | IBinding | in | yes | Outgoing binding to current value. | |
state | Set | in | no | HashSet | Set of currently open element keys. |
sorter | Comparator | in | no | Comparator to order the displayed elements by. | |
offset | literal/string | in | no | 16 | Offset in pixels for depth-indentation. |
rowStyle | literal/string | in | no | If provided the individual rows of the tree will alternate styles using the provided EvenOdd beans even and odd css classes. | |
partialBlockClass | literal/string | in | no | Optional, if specified the surrounding div block on each category or node will have its class attribute set to this value. These blocks logically surround parent/children node which enable the partial refresh of various nodes in tree. | |
linkListener | IActionListener | in | no | Specifies the listener to invoke during the execution of contentExpansion method. This enables other components to cause the tree to collapse/expand nodes on the tree AND invoke their listeners. It's basically a work around for being able to specify multiple listeners on a link. | |
openIcon | IAsset | no | /net/sf/tacos/tree/open.gif | If specified, overrides the default open node + icon used on tree. | |
closeIcon | IAsset | no | /net/sf/tacos/tree/close.gif | If specified, overrides the default close node - icon used on tree. | |
nodeLinkAjax | boolean | no | true | Allows specifying whether or not the +/- links on a node are invoked via ajax or normal http requests. Default is ajax. | |
delayedLoad | boolean | no | false | Can only be used if javascript/ajax is enabled in your browser environment, if specified the trees contents won't be displayed when being loaded by a window, but will instead be replaced with a default "Loading.." text element (replaceable by using the loadElement parameter) while an additional ajax request is made after the window.onload event is fired to get the trees contents. | |
loadElement | String | no | If delayedLoad=true, this element will be placed relatively in the center of the tree's content area during delayed loads instead of the default "Loading.." text. |
Body: rendered
Informal parameters: allowed
Reserved parameters:
Examples
This example demonstrates displaying the contents of a local filesystem.
HTML template
<div jwcid="tree" id="tree"> <span jwcid="nodeLink"> <span jwcid="icon" align="absbottom"/> <span jwcid="nodeLabel"/> </span> </div>
Page specification
<property name="file"/> <property name="selectedFile" persist="session"/> <property name="fileState" persist="session" initial-value="ognl: new java.util.HashSet()"/> <component id="nodeLink" type="tacos:AjaxDirectLink"> <binding name="listener" value="ognl:components.tree.listeners.contentExpansion"/> <binding name="parameters" value="ognl:{keyProvider.getKey(components.tree.value), not components.tree.isExpanded( components.tree.value),file.path}"/> <binding name="updateComponents" value="ognl:{file.path, 'selectedFile' }"/> <binding name="effects" value="template:%nodeLink.effects"/> </component> <component id="nodeLabel" type="Insert"> <binding name="value" value="ognl:@org.apache.commons.lang.StringEscapeUtils@escapeJavaScript(file.name)"/> <binding name="class" value="ognl:file == selectedFile ? 'selected' : null"/> </component> <bean name="evenOdd" class="org.apache.tapestry.bean.EvenOdd"/> <component id="tree" type="tacos:Tree"> <binding name="contentProvider" value="ognl:contentProvider"/> <binding name="keyProvider" value="ognl:keyProvider"/> <binding name="value" value="ognl:file"/> <binding name="state" value="ognl:fileState"/> <binding name="sorter" value="ognl:@net.sf.tacos.demo.tree.fs.FileComparator@INSTANCE"/> <binding name="rowStyle" value="ognl:beans.evenOdd"/> <binding name="partialBlockClass" value="literal:treeBlock"/> <binding name="linkListener" value="listener:select"/> <binding name="delayedLoad" value="ognl:true"/> <binding name="loadElement" value="literal:partialWait"/> </component> <component id="icon" type="Image"> <binding name="image" value="ognl:file.directory ? assets.folder : assets.item"/> </component> <asset name="folder" path="img/folder.png"/> <asset name="item" path="img/item.png"/>
Java sources
public abstract class FileTreePage extends BasePage { private static final Log log = LogFactory.getLog(FileTreePage.class); private final static String PROP_FSROOT = "net.sf.tacos.demo-filesystem-root"; private final static ITreeContentProvider CONTENT_PROVIDER = new FileTreeContentProvider(getRootPath()); /** * Gets the content provider * @return */ public ITreeContentProvider getContentProvider() { return CONTENT_PROVIDER; } /** * Gets the key provider * @return */ public IKeyProvider getKeyProvider() { return FileKeyProvider.INSTANCE; } /** * Selects a file * @param cycle */ public void select(IRequestCycle cycle) { String path = (String)cycle.getListenerParameters()[0]; if (!path.startsWith(getRootPath())) { throw new IllegalArgumentException( "Path has to be relative to root " + getRootPath()); } File f = new File(path); ensurePreviousRefresh(f); setSelectedFile(f); } /** * Makes sure that the previously selected file is * refreshed to ensure the selected css class is updated. * * @param newFile */ private void ensurePreviousRefresh(File newFile) { if (getSelectedFile() != null && getAjaxWebRequest().isValidRequest()) { AjaxWebRequest ajax = getAjaxWebRequest(); File selFile = getSelectedFile(); File pfile = getIntersectingParent(newFile, selFile); if (pfile != null) { ajax.getUpdateComponents().remove(newFile.getPath()); ajax.getUpdateComponents().remove(selFile.getPath()); ajax.getUpdateComponents().add(pfile.getPath()); return; } else { ajax.getUpdateComponents().add(selFile.getPath()); } } } /** * If specified files are both contained by a parent file, * that isn't the file returned from {@link #getRootPath()} * this method returns that file. * * @param f1 * @param f2 * @return The nearest intersecting parent node, if any. */ protected File getIntersectingParent(File f1, File f2) { // For cases where it's the same file if (f1.getPath().equals(f2.getPath())) { if (f1.getParentFile() != null && !f1.getParentFile().getPath().equals(getRootPath())) return f1 .getParentFile(); else return f1; } Stack s1 = new Stack(); Stack s2 = new Stack(); // Now build our list of parents File parent = f1.getParentFile(); while(parent != null && !parent.getPath().equals(getRootPath())) { // For cases where newFile is a child of selFile and // not a sibling node or sibling node's child if (parent.getPath().equals(f2.getPath())) return parent; log.debug("f1 traversing over parent of path:" + parent.getPath()); s1.push(parent); parent = parent.getParentFile(); } parent = f2.getParentFile(); while(parent != null && !parent.getPath().equals(getRootPath())) { log.debug("f2 traversing over parent of path:" + parent.getPath()); s2.push(parent); parent = parent.getParentFile(); } // Iterate over first stack looking for a shared // parent if (s1.size() <= 0) return null; // File pfile = (File)s1.pop(); while(!s1.isEmpty()) { File pfile = (File)s1.pop(); if (s2.contains(pfile)) return pfile; log.debug("pfile traversing over path:" + pfile.getPath()); // if (!s1.isEmpty()) pfile = (File)s1.pop(); } return null; } /** * Makes all nodes visible * @param cycle */ public void expandAll(IRequestCycle cycle) { getTreeManager().expandAll(); } /** * Collapses all nodes * @param cycle */ public void collapseAll(IRequestCycle cycle) { getTreeManager().collapseAll(); } /** * Reveals a particular node * @param cycle */ public void revealSelected(IRequestCycle cycle) { getTreeManager().reveal(getSelectedFile()); } /** * The ree manager * @return */ public ITreeManager getTreeManager() { return new TreeManager(getFileState(), getContentProvider(), getKeyProvider()); } /** get file */ public abstract File getSelectedFile(); /** sets file */ public abstract void setSelectedFile(File file); /** the tree state */ public abstract Set getFileState(); /** The global root file path */ private static String getRootPath() { String fsRoot = System.getProperty(PROP_FSROOT); if (fsRoot == null) { fsRoot = System.getProperty("user.dir"); log.warn("JVM property " + PROP_FSROOT + " not specified, defaulting to " + fsRoot); } return fsRoot; } /** Injected ajax request */ public abstract AjaxWebRequest getAjaxWebRequest(); }