Copied to clipboard

Flag this post as spam?

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


  • prl 32 posts 54 karma points
    Jan 27, 2010 @ 12:12
    prl
    0

    Using Xslt for Navigation menu

    Hello,

    This probably was asked before but I didn't find anything, so sorry if I'm repeating a question:

    In my Master Template I have a header section in which I want to include some navigation links, The structure of my contents is:

    Main (TestHomepage doc type)
      |--- About Us (TestTextpage doc type)
      |--- About Them (TestTextpage doc type)

    I want the header to look like this: "Main . About Us . About Them" .. Should be pretty simple. The code I have in my Xslt file is:

    <xsl:variable name="level" select="1"/>

    <xsl:template match="/">

    <xsl:for-each select="$currentPage/ancestor-or-self::node [@level=$level]/node [string(data [@alias='umbracoNaviHide']) != '1']">
    <a href="{umbraco.library:NiceUrl(@id)}">
    <xsl:choose>
    <xsl:when test="$currentPage/ancestor-or-self::node/@id = current()/@id">
    <xsl:attribute name="class">selected</xsl:attribute>
    </xsl:when>
    <xsl:otherwise>
    <xsl:attribute name="class">unselected</xsl:attribute>
    </xsl:otherwise>
    </xsl:choose>
    <xsl:value-of select="@nodeName"/></a> .
    </xsl:for-each>

    </xsl:template>

    My problems are:

    1. "Main" doesn's appear and I'm only able to do it like this (before the for-each):

    <xsl:variable name="rootTextpageNode" select="$currentPage/ancestor-or-self::node [@level = 1 and @nodeTypeAlias = 'TestHomepage']" />
    <xsl:value-of select="$rootTextpageNode/@nodeName"/>

    It appears but then how do I know if the "Main" page is selected so that I can apply a css class like I do in the for-each?

    2. In the for-each I add a "." after the link. Obviously I don't want this to happen in the case of the last node listed. How do I accomplish that? I probably should know beforehand the number of nodes that are going to be listed and in case it's the last one I don't add the "." .

    Thanks in advance.

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Jan 27, 2010 @ 12:22
    Lee Kelleher
    0

    Hi prl,

    There are a few ways to handle the "Main" (homepage) link, but I wont over-complicate things just yet. Try this XSLT:

    <xsl:variable name="level" select="1"/>
    
    <xsl:template match="/">
    
        <xsl:variable name="rootTextpageNode" select="$currentPage/ancestor-or-self::node [@level = 1 and @nodeTypeAlias = 'TestHomepage']" />
    
        <a href="{umbraco.library:NiceUrl($rootTextpageNode/@id)}">
            <xsl:choose>
                <xsl:when test="$currentPage/@id = $rootTextpageNode/@id">
                    <xsl:attribute name="class">selected</xsl:attribute>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:attribute name="class">unselected</xsl:attribute>
                </xsl:otherwise>
            </xsl:choose>
            <xsl:value-of select="$rootTextpageNode/@nodeName"/>
            <xsl:text>.</xsl:text>
        </a>
    
        <xsl:for-each select="$currentPage/ancestor-or-self::node [@level=$level]/node [string(data [@alias='umbracoNaviHide']) != '1']">
            <a href="{umbraco.library:NiceUrl(@id)}">
                <xsl:choose>
                    <xsl:when test="$currentPage/ancestor-or-self::node/@id = current()/@id">
                        <xsl:attribute name="class">selected</xsl:attribute>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:attribute name="class">unselected</xsl:attribute>
                    </xsl:otherwise>
                </xsl:choose>
                <xsl:value-of select="@nodeName"/>
            </a>
            <xsl:if test="position() != last()">
                <xsl:text>.</xsl:text>
            </xsl:if>
        </xsl:for-each>
    
    </xsl:template>

    Your second problem is also covered in the above XSLT.  By using the following IF statement:

    <xsl:if test="position() != last()">
        <xsl:text>.</xsl:text>
    </xsl:if>

    Hope that helps.

    Cheers, Lee.

  • prl 32 posts 54 karma points
    Jan 27, 2010 @ 12:39
    prl
    0

    Hey Lee,

    Thanks for your answer!

    The second question was really simple :)

    As for the first one, I keep getting the same error (I got to a solution close to yours and got the same error):

    Error occured
    System.OverflowException: Value was either too large or too small for an Int32.
    at System.Convert.ToInt32(Double value)
    at System.Double.System.IConvertible.ToInt32(IFormatProvider provider)
    at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
    at System.Xml.Xsl.Runtime.XmlQueryRuntime.ChangeTypeXsltArgument(XmlQueryType xmlType, Object value, Type destinationType)
    at System.Xml.Xsl.Runtime.XmlQueryContext.InvokeXsltLateBoundFunction(String name, String namespaceUri, IList`1[] args)
    at (XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime)
    at Root(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime)
    at Execute(XmlQueryRuntime {urn:schemas-microsoft-com:xslt-debug}runtime)
    at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlSequenceWriter results)
    at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter writer, Boolean closeWriter)
    at System.Xml.Xsl.XmlILCommand.Execute(IXPathNavigable contextDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter results)
    at System.Xml.Xsl.XmlILCommand.Execute(IXPathNavigable contextDocument, XmlResolver dataSources, XsltArgumentList argumentList, TextWriter results)
    at System.Xml.Xsl.XslCompiledTransform.Transform(IXPathNavigable input, XsltArgumentList arguments, TextWriter results)
    at umbraco.presentation.webservices.codeEditorSave.SaveXslt(String fileName, String oldName, String fileContents, Boolean ignoreDebugging)

    Apparently the problem is in the NiceUrl function. Maybe the @id isn't correct?

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Jan 27, 2010 @ 12:58
    Lee Kelleher
    0

    Hi prl,

    Yeah, the error is due to the validator (on the XSLT editor) not liking the "$rootTextpageNode/@id" parameter, because it doesn't know that it's an numeric!

    Quick answer would be to wrap number() around it, like so:

    <a href="{umbraco.library:NiceUrl(number($rootTextpageNode/@id))}">

    Alternatively, here is my more XSLT-like approach using an extra template:

    <?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:variable name="homepageNode" select="$currentPage/ancestor-or-self::node[@level=1]" />
            <xsl:if test="count($homepageNode) &gt; 0">
                <xsl:apply-templates select="$homepageNode" />
                <xsl:text>.</xsl:text>
                <xsl:apply-templates select="$homepageNode/descendant-or-self::node[string(data[@alias='umbracoNaviHide']) != '1']" />
            </xsl:if>
        </xsl:template>
    
        <xsl:template match="node">
            <a href="{umbraco.library:NiceUrl(@id)}">
                <xsl:choose>
                    <xsl:when test="$currentPage/ancestor-or-self::node/@id = @id">
                        <xsl:attribute name="class">selected</xsl:attribute>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:attribute name="class">unselected</xsl:attribute>
                    </xsl:otherwise>
                </xsl:choose>
                <xsl:value-of select="@nodeName" />
            </a>
            <xsl:if test="position() != last()">
                <xsl:text>.</xsl:text>
            </xsl:if>
        </xsl:template>
    
    </xsl:stylesheet>

    Good luck, Lee.

  • prl 32 posts 54 karma points
    Jan 27, 2010 @ 13:02
    prl
    0

    Lee,

    The number($rootTextpageNode/$id) still gives me the same error .

    Can you briefly descbribe the functioning of that xslt, if that's not too much work for you? :)

    Thanks!

  • Dirk De Grave 4541 posts 6021 karma points MVP 3x admin c-trib
    Jan 27, 2010 @ 13:09
    Dirk De Grave
    1

    Possible solutions:

    - Surround the offending statement with 

    <xsl:if test="string($rootTextpageNode/@id)) != ''">
      <a href="{umbraco.library:NiceUrl($rootTextpageNode/@id)}">
    </xsl:if>

    - Check the 'Skip errors' box

    Reason is that the $rootTextpageNode/@id doesn't have a value until runtime, resulting in an empty string or NaN, which is invalid as parameter fed to the NiceUrl() function

     

    Hope this helps,

    /Dirk

  • prl 32 posts 54 karma points
    Jan 27, 2010 @ 13:33
    prl
    0

    Dirk, thanks for the help. I just had to put the </xsl:if> in the end of the part for the Main page link. Full xslt looks like this:

    <?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" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets"
    exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">

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

    <xsl:param name="currentPage"/>

    <!-- Input the documenttype you want here -->
    <xsl:variable name="level" select="1"/>
    <xsl:variable name="rootTextpageNode" select="$currentPage/ancestor-or-self::node [@level = 1 and @nodeTypeAlias = 'TestHomepage']" />

    <xsl:template match="/">

    <xsl:if test="string($rootTextpageNode/@id) != '' ">
    <a href="{umbraco.library:NiceUrl($rootTextpageNode/@id)}">

    <xsl:choose>
    <xsl:when test="$currentPage/@id = $rootTextpageNode/@id">
    <xsl:attribute name="class">selected</xsl:attribute>
    </xsl:when>
    <xsl:otherwise>
    <xsl:attribute name="class">unselected</xsl:attribute>
    </xsl:otherwise>
    </xsl:choose>
    <xsl:value-of select="$rootTextpageNode/@nodeName"/>
    </a>
    <xsl:text> . </xsl:text>
    </xsl:if>

    <!-- The fun starts here -->
    <xsl:for-each select="$currentPage/ancestor-or-self::node [@level=$level]/node [string(data [@alias='umbracoNaviHide']) != '1']">
    <a href="{umbraco.library:NiceUrl(@id)}">
    <xsl:choose>
    <xsl:when test="$currentPage/ancestor-or-self::node/@id = current()/@id">
    <xsl:attribute name="class">selected</xsl:attribute>
    </xsl:when>
    <xsl:otherwise>
    <xsl:attribute name="class">unselected</xsl:attribute>
    </xsl:otherwise>
    </xsl:choose>
    <xsl:value-of select="@nodeName"/></a>
    <xsl:if test="position() != last()">
    <xsl:text> . </xsl:text>
    </xsl:if>
    </xsl:for-each>
    <br /><br />

    </xsl:template>

    </xsl:stylesheet>

    I guess I'll be stopping here a lot of times :)

    Cheers,

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Jan 27, 2010 @ 14:36
    Lee Kelleher
    0

    Hi prl, my example above with the template (match=node) is used to apply to all the <node> elements that are passed to it.

    It gives you better separate of HTML mark-up and logic.  So if you needed to quickly modify the <a> tag, lets say by adding a new class or attribute, then you only need to do it in one place, rather than twice (for the homepage and in the for-each).

    There are also performance gains with using "apply-templates" over "for-each" ... but for an XSLT this size, you'd never ever notice.

     

    Don't forget to mark an answer as the solution... and to vote up any helpful answers!

    Cheers, Lee.

  • prl 32 posts 54 karma points
    Jan 27, 2010 @ 18:52
    prl
    0

    Hey guys, I'll use this topic to post some other questions regarding this subject.

    Imagining I have a structure like this:

    Main
      |-- A
      |   |-- 1
      |   |-- 2
      |       |-- 3
      |       |-- 4
      |       |-- 5
      |-- B
      |-- C
      |-- D

    In my xslt I have the variable level with the value 1 and a simple for-each loop:

    <xsl:variable name="level" select="1"/>

    <xsl:for-each select="$currentPage/ancestor-or-self::node [@level=$level]/node [string(data [@alias='umbracoNaviHide']) != '1']">
        <xsl:value-of select="@nodeName"/>
        <xsl:if test="position() != last()">
                <xsl:text> . </xsl:text>
            </xsl:if>
    </xsl:for-each>


    First of all, the "[@level=$level]" part indicates from which level the "$currentPage/ancestor-or-self::node" is applied right? And "Main" is in level 0, "A, B, C and D" are in level 1?

    So, my first doubt regarding this is: why is this printing "Main . A . B . C . D"? If I'm on node A (currentPage is node A) shouldn't it print only "A . Main"? Isn't that what $currentPage/ancestor-or-self::node does? Why is it printing everything? (I used this for reference: http://umbraco.org/documentation/books/xslt-basics/xpath-axes-and-their-shortcuts)

    Second question is: if I want to print in my loop the path to my node (for example "Main . A . 2 . 5"), I should only remove the [@level=$level] part, right? But if I do this what I'm getting is: "Main . A . 1 . 2 . 3 . 4 . 5 . B . C . D" .. From the documentation, "ancestor-or-self" on node 5 should return "5 . 2 .A . Main".

    Thanks again.

  • prl 32 posts 54 karma points
    Jan 27, 2010 @ 19:01
    prl
    0

    Sorry, made a confusion there. "Main" is being printed outside the loop. Either way, I'm still not understanding why it's printing everything..

  • prl 32 posts 54 karma points
    Jan 28, 2010 @ 11:35
    prl
    0

    Another question (sorry guys):

    In my for-each loop where I apply a certain style to the selected page I'm doing this:

    <xsl:for-each select="$currentPage/ancestor-or-self::node [@level=$level]/node [string(data [@alias='umbracoNaviHide']) != '1']">
                   
    <a href="{umbraco.library:NiceUrl(@id)}">
                   
    <xsl:choose>
                           
    <xsl:when test="$currentPage/ancestor-or-self::node/@id = current()/@id">
                                   
    <xsl:attribute name="class">selected</xsl:attribute>
                           
    </xsl:when>
                           
    <xsl:otherwise>
                                   
    <xsl:attribute name="class">unselected</xsl:attribute>
                           
    </xsl:otherwise>
                   
    </xsl:choose>          
                   
    <xsl:value-of select="@nodeName"/></a>
                   
    <xsl:if test="position() != last()">
                           
    <xsl:text> . </xsl:text>
                   
    </xsl:if>
    </xsl:for-each>

    Shouldn't the bold and underlined line be:

     <xsl:when test="$currentPage/self::node/@id = current()/@id">

    Because, what I want is to test if the id of the page I am ($currentPage/self::node/@id) is the same as the id of the page being "looped" (current()/@id).

    I tested and it worked. Actually I don't know how it was working the other way.

    Thanks.

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Jan 28, 2010 @ 12:20
    Lee Kelleher
    0

    Hi prl,

    You don't need to put the "/self::node" bit, as the context is already there, just use "$currentPage/@id".

    The same goes for "current()/@id" - you can just use "@id".  For example:

    <xsl:when test="$currentPage/@id = @id">

    Hope that makes sense?

    Cheers, Lee.

  • prl 32 posts 54 karma points
    Jan 28, 2010 @ 16:03
    prl
    0

    It makes, thanks :)

    Well, I advanced a little more but still have doubts about some things, specially the for-each line and the axes.I read the documentation for this and understood well the functioning of it. Can someone explain me in detail how this line works?

    <xsl:for-each select="$currentPage/ancestor-or-self::node [@level=$level]/node [string(data [@alias='umbracoNaviHide']) != '1']">

    I'll use the same structure as before for my example:

    Main        (TestHomepage doc type)
     
    |-- A (TestTextpage doc type, and all the rest are the same)
      |   |-- 1
    |
      |-- 2
     
    |       |-- 3
     
    |       |-- 4
     
    |       |-- 5
     
    |-- B
     
    |-- C
     
    |-- D

    I kow that the "$currentPage/ancestor-or-self::node" part returns the node I'm in and it's ancestors (or at least it should). So if I'm on the node 5 it should return nodes 5, 2, A and Main right? the "/node" bit returns the elements "node" for I'm in right? I still don't understand what the [@level=$level] is doing and how it works. The rest ([string(data [@alias='umbracoNaviHide']) != '1']) as far as I know is a best practice for this cases and actually in my case isn't doing anything since none of my Document Types has this property.

    There are two distinct scenarios that I want to achieve:

    1. List in my header the nodes from level 1 and 2, i.e., "Main, A, B, C, D" .. That's what it's doing right now, although I still haven't figured out how exactly.

    2. List the complete path to the node I'm in, for example, "Main, A, 2, 5" . I can do this by having the for-each like this: <xsl:for-each select="$currentPage/ancestor-or-self::node"/>

     

    Thanks for all the help.

  • Lee Kelleher 4020 posts 15802 karma points MVP 13x admin c-trib
    Jan 28, 2010 @ 16:31
    Lee Kelleher
    0

    Hi prl,

    Obviously, you can't learn XSLT from a couple of forum posts, my advice would be to read up about XPath Axes on W3Schools (and also Predicates)

    You are right with your thinking about the "ancestor-or-self::node" matching "5, 2, A and Main" ... but also include the "[@level = $level]" predicate.

    For a moment, swap $level with the number 1, (as that is what you have in the <xsl:variable>) ... so now you have, "$currentPage/ancestor-or-self::node[@level=1]" ... this will navigate up the node-set (from the current node) until it finds a <node> with the attribute "level" that has a value of "1".  Which is the homepage (99% of the time).

    As for 'umbracoNaviHide' - if you aren't using that property in your doc-types, then it's a waste of time ... so remove it.  The reason it's there is because it gives you more control over which nodes you want to appear in your XSLT. (It's pretty much the de-facto for hiding nodes from sitemaps/navigation).

    Hope this helps?

    Happy XSLT'ing, Lee

     

  • prl 32 posts 54 karma points
    Jan 28, 2010 @ 17:19
    prl
    1

    Lee, thanks for all your patience. I read the documentation here in the Umbraco forum and understood almost all of it but the predicate part was confusing me. I understand it completely now...

    "$currentPage/ancestor-or-self::node[@level=1]" - this would return me all the previous nodes and the current node if it wasn't for the precidate @level=1 that limits the results to the node(s) with level = 1. In my case, only the node "Main".

    By adding "/node" to the statment, I will get all the child nodes of "Main": A, B, C and D (no predicates here so all of them are returned). So, if I want the root node (Main) to appear on the menu I have to add it before the for-each loop.

    All's clear now. Thanks!

  • vinod 2 posts 22 karma points
    Dec 03, 2013 @ 11:10
    vinod
    0

    Hi All, 

    I am trying to show few links base on user privileges parent and child li's, stuck at this code please help me, googled a lot nothing on these. these generic list items and user are from active directory and defined by numbers to show node name. 

    If parent has permission and few of the child elements share the same as parent permission want to hide. any advice.

    <xsl:variable name="visitors" select="2"/>
    <xsl:variable name="hr" select="3"/>
    <xsl:variable name="finance" select="6"/>
    <xsl:variable name="Admins" select="8"/>

    <ul class="nav-menu">
    <li class="first">
    <a href="/intranet-home.aspx" alt="Intranet-Home">Home</a>
    </li>
    <xsl:for-each select="$currentPage/ancestor-or-self::*[@isDoc and @level=$level]/* [@isDoc and string(umbracoNaviHide) != '1']">
    <li>
    <a href="{umbraco.library:NiceUrl(@id)}">
    <xsl:value-of select="@nodeName"/>
    </a>
    <xsl:if test="count(./child::*[@isDoc and string(umbracoNaviHide) != '1']) &gt; 0">
    <ul>
    <xsl:for-each select="./child::*[@isDoc and string(umbracoNaviHide) != '1']"><xsl:choose>
    <xsl:when test="contains(umbraco.library:Session('priviliges'), $PM_Privilige)"><xsl:if test="@nodeName='Updates'"><li>
                <a href="{umbraco.library:NiceUrl(@id)}">
                <xsl:value-of select="@nodeName"/>
               </a>
              </li>
      </xsl:if>
    </xsl:when>
    <xsl:otherwise>
    <li>
                <a href="{umbraco.library:NiceUrl(@id)}">
                  <xsl:value-of select="@nodeName"/>
                </a>
              </li>
    </xsl:otherwise>
    </xsl:choose>
            </xsl:for-each>
    </ul> 
    </xsl:if>
      </li>
    </xsl:for-each>
    </ul>

Please Sign in or register to post replies

Write your reply to:

Draft