How to build a tree view with PHP / SQL? - php

What's the best way to:
Get the data from the db using a single query
Loop through the results building e.g. a nested unordered list
My table has id, name and parent_id columns.
Here's an update to my last answer, with a counter that gives each ul a nesting 'level' class, and some comments.
Could anyone suggest how to adapt this to use table rows, without nesting, but with some kind of class numbering hierarchy for css/js hooks?
<?
//
// Get the data
//
include_once("inc/config.php");
$query = "SELECT c.*
FROM categories AS c
ORDER BY c.id
LIMIT 1000";
$result = pg_query($db, $query);
//
// Load all the results into the row array
//
while ($row = pg_fetch_array($result, NULL, PGSQL_ASSOC))
{
//
// Wrap the row array in a parent array, using the id as they key
// Load the row values into the new parent array
//
$categories[$row['id']] = array(
'id' => $row['id'],
'description' => $row['description'],
'parent_id' => $row['parent_id']
);
}
// print '<pre>';
// print_r($category_array);
// ----------------------------------------------------------------
//
// Create a function to generate a nested view of an array (looping through each array item)
// From: http://68kb.googlecode.com/svn-history/r172/trunk/upload/includes/application/controllers/admin/utility.php
//
function generate_tree_list($array, $parent = 0, $level = 0)
{
//
// Reset the flag each time the function is called
//
$has_children = false;
//
// Loop through each item of the list array
//
foreach($array as $key => $value)
{
//
// For the first run, get the first item with a parent_id of 0 (= root category)
// (or whatever id is passed to the function)
//
// For every subsequent run, look for items with a parent_id matching the current item's key (id)
// (eg. get all items with a parent_id of 2)
//
// This will return false (stop) when it find no more matching items/children
//
// If this array item's parent_id value is the same as that passed to the function
// eg. [parent_id] => 0 == $parent = 0 (true)
// eg. [parent_id] => 20 == $parent = 0 (false)
//
if ($value['parent_id'] == $parent)
{
//
// Only print the wrapper ('<ul>') if this is the first child (otherwise just print the item)
// Will be false each time the function is called again
//
if ($has_children === false)
{
//
// Switch the flag, start the list wrapper, increase the level count
//
$has_children = true;
echo '<ul class="level-' . $level . '">';
$level++;
}
//
// Print the list item
//
echo '<li>' . $value['description'] . '';
//
// Repeat function, using the current item's key (id) as the parent_id argument
// Gives us a nested list of subcategories
//
generate_tree_list($array, $key, $level);
//
// Close the item
//
echo '</li>';
}
}
//
// If we opened the wrapper above, close it.
//
if ($has_children === true) echo '</ul>';
}
// ----------------------------------------------------------------
//
// generate list
//
generate_tree_list($categories);
?>

function generate_list($array,$parent,$level)
{
foreach ($array as $value)
{
$has_children=false;
if ($value['parent_id']==$parent)
{
if ($has_children==false)
{
$has_children=true;
echo '<ul>';
}
echo '<li>'.$value['member_name'].' -- '.$value['id'].' -- '.$value['parent_id'];
generate_list($array,$value['id'],$level);
echo '</li>';
}
if ($has_children==true) echo '</ul>';
echo $value['parent_id'];
}
}

MySQL have created a good article on this subject: Managing Hierarchical Data in MySQL

You can create a breadcrumb view style by using arrays, without using a recursive function.
Here is my working code:
First, make a SQL query like this:
$category = CHtml::listData(TblCategory::model()->findAllCategory(array(
'distinct'=>true,
'join'=>'LEFT JOIN tbl_category b on b.id = t.cat_parent',
'join'=>'LEFT JOIN tbl_category c on c.cat_parent = 0',
'order' => 'cat_name')),'id','cat_name');
I am using yii related code so you can use normal join queries, then form an array in a foreach() function
public function findAllCategory($condition='',$params=array())
{
Yii::trace(get_class($this).'.findAll()','system.db.ar.CActiveRecord');
$criteria=$this->getCommandBuilder()->createCriteria($condition,$params);
$category = array();
$cat_before;
$parent_id = array();
$cat_before = $this->query($criteria,true);
//echo "<br><br><br><br><br><br><br>";
foreach($cat_before as $key => $val)
{
$category[$key] = $val;
$parent_id[$key]['cat_parent'] =$val['cat_parent'];
$parent_id[$key]['cat_name'] =$val['cat_name'];
foreach($parent_id as $key_1=> $val_1)
{
if($parent_id[$key]['cat_parent'] == $category[$key_1]['id'])
{
$category[$key]['cat_name']= $category[$key_1]['cat_name'] .' > '. $parent_id[$key]['cat_name'];
}
}
}
return $cat_before;
}
Then you can get result using Main cat >> subcat 1 >> subcat_1 inner >> ...

Related

remove first or specific child node xpath

this code get table.
I want to remove first and second tr tag in the table.
$data = array();
$table_rows = $xpath->query('//table[#class="adminlist"]/tr');
if($table_rows->length <= 0) { // exit if not found
echo 'no table rows found';
exit;
}
foreach($table_rows as $tr) { // foreach row
$row = $tr->childNodes;
if($row->item(0)->tagName != 'tblhead') { // avoid headers
$data[] = array(
'Name' =>trim($row->item(0)->nodeValue),
'LivePrice' => trim($row->item(2)->nodeValue),
'Change'=> trim($row->item(4)->nodeValue),
'Lowest'=> trim($row->item(6)->nodeValue),
'Topest'=> trim($row->item(8)->nodeValue),
'Time'=> trim($row->item(10)->nodeValue),
);
}
}
and question 2
In the bellow table tr have two class --- EvenRow_Print and OddRow_Print ---
$data = array();
$table_rows = $xpath->query('//table/tr');
if($table_rows->length <= 0) {
echo 'no table rows found';
exit;
}
foreach($table_rows as $tr) { // foreach row
$row = $tr->childNodes;
if($row->item(0)->tagName != 'tblhead') { // avoid headers
$data[] = array(
'Name' =>trim($row->item(0)->nodeValue),
'LivePrice' => trim($row->item(2)->nodeValue),
'Change'=> trim($row->item(4)->nodeValue),
'Lowest'=> trim($row->item(6)->nodeValue),
'Topest'=> trim($row->item(8)->nodeValue),
'Time'=> trim($row->item(10)->nodeValue),
);
}
}
How can I echo both tr in one 2d array .
examp.
Array(
[0] => Array(
//array
)
}
Thank's
For question 1 - there are different ways to skip the first and last element, e.g. removing the first entry using array_shift() and the last entry using array_pop(). But as it's not clear if it'd be better to keep the array as it is, it's possible to skip both entries in the foreach in an easy way like using a counter, continuing for the first entry and breaking for the last:
$i = 0;
$trlength = count($table_rows);
foreach( ...) {
if ($i == 0) // is true for the first entry
{
$i++; // increment counter
continue; // continue with next entry
}
else if ($i == $trlength - 1) // last entry, -1 because $i starts from 0
{
break; // exit foreach loop
}
.... // handle all other entries
$i++; // increment counter in foreach loop
}

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.

MySQL DB driven menu generator function

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 '' . $row->name . '';
//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:
First of all we need to echo all parents
Function must get current pages id as an input value (for ex. $current)
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
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.
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.
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 {$row->name}\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);

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;
}

Categories