dinamic nested menu php - php

I have a table menu like this
i have problem to display like this with ul and li tag like 2nd picture.
please help me for the solution
menu_code|desc_code
1 | menu 1
1.1 | menu 1.1
1.2 | menu 1.2
1.2.1| menu 1.2.1
2 | menu 2
i want to display my table menu with concept "unlimited level menu".

I would consider changing your table structure. You will need to loop through each parent and children, you wouldnt want to do that with string splitting. I advice you to create an extra column parent_id and binding your items like that. After that its easy to crawl recursively through its children to create a ul > li structure.
Example recursive function:
public static function buildTree($items, $parent_id = null) {
$result = [];
foreach($items as $item) {
if($item->parent_id == $parent_id) {
$children = self::buildTree($items, $item->id);
if($children) {
$item->children = $children;
}
$result[$item->id] = $item;
}
}
return $result;
}
After that you can use the result of the build function to recursively create your menu structure:
public static function treeToHtml($tree, $level = 0) {
$result = '';
$result .= '<ul>';
foreach($tree as $item) {
$has_children = isset($item->children) && count($item->children) > 0;
if($has_children) {
$result .= '<li><a href="#">';
$result .= self::treeToHtml($item->children, $level + 1);
$result .= '</li>';
} else {
$result .= '<li><a href="#"></li>';
}
}
$result .= '</ul>';
return $result;
}
Edit function accordingly and statically all depends on the context you're using it in.

Related

First loop restarting in recursive function php

