We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 24495
    • 407 Posts
    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 grin
    // ###########################################
    // 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