Copied to clipboard

Flag this post as spam?

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


  • Alex Burr 77 posts 128 karma points
    Feb 27, 2012 @ 21:49
    Alex Burr
    0

    Subnavigation from a root defined by document type

    Hi everyone,

    I'm working on an Umbraco solution for our company intranet and I'm trying to solve a specific problem. Basically I have a document type called "Site Homepage" (alias SiteHomepage) that I wish to serve as the upper and lower bound of my subnavigation XSLT. Therefore, the site homepage and all child pages will draw subnavigation going only up as far as the nearest ancestor SiteHomepage, and only as far down as a descendant SiteHomepage.

    For example, if I have the following structure:

    Content
    - Site 1 (SiteHomepage)
    - - Page 1A
    - - Page 1B
    - - Site 1C (SiteHomepage)
    - - - Page 1CA
    - - - Page 1CB
    - Site 2 (SiteHomepage)
    - - Page 2A
    - - - Page 2AA
    - - Page 2B
    - Site 3 (SiteHomepage)
    - - Page 3A
    - - Page 3B

    Then viewing page 1A would show the following subnavigation list:

    - Site 1
    - Page 1A
    - Page 1B
    - Site 1C

    Viewing page 2AA would show the following:

    - Site 2
    - Page 2A
    - - Page 2AA
    - Page 2B

    And viewing page 1CA would show the following:

    - Site 1C
    - Page 1CA
    - Page 1CB

    I am really having a hard time with the logic governing this. I've tried basing my macro off the existing Subnavigation XSL templates in Umbraco, and I've also tried using the Cogworks Flexible Navigation package, but I have difficulty seeing the trees for the forest.

    Any help would really save my skin!

  • Chriztian Steinmeier 2800 posts 8791 karma points MVP 8x admin c-trib
    Feb 27, 2012 @ 23:21
    Chriztian Steinmeier
    0

    Hi Alex,

    This is not too hard, once you figure out what is supposed to happen - I hardly ever use "generic" systems for navigation because there's always something in the current project that isn't possible (or they have 5 times more code than necessar for the present problem). I took a quick look at your structure and desired output, and wrote this, to show how I'd approach that situation:

    <?xml version="1.0" encoding="utf-8" ?>
    <xsl:stylesheet
        version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:umb="urn:umbraco.library"
        exclude-result-prefixes="umb"
    >
    
        <xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
    
        <xsl:param name="currentPage" />
    
        <!-- Grab the first SiteHomepage upwards from the current page -->
        <xsl:variable name="siteRoot" select="$currentPage/ancestor-or-self::SiteHomepage[1]" />
    
        <xsl:template match="/">
            <ul>
                <!-- Process the root and its children -->
                <xsl:apply-templates select="$siteRoot | $siteRoot/*[@isDoc]" />
            </ul>
        </xsl:template>
    
        <!-- Generral template for a document node -->
        <xsl:template match="*[@isDoc]">
            <li>
                <a href="{umb:NiceUrl(@id)}">
                    <xsl:value-of select="@nodeName" />
                </a>
                <!-- If this is a Textpage and it has children of that type, generate a sub list -->
                <xsl:if test="self::Textpage/Textpage">
                    <ul>
                        <xsl:apply-templates select="Textpage" />
                    </ul>
                </xsl:if>
            </li>
        </xsl:template>
    
    </xsl:stylesheet>
    
    Note - you didn't state the alias of the subpages so I've used "Textpage" - you should substitute your actual documenttype name where I've used that (they're bold in the sample)

    /Chriztian 

  • Alex Burr 77 posts 128 karma points
    Feb 28, 2012 @ 20:54
    Alex Burr
    0

    Thank you so much Chriztian!

    My only issue is, when I include another SiteHomepage further *down* in the tree, it should appear as a link at the appropriate level - but its children should *not* appear.

    In my example above, viewing page 1A should show the following subnavigation list:

    - Site 1
    - Page 1A
    - Page 1B
    - Site 1C

    Using your XSL, it shows the following:

    - Site 1
    - Page 1A
    - Page 1B

    (Site 1C is absent). I'm guessing I need to alter this line:

    <xsl:apply-templates select="$siteRoot | $siteRoot/*[@isDoc]" />

    To include the SiteHomepage document type in the conditions? 

  • Chriztian Steinmeier 2800 posts 8791 karma points MVP 8x admin c-trib
    Feb 28, 2012 @ 23:51
    Chriztian Steinmeier
    0

    Hi Alex,

    That's strange, because it does the right thing in my setup - I checked the three examples you provided.

    You shouldn't need to change that line - it takes the root (found by going up recursively to find the first SiteHomepage) and its children (of any Document Type) and sends them away for processing.

    In the processing template, we only list childnodes if the current node is a Textpage document (or whatever you've renamed it to), but as long as the SiteHomepage for Site 1C is a childnode of Site 1, it'll be processed by the template (but won't have its children processed).

    Is there anything else in your setup that could interfere with this?

    /Chriztian

  • Alex Burr 77 posts 128 karma points
    Feb 29, 2012 @ 16:38
    Alex Burr
    0

    Ah, apparently I was looking at the wrong node in my test example! Forehead slap.

    Things are working well with one small problem... all nodes are displayed within the siteRoot delimiters. I now need to add the "exploding" functionality that is common to so many nav functions. Right now, Page 2AA is visible in the tree when the user is viewing Site2 or Page 2B. It should be hidden until the user visits Page2A (or Page2AA).

    Here's my macro for reference. I've made some additional modifications to add whitespace and some special characters and classes I'm using. Also I had to edit the line <xsl:apply-templates select="$siteRoot | $siteRoot/*[@isDoc]" />; it was ignoring the sort order and always putting SiteRoots at a fixed position.

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [
        <!ENTITY nbsp "&#x00A0;">
        <!ENTITY rarr "&#x2192;">
    ]>
    <xsl:stylesheet
            version="1.0"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:umb="urn:umbraco.library"
            exclude-result-prefixes="umb">
    
        <xsl:output method="html" omit-xml-declaration="yes"/>
    
        <xsl:param name="currentPage" />
    
        <!-- Grab the first SiteHomepage upwards from the current page -->
        <!-- Thanks to Chriztian Steinmeier on the Umbraco forums -->
        <!-- http://our.umbraco.org/forum/developers/xslt/29195-Subnavigation-from-a-root-defined-by-document-type -->
    
        <xsl:variable name="siteRoot" select="$currentPage/ancestor-or-self::SiteHomepage[1]" />
        <xsl:template match="/">
    
            <nav class="subnav">
                <xsl:text>&#xa;</xsl:text> <!-- newline -->
                <ul>
                    <!-- Process the root -->
                    <xsl:apply-templates select="$siteRoot" />
                    <!-- Process its children -->
                    <xsl:apply-templates select="$siteRoot/*[@isDoc]">
                        <xsl:sort select="@sortOrder" order="ascending" data-type="number" />
                    </xsl:apply-templates>
                </ul>
                <xsl:text>&#xa;</xsl:text> <!-- newline -->
            </nav>
        </xsl:template>
    
        <!-- General template for a document node -->
        <xsl:template match="*[@isDoc]">
    
            <xsl:choose>
                <!-- Did the user specifiy *not* to display this node? -->
                <xsl:when test="string(umbracoNaviHide) != '1'">
    
                    <!-- Is the node selected? -->
                    <xsl:variable name="isSelected">
                        <xsl:if test="$currentPage/@id = @id">selected</xsl:if>
                    </xsl:variable>
    
                    <li class="{$isSelected}">
                        <a href="{umb:NiceUrl(@id)}">
    
                            <!-- Set the innerText for the a element -->
                            <xsl:value-of select="./pageTitle/text()"/>
                            <xsl:if test="string(./pageTitle/text()) = ''">
                                <xsl:value-of select="@nodeName"/>
                            </xsl:if>
    
                            <!-- Add &rarr; character to current page -->
                            <xsl:if test="string($isSelected) = 'selected'"> &rarr;</xsl:if>
                        </a>
    
                        <!-- Generate a sub list -->
                        <xsl:if test="self::SitePage/SitePage">
                            <xsl:text>&#xa;</xsl:text> <!-- newline -->
                            <ul>
                                <xsl:apply-templates select="SitePage" />
                            </ul>
                        </xsl:if>
                    </li>
                </xsl:when>
    
                <!-- Need to leave this blank -->
                <xsl:otherwise></xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    
    </xsl:stylesheet>
  • Chriztian Steinmeier 2800 posts 8791 karma points MVP 8x admin c-trib
    Feb 29, 2012 @ 20:40
    Chriztian Steinmeier
    0

    Hi Alex,

    That check is not that difficult - you just need to add a check for currentPage being within the branch you're rendering, i.e.:

    <xsl:if test="self::SitePage/SitePage and descendant-or-self::*[@id = $currentPage/@id]">

    OK - I have a few hints (hope you don't mind that):

    1.) If you're wrapping all of the template contents in an xsl:if or xsl:choose, you should probably add that condition to the selection instead (e.g., you're filtering all the hidden pages by checking umbracoNaviHide - so just add that predicate to the apply-templates instruction invoking the template instead.) 

    2.) Instead of adding blnk class attributes for all but the selected one, you can just add that single class on the specific item using the xsl:attribute instruction. But it has to be done before adding element content for the element you're adding it to.

    <xsl:apply-templates select="$siteRoot/*[@isDoc][not(umbracoNaviHide = 1)]">
    
    ...
    
    <!-- General template for a document node -->
    <xsl:template match="*[@isDoc]">
        <!-- Is the node selected? -->
        <xsl:variable name="isSelected" select="@id = $currentPage/@id" />
        <li>
            <xsl:if test="$isSelected">
                <xsl:attribute name="class">selected</xsl:attribute>
            </xsl:if>
            <a href="{umb:NiceUrl(@id)}">
                <!-- Set the innerText for the a element (use pageTitle but fallback to @nodeName if empty) -->
                <xsl:value-of select="(@nodeName[not(normalize-space(../pageTitle))] | pageTitle)[1]" />
    
                <!-- Add &rarr; character to current page -->
                <xsl:if test="$isSelected"> &rarr;</xsl:if>
            </a>
    
            <!-- Generate a sub list if this the selected page is somewhere down this branch -->
            <xsl:if test="self::SitePage/SitePage and descendant-or-self::*[@id = $currentPage/@id]">
                <xsl:text>&#xa;</xsl:text> <!-- newline -->
                <ul>
                    <xsl:apply-templates select="SitePage" />
                </ul>
            </xsl:if>
        </li>
    </xsl:template>
    
    (There's a one-liner too, for the pageTitle or @nodeName selection)

    Hope you get it working :-)

    /Chriztian 

  • Alex Burr 77 posts 128 karma points
    Feb 29, 2012 @ 22:41
    Alex Burr
    0

    Chriztian,

    You are great. Thanks so much for your help!

    I did follow your advice and shorten the logic quite a bit. I was actually aware that I was doing things heavy-handedly, in particular I wanted to eliminate that <xsl:choose> bit regarding umbracoNaviHide, I just couldn't figure out where to put the condition (or the logical syntax, frankly).

    Karma given and I am now following your Twitter. :)

Please Sign in or register to post replies

Write your reply to:

Draft