XBL Collapsing Tree


    The purpose of this component is to create a nested tree of variable depth (default is 5) from a single node binding. It uses a recursive approach so that the tree displayed is an actual nested unordered list. Since the list is nested we can make it collapsable without a lot of work. This is also a fairly good example of how to include javascript, CSS, XSL, and scoping to an XBL component. This shouldn't really be used as is, but more as a framework for creating and viewing trees in a collapsable fashion.

    The component is broken into four major areas. The first area falls within the xbl:script element. the javascript needed to expand and collapse the tree is defined here. It consists of two functions toggle(), and cancel().  Toggle() changes the style of the node to expanded or collapsed. Cancel() keeps the event from collapsing or expanding the entire tree. The second area is the embedded CSS for the component. The third major area is the start of the main XSL template. <xsl:template match="dlcn:tree-view">. This is where the variables from the the form are copied into the local model. The only two that are copied are @ref, and @depth. @ref uses the xbl:attr attribute to get a pointer to a single node binding, and the refers internally to that binding as $data. @depth is passed as a parameter to the 'node' template if it exists, and the value 5 is passed if it doesn't, which gives us our default. 

    The fourth major area is the 'node' template.  <xsl:template name="node">. This is the actual point of recursion and display markup. It takes a parameter called 'depth'. It outputs the name of the current node. Outputs an html list, with a list item for each attribute of the current node, using and xforms:repeat over @*. Checks to see if the depth is greater than 1. If it is, it will create a list item for each child of the current node, and within the item call the node template again, with the depth set to the current depth - 1.


With the following instance data:

<xforms:instance id="example">


        <node nodeID="1">

            <node nodeID="1.1"/>

            <node nodeID="1.2" attr1="value1" attr2="value2" attr3="value3">

                <node nodeID="1.2.1"/>

                <node nodeID="1.2.2" attr1="value1" attr2="value2" attr3="value3">

                    <node nodeID=""/>

                    <node nodeID="" attr1="value1" attr2="value2" attr3="value3"/>




        <node nodeID="2" attr1="value1" attr2="value2" attr3="value3">

            <node nodeID="2.1"/>

            <node nodeID="2.2" attr1="value1" attr2="value2" attr3="value3">

                <node nodeID="2.2.1"/>

                <node nodeID="2.2.2" attr1="value1" attr2="value2" attr3="value3">

                    <node nodeID=""/>

                    <node nodeID="" attr1="value1" attr2="value2" attr3="value3"/>






And the following markup in the xform:

<xi:include href="oxf:/xbl/delcyon/tree-view/tree-view.xbl" xxi:omit-xml-base="true"/>

<dlcn:tree-view ref="instance('example')" depth="6"/> 

The following output is created where the word node can be clicked on to expand and show it's children, and any attributes that the node itself may have.


<?xml version="1.0" encoding="UTF-8"?>


    @attribute: ref     ::= single node binding 
    @attribute: depth   ::= maximum depth of tree (optional: default 5)


    Give this component a single node binding and it will create xhtml:li for all of it's attributes,
    and sub lists for all of it's children recusively.


    Installation Location:

<xbl:xbl xmlns:xhtml="http://www.w3.org/1999/xhtml"


        // expand or collapse the list by setting the correct style on the parent list item.
        function toggle( e )
            // apply style to hide or show list elements
            if( e.className == 'expanded' )
                e.className = 'collapsed';
                e.className = 'expanded';


        // prevent a click on a child list element from reaching the parent
        function cancel( evt )
            // stop event from bubbling
            if( window.event )
                window.event.cancelBubble = true;  // ie
            else if (evt.stopPropagation)
                evt.stopPropagation();  // firefox


    <xbl:binding  element="dlcn|tree-view">




                /* START COLLAPSE/EXPAND STYLES */
                .collapsed li { display:none; } /* target list items in "collapse" mode. */
                .collapsed { list-style-type: square; } /* change bullet to "collapse" */


                .expanded li { display:normal; }  /* target list items in "expanded" mode. */
                .expanded { list-style-type: circle; } /* change bullet to "expanded" */


                ul { list-style-type: disc; } /* set the default bullet style */
                /* END COLLAPSE/EXPAND STYLES */


                    font-size: larger; 
                    font-weight: bold;


                    font-size: larger;                     


                    font-weight: bold;







        <xbl:template xxbl:transform="oxf:xslt">


            <xsl:transform version="2.0">


                <xsl:template match="@*|node()">
                        <xsl:apply-templates select="@*|node()" />


                <xsl:template match="dlcn:tree-view">
                        <xforms:group xbl:attr="ref" xxbl:scope="outer">
                            <!-- Start copying the variables over that we need -->


                            <xxforms:variable name="data" xxbl:scope="inner" >
                                <xxforms:sequence select="." xxbl:scope="outer"/>


                            <!-- end external variable copying -->


                            <!-- START MAIN GROUP-->
                            <xforms:group xxbl:scope="inner">
                                <xforms:group ref="$data">
                                        <xsl:when test="exists(@depth)">
                                            <xsl:call-template name="node">
                                                <xsl:with-param name="depth" select="@depth"/>    
                                            <xsl:call-template name="node">
                                                <xsl:with-param name="depth">5</xsl:with-param>    
                            <!-- END MAIN GROUP -->




                <xsl:template name="node">
                    <xsl:param name="depth"/>
                    <span class="nodename"><xforms:output value="local-name()"/></span>
                    <xhtml:ul onclick="cancel( event )">
                        <!-- This will display all of the attributes in this element -->
                        <xforms:repeat nodeset="@*">
                                <span class="leafname"><xforms:output value="local-name()"/></span>
                                <span class="leafvalue"><xforms:output value="."/></span>
                        <!-- Repeat over each child node of the root node -->
                        <xsl:if test="$depth > 1">
                            <xforms:repeat nodeset="*">
                                <xhtml:li class="hidden" onclick="toggle( this )">                                        
                                    <!-- if this element has children recurse into it -->                                       
                                    <xsl:call-template name="node">
                                        <xsl:with-param name="depth" select="$depth - 1"/>