Navigation macro that accounts for non navigation content items

Standard disclaimers apply. I have removed some specific code related to checking for “special navigation items” (ie. items beginning with “–” that are specific for our implementation). This code is pretty old (once I had it working I tried not to touch it again…so there might be redundant calls…I will eventually get back to streamlining it, but right now it isn’t broken so…). Because velocity (at least the one included in 6.5 and 6.7) has a…differing… concept of variable scope, macros are needed for each navigation level (so that variables used for determining the current level of navigation are not clobbered with sub navigation…ie i tried to make nextnavlevel a recursive function and no luck there).

Note:
[ul]
[li]our navigation content types are called “vtNavBase” and “vtNavBranch”. You will need to change those to the defaults (ie. “rffNavTree” and “rffNavOn”).
[/li][li]also to add non navigation items to the nav submenu, you’ll have to edit the slot “vtSNavSubmenu” (or the default is “rffNavSubmenu”…you will probably need to do a find/replace to switch those out in the code)
[/li][/ul]

Let me know if you find any improvements or have any comments!


##
## on snippet template for navigation
## 
#set($this_cid = $sys.assemblyItem.getNode().getProperty('rx:sys_contentid').String)##
#set($parent_cid = $sys.assemblyItem.getCloneParentItem().getNode().getProperty('rx:sys_contentid').String)##
#set($thisN = $sys.assemblyItem.clone())##
##
##
#if($parent_cid.length()>0)##
#rootlevel($nav.root $thisN $parent_cid "0" "4")##
#else##
#rootlevel($nav.root $thisN $this_cid "0" "4")##
#end## if $parent_cid
##
##


## --- BEGIN Navigation related Macros -----------------
##
##
##
## Root level, starting the navigation off.
## type:
## 0 = default (only expand out to current level)
## 1 = show all levels
## 2 = show third level all at current second level...for horizontal navigation
#macro(rootlevel $node $thisN $item_cid $type $maxLevel)##
#set($submenu = $node.getNodes("nav:submenu"))## 
#vtReturnFieldEsc($node "rx:displaytitle" $title)##
#set($landing_page = $node.getProperty("nav:url").String)##
#set($axis = $node.getProperty("nav:axis").String)##
#if($node)##
<ul>
#set($this_cid = $node.getProperty("nav:landingPage").getNode().getProperty("rx:sys_contentid").String)##
  <li id="vt_nav_home">
#if($axis=="SELF" && $parent_cid.equals($this_cid))##
    <a class="vt_active" href="$landing_page" title="$title">$title</a>
#else##
    <a href="$landing_page" title="$title">$title</a>
#end## if $axis=="SELF" ...
  </li>
#isCurrentInSubmenu($submenu $isInLevel $item_cid)##
$thisN.setNode($node.getProperty("nav:landingPage").getNode())##
#set($submenu = $node.getNodes("nav:submenu"))##
#foreach ($navon in $submenu)##
#nextnavlevel ($navon $isInLevel "1" $velocityCount $thisN $item_cid $type $maxLevel)##
#end##
</ul>
#end## if $node
#end## macro rootlevel 
##
##
## NOTE: Have to break out nav because $lvlNum becomes "global" variable, and thus we can't do a recursive
## call on one macro...
#macro(nextnavlevel $node $isInThisLevel $lvlNum $counter $thisN $item_cid $type $maxLevel)##
#set($thisActive = $tools.math.toInteger($lvlNum) - 1)##
#if($thisActive == 0)##
#set($showActive = "")## 
#else##
#set($showActive=$thisActive)##
#end##
#vtReturnFieldEsc($node "rx:displaytitle" $title)##
#set($sys_title = $node.getProperty("rx:sys_title").String)##
#set($nodetype = $node.getPrimaryNodeType().getType())##
   <li>
#if ($nodetype == "vtNavBase" || $nodetype == 'vtNavBranch')##
#set($landing_page = $node.getProperty("nav:url").String)##
#set($submenu = $node.getNodes("nav:submenu"))##
#set($axis = $node.getProperty("nav:axis").String)##
$thisN.setNode($node.getProperty("nav:landingPage").getNode())##
#set($this_cid = $thisN.getNode().getProperty('rx:sys_contentid').String)##
#isCurrentInSubmenu($submenu $isInNextLevel $item_cid)##
#if ($axis=="PARENT" || $axis=="ANCESTOR"||$axis=="SELF")##
    <a class="vt_active${showActive}" href="$landing_page" title="${title}">$title</a>
#else##
    <a href="$landing_page" title="$title">$title</a>
#end## $axis==PARENT
#if ($tools.math.toInteger($submenu.size) > 0)##
#if  ($axis=="SELF" || $axis=="PARENT" || $axis=="ANCESTOR")##
#set($submenu = $node.getNodes("nav:submenu"))##
<div class="vt_subnav${lvlNum}_block">
    <ul class="vt_subnav${lvlNum}">
#set($nextLevelNum = $tools.math.toInteger($lvlNum) + 1)##
#if($nextLevelNum < $tools.math.toInteger($maxLevel))##
#foreach ($navon in $submenu)##
#nextnavlevel2 ($navon $isInNextLevel $nextLevelNum $velocityCount $thisN $item_cid $type $maxLevel)##
#end## foreach $navon
#end## if $nextLevelNum
    </ul>
</div>
#elseif ($type.equals("1"))##
#isCurrentInSubmenu($submenu $isInNextLevel $item_cid)##
#set($submenu = $node.getNodes("nav:submenu"))##
<div class="vt_subnav${lvlNum}_block">
    <ul class="vt_subnav${lvlNum}_inactive">
#set($nextLevelNum = $tools.math.toInteger($lvlNum) + 1)##
#if($nextLevelNum <  $tools.math.toInteger($maxLevel))##
#foreach ($navon in $submenu)##
#nextnavlevel2 ($navon $isInNextLevel $nextLevelNum $velocityCount $thisN $item_cid $type $maxLevel)##
#end## foreach $navon
#end## if $nextLevelNum
    </ul>
</div>
#end## $axis==SELF
#end## $tools.math
#else##
#set($thisNum = $counter)##
#if($node.parent)##
##$nodetype $thisNum $thisActive $item_cid $node.getProperty('rx:sys_contentid').String
#showItem($node.parent $nodetype $thisNum $thisActive $item_cid $node.getProperty('rx:sys_contentid').String)##
#else##
#showItem($node $nodetype $thisNum $thisActive $item_cid $node.getProperty('rx:sys_contentid').String)##
#end## if $node_parent
#end## $nodetype == vtNavBase
   </li>
#end## macro nextnavlevel
##
##
#macro(nextnavlevel2 $node $isInThisLevel $lvlNum2 $counter $thisN $item_cid $type $maxLevel)##
#set($thisActive = $tools.math.toInteger($lvlNum2) - 1)##
#if($thisActive == 0)##
#set($showActive = "")## 
#else##
#set($showActive=$thisActive)##
#end##
#vtReturnFieldEsc($node "rx:displaytitle" $title)##
#set($sys_title = $node.getProperty("rx:sys_title").String)##
#set($nodetype = $node.getPrimaryNodeType().getType())##
   <li>
#if ($nodetype == "vtNavBase" || $nodetype == 'vtNavBranch')##
#set($landing_page = $node.getProperty("nav:url").String)##
#set($submenu = $node.getNodes("nav:submenu"))##
#set($axis = $node.getProperty("nav:axis").String)##
$thisN.setNode($node.getProperty("nav:landingPage").getNode())##
#set($this_cid = $thisN.getNode().getProperty('rx:sys_contentid').String)##
#isCurrentInSubmenu($submenu $isInNextLevel $item_cid)##
#if ($axis=="PARENT" || $axis=="ANCESTOR"||$axis=="SELF")##
    <a class="vt_active${showActive}" href="$landing_page" title="$title">$title</a>
#else##
    <a href="$landing_page" title="$title">$title</a>
#end## $axis==PARENT
#if ($tools.math.toInteger($submenu.size) > 0)##
#if  ($axis=="SELF" || $axis=="PARENT" || $axis=="ANCESTOR")##
#set($submenu = $node.getNodes("nav:submenu"))##
<div class="vt_subnav${lvlNum2}_block">
    <ul class="vt_subnav${lvlNum2}">
#set($nextLevelNum2 = $tools.math.toInteger($lvlNum2) + 1)##
#if($nextLevelNum2 < $tools.math.toInteger($maxLevel))##
#foreach ($navon in $submenu)##
#nextnavlevel3 ($navon $isInNextLevel $nextLevelNum2 $velocityCount $thisN $item_cid $type $maxLevel)##
#end## foreach $navon
#end## if $nextLevelNum
    </ul>
