I cross-modified some dropmenu hacks (especially the showActiveTreeOnly hack) found in this forum to realise this menu:
- start
- item 1
- item 1.1
- item 1.2
- ...
- item 2
- item 2.1
...
Clicked tree/subtree shows only the active subtree like this:
- start
- item 1
- item 2
- item 2.1 <-- I'm here
- item 2.2
- item 3
...
Rendered menu list has only nested ul's without classes on ul/li/a (don't need/want classes in this menu). The active menu item is without link, it's link text is wrapped in strong-tag. I know there are "hacks" with CSS hiding unwanted menu subtrees, but I think PHP should do the menu logic.
Here is the code. I know my hacks are horrible because I'm inexperienced in PHP coding. It was a try-and-error-coding

// ###########################################
// DropMenu ver. G1 #
// ###########################################
// Configurable menu / navigation builder using UL tags
// Offers optional DIV wrappers for top level and nested menus (useful for hover zones)
// as well as configurable classes for the DIV, UL, and LI elements. It even
// marks ancestors of and the current element with a hereClass (indicating you are here
// and in this area of the site). Also applies .last CSS class to final LI in each UL.
//
// Developed by Vertexworks.com and Opengeek.com
// Feel free to use if you keep this header and credits in place
//
// Inspired by List Site Map by Jaredc, SimpleList by Bravado,
// and ListMenuX by OpenGeek
//
// Corrected and modified By dev3.net
//
// Modified by 52eins
//
// Configuration parameters:
//
// &menuName - name of a placeholder for placing the output in the layout
// &topnavClass - CSS class for styling the class assigned to the outermost UL
//
// TO DO: configuration parameters above, more usage examples, CSS examples, output indenting
// ###########################################
// Usage Examples #
// ###########################################
// Creates menu with wrapping DIV with id=myMenu, starting at the site root, two levels deep,
// with descriptions next to the links, and nested UL elements with class=nestedLinks; output
// of menu can be placed in layout using placeholder named myMenu ( e.g. [ +myMenu+ ] )
// [[DropMenu? &menuName=`myMenu` &startDoc=`0` &levelLimit=`2` &topdiv=`true` &showDescription=`true` &subnavClass=`nestedLinks`]]
//
// Creates topMenu from site root, including only 1 level, with class=topMenubar applied to the top level UL
// and class=activeLink applied to current page LI
// [[DropMenu? &menuName=`topMenu` &startDoc=`0` &levelLimit=`1` &topnavClass=`topMenubar` &here=`activeLink`]]
//
// Creates dropmenu 3 levels deep, with DIV wrappers around all nested lists styled with class=hoverZone
// and currentPage LI styled with class=currentPage
// [[DropMenu? &levelLimit=3 &subdiv=true &subdivClass=hoverZone &subnavClass=menuZone &here=currentPage]]
//
// Creates dropmenu of infinite levels, ordered by menutitle in descending order
// [[DropMenu?orderBy=menutitle&orderDesc=true]]
// ###########################################
// Configuration parameters #
// ###########################################
// $phMode [ true | false ]
// Whether you want it to output a [+placeholder+] or simply return the output.
// Defaults to false.
$phMode = false;
// $menuName [ string ]
// Sets the name of the menu, placeholder, and top level DIV id (if topdiv
// option is true). Set to "dropmenu" by default.
$phName = (!isset($phName)) ? 'dropmenu' : "$phName";
// $siteMapRoot [int]
// The parent ID of your root. Default 0. Can be set in
// snippet call with startDoc (to doc id 10 for example):
// [[DropMenu?startDoc=10]]
$siteMapRoot = 0;
// $removeNewLines [ true | false ]
// If you want new lines removed from code, set to true. This is generally
// better for IE when lists are styled vertically.
$removeNewLines = (!isset($removeNewLines)) ? false : ($removeNewLines==true);
// $maxLevels [ int ]
// Maximum number of levels to include. The default 0 will allow all
// levels. Also settable with snippet variable levelLimit:
// [[DropMenu?levelLimit=2]]
$maxLevels = 0;
// $textOfLinks [ string ]
// What database field do you want the actual link text to be?
// The default is pagetitle because it is always a valid (not empty)
// value, but if you prefer it can be any of the following:
// menutitle, id, pagetitle, description, parent, alias, longtitle, introtext
// TO DO: set text to be first non-empty of an array of options
$textOfLinks = (!isset($textOfLinks)) ? 'menutitle' : "$textOfLinks";
// $linkTitle [ true | false ]
// Define if the links should have titles (true) or not (false = default)
$linkTitle = (!isset($linkTitle)) ? false : ($linkTitle==true);
// $titleOfLinks [ string ]
// What database field do you want the title of your links to be?
// The default is pagetitle because it is always a valid (not empty)
// value, but if you prefer it can be any of the following:
// menutitle, id, pagetitle, description, parent, alias, longtitle, introtext
$titleOfLinks = (!isset($titleOfLinks)) ? 'description' : "$titleOfLinks";
// $pre [ string ]
// Text to append before links inside of LIs
$pre = (!isset($pre)) ? '' : "$pre";
// $post [ string ]
// Text to append before links inside of LIs
$post = (!isset($post)) ? '' : "$post";
// $selfAsLink [ true | false ]
// Define if the current page should be a link (true) or not (false)
$selfAsLink = (!isset($selfAsLink)) ? false : ($selfAsLink==true);
// $hereClass [ string ]
// CSS Class for LI and A when they are the currently selected page, as well
// as any ancestors of the current page (YOU ARE HERE)
$hereClass = (!isset($hereClass)) ? 'here' : $hereClass;
// $showDescription [true | false]
// Specify if you would like to include the description
// with the page title link.
$showDescription = (!isset($showDescription)) ? false : ($showDescription==true);
// $descriptionField [ string ]
// What database field do you want the description to be?
// The default is description. If you specify a field, it will attempt to use it
// first then fall back until it finds a non-empty field in description, introtext,
// then longtitle so it really tries not be empty. It can be any of the following:
// menutitle, id, pagetitle, description, parent, alias, longtitle, introtext
// TO DO: set description to the first non-empty of an array of options
$descriptionField = (!isset($descriptionField)) ? 'description' : "$descriptionField";
// $topdiv [ true | false ]
// Indicates if the top level UL is wrapped by a containing DIV block
$topdiv = (!isset($topdiv)) ? false : ($topdiv==true);
// $topdivClass [ string ]
// CSS Class for DIV wrapping top level UL
$topdivClass = (!isset($topdivClass)) ? 'topdiv' : "$topdivClass";
// $topnavClass [ string ]
// CSS Class for the top-level (root) UL
$topnavClass = (!isset($topnavClass)) ? '' : "$topnavClass";
// $topnavID [ string ]
// CSS ID for the top-level (root) UL
$topnavID = (!isset($topnavID)) ? '' : "$topnavID";
// $useCategoryFolders [ true | false ]
// If you want folders without any content to render without a link to be used
// as "category" pages (defaults to true). In order to use Category Folders,
// the template must be set to (blank) or it won't work properly.
$useCategoryFolders = (!isset($useCategoryFolders)) ? true : "$useCategoryFolders";
// $categoryClass [ string ]
// CSS Class for folders with no content (e.g., category folders)
$categoryClass = (!isset($categoryClass)) ? 'category' : "$categoryClass";
// $subdiv [ true | false ]
// Indicates if nested UL's should be wrapped by containing DIV blocks
// This is useful for creating "hover zones"
// (see http://positioniseverything.net/css-dropdowns.html for a demo)
// TO CONSIDER: Setting a subdiv class at all turns on hover DIVs?
$subdiv = (!isset($subdiv)) ? false : ($subdiv==true);
// $subdivClass [ string ]
// CSS Class for DIV blocks wrapping nested UL elements
$subdivClass = (!isset($subdivClass)) ? 'subdiv' : "$subdivClass";
// $orderBy [ string ]
// Document field to sort menu by
$orderBy = (!isset($orderBy)) ? 'menuindex' : "$orderBy";
// $orderDesc [true | false]
// Order results in descending order? default is false
$orderDesc = (!isset($orderDesc)) ? false : ($orderDesc==true);
// $currentItem [ true | false ]
// Should show current item class? default is false
$currentItem = (!isset($currentItem)) ? false : "$currentItem";
// $currentItemClass [ string ]
// Current item class - default is 'current_item'
$currentItemClass = (!isset($currentItemClass)) ? 'current_item' : "$currentItemClass";
// $activeTreeOnly [ true | false ] - default is false (if you want it false just don't set it)
// Define if the menutree should show all menuitems or only those in active menutree branch
$activeTreeOnly = (!isset($activeTreeOnly)) ? false : ($activeTreeOnly==true);
// $currentSpan [ string ]
// Current item marker as a html tag - default is strong
$currentSpan = (!isset($currentSpan)) ? 'strong' : "$currentSpan";
// ###########################################
// End config, the rest takes care of itself #
// ###########################################
// Current item wrapper (opening tag)? default is <currentSpan>
$prehere = ($currentSpan!='') ? "<$currentSpan>" : '';
// Current item wrapper (closing tag)? default is </currentSpan>
$posthere = ($currentSpan!='') ? "</$currentSpan>" : '';
$debugMode = false;
// Initialize
$MakeMap = "";
$siteMapRoot = (isset($startDoc)) ? $startDoc : $siteMapRoot;
$maxLevels = (isset($levelLimit)) ? $levelLimit : $maxLevels;
$ie = ($removeNewLines) ? '' : "\n";
//Added by Remon: (undefined variables php notice)
$activeLinkIDs = array();
$subnavClass = '';
// Overcome single use limitation on functions
global $MakeMap_Defined;
if (!isset ($MakeMap_Defined)) {
function filterHidden($var) {
return (!$var['hidemenu']==1);
}
function filterEmpty($var) {
return (!empty($var));
}
function MakeMap($modx, $listParent, $listLevel, $description, $linkTitle, $titleOfLinks, $maxLevels, $inside, $pre, $post, $selfAsLink, $ie, $activeLinkIDs, $topdiv, $topdivClass, $topnavClass, $topnavID, $subdiv, $subdivClass, $subnavClass, $hereClass, $useCategoryFolders, $categoryClass, $showDescription, $descriptionField, $textOfLinks, $orderBy, $orderDesc, $prehere, $posthere, $currentItem, $currentItemClass, $activeTreeOnly, $debugMode) {
// Added by Remon. Define this variable _here_ ;-)
$output = '';
$children = $modx->getActiveChildren($listParent, $orderBy, (!$orderDesc) ? 'ASC' : 'DESC', 'id, pagetitle, description, isfolder, parent, alias, longtitle, menutitle, hidemenu, introtext, content_dispo, contentType, type, template');
// filter out the content that is set to be hidden from menu snippets
$children = array_filter($children, "filterHidden");
$numChildren = count($children);
if (is_array($children) && !empty($children)) {
// determine if it's a top category or not
$toplevel = !$inside;
// build the output
$topdivcls = (!empty($topdivClass)) ? ' class="'.$topdivClass.'"' : '';
$topdivblk = ($topdiv) ? "<div$topdivcls>" : '';
$topnavcls = (!empty($topnavClass)) ? ' class="'.$topnavClass.'"' : '';
$topnavid = (!empty($topnavID)) ? ' id="'.$topnavID.'"' : '';
$subdivcls = (!empty($subdivClass)) ? ' class="'.$subdivClass.'"' : '';
$subdivblk = ($subdiv) ? "<div$subdivcls>$ie" : '';
$subnavcls = (!empty($subnavClass)) ? ' class="'.$subnavClass.'"' : '';
$output = ($toplevel) ? "$topdivblk<ul$topnavid$topnavcls>$ie" : "$ie$subdivblk<ul>$ie";
//loop through and process subchildren
foreach ($children as $child) {
// figure out if it's a containing category folder or not
$numChildren --;
$isFolder = $child['isfolder'];
$itsEmpty = ($isFolder && ($child['template'] == '0'));
$itm = "";
// if menutitle is blank fall back to pagetitle for menu link
$textOfLinks = (empty($child['menutitle'])) ? 'pagetitle' : "$textOfLinks";
$childOfTextOfLinks = $child[$textOfLinks];
if($child['id'] == $modx->documentIdentifier){
$childOfTextOfLinks = $prehere.$childOfTextOfLinks.$posthere;
}
// If at the top level
if (!$inside)
{
$itm .= ((!$selfAsLink && ($child['id'] == $modx->documentIdentifier)) || ($itsEmpty && $useCategoryFolders)) ?
$pre.$childOfTextOfLinks.$post . (($debugMode) ? ' self|cat' : '') :
'<a href="[~'.$child['id'].'~]">'.$pre.$childOfTextOfLinks.$post.'</a>';
$itm .= ($debugMode) ? ' top' : '';
}
// it's a folder and it's below the top level
elseif ($isFolder && $inside)
{
if($itsEmpty && $useCategoryFolders){
$itm .= $pre.$childOfTextOfLinks.$post . (($debugMode) ? 'subfolder T': '');
}
else{
// $itm .= ($child['alias'] > '0' && !$selfAsLink && ($child['id'] == $modx->documentIdentifier)) ? $childOfTextOfLinks : '<a href="[~'.$child['id'].'~]">'.$childOfTextOfLinks.'</a>';
$itm .= (!$selfAsLink && ($child['id'] == $modx->documentIdentifier)) ?
$pre.$childOfTextOfLinks.$post . (($debugMode) ? ' doc' : '') :
'<a href="[~'.$child['id'].'~]">'.$pre.$childOfTextOfLinks.$post.'</a>';
$itm .= ($debugMode) ? ' doc' : '';
}
}
// it's a document inside a folder
else
{
// $itm .= ($child['alias'] > '0' && !$selfAsLink && ($child['id'] == $modx->documentIdentifier)) ? $childOfTextOfLinks : '<a href="[~'.$child['id'].'~]">'.$childOfTextOfLinks.'</a>';
$itm .= (!$selfAsLink && ($child['id'] == $modx->documentIdentifier)) ?
$pre.$childOfTextOfLinks.$post . (($debugMode) ? ' doc' : '') :
'<a href="[~'.$child['id'].'~]">'.$pre.$childOfTextOfLinks.$post.'</a>';
$itm .= ($debugMode) ? ' doc' : '';
}
$itm .= ($debugMode)? "$useCategoryFolders $isFolder $itsEmpty" : '';
// BEGIN HACK: activeTreeOnly
// loop back through if the doc is a folder and has not reached the max levels
if ($isFolder && (($maxLevels == 0) || ($maxLevels > $listLevel +1))) {
// activeTreeOnly TRUE
if ($activeTreeOnly)
{
$makeMapYes = FALSE;
if (is_array($activeLinkIDs))
{
if ($child['id'] == $modx->documentIdentifier || in_array($child['id'], $activeLinkIDs))
{
$makeMapYes = TRUE;
}
}
}
// activeTreeOnly FALSE or child is in active tree
if ((!$activeTreeOnly) || $makeMapYes || ($activeTreeOnly && $itsEmpty))
{
$itm .= MakeMap($modx, $child['id'], $listLevel +1, $description, $linkTitle, $titleOfLinks, $maxLevels, true, $pre, $post, $selfAsLink, $ie, $activeLinkIDs, $topdiv, $topdivClass, $topnavClass, $topnavID, $subdiv, $subdivClass, $subnavClass, $hereClass, $useCategoryFolders, $categoryClass, $showDescription, $descriptionField, $textOfLinks, $orderBy, $orderDesc, $prehere, $posthere, $currentItem, $currentItemClass, $activeTreeOnly, $debugMode);
}
}
// END HACK: activeTreeOnly
if ($itm && ($child['id'] == $modx->documentIdentifier)) {
$output .= " <li>$itm</li>$ie";
}
elseif ($itm) {
// Added by Remon
// define it here:
$class = '';
if ($numChildren == 0) {
$class = 'last';
}
if (is_array($activeLinkIDs)) {
if (in_array($child['id'], $activeLinkIDs)) {
$class .= ($class ? ' ' : '').$hereClass;
}
}
// it's an empty folder and using Category Folders
if ($useCategoryFolders && $itsEmpty) {
$class .= ($class ? ' ' : '').$categoryClass;
}
if ($class) {
$class = ' class="'.$class.'"';
}
// TO DO: set description to the first non-empty of an array of options
if ($showDescription && (!empty($child['$descriptionField']))) {
$desc = " – ".$child['$descriptionField'];
} elseif ($showDescription && (!empty($child['description']))) {
$desc = ' – ' . $child['description'];
} elseif ($showDescription && (!empty($child['introtext']))) {
$desc = ' – ' . $child['introtext'];
} elseif ($showDescription && (!empty($child['longtitle']))) {
$desc = ' – ' . $child['longtitle'];
} else {
$desc = '';
}
$output .= "<li>$itm$desc</li>$ie";
$class = '';
}
}
$output .= "</ul>$ie";
$output .= ($toplevel) ? (($topdiv) ? "</div>$ie" : "") : (($subdiv) ? "</div>$ie" : "");
}
return $output;
}
$MakeMap_Defined = true;
}
$currentID = $modx->documentIdentifier;
$parentID = $currentID;
// find the parent docs of the current "you-are-here" doc
// used in the logic to mark parents as such also
while ($parentID != $siteMapRoot && $parentID != 0) {
$parent = $modx->getParent($parentID, 0);
if ($parent) {
$parentID = $parent['id'];
$activeLinkIDs[] = $parentID;
} else {
$parentID = 0;
}
}
if ($phMode) {
// output to a [+placeholder+]
$modx->setPlaceholder($phName, MakeMap($modx, $siteMapRoot, 0, $showDescription, $linkTitle, $titleOfLinks, $maxLevels, false, $pre, $post, $selfAsLink, $ie, $activeLinkIDs, $topdiv, $topdivClass, $topnavClass, $topnavID, $subdiv, $subdivClass, $subnavClass, $hereClass, $useCategoryFolders, $categoryClass, $showDescription, $descriptionField, $textOfLinks, $orderBy, $orderDesc, $prehere, $posthere, $currentItem, $currentItemClass, $activeTreeOnly, $debugMode));
} else {
// return the output a "usual"
return MakeMap($modx, $siteMapRoot, 0, $showDescription, $linkTitle, $titleOfLinks, $maxLevels, false, $pre, $post, $selfAsLink, $ie, $activeLinkIDs, $topdiv, $topdivClass, $topnavClass, $topnavID, $subdiv, $subdivClass, $subnavClass, $hereClass, $useCategoryFolders, $categoryClass, $showDescription, $descriptionField, $textOfLinks, $orderBy, $orderDesc, $prehere, $posthere, $currentItem, $currentItemClass, $activeTreeOnly, $debugMode);
}
What I need is an additional hack by an experienced coder with following result:
With an additional parameter in dropmenu (may be "restrictTo") call I want restrict the menu (sub)tree to a defined level. Example:
Full Menu is:
- start
- item 1
- item 1.1
- item 1.1.1
- item 1.1.2
- item 1.2
- item 1.2.1
- item 1.2.2
- item 1.3
- item 2
- item 3
...
Clicked at item 1 the menu shows only this item as active (done with &levelLimit=`1`). On this page is a second navi showing the submenu (= subtree of item 1). Now I click for instance at item 1.2 in the submenu. I'm on this page now, the submenu shows item 1.2 inactive, but in main menu the item 1 is an active link because it's not the "I'm-here-page".
The hack I need should set item 1 in main menu as a "virtually" inactive item for all child pages. Sorry my English is a bit ... erm ... Hope anybody understand my problem and can solve this. It's urgent. The menu shows the document tree in modx manager and I dont alter this because it's the structure of a blog (main page = item 1) with categories (subpages = item 1.1,1.2,...) and blog posts (items 1.1.1, and so on).
Can anybody help me? (It would be great if You can "clean" my horrible code thereby.)
Thanks, Rainer