I'm trying to create a series of <ul> and <li> to create a directory/file structure to navigate some files created from a table in a dB. The table (tb_lib_manual) contains both files and folders.
If a record has a null entry for fileID then it is a folder not a file. each record has a parentID to show which folder is parent, for files and folders in the root this is 0.
The php code is thus:
class library_folders extends system_pageElement
{
private $html = '';
private $i = 0;
private $stmtArray = array();
private $objectArray = array();
function __construct()
{
parent::__construct();
$this->nextList();
}
function nextList($parentID = 0)
{
$qSQL = 'SELECT * FROM tb_lib_manual WHERE parentID=:parentID';
$stmtArray[$this->i] = $this->dbConnection->prepare($qSQL);
$stmtArray[$this->i]->bindValue(':parentID', $parentID, PDO::PARAM_INT);
$stmtArray[$this->i]->execute();
if($stmtArray[$this->i]->rowCount() > 0)
{
$display ='';
if($parentID != 0)
{
$display = ' style="display:none"';
}
$this->html .= '<ul' . $display . '>';
}
while ($this->objectArray[$this->i] = $stmtArray[$this->i]->fetchObject())
{
$this->html .= '<li>' . $this->objectArray[$this->i]->title;
if($this->objectArray[$this->i]->fileID == null)
{
//we have a folder!
$manualID = $this->objectArray[$this->i]->manualID;
$this->i ++;
$this->nextList($manualID);
$this->i--;
}
$this->html .= '</li>';
}
if($stmtArray[$this->i]->rowCount() > 0)
{
$this->html .= '</ul>';
}
echo $this->html;
}
function __destruct()
{
parent::__destruct();
}
}
The problem is when the code returns back to the while loop after calling itself it restarts the loop rather than carrying on where it left off, causing a repeat in the child folder. Is there either a better way to do this or am I doing something wrong!?
Table looks like this:
output like this:
'A ManualA ManualFolder 1Nasa exerciseA ManualA ManualFolder 1Nasa exercise'
Fixed the code, very silly mistake, echo in the wrong place...
class library_folders extends system_pageElement
{
private $html = '';
private $i = 0;
private $stmtArray = array();
private $objectArray = array();
function __construct()
{
parent::__construct();
$this->nextList();
echo $this->html;
}
function nextList($parentID = 0)
{
$qSQL = 'SELECT * FROM tb_lib_manual WHERE parentID=:parentID';
//echo $this->i;
$stmtArray[$this->i] = $this->dbConnection->prepare($qSQL);
$stmtArray[$this->i]->bindValue(':parentID', $parentID, PDO::PARAM_INT);
$stmtArray[$this->i]->execute();
if($stmtArray[$this->i]->rowCount() > 0)
{
$this->html .= '<ul>';
}
while ($this->objectArray[$this->i] = $stmtArray[$this->i]->fetchObject())
{
$this->html .= '<li>' . $this->objectArray[$this->i]->title;
if($this->objectArray[$this->i]->fileID == null)
{
//we have a folder!
$manualID = $this->objectArray[$this->i]->manualID;
$this->i ++;
$this->nextList($manualID);
$this->i--;
}
$this->html .= '</li>';
}
if($stmtArray[$this->i]->rowCount() > 0)
{
$this->html .= '</ul>';
}
}
function __destruct()
{
parent::__destruct();
}
}
Yes, there are better ways of doing this.
If you fetch the whole tree every time, you might as well load all the nodes into a big array (of objects), put each node's id as index and then loop over the array once to create the parent references by for example
// create a fake root node to start your traversal later on
// only do this if you don't have a real root node
// which I assume you don't
$root = (object) ["children" => []];
// loop over all nodes
foreach ($nodes as $node)
{
// if the node has a parent node that is not root
if ($node->parentId > 0)
{
// put it into it's parent's list of children
$nodes[ $node->parentId ]->children[] = $node;
}
else
{
// otherwise put it into root's list of children
$root->children[] = $node;
}
}
Complexity: You do one query and you have to iterate all your nodes once.
For this to work your nodes need to be objects. Otherwise each assignment to $node->children will create a copy of the assigned node where you wanted a reference.
If you do not want to fetch all nodes, you can go through your tree level by level by creating a list of node ids from the previous level.
function fetchLevel ($nodes, $previousLevelIds)
{
// get all children from the previous level's nodes
// I assume $previousLevelIds to be an array of integers. beware of sql injection
// I refrained from using prepared statements for simplicity
$stmt = $pdo->query("SELECT id, parentId FROM nodes WHERE parentId IN (".implode(",",$previousLevelIds).")");
// fetch nodes as instances of stdclass
$currentLevelNodes = $stmt->fetchAll(PDO::FETCH_OBJ);
$nextLevelIds = [];
foreach ($currentLevelNodes as $node)
{
// ids for next level
$nextLevelIds[] = $node->id;
// parent <-> child reference
$nodes[ $node->parentId ]->children[] = $node;
}
// fetch the next level only if we found any nodes in this level
// this will stop the recursion
if ($nextLevelIds)
fetchLevel($nodes, $nextLevelIds);
}
// start by using a fake root again
$root = (object) ["id" => 0, "children" => []];
$nodes = [0 => $root];
fetchLevel($nodes, [0]);
// or start with a specific node in the tree
$node = $pdo->query("SELECT id, parentId FROM nodes WHERE id = 1337")->fetch(PDO::FETCH_OBJ);
$nodes = [$node->id => $node];
fetchLevel($nodes, [$node->id]);
// or a number of nodes which don't even have to
// be on the same level, but you might fetch nodes multiple times
// if you it this way
Complexity: Number of queries <= height of your tree. You only iterate each fetched node once.
For displaying the tree as html list you iterate once more:
class Foo {
public $html;
public function getList ($nodes)
{
// outer most ul
$this->html = '<ul>';
$this->recurseList($nodes);
$this->html .= '</ul>';
}
protected function recurseList ($nodes)
{
foreach ($nodes as $node)
{
$this->html .= "<li><span>".$node->name."</span>";
if ($node->children)
{
if ($node->parentId > 0)
$this->html .= '<ul style="display:none">';
else
$this->html .= '<ul>';
$this->recurseList($node->children);
$this->html .= "</ul>";
}
$this->html .= "</li>";
}
}
}
Some unrelated remarks:
instead of a hard-coded style="display:none" you could just use a css rule like ul li ul {display:none} to hide all lists below root
I split fetching the data from the database and converting the data for displaying into two separate scripts, which is the standard when you develop using MVC. If you don't want to do this run the scripts in succession.
PHP itself is a template engine, but consider using another template engine to display your html like Smarty. I personally prefer smarty for it's simpler syntax.
stackoverflow answer on how to bind a PHP array to a MySQL IN-operator using prepared statements
if you need to fetch subtrees often consider using a special table structure to reduce the number of queries

