Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Ian Huntington 23 posts 53 karma points
    Jun 24, 2009 @ 18:02
    Ian Huntington
    0

    Multi-column navigation

    Hi,

    I am making a menu which lists all items but every four items a new list is created next to it:-

    <ul>
    - item 1
    - item 2
    - item 3
    - item 4
    </ul>
    <ul>
    - item 5
    - item 6
    </ul>

    Below is my XSL so far

    <xsl:template name="create-menu">
       
         <ul>
         <xsl:for-each select="$currentPage/ancestor-or-self::node [@level = 2]/node">

            <xsl:sort select="@nodeName" order="ascending"/>

                <li>
                  <xsl:value-of select="position()" />
                  <a>
                    <xsl:attribute name="href">
                      <xsl:value-of select="umbraco.library:NiceUrl(@id)" />
                    </xsl:attribute>
                    <xsl:value-of select="@nodeName"/>
                 
                </li>
            <xsl:if test="position() mod 4 = 0">
              ========== end ul and create new ul
            </xsl:if>
             
          </xsl:for-each>    
        </ul>
       
      </xsl:template>

    I know that I need to put the closing <ul> in the if mod tag but this will throw an error as expected. I'm not sure how I'm supposed to say create an ul tag then run the for-each code then after the mod 4 = 0 to close the ul and to create a new list running and continue the for-each.

    Thanks

  • kmacdonell 28 posts 45 karma points
    Jun 24, 2009 @ 18:25
    kmacdonell
    0

    As you say, the xslt will fail unless all tags are complete. I think you should use recursion and keep a count of total nodes and the current position in that node sequence. 

    Need to go - I'll flesh it out later.

    Cheers,
    Kev

  • Tommy Poulsen 514 posts 708 karma points
    Jun 24, 2009 @ 20:47
    Tommy Poulsen
    0

    Hi, I would go for your own mod 4 approach, and you can provide </ul><ul> with this expression:

    <xsl:text disable-output-escaping="yes">&lt;/ul&gt;&lt;ul&gt;</xsl:text>

    >Tommy

     

  • Jamie Pollock 27 posts 102 karma points c-trib
    Jun 24, 2009 @ 22:12
    Jamie Pollock
    1

    Hi Bobby a few things you could do, expanding upon what Tommy explained above.

    Taking your original code and modify it to to be more robust, sorry there is no colour coding, the code however is fully commented.<xsl:template match="/">
    <!-- important to create a variable first so we can check if there are any menu nodes -->
    <xsl:variable name="menuNodes" select="$currentPage/ancestor-or-self::node[@level = 2]/node" />
    <xsl:if test="count($menuNodes) &gt; 0">
    <xsl:for-each select="$menuNodes">
    <!-- sorting by sortOrder may help more if you're doing a navigation -->
    <xsl:sort select="@sortOrder" order="ascending"/>
    <!-- add in the starting tags if we're at the start -->
    <xsl:if test="position() mod 4 = 1">
    <xsl:text disable-output-escaping="yes"><![CDATA[<ul>]]></xsl:text>
    </xsl:if>
    <li>
    <!-- I've left the position in here, but if you're looking for an order list you may want to use the ol tag -->
    <xsl:value-of select="position()" />
    <!-- here we are using inline xslt it looks nicer the code goes inside {...} (only use this for short xslt)-->
    <a href="{umbraco.library:NiceUrl(@id)}">
    <xsl:value-of select="@nodeName"/>

    </li>
    <!-- add in the closing tag if we've reached 4 nodes -->
    <xsl:if test="position() mod 4 = 0 or position() = last()">
    <xsl:text disable-output-escaping="yes"><![CDATA[</ul>]]></xsl:text>
    </xsl:if>
    </xsl:for-each>
    </xsl:if>
    </xsl:template></span><span class="pln">
    </span>

    Just a run down on the important points here, I've used a variable called menuNodes which is a quick node set so we can test if there is any need to render the code at all (in the current situation there is no need to do this, the for-each will not fire as in the event there are no nodes it won't work), if you were to put a div container on this bit of code then you'd end up with an empty div not good!

    I've also used two if statements, it's always a good idea to do this conditional inclusion as you never know when the html code will close, notice the second conditional is also includes position() = last(), to catch when you're on the last node.

    If this helped, please mark it as the solution, if my conditionals are incorrect please excuse me, I was up at 5:30am (uk, 6:30 dk time) flying home from Codegarden 09... ;_; as a result I'm really tired lol...

  • Jamie Pollock 27 posts 102 karma points c-trib
    Jun 24, 2009 @ 22:14
    Jamie Pollock
    0

    wah! my reply messed up, okay your code is between the "pre span" tags, sorry if there was any confusion.

  • kmacdonell 28 posts 45 karma points
    Jun 24, 2009 @ 23:54
    kmacdonell
    0

    Sorry for the wait ....

    This works by looping through the node set snd checking whether the current node is within the range of start position to (start position + (menu length - 1). Once the first menu of x items is built it checks to see whether the menu is finished, if not it calls itself seeded by the start position of the next menu item. 

    It produces a nice clean output and is very flexible regarding the size of each menu.

  • kmacdonell 28 posts 45 karma points
    Jun 24, 2009 @ 23:55
    kmacdonell
    0

    Sorry, that was a mess ....

    <pre>

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxml="urn:schemas-microsoft-com:xslt" xmlns:umbraco.library="urn:umbraco.library" exclude-result-prefixes="msxml umbraco.library"> <xsl:output method="xml" omit-xml-declaration="yes"/> <xsl:param name="currentPage"/> <xsl:template match="/"> <xsl:call-template name="MultiMenu"> <xsl:with-param name="startPosition" select="1"/> <xsl:with-param name="menuLevel" select="1"/> <xsl:with-param name="menuLength" select="4"/> </xsl:call-template> </xsl:template> <xsl:template name="MultiMenu"> <xsl:param name="startPosition"></xsl:param> <xsl:param name="menuLevel"></xsl:param> <xsl:param name="menuLength"></xsl:param> <xsl:variable name="menuNodeCount" select="count($currentPage/ancestor-or-self::node [@level= $menuLevel]/node)" /> <xsl:element name="ul"> <xsl:attribute name="style"> <xsl:text>border: 2px solid black; padding: 1em; margin: 1em;</xsl:text> </xsl:attribute> <xsl:for-each select="$currentPage/ancestor-or-self::node [@level= $menuLevel]/node"> <xsl:if test="(position() &gt;= $startPosition) and (position() &lt;= ($startPosition + ($menuLength -1)))"> <xsl:element name="li"> Node <xsl:value-of select="position()"/> </xsl:element> </xsl:if> </xsl:for-each> </xsl:element> <xsl:if test="($startPosition + $menuLength) &lt;=$menuNodeCount"> <xsl:call-template name="MultiMenu"> <xsl:with-param name="startPosition" select="$startPosition + $menuLength"/> <xsl:with-param name="menuLevel" select="$menuLevel"/> <xsl:with-param name="menuLength" select="$menuLength"/> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet></span>

    </pre>

  • kmacdonell 28 posts 45 karma points
    Jun 24, 2009 @ 23:57
    kmacdonell
    1

    Sorry ......

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
    <xsl:stylesheet 
        version="1.0" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        xmlns:msxml="urn:schemas-microsoft-com:xslt"
        xmlns:umbraco.library="urn:umbraco.library"
        exclude-result-prefixes="msxml umbraco.library">
    
    
    <xsl:output method="xml" omit-xml-declaration="yes"/>
    
    <xsl:param name="currentPage"/>
    
    
    <xsl:template match="/">
    
        <xsl:call-template name="MultiMenu">
            <xsl:with-param name="startPosition" select="1"/>
            <xsl:with-param name="menuLevel" select="1"/>
            <xsl:with-param name="menuLength" select="4"/>
        </xsl:call-template>
    
    </xsl:template>
    
    
    
        <xsl:template name="MultiMenu">
    
            <xsl:param name="startPosition"></xsl:param>
            <xsl:param name="menuLevel"></xsl:param>
            <xsl:param name="menuLength"></xsl:param>
            <xsl:variable name="menuNodeCount" select="count($currentPage/ancestor-or-self::node [@level= $menuLevel]/node)" />
    
    
            <xsl:element name="ul">
                        <xsl:attribute name="style">
                            <xsl:text>border: 2px solid black; padding: 1em; margin: 1em;</xsl:text>
                        </xsl:attribute>
    
                <xsl:for-each select="$currentPage/ancestor-or-self::node [@level= $menuLevel]/node">
    
                    <xsl:if test="(position() &gt;= $startPosition) and (position() &lt;= ($startPosition + ($menuLength -1)))">
                        <xsl:element name="li">
                            Node <xsl:value-of select="position()"/>
                        </xsl:element>
                    </xsl:if>
    
                </xsl:for-each>
    
            </xsl:element>
    
    
            <xsl:if test="($startPosition + $menuLength) &lt;=$menuNodeCount">
                <xsl:call-template name="MultiMenu">
                    <xsl:with-param name="startPosition" select="$startPosition + $menuLength"/>
                    <xsl:with-param name="menuLevel" select="$menuLevel"/>
                    <xsl:with-param name="menuLength" select="$menuLength"/>
                </xsl:call-template>
            </xsl:if>
    
    
        </xsl:template>
    
    
    </xsl:stylesheet></span>
  • Ian Huntington 23 posts 53 karma points
    Jun 25, 2009 @ 10:46
    Ian Huntington
    0

    Kev, Jamie (mad man up at 5.30am) thanks for your efforts, both of your approaches worked superbly! At a loss to which method to use.

  • Ian Huntington 23 posts 53 karma points
    Jun 25, 2009 @ 11:10
    Ian Huntington
    3

    A colleague of mine has just come up with another solution

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [
      <!ENTITY nbsp "&#x00A0;">
    ]>
    <xsl:stylesheet
            version="1.0"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:msxml="urn:schemas-microsoft-com:xslt"
            xmlns:umbraco.library="urn:umbraco.library"
            exclude-result-prefixes="msxml umbraco.library">
      <xsl:output method="html" omit-xml-declaration="yes"/>
      <xsl:param name="currentPage"/>

      <xsl:variable name="count">4</xsl:variable>
      <xsl:template match="/">
        <xsl:for-each select="$currentPage/ancestor-or-self::node [@level= 2]/node[position() mod $count = 1]">
          <ul>
            <xsl:apply-templates select=".|following-sibling::node[position() &lt; $count]" />
          </ul>
        </xsl:for-each>
      </xsl:template>

      <xsl:template match="node">
        <li>
          <a href="{umbraco.library:NiceUrl(@id)}">
            <xsl:value-of select="@nodeName"/>
         
        </li>
      </xsl:template>
    </xsl:stylesheet>
  • kmacdonell 28 posts 45 karma points
    Jun 25, 2009 @ 11:18
    kmacdonell
    0

    That is a very clean way of solving the problem - the xslt mechanics involved are relatively new to me but it looks like a very compact, easy to maintain solution.

    Cheers,
    Kev

  • Ian Huntington 23 posts 53 karma points
    Jun 25, 2009 @ 11:41
    Ian Huntington
    3

    An improvement to the code I previously posted we added sorting which required the msxsl:node-set() function so remember to include the namespace!

    Here is the full xslt file

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [
    <!ENTITY nbsp "&#x00A0;">
    ]>
    <xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    xmlns:umbraco.library="urn:umbraco.library"
    exclude-result-prefixes="msxml umbraco.library">


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

    <xsl:param name="currentPage"/>

    <xsl:variable name="count">4</xsl:variable>

    <xsl:variable name="sorted">
    <xsl:for-each select="$currentPage/ancestor-or-self::node [@level= 2]/node">
    <xsl:sort select="@nodeName" data-type="text" />
    <xsl:copy-of select="." />
    </xsl:for-each>
    </xsl:variable>

    <xsl:template match="/">
    <div class="subNav">
    <xsl:for-each select="msxsl:node-set($sorted)/node[position() mod $count = 1]">
    <xsl:sort select="@nodeName" order="ascending"/>
    <ul>
    <xsl:apply-templates select=".|following-sibling::node[position() &lt; $count]" />
    </ul>
    </xsl:for-each>
    </div>
    </xsl:template>

    <xsl:template match="node">
    <li>
    <xsl:choose>
    <xsl:when test="$currentPage/ancestor-or-self::node/@id = current()/@id">
    <span>
    <xsl:value-of select="@nodeName"/>
    </span>
    </xsl:when>
    <xsl:otherwise>
    <a href="{umbraco.library:NiceUrl(@id)}">
    <xsl:value-of select="@nodeName"/>

    </xsl:otherwise>
    </xsl:choose>
    </li>
    </xsl:template>


    </xsl:stylesheet>
  • Nik Wahlberg 639 posts 1237 karma points MVP
    Jul 30, 2009 @ 14:00
    Nik Wahlberg
    0

    thanks for sharing your solution. Much appreciated.

    -- Nik

  • Rick Mather 42 posts 124 karma points
    Jul 31, 2009 @ 12:01
    Rick Mather
    0

    A vote up for Huntington's solution. XSLT 'mod' and 'following-sibling' are a very convenient way of rendering columns or grouped data.

Please Sign in or register to post replies

Write your reply to:

Draft