SitePoint Sponsor

User Tag List

Results 1 to 12 of 12
  1. #1
    SitePoint Addict
    Join Date
    Apr 2005
    Posts
    314
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Recursive PHP menu function

    Hi,

    I'm having difficulty with a function I am trying to write, basically, my function accepts a pre-defined (hard-coded) array and generates a menu from it. The menu can have as many levels as possible which is the reason behind my thinking of using a recursive function.

    An example of the array is as follows:

    PHP Code:
    $aMenu['aOptions']['activeClass'] = 'activeClass1';
    $aMenu['aOptions']['inactiveClass'] = 'inactiveClass1';
    $aMenu[1]['title'] = 'Home';
    $aMenu[1]['url'] = 'link';
    $aMenu[2]['title'] = 'MyPerformance';
    $aMenu[2]['url'] = 'link';
    $aMenu[2]['aOptions']['activeClass']    ='activeClass2';
    $aMenu[2]['aOptions']['inactiveClass']    ='inactiveClass2';
    $aMenu[2][0]['title'] = 'Child 1';
    $aMenu[2][0]['url'] = 'link';
    $aMenu[2][1]['title'] = 'Child 2';
    $aMenu[2][1]['url'] = 'link';
    $aMenu[2][2]['title'] = 'Child 3';
    $aMenu[2][2]['url'] = 'link';
    $aMenu[2][2]['aOptions']['activeClass']    ='activeClass3';
    $aMenu[2][2]['aOptions']['inactiveClass']    ='inactiveClass3';
    $aMenu[2][2][0]['title'] = 'Child 1';
    $aMenu[2][2][0]['url'] = 'link';
    $aMenu[2][2][1]['title'] = 'Child 2';
    $aMenu[2][2][1]['url'] = 'link';
    $aMenu[2][2][2]['title'] = 'Child 3';
    $aMenu[2][2][2]['url'] = 'link';
    $aMenu[3]['title'] = 'MyNotepad';
    $aMenu[3]['url'] = 'link';
    $aMenu[4]['title'] = 'OurBusiness';
    $aMenu[4]['url'] = 'link';
    $aMenu[5]['title'] = 'OurLibraries';
    $aMenu[5]['url'] = 'link';
    $aMenu[5]['aOptions']['activeClass'] = 'activeClass';
    $aMenu[5]['aOptions']['inactiveClass'] = 'inactiveClass';
    $aMenu[5][0]['title'] = 'Child 1';
    $aMenu[5][0]['url'] = '';
    $aMenu[5][1]['title'] = 'Child 2';
    $aMenu[5][1]['url'] = '';
    $aMenu[5][2]['title'] = 'Child 3';
    $aMenu[5][2]['url'] = '';
    $aMenu[6]['title'] = 'Reports';
    $aMenu[6]['url'] = 'link';
    $aMenu[7]['title'] = 'Dashboard';
    $aMenu[7]['url'] = 'link'
    As can be seen from the array each child menu needs to their own set of active and inactive styles. My intention was to detect the ['aOptions'] section to determine when a child menu starts.

    Unfortunately the function I have been working on doesn't work or generate the output I require.

    PHP Code:
    foreach ($aMenu as $section=>$link) {
          
    $ln.= '<ul>';
          if (isset(
    $link['aOptions'])) {
              
    $ln.= $this->printMenu($link);
          } 
          
    $ln.= '<li><a href='.$link['url'].'>'.$link['title'].'</li>';
          
    $ln.= '</ul>';
    // end of foreach loop 
    I'm aware that this code isn't going to function correctly, but the question is how can I generate code which will correctly recurse and output the correct links with urls and titles.

    Thanks

  2. #2
    SitePoint Wizard silver trophybronze trophy Stormrider's Avatar
    Join Date
    Sep 2006
    Location
    Nottingham, UK
    Posts
    3,133
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    The basic form of a recursive function to traverse a tree, using a generic object as an example:

    PHP Code:
    function printMenu ($arrTreeToTraverse) {
      
    //Pre loop stuff
      
    echo "<ul>\r\n";

      foreach (
    $arrTreeToTraverse as $objItem) {
        
    //Stuff relevant to the item, before looping over its children
        
    echo "<li><a href=\"" $objItem->getURL() . "\">" $objItem->getName() . "</a>";

        if (
    $objItem->hasChildren()) {
          echo 
    "\r\n";

          
    //Call the function again on the children
          
    printMenu($objItem->getChildren());
        }
    //if

        
    echo "</li>\r\n";
      }
    //foreach

      //Post loop stuff
      
    echo "</ul>\r\n";
    }
    //function 
    Your function looks generally ok, but you can't tell without posting the whole function - you look like you just have the middle bit there.

  3. #3
    SitePoint Addict
    Join Date
    Apr 2005
    Posts
    314
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I don't really have anything else, I never had to write a recursive function before so am not sure of the procedures I should follow (if any). The function is part of a class file I am currently writing.

    In the function you have posted, why are you calling the function hasChildren and getChildren, I thought the whole point of a recursive function was to call the same function over and over until the array can't be walked over again?

  4. #4
    SitePoint Addict
    Join Date
    Sep 2005
    Posts
    335
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Arrow

    You can't do recursion with a "foreach" statement. You need to wrap your code in a function and call the function with pointer to the top of your array. Then, within the function, you need to call the same function with a pointer to the next level if it exists. Something like:

    function DoMenu($parent){
    foreach($parent as $node){
    if (something that tells you there is a sub-array)
    DoMenu($node);
    }
    // do something on the array here
    }

    Good luck with your project
    PHP Shopping Cart Software Easy Ecommerce Shopping Cart Script.
    PHP Super Cart is 100% template driven.

  5. #5
    SitePoint Addict
    Join Date
    Apr 2005
    Posts
    314
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hi jondolar, I'm not quite sure what you mean by not being able to do recursion within a foreach. The example you have posted is doing recursion within a foreach. h actually I see what you mean, I understand now. Maybe I didn't make it clear but the foreach is within a function I just didn't have this in the code I posted.

    So in theory I call the function like this $menu->doMenu($parent='');

    And within the doMenu() function I will loop over the menu and check for my ['aOptions'] array key, if this is set, then I know there are some child menu's to loop over, therefore call the doMenu() function again.

    What do I pass though for the function to know the child links?

    Excuse my lack of knowledge on this subject but I'm trying to think exactly how it works, it's the recursion that is throwing me.

  6. #6
    SitePoint Wizard silver trophybronze trophy Stormrider's Avatar
    Join Date
    Sep 2006
    Location
    Nottingham, UK
    Posts
    3,133
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by mattih5 View Post
    I don't really have anything else, I never had to write a recursive function before so am not sure of the procedures I should follow (if any). The function is part of a class file I am currently writing.

    In the function you have posted, why are you calling the function hasChildren and getChildren, I thought the whole point of a recursive function was to call the same function over and over until the array can't be walked over again?
    It is just a generic example, using an array of objects I made up. The function calls itself again on the children of the current node, basically, but if there are no children, you can just skip that step.

  7. #7
    SitePoint Zealot
    Join Date
    Dec 2007
    Location
    Mackay, QLD, Australia
    Posts
    158
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Here is an example of what I use a lot in my code. It steps over an array, just like in your case, and it will construct HTML code which is returned and then echo'd out.

    Code PHP:
    <?php
    	// Define your menu array here.
    	$aMenu = array();
     
    	function recursiveMenu($menu)
    	{
    		$html = '';
     
    		foreach ($menu as $item)
    		{
    			// Here we check to see if the item we are looking at
    			// is simply another array. In other words, if the item
    			// has children.
    			if (is_array($item))
    			{
    				// if it does, then call this function again, passing
    				// the item with children as a parameter.
    				$html .= recursiveMenu($item);
    			}
    			else
    			{
    				// Here you would construct the menu item in HTML code
    				$html .= '...';
    			}
    		}
     
    		return $html;
    	}
     
    	// call the menu function and echo out the html code
    	echo recursiveMenu($aMenu);
    ?>

    I've added comments all over that script, but essentially it works on the concept that when you're stepping through an array, the element you're currently looking at can either be text data (in which case it's a menu item) or it can be another array. In that case it is a sub-menu and needs to be iterated through separately.

    The main idea of a recursive function is that it calls itself again if certain conditions are met. So in the case of this function, if the element currently being looked at is another array, it calls itself again. Recursive functions are extremely powerful, but they can also be very confusing which could lend to their misuse. So be careful.

  8. #8
    SitePoint Addict
    Join Date
    Apr 2005
    Posts
    314
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hi Lamiunto, thanks for that, it is something similar to what I currently have. If for example each child menu requires there own css styles for active and inactive states, how can I apply something like this in the function?

    Another thought is how do I detect which menu's above an active menu to show. I.e. Menu 3 could be active and as a result I would need to highlight to two relevant menu links above it.

    Currently I am passing these styles like so:

    $aMenu[2]['aOptions']['activeClass'] ='activeClass2';
    $aMenu[2]['aOptions']['inactiveClass'] ='inactiveClass2';

  9. #9
    SitePoint Addict
    Join Date
    Apr 2005
    Posts
    314
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The problem I'm having now is that each item within the array is being output to screen rather than 'title' and 'link' being classed as one item, see output:

    [html]
      • <A href="">activeClass1

      • <A href="">inactiveClass1

      • <A href="">Home

      • <A href="">link

      • <A href="">MyPerformance

      • <A href="">link

        • <A href="">activeClass2

        • <A href="">inactiveClass2

        • <A href="">Child 1

        • <A href="">link

        • <A href="">Child 2

        • <A href="">link

        • <A href="">Child 3

        • <A href="">link

          • <A href="">activeClass3

          • <A href="">inactiveClass3

          • <A href="">Child 1

          • <A href="">link

          • <A href="">Child 2

          • <A href="">link

          • <A href="">Child 3

          • <A href="">link

            • <A href="">activeClass4

            • <A href="">inactiveClass4

            • <A href="">Child 1

            • <A href="">link

          • <A href="">activeClass2

          • <A href="">inactiveClass2

          • <A href="">Child 5

          • <A href="">link

      • <A href="">MyNotepad

      • <A href="">link

      • <A href="">OurBusiness

      • <A href="">link

      • <A href="">OurLibraries

      • <A href="">link

        • <A href="">activeClass

        • <A href="">inactiveClass

        • <A href="">Child 1

        • <A href="">

        • <A href="">Child 2

        • <A href="">

        • <A href="">Child 3

        • <A href="">

      • <A href="">Reports

      • <A href="">link

      • <A href="">Dashboard

      • link

    [/html/
    For example the 'home' is correct but 'link' below it should be the link of home and not a seperate item.

  10. #10
    SitePoint Wizard silver trophybronze trophy Stormrider's Avatar
    Join Date
    Sep 2006
    Location
    Nottingham, UK
    Posts
    3,133
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    I think your trouble comes because your array structure isn't very consistent. You have the title and url for each thing, but then you also just number the submenu items and put them at the same level.

    You should create another array key for sub menu items and use that, eg 'children' or something.

    So instead of:

    PHP Code:
    $aMenu[2]['title'] = 'MyPerformance';
    $aMenu[2]['url'] = 'link';
    $aMenu[2]['aOptions']['activeClass']    ='activeClass2';
    $aMenu[2]['aOptions']['inactiveClass']    ='inactiveClass2';
    $aMenu[2][0]['title'] = 'Child 1';
    $aMenu[2][0]['url'] = 'link'
    use

    PHP Code:
    $aMenu[2]['title'] = 'MyPerformance';
    $aMenu[2]['url'] = 'link';
    $aMenu[2]['aOptions']['activeClass']    ='activeClass2';
    $aMenu[2]['aOptions']['inactiveClass']    ='inactiveClass2';
    $aMenu[2]['children'][0]['title'] = 'Child 1';
    $aMenu[2]['children'][0]['url'] = 'link'
    This will make it a lot easier to iterate over the children of the array without including the options, url etc.

  11. #11
    SitePoint Zealot
    Join Date
    Dec 2007
    Location
    Mackay, QLD, Australia
    Posts
    158
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    If I were making that menu, I would use an XML file for the simple reason that iterating through the menu's would be easier, and retrieving different properties would also not require much code.

    Your XML file could look something like this
    Code XML:
    <menu>
      <item>
        <url>http://www.somewhere.com/</url>
        <title>Some link</title>
        <active>activeClass</active>
        <inactive>inactiveClass</inactive>
        <children>
          <child>
            <url>...specify more links, etc...</url>
          </child>
        </children>
      </item>
     
      <item>
        ....
      </item>
    </menu>

    You would use SimpleXML to iterate through your XML file and construct a menu.

    It's a bit late here, so if someone else is up to drafting the code to iterate through an XML file with SimpleXML, go for it.

  12. #12
    SitePoint Addict
    Join Date
    Apr 2005
    Posts
    314
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks all, still not been able to figure it out. Think we are going to change the functonality of the menu as we need three menu's rather than a menu with sub-menu's inside.

    The menu will be called on each page using something like this $oPage->printMenu(2,2,1); The numbers signify the level at which the menu should be active i.e. on the first level, menu 2 will be active followed by the child menu where 2 is also active and the third where 1 is active.

    I'll then write a function to highlight the necessary menu based on these.

    Watch this space


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •