0

Recently I've written recursive PHP function which generates website navigation based on parent-child structure like this

<ul>
  <li>parent
     <li>child</li>
  </li>
</ul>

Code looks like that

function generateMenu($parent, $level, $db){
  $q = $db->query("select id, name FROM menu WHERE parent = '$parent'");

  if($level > 0 && $q->num_rows > 0) echo "\n<ul>\n";

   while($row=$q->fetch_object()){
      echo "<li>";
      echo '<a href="?page=' .$page. '">' . $row->name . '</a>';
      //display this level's children
      generateMenu($row->id, $level++, $menu, $db);
      echo "</li>\n\n";
    }

    if($level > 0 &&  $q->num_rows > 0) echo "</ul>\n";
}

The piece of code above simply echoes <ul><li> structure (like given above example) from db table.

Now the questions is, how to create navigation menu like on this website?

Please take a look at left sidebar.

http://www.smithsdetection.com/continuous_vapour_sampling.php

Now i think that:

  1. First of all we need to echo all parents
  2. Function must get current pages id as an input value (for ex. $current)
  3. Function must echo til' current pages level

I can't figure out how to modify my function, to get output like on given website. PLease help.

BTW

My db table looks like that

enter image description here

NOTE Please don't post answers about sql injection holes, I've already taken care about them: checking with in_array (if variable listed in column names array) and passing through real_escape.

4
  • generateMenu($row->id, $level++, $menu, $db); should probably be generateMenu($row->id, $level+1, $menu, $db);. Commented Oct 7, 2011 at 12:27
  • )) php.net/manual/en/language.operators.increment.php Commented Oct 7, 2011 at 12:29
  • 2
    I am aware of the increment operator, but you are using $level after it is incremented, too. Consider when $level = 0: if($level > 0 && $q->num_rows > 0) echo "\n<ul>\n"; will NOT output an opening <ul>, then later generateMenu($row->id, $level++, $menu, $db); increments $level to 1 so that after your while loop, if($level > 0 && $q->num_rows > 0) echo "</ul>\n"; WILL output a closing </ul>, which is not matched to any opening one. Check your HTML that gets returned to see. Commented Oct 7, 2011 at 12:58
  • 2
    Furthermore... if $level = 0, when you call generateMenu($row->id, $level++, $menu, $db) $level actually gets incremented after you pass it to the function, so the function always sees $level = 0. Compare: $level++ with ++$level, and read the manual link you posted above. Commented Oct 7, 2011 at 13:05

2 Answers 2

3

Have a look at this: http://www.ferdychristant.com/blog/archive/DOMM-7QJPM7

You should try to fetch the whole hierarchy with one query to fix performance issue.

Sign up to request clarification or add additional context in comments.

4 Comments

is my question about performance problems? it's not the answer. please comment it or delete
than just have a look at the blog post. your question is about hierarchical data, and there is a solution for it.
@user978733 You're a bundle of joy
Or (assuming the menu table does not get changed too often) index the parent column. That way the difference in approaches will be very small.
2

Assuming the current page id is in the var $current and that $db is an open MySQLi DB connection:

// first get your current page's path back to root:
// $stack will be a stack of menus to show
$stack = array();

// always expand current menu:
$stack[] = $current;

// now starting at $current, we go through the `menu` table adding each parent
// menu id to the $stack until we get to 0:
$i = $current;
while ( $i > 0 ) {
  // get parent of $i
  $query = sprintf('SELECT `parent` FROM `menu` WHERE id=%d LIMIT 1', $i);
  $result = $db->query($query);

  if (!$result) {
    // do error handling here
  }

  $row = $result->fetch_assoc();

  // save parent id into $i...
  $i = $row['parent'];

  // ...and push it onto $stack:
  $stack[] = $i;
}

/**
 * @param int $parent the parent ID of the menu to draw.
 * @param array $stack the stack of ids that need to be expanded
 * @param string $indent string for pretty-printing html
 * @param MySQLi $db Open db connection
 */
function generateMenu($parent, $stack, $indent, $db){

  // $next is the next menu id that needs expanding
  $next = array_pop($stack);

  $query = sprintf('SELECT `id`, `name` FROM `menu` WHERE `parent`=%d', $parent);

  $result = $db->query($query);

  if ( ! $result ) {
    // do error handling here
  }

  if ($result->num_rows > 0) {
    echo "\n$indent<ul>\n";

    while($row = $result->fetch_object()){
      echo "$indent  <li>\n";
      echo "$indent    <a href=\"?page={$row->id}\">{$row->name}</a>\n";

      //display this level's children, if it's the $next menu to need to be drawn:
      if ($row->id == $next)
        generateMenu($next, $stack, "$indent    ", $db);

      echo "$indent  </li>\n\n";
    }
    echo "$indent</ul>\n";
  }
  $result->free();
}

$first = array_pop($stack); // should always be 0
generateMenu($first, $stack, '', $db);

5 Comments

I don't understand this part $stack[] = $current; $i = $current; $current is the current pages id as we declared above right?
Yes. We need to add $current to the stack of menus to display. $stack[] = $current; does that. We are then using $i to travel backwards through the DB finding all ancestors of $current. The first ancestor we are looking for is the parent of $current, so we set $i = $current then in the while loop we execute a query to find the parent id, then change $i to = the parent id, then push the parent id onto the stack and if the parent id (i.e., $i) is > 0, we do it again. Looking at your table, if $current = 12, then after the while loop, $stack = array(12, 10, 4, 0);
Try following it through, line-by-line, and writing down what each variable is at each line and I'm sure you'll see what happens.
how to set as active current oage? class=active i mean
Just add if ($row->id == $current) in the while loop. Do you want to add the class to the <li> or or <a>?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.