tags:

views:

4468

answers:

3

Hi.

I'm trying to build a multi-level dropdrown CSS menu for a website I'm doing on the umbraco content management system.

I need to build it to have the following structure:

<ul id="nav">
  <li><a href="..">Page #1</a></li>
  <li>
    <a href="..">Page #2</a>
    <ul>
      <li><a href="..">Subpage #1</a></li>
      <li><a href="..">Subpage #2</a></li>        
    </ul>
  </li>
</ul>

So now I'm trying to figure out how to do the nesting using XSLT. This is what I have so far:

<xsl:output method="xml" omit-xml-declaration="yes"/>

<xsl:param name="currentPage"/>

<!-- update this variable on how deep your menu should be -->
<xsl:variable name="maxLevelForMenu" select="4"/>

<xsl:template match="/">
  <ul id="nav">
    <xsl:call-template name="drawNodes">  
      <xsl:with-param 
       name="parent" 
       select="$currentPage/ancestor-or-self::node [@level=1]"
      />  
    </xsl:call-template>
  </ul>
</xsl:template>

<xsl:template name="drawNodes">
  <xsl:param name="parent"/> 
  <xsl:if test="umbraco.library:IsProtected($parent/@id, $parent/@path) = 0 or (umbraco.library:IsProtected($parent/@id, $parent/@path) = 1 and umbraco.library:IsLoggedOn() = 1)">
    <xsl:for-each select="$parent/node [string(./data [@alias='umbracoNaviHide']) != '1' and @level &lt;= $maxLevelForMenu]"> 
      <li>
        <a href="{umbraco.library:NiceUrl(@id)}">
          <xsl:value-of select="@nodeName"/>
        </a>  
        <xsl:if test="count(./node [string(./data [@alias='umbracoNaviHide']) != '1' and @level &lt;= $maxLevelForMenu]) &gt; 0">   
          <xsl:call-template name="drawNodes">    
            <xsl:with-param name="parent" select="."/>    
          </xsl:call-template>  
        </xsl:if> 
      </li>
    </xsl:for-each>
  </xsl:if>
</xsl:template>

What I can't seem to figure out is how to check if the first level (here Page #1 and Page #2) has any children, and if they do add the extra <ul> to contain the <li> children.

Anyone out there to point me in the right direction?

+2  A: 

First off, no need pass the a parent parameter around. The context will transport this information.

Here is the XSL stylesheet that should solve your problem:

<!-- update this variable on how deep your menu should be -->
<xsl:variable name="maxLevelForMenu" select="4"/>

<!--- match the document root --->
<xsl:template match="/root">
  <div id="nav">
    <xsl:call-template name="SubTree" />
  </div>
</xsl:template>

<!-- this will be called by xsl:apply-templates -->
<xsl:template match="node">
  <!-- the node is either protected, or the user is logged on (no need to check for IsProtected twice) -->
  <xsl:if test="umbraco.library:IsProtected($parent/@id, $parent/@path) = 0 or umbraco.library:IsLoggedOn() = 1">
    <li>
      <a href="umbraco.library:NiceUrl(@id)}"><xsl:value-of select="@nodeName"/></a>
      <xsl:call-template name="SubTree" />
    </li>
  </xsl:if>
</xsl:template>

<xsl:template name="SubTree">
  <!-- render sub-tree only if there are any child nodes --->
  <xsl:if test="node">
    <ul>
      <xsl:apply-templates select="node[data[@alias='umbracoNaviHide'] != '1'][@level &lt;= $maxLevelForMenu]">
        <!-- ensure sorted output of the child nodes --->
        <xsl:sort select="@sortOrder" data-type="number" />
      </xsl:apply-templates>
    </ul>
  </xsl:if>
</xsl:template>

This is the XML I tested it on (I don't know much about Umbraco, but after looking at some samples I hope I got close to an Umbraco document):

<root id="-1">
  <node id="1" level="1" sortOrder="1" nodeName="Page #1">
    <data alias="umbracoNaviHide">0</data>
  </node>
  <node id="2" level="1" sortOrder="2" nodeName="Page #2">
    <data alias="umbracoNaviHide">0</data>
    <node id="3" level="2" sortOrder="2" nodeName="Subpage #2.2">
      <data alias="umbracoNaviHide">0</data>
    </node>
    <node id="4" level="2" sortOrder="1" nodeName="Subpage #2.1">
      <data alias="umbracoNaviHide">0</data>
      <node id="5" level="3" sortOrder="3" nodeName="Subpage #2.1.1">
        <data alias="umbracoNaviHide">0</data>
      </node>
    </node>
    <node id="6" level="2" sortOrder="3" nodeName="Subpage #2.3">
      <data alias="umbracoNaviHide">1</data>
    </node>
  </node>
  <node id="7" level="1" sortOrder="3" nodeName="Page #3">
    <data alias="umbracoNaviHide">1</data>
  </node>
</root>

This is the output:

<div id="nav">
  <ul>
    <li><a href="http://foo/"&gt;Page #1</a></li>
    <li><a href="http://foo/"&gt;Page #2</a>
      <ul>
        <li><a href="http://foo/"&gt;Subpage #2.1</a>
          <ul>
            <li><a href="http://foo/"&gt;Subpage #2.1.1</a></li>
          </ul>
        </li>
        <li><a href="http://foo/"&gt;Subpage #2.2</a></li>
      </ul>
    </li>
  </ul>
</div>
Tomalak
A: 

There is nothing very special about this problem. The following solution tests that the node-list for

<xsl:apply-templates/>

is not empty, before applying the templates:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes"/>
  <xsl:variable name="vLevel" select="0"/>

    <xsl:template match="root">
      <xsl:variable name="vnextLevelNodes"
           select="node[@level = $vLevel+1]"/>
      <xsl:if test="$vnextLevelNodes">
       <ul>
         <xsl:apply-templates select="$vnextLevelNodes"/>
       </ul>
      </xsl:if>
    </xsl:template>

    <xsl:template match="node">
  <!-- the node is either protected, or the user is logged on (no need to check for IsProtected twice) -->
    <!-- <xsl:if test=
      "umbraco.library:IsProtected($parent/@id, $parent/@path) = 0
      or
       umbraco.library:IsLoggedOn() = 1"> -->
    <xsl:if test="1">
        <li>
          <!-- <a href="{umbraco.library:NiceUrl(@id)}"> -->
          <a href="'umbraco.library:NiceUrl(@id)'">
            <xsl:value-of select="@nodeName"/>
          </a>

         <xsl:variable name="vnextLevelNodes"
              select="node[@level = current()/@level+1]"/>
         <xsl:if test="$vnextLevelNodes">
          <ul>
            <xsl:apply-templates select="$vnextLevelNodes"/>
          </ul>
         </xsl:if>
        </li>
    </xsl:if>
    </xsl:template>
</xsl:stylesheet>

I have used the following XML source document:

<root id="-1">
    <node id="1" level="1" sortOrder="1" nodeName="Page #1">
     <data alias="umbracoNaviHide">0</data>
    </node>
    <node id="2" level="1" sortOrder="2" nodeName="Page #2">
     <data alias="umbracoNaviHide">0</data>
     <node id="3" level="2" sortOrder="2" nodeName="Subpage #2.2">
      <data alias="umbracoNaviHide">0</data>
     </node>
     <node id="4" level="2" sortOrder="1" nodeName="Subpage #2.1">
      <data alias="umbracoNaviHide">0</data>
      <node id="5" level="3" sortOrder="3" nodeName="Subpage #2.1.1">
       <data alias="umbracoNaviHide">0</data>
      </node>
     </node>
     <node id="6" level="2" sortOrder="3" nodeName="Subpage #2.3">
      <data alias="umbracoNaviHide">1</data>
     </node>
    </node>
    <node id="7" level="1" sortOrder="3" nodeName="Page #3">
     <data alias="umbracoNaviHide">1</data>
    </node>
</root>

Also, I have commented out any code referencing Umbraco extension functions, as I don't have access to them.

When the above transformation is applied on this source XML document, the correct, wanted result is produced:

<ul>
    <li>
     <a href="'umbraco.library:NiceUrl(@id)'">Page #1</a>
    </li>
    <li>
     <a href="'umbraco.library:NiceUrl(@id)'">Page #2</a>
     <ul>
      <li>
       <a href="'umbraco.library:NiceUrl(@id)'">Subpage #2.2</a>
      </li>
      <li>
       <a href="'umbraco.library:NiceUrl(@id)'">Subpage #2.1</a>
       <ul>
        <li>
         <a href="'umbraco.library:NiceUrl(@id)'">Subpage #2.1.1</a>
        </li>
       </ul>
      </li>
      <li>
       <a href="'umbraco.library:NiceUrl(@id)'">Subpage #2.3</a>
      </li>
     </ul>
    </li>
    <li>
     <a href="'umbraco.library:NiceUrl(@id)'">Page #3</a>
    </li>
</ul>

Hope this helped.

Cheers,

Dimitre Novatchev

Dimitre Novatchev
A: 

Thank you for your responses. I will need to get a XSLT book, but your answers solved my problem at hand.