</div>
#elseif ($type.equals("1")|| $type.equals("2"))##
#isCurrentInSubmenu($submenu $isInNextLevel $item_cid)##
#set($submenu = $node.getNodes("nav:submenu"))##
<div class="vt_subnav${lvlNum2}_block">
<ul class="vt_subnav${lvlNum2}_inactive">
#set($nextLevelNum2 = $tools.math.toInteger($lvlNum2) + 1)##
#if($nextLevelNum2 <  $tools.math.toInteger($maxLevel))##
#foreach ($navon in $submenu)##
#nextnavlevel3 ($navon $isInNextLevel $nextLevelNum2 $velocityCount $thisN $item_cid $type $maxLevel)##
#end## foreach $navon
#end## if $nextLevelNum
    </ul>
</div>
#end## $axis==SELF
#end## $tools.math
#else##
#set($thisNum = $counter)##
#if($node.parent)##
#showItem($node.parent $nodetype $thisNum $thisActive $item_cid $node.getProperty('rx:sys_contentid').String)##
#else##
#showItem($node $nodetype $thisNum $thisActive $item_cid $node.getProperty('rx:sys_contentid').String)##
#end## if $node_parent
#end## $nodetype == vtNavBase
   </li>
#end## macro nextnavlevel2
##
## macro nextnavlevel3 follows similar to nextnavlevel2
##
## ----------- Helper Macros ---------------------
##
## checks to see if the current item (parent_cid in this case) is in the submenu)
#macro (isCurrentInSubmenu $nodeSubmenu $inSubmenu $item_cid)##
#set($inSubmenu = 'false')##
#foreach ($item in $nodeSubmenu)##
#set($nodetype = $item.getPrimaryNodeType().getType())##
#if ($nodetype == "vtNavBase" || $nodetype == "vtNavBranch")##
#set($this_cid=$item.getProperty("nav:landingPage").getNode().getProperty('rx:sys_contentid').String)##
#else##
#set($this_cid=$item.getProperty("nav:proxiedNode").getNode().getProperty('rx:sys_contentid').String)##
#end## if $nodetype 
#if ($item_cid==$this_cid)##
#set($inSubmenu= 'true')##
#end## if $parent_cid
#end## foreach $item
#end## macro isCurrentInSubmenu
##
##
## Show item in navigation submenu slot
#macro(showItem $nodeSubmenu $thisNodeType $submenuNum $activeNum $item_cid $current_cid)##
#__slotsetup("vtSNavSubMenu" "")##
#set($nodeItem = $sys.assemblyItem.clone())##
$nodeItem.setNode($nodeSubmenu)##
#set($relresults  =  $rx.asmhelper.assemble($nodeItem,$sys.currentslot.slot,$completeparams))##
##
#set($itemtoGet = $tools.math.sub($submenuNum,"1"))##
#if($relresults.get($itemtoGet).getNode().getProperty('rx:sys_contentid').String.equals($curent_cid))##
#set($relresult = $relresults.get($itemtoGet))##
#else##
#set($relresult="")##
#foreach($item_result in $relresults)##
#if($item_result.getNode().getProperty('rx:sys_contentid').String.equals($current_cid))##
#set($relresult=$item_result)##
#end##  $item_result.getNode()
#end## foreach $item_result
#end## $relresult.size
#if (!$relresult.equals(""))##
#if($thisNodeType == 'vtBrief'	|| $thisNodeType == 'vtExternalLink')##
## don't comment out the ending line so it creates a newline in source view
$rx.doc.extractBody($relresult).trim()##
#else##
## vtGeneric or other contenttypes with full pages that require an "active" state
#set($this_cid = $relresult.getNode().getProperty('rx:sys_contentid').String)##
#set($docText = $rx.doc.extractBody($relresult).trim())##
#vtExtractFirstURL ($docText "href" "" $thisNURL)##
#vtReturnFieldEsc($relresult.getNode() "rx:displaytitle" $thisNName)##
#if($item_cid.equals($this_cid))##
#if($activeNum.equals(0))##
#set($activeNum = "")##
#end##
  <a class="vt_active$!{activeNum}" href="$thisNURL" title="$thisNName">$thisNName</a>
#else##
  <a href="$thisNURL" title="$thisNName">$thisNName</a>
#end## if $parent_cid
#end## if $thisNodeType	
#end## if $relresult
#end## macro showItem
##

Jitendra,

I ran into a similar issue… here’s how I solved it. Disclaimer: this template is for direct child navigation only… I think Jitendra’s solution outputs entire hierarchies.


#initslot("rffNavSubMenu" "")##
#set($relresults = $sys.currentslot.relresults)
#endslot

