Sitemap XML, and an InPath attribute

Topics: General, XSLT
Nov 13, 2010 at 5:58 PM

Hi Guys,

I'm creating a site where i've split my navigation into a standard Top/Left navigation.

I've created 2 functions that both utilize the SitemapXml functions output.

My navigation structure is like this:

Frontpage
- Subpage
- Subpage 2
-- Subpage 2.1
-- Subpage 2.2
- Subpage 3 
- Subpage 4 

My problem is that when i'm on "Subpage 2.1" i'd like a css class added to "Subpage 2" telling my that it's open/inpath. I just can't figure out how :-)

My mainmenu functions lists first level of pages under Frontpage,

And my secondary menu lists pages under the selected page.

 

My function calls XML looks like this for my Mainmenu:

<f:functions xmlns:f="http://www.composite.net/ns/function/1.0">
	<f:function name="Composite.Pages.GetPageId" localname="GetPageId" />
	<f:function name="Composite.Pages.SitemapXml" localname="SitemapXml">
		<f:param name="SitemapScope" value="Level2AndSiblings" />
		<f:param name="SourcePage" value="0f1f199d-c0aa-40ed-b262-314b9f8e7097" />
	</f:function>
</f:functions>

And my template looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:in="http://www.composite.net/ns/transformation/input/1.0" xmlns:lang="http://www.composite.net/ns/localization/1.0" xmlns:f="http://www.composite.net/ns/function/1.0" xmlns="http://www.w3.org/1999/xhtml" exclude-result-prefixes="xsl in lang f">
	<xsl:template match="/">
		<html>
			<head>
				<!-- markup placed here will be shown in the head section of the rendered page --></head>
			<body>
				<!-- markup placed here will be the output of this rendering -->
				<xsl:variable name="currentPage" select="/in:inputs/in:result[@name='GetPageId']" />
				<xsl:variable name="pages" select="/in:inputs/in:result[@name='SitemapXml']/Page" />
				<xsl:if test="count($pages) &gt; 0">
					<ul id="mainmenu" class="grid_13">
					    <li><img src="/Frontend/gfx/nav_start.png" /></li>
					   <li><a href="/">Forside</a></li>
						<xsl:for-each select="$pages">
							<li>
							 <xsl:if test="@Id = $currentPage">
							     <xsl:attribute name="class">active</xsl:attribute>
							 </xsl:if>
							
								<a href="{@URL}" title="{@Title}">
									<xsl:value-of select="@UrlTitle" />
									<xsl:value-of select="@IsOpen" />
								</a>
								<textarea>
								    <xsl:copy-of select="."/>
								</textarea>
							</li>
						</xsl:for-each>
						<li><img src="/Frontend/gfx/nav_end.png" /></li>
					</ul>
				</xsl:if>
			</body>
		</html>
	</xsl:template>
</xsl:stylesheet>

How do i implement an "InPath" class?, i see the need for the same feature when my secondary navigation get multileveled.

Regards

 Martin

 

 

Coordinator
Nov 13, 2010 at 8:39 PM

Hi Martin

Function Composite.Pages.GetPageId has a "SiteMapScope" attribute, if you set it to "Level2", the function will the return the current Level2 page, or Guid.Empty if a root page is selected.

Something like that:

 

<f:functions xmlns:f="http://www.composite.net/ns/function/1.0">
	<f:function name="Composite.Pages.GetPageId" localname="GetPageId">
		<f:param name="SitemapScope" value="Level2" />
	</f:function>
	<f:function name="Composite.Pages.SitemapXml" localname="SitemapXml">
		<f:param name="SitemapScope" value="Level2AndSiblings" />
                <!-- I guess it is better not to use the id of the frontpage here since it will work even without doing so -->
		<!--f:param name="SourcePage" value="0f1f199d-c0aa-40ed-b262-314b9f8e7097" /-->
	</f:function>
</f:functions>

 

