sourceforge > tacos
 

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();
}