#foreach( $node in $nav.self.getNodes('nav:submenu'))
#set($title = "$!{node.getProperty('rx:displaytitle').String}")
#set($link = "$!{node.getProperty('nav:url').String}")
#if($link.length() > 2)
  <a href="$link">$title</a>
#else
## the element is not a navon, so it should be treated as a content item.
  $rx.doc.extractBody($relresults.get($tools.math.sub($velocityCount,1)))
#end
#end

Ah yes, I like your solution, but one of the reasons that we went along the path that we did was because the items in the navigation needed to have a css active state specified (granted this could be done by javascript, but that wasn’t acceptable). We do use $rx.doc.extractBody to get the actual link to the page and also to render briefs and such but I seem to recall that in some occasions the rendered link to a page was not correct depending on which folder the item was in. This led to the thrill and excitement that is setting assembly nodes. Of course, this started off in 6.5.2 and so this might not be a problem any more.

You are correct that it should / could output the entire navigation (upto three levels) and the reason each level needed its own macro was in the velocity version we were using (on 6.5.2), the scope of defined variables…well lets just say I really wanted an iterative function and it was a no go :frowning: (I think the newer velocity version may actually support variable scopes!).

If I were to start from scratch now, I would start off with what you have, rather than the Frankenstein that is our navigation…But it is chugging along quite well…so far :slight_smile:

As far as variable scope is concerned, a velocity macro is very similar to a macro from much more basic Assembly languages. The issue you take with it is apparently a very common misconception of the concept of a macro in general. A macro is not a procedure, function, or any kind of scoped construct that we are familiar with for high-level languages - instead, it is essentially an inline code replacement. The language C is the only mid-level programming language that maintains the macro concept. Velocity prior to version 1.7 does not have functions, variable scopes or any such advanced features common in mid- and high-level programming languages.

That said: this is all in the default velocity configuration…

Velocity 1.7 (which is still slightly buggy, but I’ve started using it already for one of my clients) has configuration directives which turn on local variable scoping for loops, macros, evaluates and a new “define” construct. I’m not sure where in Percussion CM System you could set these velocity directives though…

I seem to recall one of the percussion seminars where a PSO Consultant was using the define macro, so I think 6.7 (which we are on) does have that (but I may be mistaken because I haven’t actually used that). You are correct with my gripe and I had previously looked at why it wasn’t doing what I expected (and how to change it). But in the end, I decided it wasn’t worth the potential problems that it might cause…of course this doesn’t mean I’ll stop complaining about it :wink:

I really like your explanation though and I wish someone had made that abundantly clear when I first started dealing with macros!

I accomplished building a site map (site index) with every page on my site, not just landing pages. It took three slots:

  1. Page contains a managed nav slot based on the fast forward left side nav (rffSnEiNavLeft)

  2. The managed nav slot contains an auto-slot that is passed the content_id from within the node:
    Code:


#macro(firstlevel $node)
  ## this macro processes the first level navons
  #set($title = $node.getProperty("rx:displaytitle").String)
  
  #set($landing_page = $node.getProperty("nav:url").String)
  #set($submenu = $node.getNodes("nav:submenu"))
  #set($this_cid = $node.getProperty('rx:sys_contentid').String)
  
  #if ( $landing_page )
    ## don't process this nav if there is no Landing page
    <h4>
      <a href="$landing_page">$title</a>
    </h4>
	## This is the new slot to display every page at this node
	#slot("SiteMap_NavOn_Slot" "" "" "" "" "contentID=$this_cid")
	
    #if ( $submenu )
      <ul class="$indentclass">
		#foreach ($navon in $submenu)
			#secondlevel ($navon)
		#end
      </ul>
    #end
  #end
#end
  1. The SiteMap_NavOn_Slot is an auto-slot containing this query:
SELECT * FROM rffNavon WHERE rx:sys_contentid = :contented
  1. The SiteMap_NavOn_Slot slot’s snippet template has the binding variable:

var: $folderpath 
Expression: $user.psoFolders.getParentFolderPath($sys.item.guid)

The snippet template calls another auto slot, passing it the $node path variable
5. The third slot has the following query which selects all page content types, except landing page content types (so the landing pages do not show up twice in the site map):

SELECT * FROM rx: GeneralPage_CT, rx:ProfilePage _CT WHERE jcr:path like :folderPath

This slot’s snippet template just outputs the display name and the page link

Note: You must install the PSOToolkit for this to work:
http://forum.percussion.com/showthread.php?2578-PSOToolkit-for-6.6-and-6.7