Jump to content



Photo

Using Recursion To Create A Nested Navigation Menu


  • Please log in to reply
22 replies to this topic

#1 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 19 September 2011 - 2:15 PM

I'm using a modification of script 1.3 to create a nested navigation menu. So far I've got the structure working great, the question I have is how do I add another field to the list() array - or in my case the link() array. What I have now is the navigation name showing in the structure, but I need the value from another field for the link. Here is what I have:

// Funtion for displaying list.
// Receives one argument: an array.
function make_list ($parent) {
 
  // Need the main pages array:
global $links;
 
// Start an unordered list:
echo '<ul class="sf-menu sf-vertical">';
 
// Loop through each subarray:
foreach ($parent as $pid => $nav_name) {
 
// Display the list item:
echo "<li class=\"sfHover\"><a href=\"$link_name\">$nav_name</a>";
 
// Check for subpages:
if (isset($links[$pid])) {
 
  // Call this function:
  make_list($links[$pid]);
 
  }
 
  // Complete the list item:
  echo '</li>';
 
} // End of FOREACH loop.
 
// Close the unordered list:
echo '</ul>';
 
} // end of make_list() function
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Test nested navigation</title>
</head>
<body>
<?php
 
// Initialize the storage array:
$links = array();
while (list($page_id, $pid, $nav_name, $link_name ) = mysql_fetch_array($pageList, MYSQL_NUM)) {
// Add to the array:
$links[$pid][$page_id] = $nav_name;
 
}
// For debugging:
echo '<pre>' . print_r($links,1) . '</pre>';
// Send the first array element
// to the make_list() function:
make_list($links[$start]);

As you can see, I've added $link_name to the while(list()) but I can't understand how to get $link_name added to the array which contains the nested navigation items. You can also see how I have added $link_name to the <a> element in the <li> but when I test the script I only get <a href="">whatever</a>.

Here is my SQL:

mysql_select_db($database_siteuser, $siteuser);
$query_pageList = "SELECT page_id, pid, nav_name, link_name, ordr FROM webtext WHERE page_id != 0 AND navInclude = 'y' OR sidebar_disp = 'y' ORDER BY pid, ordr";
$pageList = mysql_query($query_pageList, $siteuser) or die(mysql_error());
$row_pageList = mysql_fetch_assoc($pageList);

Any help would be appreciated.
  • 0

#2 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,964 posts
  • LocationState College, PA (USA)

Posted 19 September 2011 - 2:35 PM