display category of subcategories and sub/subcategories

I'm beginner of Php .. I Develop my first dynamic website -> http://www.afrogfx.com
if you look at my sidebar you will see categories list my problem located here if i create sub sub categories like that
CAT A
- SUB CAT A-1
- SUB CAT A-2
-- SUB CAT A-3 ( problem Here )
CAT B
- SUB CAT B-1
- SUB CAT B-2
- SUB CAT B-2-a
-- SUB CAT B-2-b ( problem Here )
--- SUB CAT B-3 ( problem Here )
categories list code
<?php
mysql_select_db($db_name, $conn); // Change for your database
$query_Recordset1 = "SELECT catid,catname,parentid FROM categories ";
$Recordset1 = mysql_query($query_Recordset1, $conn) or die(mysql_error()); // Change for your database
while ( $row = mysql_fetch_assoc($Recordset1) )
{
$menu_array[$row['catid']] = array('catname' => $row['catname'],'catid' => $row['catid'],'parentid' => $row['parentid']);
}
//recursive function that prints categories as a nested html unordered list
function generate_menu($parent)
{
$has_childs = false;
//this prevents printing 'ul' if we don't have subcategories for this category
global $menu_array;
//use global array variable instead of a local variable to lower stack memory requierment
foreach($menu_array as $key => $value)
{
if ($value['parentid'] == $parent)
{
//if this is the first child print '<ul>'
if ($has_childs === false)
{
//don't print '<ul>' multiple times
$has_childs = true;
//echo '<ul>';
echo '<ul id="categories">';
}
echo '<li>' . $value['catname'] . '';
echo '<input type="hidden" value="' . $value['catname'] . '" />';
generate_menu($key);
//call function again to generate nested list for subcategories belonging to this category
echo '</li>';
}
}
if ($has_childs === true) echo '</ul>';
}
//generate menu starting with parent categories (that have a 0 parent)
?>
now i need function to select all topic in main category when i select it and sub category to & sub sub category !! how can i do it ?? !!
i use this code for menu with submenus
this is the function
// Menu builder function, parentId 0 is the root
function buildMenu($parent, $menu) {
$html = "";
if (isset($menu['parents'][$parent]))
{
$html .= "
<ul>\n";
foreach ($menu['parents'][$parent] as $itemId)
{
if(!isset($menu['parents'][$itemId]))
{
$html .= "<li>\n <a href='".$menu['items'][$itemId]['link']."'>".$menu['items'][$itemId]['label']."</a>\n</li> \n";
}
if(isset($menu['parents'][$itemId]))
{
$html .= "
<li><span>" . $menu['items'][$itemId]['label'] . "<b></b></span>" ;
$html .= buildMenu($itemId, $menu);
$html .= "</li> \n";
}
}
$html .= "</ul> \n";
}
return $html;
}
this is in the call
// Select all entries from the menu table
$sql = "SELECT id, label, link, parent FROM dbo.Menu ORDER BY parent, sort, label";
$result = $database->query($sql);
$menu = array(
'items' => array(),
'parents' => array()
);
// Builds the array lists with data from the menu table
while ($items = sqlsrv_fetch_array( $result )) {
// Creates entry into items array with current menu item id ie. $menu['items'][1]
$menu['items'][$items['id']] = $items;
// Creates entry into parents array. Parents array contains a list of all items with children
$menu['parents'][$items['parent']][] = $items['id'];
}
i have a SQL table in my database with the columns
id
Label
Link
Parent
Sort
if you put all your top menu items as parent 0 then every item you want to be a submenu under a parent put in the parent id i

Parent child Relationship in mysql

