Click here to Skip to main content
Click here to Skip to main content

Using XSLT to generate a multi-level tree menu from XML

By , 28 Feb 2002
 

Sample Image - TreeFromXMLUsingXSLT.gif

Introduction

This article will demonstrate a simple and generic way to use XSLT to generate a multi-level HTML tree menu from an XML source. JScript is used for expanding and contracting the menu entries. (This requires a minimum of Internet Explorer 5. I have not tested this on any other browsers.)

The XML Source

This is a subset of the source XML (full XML source is in the zip file).

<menu>
 <entry>
  <text>In-House</text>
  <url>InHouse.htm</url>
  <entry>
   <text>Web Development</text>
   <url>WebDev.htm</url>
  </entry>
 </entry>
</menu>

The XSLT

For each menu entry, the XSLT processes it, then drills down until it has processed that entry's last child.

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
 <xsl:for-each select="//menu/entry">
  <xsl:call-template name="SubMenu">
   <xsl:with-param name="strCSS">Parent IsVisible</xsl:with-param>
  </xsl:call-template>
 </xsl:for-each>
</xsl:template>

<xsl:template name="SubMenu">
 <xsl:param name="strCSS" />
 
 <xsl:variable name="strURL" select="url" />
 
 <div class="{$strCSS}">
  <xsl:choose>
   <xsl:when test="count(entry) > 0">
    <!-- Element has children, it can be expanded -->
    <input type="hidden" id="hidIsExpanded" value="0" />
    <label id="lblExpand" class="Expander" onclick="ExpanderClicked()">+
    </label>
   </xsl:when>
   <xsl:otherwise>
    <label class="Expander">  </label>
   </xsl:otherwise>
  </xsl:choose>
  
  <a href="{$strURL}"><xsl:value-of select="text" /></a>
  <xsl:for-each select="entry">
   <xsl:call-template name="SubMenu">
    <xsl:with-param name="strCSS">NotVisible</xsl:with-param>
   </xsl:call-template>
  </xsl:for-each>
 </div>
</xsl:template>

</xsl:stylesheet>

Transforming the XML on the server

ASP is used to perform server-side transformation of the XML.

<%
   dim xmlMenu
   dim xslMenu
   
   'Get the source XML
   set xmlMenu = server.CreateObject("Microsoft.XMLDOM")
   xmlMenu.async = false
   xmlMenu.load server.MapPath("TreeFromXMLUsingXSLT.xml")
   
   'Get the XSLT to transform the XML
   set xslMenu = server.CreateObject("Microsoft.XMLDOM")
   xslMenu.async = false
   xslMenu.load server.MapPath("TreeFromXMLUsingXSLT.xsl")
   
   'Transform the source XML using XSLT
   Response.Write xmlMenu.transformNode(xslMenu)
   
   set xmlMenu = nothing
   set xslMenu = nothing
%>

Controlling the menu entries

Client-side JScript is used to first determine whether a menu entry has been expanded or collapsed, and then adjusts the selected menu entry's style settings.

<script language="jscript">
function ExpanderClicked()
{
    //Get the element that was clicked
    var ctlExpander = event.srcElement;
    var ctlSelectedEntry = ctlExpander.parentElement;
    //Get all the DIV elements that are direct descendants
    var colChild = ctlSelectedEntry.children.tags("DIV");
    if(colChild.length > 0)
    {
        var strCSS;
        //Get the hidden element that 
        //indicates whether or not entry is expanded
        var ctlHidden = ctlSelectedEntry.all("hidIsExpanded");
      
        if(ctlHidden.value == "1")
        {
            //Entry was expanded and is being contracted
            ctlExpander.innerHTML = "+ ";
            ctlHidden.value = "0";
            strCSS = "NotVisible";
        }
        else
        {
            //Entry is being expanded
            ctlExpander.innerHTML = "- ";
            ctlHidden.value = "1";
            strCSS = "IsVisible";
        }
        //Show all the DIV elements that are direct children
        for(var intCounter = 0; intCounter < colChild.length; intCounter++)
        {
            colChild[intCounter].className = strCSS;
        }
    }
}
</script>

Style setting

CSS settings are used to specify if a menu entry should be visible or hidden. The IsVisible class sets the element to be displayed, as a block element. The NotVisible class sets the element to be hidden.