You would replace this line:
$links[$pid][$page_id] = $nav_name;
with:
$links[$pid][$page_id] = array('nav' => $nav_name, 'link' => $link_name);
That will store the link-name as an array. Then, to display them, you would replace:
foreach ($parent as $pid => $nav_name) {
with:
foreach ($parent as $pid => $item) {
And within the foreach loop, you'd use $item['nav'] and $item['link']
  • 0

#3 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 20 September 2011 - 1:22 PM

Hi Larry,

Thanks for the reply. I made those changes and the structure is correct (I see the sub-array with 'link' and 'nav') but I'm having difficulty with the output. If I have:
echo "<li class=\"sf_hover\">$nav_name"; I see the structure (with the debugging uncommented) though there is nothing but an empty ul li.
If I have:
echo "<li class=\"sf_hover\">$item['nav']"; I see nothing, just a blank page.
What is happening?
  • 0

#4 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 21 September 2011 - 7:57 AM

As I suspected, the problem was with escaping single and double quotes. Instead of trying to find my way through that labyrinth, I just combined HTML and code using the concatenator ".", now all is well.

Thanks Larry.
  • 0

#5 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,964 posts
  • LocationState College, PA (USA)

Posted 21 September 2011 - 8:11 AM

Glad it's working and thanks for letting us know.
  • 0

#6 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 21 September 2011 - 2:12 PM

Hi Larry,

I have another question, is there a way to show the "path" of sub-navigation items within this script?

Say the main category is Fresh Fruits & Vegetables, a sub-category would be Fruits, and the item would be Apples. I would like to have the hierarchy part of the <li> element for linking purposes. So something like:

fresh-fruits-and-vegetables/fruits/apples/

In the multidimensional array structure the "categories" are represented by the pid, so I would need to use the associated link_name for each level or iteration. Can this be done?

Many thanks,

Brett
  • 0

#7 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,964 posts
  • LocationState College, PA (USA)

Posted 23 September 2011 - 8:11 AM

Hmmm...interesting question. It certainly can be done, it's just a matter of how. My initial thought is to pass the preface (e.g., fresh-fruits-and-vegetables/fruits/) along as a second argument to the function, so that the function can add that to subcategories. You'd just need a little logic as to when the preface gets passed along and when it changes.
  • 0

#8 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 23 September 2011 - 10:10 AM

The recursion function "walks the tree", iteration by iteration, mapping the entire tree. But as it does so, it replaces the variable value during each iteration. So how would the "preface" be created? It seems to me that what I need is a second function that can walk the tree in the opposite direction creating an array of all link_names related by pid and page_id up to 0.

As you have probably figured out, I'm trying to create a dynamic menu for use with my .htaccess file. Using your modularized web site approach, I have the home page at the site root, then I have sub-directories (what I call category directories) which contain modularized index pages (all category pages have a pid value of 0). All pages below each category page can be further nested by making the parent page (page_id) it is descended from the pid. Though the descendant category pages are really just the index page with the content being replaced, I need to show the hierarchical relationship to match the RewriteRule in my .htaccess file.

This is an issue that must be common, since all frameworks use this method. Do you know how they have solved this problem? I assume some form of recursion is necessary, since the level of depth is unknown.

Thanks for your continued help.
  • 0

#9 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,964 posts
  • LocationState College, PA (USA)

Posted 23 September 2011 - 12:19 PM

You already have recursion, so you shouldn't need to do a reverse recursion, too. What I was thinking is you start by defining the recursive function so it takes an optional second argument:
function make_list ($parent, $preface = '') {

Then, when you call the function recursively, you can send the current item along as the preface of the subelements:
make_list($links[$pid], $nav_name);

And you change the function so that it uses the preface, when appropriate, in creating the link.

That's the basic idea. To acknowledge sub-sub links, you can append the next the next preface onto the current preface and pass that along. The only thing that I don't know the answer to off the top of my head is when/how you reset the preface. Perhaps you have thoughts on that or I might if I had a visual of the sample data.
  • 0

#10 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 13 October 2011 - 2:10 PM

Hi Larry,

I'm sorry for the long delay in responding to your last post, I was sick for a week and when I got back to work I had a big backlog of work and looming deadlines.

I have tried a couple of things so far but nothing is working. I tried adding the second argument to the function, but when I called the function with $nav_name as the value of the second parameter, nothing at all appeared - there was no value.

I thought it might help for you to see the nesting structure so you can see what I'm trying to accomplish:

Array
(
    [0] => Array
	    (
		    [34] => Array
			    (
				    [pid] => 0
				    [nav] => Mouldings
				    [link] => mouldings
			    )
		    [35] => Array
			    (
				    [pid] => 0
				    [nav] => News
				    [link] => news
			    )
		    [57] => Array
			    (
				    [pid] => 0
				    [nav] => Gallery
				    [link] => gallery
			    )
	    )
    [34] => Array
	    (
		    [36] => Array
			    (
				    [pid] => 34
				    [nav] => Historical
				    [link] => historical-mouldings
			    )
		    [37] => Array
			    (
				    [pid] => 34
				    [nav] => Stock
				    [link] => stock-mouldings
			    )
		    [46] => Array
			    (
				    [pid] => 34
				    [nav] => Custom
				    [link] => custom-mouldings
			    )
	    )
    [35] => Array
	    (
		    [44] => Array
			    (
				    [pid] => 35
				    [nav] => Industry
				    [link] => industry-news
			    )
	    )
    [36] => Array
	    (
		    [45] => Array
			    (
				    [pid] => 36
				    [nav] => Colonial
				    [link] => colonial-style-mouldings
			    )
		    [38] => Array
			    (
				    [pid] => 36
				    [nav] => Craftsman
				    [link] => craftsman-style-mouldings
			    )
		    [39] => Array
			    (
				    [pid] => 36
				    [nav] => Federal
				    [link] => federal-style-mouldings
			    )
		    [47] => Array
			    (
				    [pid] => 36
				    [nav] => Georgian & Wren
				    [link] => georgian-and-wren
			    )
		    [59] => Array
			    (
				    [pid] => 36
				    [nav] => Victorian
				    [link] => victorian-style-mouldings
			    )
	    )
    [47] => Array
	    (
		    [58] => Array
			    (
				    [pid] => 47
				    [nav] => Georgian
				    [link] => georgian-style-mouldings
			    )
		    [48] => Array
			    (
				    [pid] => 47
				    [nav] => Wren
				    [link] => wren-style-mouldings
			    )
	    )
)
    Mouldings
	    Historical
		    Colonial
		    Craftsman
		    Federal
		    Georgian & Wren
			    Georgian
			    Wren
		    Victorian
	    Stock
	    Custom
    News
	    Industry
    Gallery


This is not all of the structure but it will show you the level of nesting involved.

Your continued help is appreciated.
  • 0

#11 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 17 October 2011 - 3:39 PM

Hi Larry,

I've been able to get the parent link added to the path of the current link, but I still haven't figured out how to append the new parent link onto the previous "preface" and continue that process to the last child. Also, it's unclear to me how the recursion process works - I hope I can describe what I mean because I'm sure it makes very big difference. During recursion, does the "path" begin at the root level and proceed down every child to the end, creating a complete map for each child, or does it map only from the parent node for each? I'm assuming, based on your previous post about resetting, that it only maps from the parent node.

Here is what I have right now that is working - sort of:

// Funtion for displaying list.
// Receives one argument: an array.
function make_list ($parent, $preface = '') {

  // Need the main pages array:
global $links;

// Start an unordered list:
echo '<ul class="sf-menu sf-vertical">';

// Loop through each subarray:
foreach ($parent as $pid=>$item) {

// Display the list item:
?> <li class="sf-hover"><a href="<?php echo $basedir . "/" . $preface . "/" . $item['link'] ?>/"> <?php echo $item['nav'];

// Check for subpages:
if (isset($links[$pid])) {

  // Call this function:
  make_list($links[$pid], $item['link']);
 
  }

  // Complete the list item:
  echo '</li>';
 
} // End of FOREACH loop.

// Close the unordered list:
echo '</ul>';

} // end of make_list() function
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Test nested navigation</title>
</head>
<body>
<?php
// Initialize the storage array:
$links = array();
while (list($page_id, $pid, $nav_name, $link_name ) = mysql_fetch_array($pageList, MYSQL_NUM)) {
// Add to the array:
$links[$pid][$page_id] = array('pid'=>$pid, 'nav'=>$nav_name,'link'=>$link_name);

}
// For debugging:
echo '<pre>' . print_r($links,1) . '</pre>';
// Send the first array element
// to the make_list() function:
make_list($links[0]);
?>
</body>

Thanks for your help.
  • 0

#12 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 17 October 2011 - 4:22 PM

Well, I've figured out how to append the parents to the child to make the links correct. EXCEPT, as you said, I haven't been able to figure out how to get it to reset.

Anyway, here is my modified code that at least creates the full path - for one branch of the tree:

// Funtion for displaying list.
// Receives one argument: an array.
function make_list ($parent, $preface = '') {
 
  // Need the main pages array:
global $links;
 
// Start an unordered list:
echo '<ul class="sf-menu sf-vertical">';
 
// Loop through each subarray:
foreach ($parent as $pid=>$item) {
 
// Display the list item:
?> <li class="sf-hover"><a href="<?php echo $basedir . "/" . $preface . $item['link'] . "/" ?>"> <?php echo $item['nav'];
 
// Check for subpages:
if (isset($links[$pid])) {
 
  // Call this function:
  make_list($links[$pid], ($preface .= $item['link'] . "/") );
 
  }
 
  // Complete the list item:
  echo '</li>';
 
} // End of FOREACH loop.
 
// Close the unordered list:
echo '</ul>';
 
} // end of make_list() function

	Mouldings
		Historical
			Colonial
			Craftsman
			Federal
			Georgian & Wren
				Georgian
				Wren
			Victorian
		Stock
		Custom
	News
		Industry
	Gallery

So from the structure shown above, the link for Wren is:
http://www.whatever....tyle-mouldings/

Great, but the very next link in the list is Victorian and that link is:
http://www.whatever....tyle-mouldings/

but it should be:
http://www.whatever....tyle-mouldings/

but it's not, because I don't know how to reset the preface.

To give you a little more info on what is being produced by this code, the link for Stock is:
http://www.whatever....tock-mouldings/

it should be:
http://www.whatever....tock-mouldings/

Preface seems to reset itself on links above itself, but not adjacent to itself, which is why the News link is:
http://www.whatever....mouldings/news/

but should be:
http://www.whatever.com/news/

news is adjacent to mouldings and so is gallery - all three are at the same nesting level.

One last link to show you is for Gallery:
http://www.whatever....s/news/gallery/

Here you can see how all previous adjacent links are added to the path, though they shouldn't be. Anyway, I hope this gives you some ideas on how this can be corrected and you might have a suggestion for me.

Thanks
  • 0

#13 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,964 posts
  • LocationState College, PA (USA)

Posted 17 October 2011 - 9:54 PM

Okay. Thanks for sharing where you're at. Give me a couple of days to look this over and see what I can suggest.
  • 0

#14 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 18 October 2011 - 10:43 AM

Larry,

Thanks, I would appreciate any suggestions you might be able to offer.
  • 0

#15 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 4 November 2011 - 2:21 PM

Hi Larry,

I'm still struggling to figure out how to reset $preface and I'm hoping you can provide some guidance.

As I said in an earlier post, $preface resets itself when moving up to the parent level but it does not reset itself at the sibling level. It seems to me what I need to do is find the $key=>$value pair where the $key = the current $pid, then locate that $value in the $preface string so I can use substr() and stripos() to replace $preface. So my question is how do I search - within the foreach loop - for the $key=>$value pair where the $key = the current $pid?
  • 0

#16 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,964 posts
  • LocationState College, PA (USA)

Posted 4 November 2011 - 7:25 PM

Sorry for the lack of reply. This has been on my to-reply list, I just haven't had time to get to it. I'll do my best to run some code and come up with a solution in the next few days.
  • 0

#17 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 5 November 2011 - 12:34 PM

Hey Larry,

I know you are busy and I also know that you give your time out of the goodness of your heart, so I want to thank you for spending a little of that valuable time on me.

Warm regards,

Brett
  • 0

#18 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,964 posts
  • LocationState College, PA (USA)

Posted 22 November 2011 - 10:47 PM

Hey Brett. I'm sorry. I've really meant to get to this, but I'm so behind in my own work and about to take off for the holidays.

I haven't tested this yet, but I think the solution would be to use an array for the prefaces. For each new level, you add another item to the end of the array. And you pass the array to the function with each recursive call. Then, after the last recursive function call and before the next one (i.e., after the make_list() call but before the end of the foreach), you would pop off the last element in the array. I'm not positive that will work, but that's what I would test next.

Apologies for the delay and for not having the time to test this theory before offering it up.
  • 0

#19 banacan

banacan

    Member

  • Members
  • PipPip
  • 48 posts

Posted 19 December 2011 - 5:07 PM

Hey Larry,

Thanks for your reply, I totally understand about being behind on work, I too am overloaded at the moment which is why I'm so late in replying to you.

It seems to me, if I understand what you are recommending, lopping off the last element of the array will work if each nested item is only one level away. But what happens if one nav link is 4 levels deep and the very next nav link is only 1 level deep? I'd only be lopping off to the third level which would make the hierarchy incorrect and the link wrong. Does that make sense?

So I'm wondering, would there be a way to assign the depth level to each nav link and to the current depth level of the preface so that the array string could be cut back the right number of levels based on each nav link level? Could that work?

Thanks,

Brett
  • 0

#20 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,964 posts
  • LocationState College, PA (USA)

Posted 20 December 2011 - 9:34 AM

No, lopping off the last element of the array would be done after the recursive call. So if you were to go down four levels, there would be four pops of the last array element off of the array before getting back to that top level.
  • 0