I am having a table like the following,need to display as Parent and child format
--------------------------------------------------------
id role_name role_id parent_id
--------------------------------------------------------
1 NSM 1 0
2 MR 5 2
3 ASM 4 3
4 ZSM 3 4
5 RSM 2 1
---------------------------------------------------------
the result is like to be the following
NSM
---RSM
-----ZSM
-----NSM
-----MR
NSM->ROOT
RSM->FIRST CHILD
ZSM->SECOND CHILD
NSM->THIRD CHILD
MR->LEAF
// Fetch all the roles
$result = mysql_query("select * from roles");
$roles = array();
while( $role = mysql_fetch_assoc($result) ) {
$roles[] = $role;
}
// Function that builds a tree
function build_tree($roles, $parent_id=0) {
$tree = array();
foreach ($roles as $role) {
if ($role['parent_id'] == $parent_id) {
$tree[] = array(
'role' => $role,
'children' => build_tree($roles, $role['parent_id'])
);
}
}
return $tree;
}
// Function that walks and outputs the tree
function print_tree($tree) {
if (count($tree) > 0) {
print("<ul>");
foreach($node in $tree) {
print("<li>");
htmlspecialchars($node['role']['role_name']);
print_tree($node['children']);
print("</li>");
}
print("</ul>");
}
}
SQL Results are always flat - you'll not be able to return a hierarchy view of that data in a query.
Instead, I would suggest using whichever client components you are using to show that (is it a tree? what exactly?) that knows how to go thru a flat list and build a hierarchy out of that.
If you want to print a view like that in a console (why would you ever want to do that?), you could do like this:
$data = array();
$query = mysql_query("SELECT * FROM table ORDER BY parent_id");
while($array = mysql_fetch_assoc($query))
{
$data[$array['parent_id']][] = $array;
}
function output_hierarchy($id, $prepend)
{
$current = $data[$id];
foreach($current as $item)
{
print $prepend . " " . $item['role_name'];
if(count($data[$item['id']]) > 0)
{
output_hierarchy($item['id'], $prepend . "--");
}
}
}
output_hierarchy(0, '');
If you want to use this on your website, you can easily adapt it. Code should be self-explanatory.

PHP Multidimensional array to unordered list, building up url path

I have a multidimensional array in PHP produced by the great examples of icio and ftrotter (I am use ftrotterrs array in arrays variant):
Turn database result into array
I have made this into a unordered list width this method:
public function outputCategories($categories, $startingLevel = 0)
{
echo "<ul>\n";
foreach ($categories as $key => $category)
{
if (count($category['children']) > 0)
{
echo "<li>{$category['menu_nl']}\n";
$this->outputCategories($category['children'], $link
, $start, $startingLevel+1);
echo "</li>\n";
}
else
{
echo "<li>{$category['menu_nl']}</li>\n";
}
}
echo "</ul>\n";
}
So far so good.
Now I want to use the url_nl field to build up the url's used as links in the menu. The url has to reflect the dept of the link in de tree by adding up /url_nl for every step it go's down in the tree.
My goal:
- item 1 (has link: /item_1)
* subitem 1 (has link: /item_1/subitem_1)
* subitem 2 (has link: /item_1/subitem_1)
* subsubitem 1 (has link: /item_1/subitem_2/subsubitem_1)
- item 2 (has link: /item_2)
the table
id
id1 (parent id)
menu_nl
url_nl
title_nl
etc
What I have so far:
public function outputCategories($categories, $link, $start, $startingLevel = 0)
{
// if start not exists
if(!$start)
$start = $startingLevel;
echo "<ul>\n";
foreach ($categories as $key => $category)
{
$link.= "/".$category['url_nl'];
if($start != $startingLevel)
$link = strrchr($link, '/');
if (count($category['children']) > 0)
{
echo "<li>".$start." - ".$startingLevel.
"<a href='$link'>{$category['menu_nl']}</a> ($link)\n";
$this->outputCategories($category['children'], $link
, $start, $startingLevel+1);
echo "</li>\n";
}
else
{
$start = $startingLevel+1;
echo "<li>".$start." - ".$startingLevel.
"<a href='$link'>{$category['menu_nl']}</a> ($link)</li>\n";
}
}
echo "</ul>\n";
}
As you see in the example I have used a url_nl field which is recursively added so every level of the list has a link with a path which is used as a url.
Anyhow, I have problems with building up these links, as they are not properly reset while looping to the hierarchical list. After going down to the child in de list the first one is right but the second one not.
I'm stuck here...
It looks like you modify the $link variable inside the foreach loop, So you add item1 to $link, loop thru its subitems and return to the first iteration and add item2 to the variable...
replace this
$link .= "/".$category['url_nl'];
with
$insidelink = $link . "/".$category['url_nl'];
(and change remaining $link inside the loop to $insidelink)
Adding: This is also true for $startingLevel. Do not modify it, use +1 inline:
echo "<li>".$start." - ".$startingLevel +1.
"<a href='$link'>{$category['menu_nl']}</a> ($link)</li>\n";
Here is an easier way:
$inarray = your multi-dimensional array here. I used directory_map in codeigniter to get contents of directory including it's subdirectories.
$this->getList($filelist2, $filelist);
foreach ($filelist as $key => $val) {
echo $val;
}
function getList($inarray, &$filelist, $prefix='') {
foreach ($inarray as $inkey => $inval) {
if (is_array($inval)) {
$filelist = $this->getList($inval, $filelist, $inkey);
} else {
if ($prefix)
$filelist[] = $prefix . '--' . $inval;
else
$filelist[] = $inval;
}
}
return $filelist;
}