body
{
   font-family: Verdana;
   font-size: x-small;
}
.IsVisible
{
   display: block;
}
.NotVisible
{
   display: none;
}
.Expander
{
   cursor: hand;
   font-family: Courier;
}
.Parent DIV
{
   margin-Left: 15px !important;
}

The last setting (.Parent DIV) indicates that only the elements below the one with the "Parent" class, will have a margin. This is to prevent the root menu entries from also having a margin.

Using images instead of + and -

It is fairly simple to show images instead of a "+" and "-" next to each entry. To do this, alter the XSLT and replace the <label> element that is next to the link, with an <img> element. In the JScript, instead of setting the InnerHTML property of the ctlExpander variable, set the src attribute to show a different image. Here is an example:

Sample Image - TreeFromXMLUsingXSLT2.gif

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

MS le Roux
Web Developer
South Africa South Africa
Member
I live in the Northern Suburbs of Cape Town (South Africa).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionopen some url in a new windowmembermetador0919 May '13 - 17:57 
Would like to open some URL's in a new window. Would like to call from database.
GeneralJavaScript not workingmembersneakyhippie28 Aug '09 - 10:56 
I've been trying to get this to work with no success. I can't even get it to work imbeding it in simple html. I used the javascript above and put the following in the body to just do a single child/parent tree. The parent is visible and the child is not, like it should be, but it won't expand.
 

<input type="hidden" id="hidIsExpanded" value="0" />
+</label>
Message:

<label class="Expander"> </label>
asdf = asdf


GeneralRe: JavaScript not workingmembersneakyhippie28 Aug '09 - 10:59 
Crap forgot to turn off HTML interpretting.
 
<div class="Parent IsVisible">
     <input type="hidden" id="hidIsExpanded" value="0" />
     <label id="lblExpand" class="Expander" onclick="ExpanderClicked(event)">+</label>
     <span>Message:</span>
<div class="NotVisible">
     <label class="Expander"> </label>
     <span>asdf = asdf</span><br/>
</div>
GeneralRe: JavaScript not workingmembersneakyhippie28 Aug '09 - 11:03 
Ya, I'm retarded I had a typo. It works in straight html but still not working with my xslt...i'll get back with a more specific question about my xslt code tomorrow...
GeneralCross-browser compatible JavaScript-codememberKarl-Johan G14 Apr '08 - 2:09 
Hi,
 
I have made a cross-browser compatible version of the JavaScript. Replace tree.js with this code:
function ExpanderClicked(event) {
 
  var ctlExpander;
 
  //Get the element that was clicked
  if (event.target) {
    ctlExpander=event.target;
  }
  else if (event.srcElement) {
    ctlExpander=event.srcElement;
  }
 
  var parent = ctlExpander.parentNode;
  var childs = parent.childNodes;
 
  var count = 0;
  for (var i = 0; i < childs.length; i++) {
    if (childs[i].nodeType == 1) {
      if (childs[i].nodeName == 'DIV') {
        if (childs[i].parentNode == parent) {
          count++;
        }
      }
    }
  }
 
  //Get all the DIV elements that are direct descendants
  if (count > 0) {
    var strCSS;
 
    //Get the hidden element that indicates whether or not entry is expanded
    elem = ctlExpander.previousSibling;
    while (elem.innerHTML == null) {
      elem = elem.previousSibling;
    }
 
    if (elem.value == "1") {
      //Entry was expanded and is being contracted
      ctlExpander.innerHTML = "+ ";
      elem.value = "0";
      strCSS = "NotVisible";
    }
    else {
      //Entry is being expanded
      ctlExpander.innerHTML = "- ";
      elem.value = "1";
      strCSS = "IsVisible";
    }
 
    //Make all DIV elements that are direct children visible
    for (var i = 0; i < childs.length; i++) {
      if (childs[i].nodeType == 1) {
        if (childs[i].nodeName == 'DIV') {
          if (childs[i].parentNode == parent) {
            childs[i].className = strCSS;
          }
        }
      }
    }
  }
}
You also need to replace ExpanderClicked() with ExpanderClicked(event) in the XSLT.
 
This code was tested 14 april 2008 with Internet Explorer 7, Firefox 2, Opera 9, Netscape Navigator 8, and Safari 3.
QuestionHow to use attribute to be a condition?membermathuros_paiboon18 Jul '05 - 5:27 
Hello
 
      Can someone help me! I have a problem about xslt I want to use attribute to be a condition what should I write xslt command or function
this is my xml file
 
<assess_controls>
     <assess_control>
          <<b>company_name</b>><u>xxx</u></company_name>
          <<b>form_name</b> <b>no</b>="<u>1</u>">form_xxx</form_name>
          <assesser_name>xxx</assesser_name>
         
          <control_type>xxx</control_type>
         
          <control_subgroup sub_group="1 xxx">
               <activitys>
                    <activity>xxx</activity>
                    <comment>comment 1</comment>
               </activitys>
          </control_subgroup>
          <control_subgroup sub_group="2 xxx">
               <activitys>
                    <activity>xxx3 </activity>
                    <comment>comment 3</comment>
               </activitys>
          </control_subgroup>
     </assess_control>
            <assess_control>
          <company_name>xxx</company_name>
          <form_name no="2">form_xxx</form_name>
          <assesser_name>yyy</assesser_name>
         
          <control_type>yyy</control_type>
         
          <control_subgroup sub_group="1 yyy">
               <activitys>
                    <activity>yyy</activity>
                    <comment>comment 1</comment>
               </activitys>
          </control_subgroup>
          <control_subgroup sub_group="2 yyy">
               <activitys>
                    <activity>yyy3 </activity>
                    <comment>comment 3</comment>
               </activitys>
          </control_subgroup>
     </assess_control>
</assess_controls>
 

<big>I want to use <company_name>, <form_name> and   attribute of form_name "no"
and xslt if below</big>
 

      <xsl:param name="<b>comp</b>">admin</xsl:param>
      <xsl:param name="<b>formname</b>">pppp</xsl:param>
      <xsl:param name="<b>formno</b>">1</xsl:param>
     
   <xsl:template match="/">
      <HTML>
         <BODY STYLE="font-family:Arial, helvetica, sans-serif; font-size:10pt;
                  background-color:#EEEEEE">
                 
            <xsl:apply-templates/>     
           
         </BODY>
      </HTML>    
</xsl:template>
  
<xsl:template match="assess_control">
<xsl:if test="company_name = <b>$comp</b>">
     <xsl:if test="form_name = <b>$formname</b>">
    
     <xsl:apply-templates select= "form_name"/>
    
        <table class = "borders" border = "1">
                         <tr class = "largebluetext">
                         <td><xsl:value-of select="company_name"/></td>
                         <td><xsl:value-of select="control_type"/></td>
                         <td><xsl:value-of select="control_group"/></td></tr>
                         <tr><td><xsl:value-of select ="form_name"/></td></tr>
        </table>
       
               <xsl:apply-templates select ="control_subgroup "/>
            <xsl:value-of select="@sub_group"/>
     </xsl:if>
</xsl:if>
</xsl:template>
<xsl:template match="form_name">
     <xsl:if test="<b>$formno</b>">
          <table><tr><td>no: <xsl:value-of select="@no"/></td></tr></table>
     </xsl:if>
</xsl:template>
 

<xsl:template   match="control_subgroup">
<span class="blacktext">   <xsl:value-of select="@sub_group"/>   </span>
<xsl:for-each select="activitys">
     <table class = "borders" border = "1">
          <tr class = "largeyellowtext">
          <td class = "borders1"><xsl:value-of select="activity"/></td>
          <td class = "borders2"><xsl:value-of select="score"/></td>
          <td class = "borders3"><xsl:value-of select="comment"/></td></tr>
     </table>
</xsl:for-each>
</xsl:template>
 
</xsl:stylesheet>
 

The problem is I can't specify form_name[@no] I want.
What wrong of my xslt code?
 

 
Thank you very much
 


QuestionHow to use attribute to be a conditionmembermathuros_paiboon18 Jul '05 - 5:15 
Hello
 
      Can someone help me! I have a problem about xslt I want to use attribute to be a condition what should I write xslt command or function