That should fix the mainmenu function

 


 

>> How do i implement an "InPath" class?, i see the need for the same feature when my secondary navigation get multileveled.

you also can get the whole SiteMap, and by doing so you can check whether a page has a descendant page which is the "current page". As far as I remember it will have some kind of attribute, something like isCurrentPage="true", so you don't have to compare attributes.

 

  <xsl:if test="count(descendant::Page[count(@isCurrentPage) > 0]) > 0"> 
    <xsl:attribute name="class">InPath</xsl:attribute>
  </xsl:if>
The last approach wouldn't work if you have 1000  pages - xslt will have super slow

 

// Dmitry

Coordinator
Nov 13, 2010 at 10:55 PM

Hi Martin,

In your XSLT Function, consider simplifying you function calling to just this:

<f:functions xmlns:f="http://www.composite.net/ns/function/1.0">
    <f:function name="Composite.Pages.SitemapXml" localname="SitemapXml">
        <f:param name="SitemapScope" value="Level2AndSiblings" />
    </f:function>
</f:functions>

(just the sitemap, from level 2 and down - let the function resolve the SourcePage by it own, default is 'Page being rendered')

Then go to your 'Settings' tab - in the 'Debug' change the 'Page' to some page deeper down your structure you want to test with.
By default this is the homepage, but you want to simulate deeper. Changes here will influence your preview XML so you can 'simulate' different pages. 

Then click the 'Preview' tab - notice the XML showing the page structure - in here you will find isopen="true" and iscurrent="true" attributes pn those pages which are open and the one that is active..
They are 'true' depending on the page relative to the one you specified in Settings-->Debug-->Page.

So the XML you get already has the structure and open/selected info you need - any you should be able to have a very simple, perhaps recoursive XSLT.

Consider downloading the package Composite.Navigation.Distributed - either for inspiration or direct use.

Below is some XSLT which work with Sitemap XML - not solving your specific task, but perhaps useful:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="xsl in"
	xmlns:in="http://www.composite.net/ns/transformation/input/1.0"
	xmlns:lang="http://www.composite.net/ns/localization/1.0" 
	xmlns="http://www.w3.org/1999/xhtml">
	
	<xsl:variable name="map" select="/in:inputs/in:result[@name='SitemapXml']"/>
	
	<xsl:template match="/">
		<html>
			<head/>
			<body>
				<nl>
					<xsl:apply-templates select="$map/Page/Page[@isopen='true']/Page[@MenuTitle!='']"/>
				</nl>
			</body>
		</html>
	</xsl:template>
	
	<xsl:template match="Page">
		<li href="{@URL}">
			<xsl:if test="@iscurrent='true'">
				<xsl:attribute name="class">selected</xsl:attribute>
			</xsl:if>
			<xsl:value-of select="@MenuTitle"/>
			<xsl:if test="@isopen='true' and Page[@MenuTitle!='']">
				<nl>
					<xsl:apply-templates select="Page[@MenuTitle!='']"/>
				</nl>
			</xsl:if>
		</li>
	</xsl:template>
	
</xsl:stylesheet>

 

Marcus

Nov 13, 2010 at 11:02 PM

In simple .Net it would be

private bool isOpen(SiteMapNode node)
{
    return SiteMap.CurrentNode.Equals(node) || SiteMap.CurrentNode.IsDescendantOf(node);
}

Nov 14, 2010 at 7:33 PM

Hi All,

Thank you for you input and suggestions.

I solved the problem by changing the SiteScope to "All pages", which added the missing "isopen" attributes to the XML.

If i go back to my sample structure from above, it makes sense that the "isopen" attribute isn't in the XML from my example, but shouldn't i be able to see that a subpage is selected, even if the current page isn't in the current scope?
Ofcause a different attribute would be needed to keep it logic.

PS.

Thanks for the tip with the Debug settings btw, didn't notice them before, "is very nice" as Borat would say :-)

 

Regards
 Martin