how to change a while sql query loop into an array loop

i record number of queries of my website and in page the below script runs , 40 extra queries added to page .
how can I change this sql connection into a propper and light one
function tree_set($index)
{
//global $menu; Remove this.
$q=mysql_query("select id,name,parent from cats where parent='$index'");
if(mysql_num_rows($q) === 0)
{
return;
}
// User $tree instead of the $menu global as this way there shouldn't be any data duplication
$tree = $index > 0 ? '<ul>' : ''; // If we are on index 0 then we don't need the enclosing ul
while($arr=mysql_fetch_assoc($q))
{
$subFileCount=mysql_query("select id,name,parent from cats where parent='{$arr['id']}'");
if(mysql_num_rows($subFileCount) > 0)
{
$class = 'folder';
}
else
{
$class = 'file';
}
$tree .= '<li>';
$tree .= '<span class="'.$class.'">'.$arr['name'].'</span>';
$tree .=tree_set("".$arr['id']."");
$tree .= '</li>'."\n";
}
$tree .= $index > 0 ? '</ul>' : ''; // If we are on index 0 then we don't need the enclosing ul
return $tree;
}
//variable $menu must be defined before the function call
$menu = '....<ul id="browser" class="filetree">'."\n";
$menu .= tree_set(0);
$menu .= '</ul>';
echo $menu;
i heard , this can be done by changing it into an array , but i don't know how to do so
thanks in advance
Try this (untested code):
function tree_set($index)
{
//global $menu; Remove this.
$q=mysql_query("select id,name,parent from cats where parent='$index'");
if(mysql_num_rows($q) === 0)
return;
$cats = array();
$cat_ids = array();
while($arr=mysql_fetch_assoc($q))
{
$id = intval($arr['id']);
$cats[$id] = $arr;
}
$subFilesCountQuery="select parent,count(*) as subFileCount from cats where parent=".
join(" OR parent=",array_keys($cats))." GROUP BY parent";
$subFileCountResult=mysql_query($subFilesCountQuery);
while($arr=mysql_fetch_assoc($subFileCountResult))
{
$id = intval($arr['parent']);
$cats[$id]['subFileCount'] = $arr['subFileCount'];
}
// If we are on index 0 then we don't need the enclosing ul
$tree = $index > 0 ? '<ul>' : '';
foreach($cats as $id => $cat)
{
if($cat['subFileCount'] > 0)
$class = 'folder';
else
$class = 'file';
$tree .= '<li>';
$tree .= '<span class="'.$class.'">'.$arr['name'].'</span>';
$tree .=tree_set("".$arr['id']."");
$tree .= '</li>'."\n";
}
$tree .= $index > 0 ? '</ul>' : '';
What I'm doing is two queries: One to fetch all the categories (your original first query) followed by a second query to fetch all the subcategory counts in one fell swoop. I am also storing all categories in an array which you can loop through, rather than displaying as you fetch from the database.
It can be done by copying your data out into an array, and then using that copy: i.e.
while($arr=mysql_fetch_assoc($q))
{
$results[] = $arr;
}
later on, you then do whatever op you want on $results
The main problem with your code is you are mixing your display logic all in with your SQL query.
Select whole tree in single query, like "select id,name,parent from cats". Iterate on result, build array in PHP that will represent your tree, then draw HTML using array as source

Categories