this is my xml file
<assess_controls>
     <assess_control>
          <company_name>xxx</company_name>
          <form_name no="1">form_xxx</form_name>
          <assesser_name>xxx</assesser_name>
          <control_type>xxx</control_type>
             <control_subgroup sub_group="1 xxx">
               <activitys>
                    <activity>xxx</activity>
                    <comment>comment 1</comment>
               </activitys>
             </control_subgroup>
          <control_subgroup sub_group="2 xxx">
               <activitys>
                    <activity>xxx3 </activity>
                    <comment>comment 3</comment>
               </activitys>
          </control_subgroup>
     </assess_control>
            <assess_control>
          <company_name>yyy</company_name>
          <form_name no="2">form_xxx</form_name>
          <assesser_name>yyy</assesser_name>
          <control_type>yyy</control_type>
          <control_subgroup sub_group="1 yyy">
               <activitys>
                    <activity>yyy</activity>
                    <comment>comment 1</comment>
               </activitys>
          </control_subgroup>
          <control_subgroup sub_group="2 yyy">
               <activitys>
                    <activity>yyy3 </activity>
                    <comment>comment 3</comment>
               </activitys>
          </control_subgroup>
     </assess_control>
</assess_controls>
 

<big>I want to use <company_name>, <form_name> and   attribute of form_name "no"
and xslt if below</big>
 

      <xsl:param name="<b>comp</b>">admin</xsl:param>
      <xsl:param name="<b>formname</b>">pppp</xsl:param>
      <xsl:param name="<b>formno</b>">1</xsl:param>
     
   <xsl:template match="/">
      <HTML>
         <BODY STYLE="font-family:Arial, helvetica, sans-serif; font-size:10pt;
                  background-color:#EEEEEE">
               <xsl:apply-templates/>     
           
         </BODY>
      </HTML>    
</xsl:template>
  
<xsl:template match="assess_control">
<xsl:if test="company_name = <b>$comp</b>">
     <xsl:if test="form_name = <b>$formname</b>">
    
     <xsl:apply-templates select= "form_name"/>
    
        <table border = "1">
                         <tr>
                         <td><xsl:value-of select="company_name"/></td>
                         <td><xsl:value-of select="control_type"/></td>
                         <td><xsl:value-of select="control_group"/></td></tr>
                         <tr><td><xsl:value-of select ="form_name"/></td></tr>
        </table>
       
               <xsl:apply-templates select ="control_subgroup "/>
            <xsl:value-of select="@sub_group"/>
     </xsl:if>
</xsl:if>
</xsl:template>
 

<xsl:template match="form_name">
     <xsl:if test="<b>$formno</b>">
          <table><tr><td>no: <xsl:value-of select="@no"/></td></tr></table>
     </xsl:if>
</xsl:template>
 

<xsl:template   match="control_subgroup">
<span class="blacktext">   <xsl:value-of select="@sub_group"/>   </span>
<xsl:for-each select="activitys">
     <table border = "1">
          <tr class = "largeyellowtext">
          <td class = "borders1"><xsl:value-of select="activity"/></td>
          <td class = "borders2"><xsl:value-of select="score"/></td>
          <td class = "borders3"><xsl:value-of select="comment"/></td></tr>
     </table>
</xsl:for-each>
</xsl:template>
 
</xsl:stylesheet>
 

The problem is I can't specify form_name[@no] I want.
What wrong of my xslt code?
 

 
Thank you very much
 


GeneralIs it possible to create Tabular treememberKrisBabu30 Jul '04 - 0:05 
i need a Tabular tree conrol thro' xml xslt.If you have source code, let me know.
 
tabular tree control is similar to squishy control(server side).
I need a client side control.
QuestionBug??memberHyperJ29 Oct '03 - 6:12 
I believe there is a small bug when the root node is contracted, it seems to lose the value of the ctlHidden from time to time (something to do with lower nodes being expanded)
Simply, if you add:  
   alert(ctlHidden.value)
before the staus check of node:
(if(ctlHidden.value == "1"))
you will see that it can come back as undefined.
 
After looking into this further, it seems to lose which element the event was fired from at the begining of the funcion.
 
   var ctlExpander = event.srcElement;
   var ctlSelectedEntry = ctlExpander.parentElement;
 
and as such does not know what the vtlHidden value is set to...
 
I cannot find a way to fix this problem, it looks like it may be an inherent event handling bug with ie6.
 
Any suggesttions/comments?

 
J
Generalmozillasusswill_mad23 Jul '03 - 1:52 
anyone know how to make the javascript working with Mozilla?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 1 Mar 2002
Article Copyright 2002 by MS le Roux
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid