</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.
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.
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) > 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...
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.
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.
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:-
Below is my XSL so far
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
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
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"></ul><ul></xsl:text>
>Tommy
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) > 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...
wah! my reply messed up, okay your code is between the "pre span" tags, sorry if there was any confusion.
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.
Sorry, that was a mess ....
<pre>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp " "> ]> <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() >= $startPosition) and (position() <= ($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) <=$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>
Sorry ......
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp " "> ]> <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() >= $startPosition) and (position() <= ($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) <=$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>
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.
A colleague of mine has just come up with another solution
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
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
thanks for sharing your solution. Much appreciated.
-- Nik
A vote up for Huntington's solution. XSLT 'mod' and 'following-sibling' are a very convenient way of rendering columns or grouped data.
is working on a reply...