Build boostrap4 navmenu from multiple mysql tables - php

I want to make a bootstrap 4 nav-menu, I have the following SQL query and I have some codes below it but I can't get my head around how to do this!
These are the tables
TABLE menu
--------------------------------------
| id | title | url |
| 1 | Home | index.php |
| 2 | Menu | # |
| 3 | Contact | # |
| 2 | Winkelwagen | winkelwagen.php |
--------------------------------------
TABLE categories
-------------------------------------
| id | title_cat | url | cparent_id |
| 1 | Auto's | # | 2 |
| 2 | Drank | # | 2 |
-------------------------------------
TABLE products
-------------------------------------
| id | product | url | pparent_id |
| 1 | Ferrari | # | 1 |
| 2 | Heineken | # | 2 |
-------------------------------------
Here is the query:
$query = "SELECT
X.level,
X.id,
X.name,
X.url,
X.parent_id
FROM
(
SELECT
1 AS LEVEL,
id AS id,
title AS NAME,
url AS url,
0 AS parent_id,
id AS id_1,
-1 AS id_2,
-1 AS id_3
FROM
menu
WHERE
1
UNION
SELECT
2 AS LEVEL,
id AS id,
title_cat AS NAME,
url AS url,
cparent_id AS parent_id,
cparent_id AS id_1,
id AS id_2,
-1 AS id_3
FROM
categories
WHERE
1
UNION
SELECT
3 AS LEVEL,
products.id AS id,
products.product AS NAME,
products.url AS url,
products.pparent_id AS parent_id,
categories.cparent_id AS id_1,
categories.id AS id_2,
products.id AS id_3
FROM
products
LEFT JOIN categories ON products.pparent_id = categories.id
WHERE
1
) X
WHERE
1
ORDER BY
id_1,
id_2,
id_3";
Which gives the following table with levels (and I added the parent_id too, but with the parent_id buildTree($array) goes into a loop):
level id name url parent_id
1 1 Home index.php 0
1 2 Menu # 0
2 1 Auto's # 2
3 1 Ferrari # 1
2 2 Drank # 2
3 2 Heineken # 2
1 3 Contact contact.php 0
1 4 Winkelwagen winkelwagen.php 0
I want the nav-menu to look like this:
<li class="nav-item">
<a class="nav-link" href="index.php">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Menu</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#">Auto's</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Ferrari</a>
</div>
</div>
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#">Drank</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Heineken</a>
</div>
</div>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Contact</a>
</li>
<li class="nav-item">
<a class="nav-link" href="winkelwagen.php">Winkelwagen</a>
</li>
I have the following codes, first we make a array from the query fetched which you already saw above:
$sql = $pdo->prepare($query);
function menu_builder($sql) {
if ($sql->execute()) {
while ($row = $sql->fetch(PDO::FETCH_ASSOC)) {
$array[] = $row;
}
buildTree($array); // or menu_builder($sql);
}
}
The next code doesn't work because it goes into a infinite loop (and if it works I still need to make the html right :):
function buildTree($array, $parent_id = 0, $parents = array()) {
if($parent_id == 0) {
foreach ($array as $element) {
if (($element['parent_id'] != 0) && !in_array($element['parent_id'], $parents)) {
$parents[] = $element['parent_id'];
}
}
}
$menu_html = '';
foreach($array as $element) {
if($element['parent_id'] == $parent_id) {
if(in_array($element['id'], $parents)) {
$menu_html .= '<li class="dropdown">';
$menu_html .= ''.$element['name'].' <span class="caret"></span>';
}
else {
$menu_html .= '<li>';
$menu_html .= '' . $element['name'] . '';
}
if(in_array($element['id'], $parents)) {
$menu_html .= '<ul class="dropdown-menu" role="menu">';
$menu_html .= buildTree($array, $element['id'], $parents);
$menu_html .= '</ul>';
}
$menu_html .= '</li>';
}
}
return $menu_html;
}
And this one makes a normal <ul>/<li> menu which I don't know how to get it working for me with bootstrap:
function menu_builder($sql) {
$level = 0;
if ($sql->execute()) {
while ($row = $sql->fetch(PDO::FETCH_ASSOC)) {
while($level < $row['level']) {
echo "<ul>" . PHP_EOL;
$level++;
}
while($level > $row['level']) {
echo "</ul>" . PHP_EOL;
$level--;
}
echo " <li>#" . $row['id'] . "->" . $row['name'] . "</li>" . PHP_EOL;
}
}
while($level-- > 0) {
echo "</ul>" . PHP_EOL;
}
}
If you need more information please ask me, I tried to make the question as clear as possible with the codes I'm trying and the table I'm using.
jQuery:
$('.dropdown-menu a.dropdown-toggle').on('click', function(e) {
if (!$(this).next().hasClass('show')) {
$(this).parents('.dropdown-menu').first().find('.show').removeClass("show");
}
var $subMenu = $(this).next(".dropdown-menu");
$subMenu.toggleClass('show');
$(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function(e) {
$('.dropdown-submenu .show').removeClass("show");
});
return false;
});
CSS:
.dropdown-submenu {
position: relative;
}
.dropdown-submenu a::after {
transform: rotate(-90deg);
position: absolute;
right: 6px;
top: .8em;
}
.dropdown-submenu .dropdown-menu {
top: 0;
left: 100%;
margin-left: .1rem;
margin-right: .1rem;
}

I've gone for a more linear approach than your recursive buildTree. It's a little easier this way because of the need to output different HTML dependent on the level of the tree. I've created an SQLFiddle for your data with a few extra values I added for testing purposes. The query changes so that I can see whether a menu item has a submenu and also if that submenu has a product, all in one row:
SELECT m.title AS title, m.url AS m_url,
c.title_cat AS title_cat, c.url AS c_url,
p.product AS product, p.url AS p_url
FROM menu m
LEFT JOIN categories c
ON c.cparent_id = m.id
LEFT JOIN products p
ON p.pparent_id = c.id
ORDER BY m.id, c.id, p.id
The output of this query (based on the expanded data) is:
title m_url title_cat c_url product p_url
Home index.php (null) (null) (null) (null)
Menu # Auto's # Ferrari www.ferrari.com
Menu # Auto's # Maserati #
Menu # Drank # Heineken #
Menu # Food # (null) (null)
Second Menu # Hotels # The Ritz www.ritzparis.com
Contact contact.php (null) (null) (null) (null)
Winkelwagen winkelwagen.php (null) (null) (null) (null)
The basic query call remains the same although instead of fetching all the data and then processing it, I fetch the data and process it at the same time.
$sql = $pdo->prepare($query);
$sql->execute() or die("Unable to execute query!");
buildTree($sql);
The buildTree routine. I think it's fairly self-explanatory with the comments but basically it goes through each row of data and determines if it needs to create a new menu item, a new submenu, or a new submenu item in turn.
function buildTree($sql) {
$thisTitle = '';
$thisCategory = '';
while ($element = $sql->fetch(PDO::FETCH_ASSOC)) {
if (!$element['c_url']) {
// simple top element
// do we need to close any prior menus?
if ($thisCategory != '') {
echo " </div>\n </div>\n";
$thisCategory = '';
}
if ($thisTitle != '') {
echo "</li>\n";
$thisTitle = '';
}
echo <<<EOD
<li class="nav-item">
<a class="nav-link" href="{$element['m_url']}">{$element['title']}</a>
</li>
EOD;
}
else {
// got a category
// do we need a new menu item?
if ($element['title'] != $thisTitle) {
// is it the first menu item? if not, need to close the previous one
if ($thisTitle != '') {
// do we also need to close a previous category menu?
if ($thisCategory != '') {
echo " </div>\n </div>\n";
$thisCategory = '';
}
echo "</li>\n";
}
$thisTitle = $element['title'];
echo <<<EOD
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="{$element['m_url']}" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">$thisTitle</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
EOD;
}
// do we need a new submenu?
if ($element['title_cat'] != $thisCategory) {
// is it the first submenu? if not, need to close the previous one
if ($thisCategory != '') echo " </div>\n";
$thisCategory = $element['title_cat'];
// create a submenu
echo <<<EOD
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="{$element['c_url']}">$thisCategory</a>
EOD;
}
// is there a product?
if ($element['p_url']) {
// create a product menu item
echo <<<EOD
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{$element['p_url']}">{$element['product']}</a>
</div>
EOD;
}
}
}
}
The output of this code for the extended data is:
<li class="nav-item">
<a class="nav-link" href="index.php">Home</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Menu</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#">Auto's</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="www.ferrari.com">Ferrari</a>
</div>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Maserati</a>
</div>
</div>
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#">Drank</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Heineken</a>
</div>
</div>
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#">Food</a>
</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Second Menu</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#">Hotels</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="www.ritzparis.com">The Ritz Paris</a>
</div>
</div>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="contact.php">Contact</a>
</li>
<li class="nav-item">
<a class="nav-link" href="winkelwagen.php">Winkelwagen</a>
</li>

I think part of what is making this difficult is trying to combine creating your data structures with presentation. Usually it is best to structure your data first before worrying about how to present it.
I tried my best to break this up into separate steps as best I could. I added some commented out print_r statements so you can see what each step is doing. Let me know if you have any questions about it.
#!/usr/bin/php
<?php
// The query results
$results = [
// Level, id, title, link, parent
[1, 1, 'Home', 'index.php', 0],
[1, 2, 'Menu', '#', 0],
[2, 1, "Auto's", '#', 2],
[3, 1, 'Ferrari', '#', 1],
[2, 2, 'Drank', '#', 2],
[3, 2, 'Heineken', '#', 2],
[1, 3, 'Contact', 'contact.php', 0],
[1, 4, 'Winkelwagen', 'winkelwagen.php', 0],
];
// creates a constant for the query result row keys (you should avoid using a global constant for this)
define('KEYS', ['level', 'id', 'title', 'link', 'parent_id']);
// adds the keys to each result row (this is just to help readability/maintainability)
$rows = array_map(function (array $item): stdClass {
// cast this as an object so we don't have to use pass by reference later (I think this improves readability).
return (object)array_combine(KEYS, $item);
}, $results);
// uncomment to see raw $rows
//print_r($rows);die;
// creates a key for each row based on the level and id (this way they will be unique)
$keys = array_map(function (stdClass $row): string {
$key = "$row->level-$row->id";
$row->key = $key;
return $key;
}, $rows);
$keyed = array_combine($keys, $rows);
// uncomment to see $keyed values
//print_r($keyed);die;
// converts the keyed records into a tree.
$tree = [];
foreach($keyed as $item) {
if (1 === $item->level) {
$tree[] = $item;
continue;
}
$parent = ($item->level - 1).'-'.$item->parent_id;
if (!isset($keyed[$parent])) {
throw new Exception("could not find parent element '$parent'");
}
$keyed[$parent]->children[] = $item;
}
// uncomment to see $tree structure
//print_r($tree);die;
?>
<!-- add ul classes for style -->
<ul>
<?php foreach ($tree as $trunk): ?>
<?php if (!isset($trunk->children)): ?>
<li class="nav-item">
<a class="nav-link" href="<?=$trunk->link?>"><?=$trunk->title?></a>
</li>
<?php else: ?>
<li class="nav-item dropdown">
<!-- id attributes must be unique so we add the item key to the end of it to make it unique-->
<a class="nav-link dropdown-toggle" role="button"
href="<?=$trunk->link?>" id="navbarDropdown<?=$trunk->key?>"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><?=$trunk->title?></a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<?php foreach ($trunk->children as $branch): ?>
<?php if (!isset($branch->children)): ?>
<!-- add style to this if needed -->
<?=$branch->title?>
<?php else: ?>
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" id="navbarDropdown<?=$branch->key?>"
href="<?=$branch->link?>"><?=$branch->title?></a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown<?=$branch->key?>">
<!-- You may need to add some additional style code to this for it to work with multiple items -->
<?php foreach ($branch->children as $leaf): ?>
<a class="dropdown-item" href="<?=$branch->link?>"><?=$branch->title?></a>
<?php endforeach; ?>
</div>
</div>
<?php endif ?>
<?php endforeach; ?>
</div>
</li>
<?php endif ?>
<?php endforeach ?>
</ul>
As a rule of thumb I try to avoid using nested loops, but in this instance each level seemed to have separate styling. If you ever want to make this able to handle more then three levels you may want to think about using a recursive function.

Try This
<?php
$servername = "localhost";
$username = "root";
$password = "";
try {
$pdo = new PDO("mysql:host=$servername;dbname=testing", $username, $password);
// set the PDO error mode to exception
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
$querySubmenu = "SELECT a.id, a.title, a.url, b.title_cat AS cat_name, b.url AS cat_url, c.product AS product_name, c.url AS product_url FROM menu a
JOIN (SELECT * FROM categories) b ON a.id = b.cparent_id
JOIN (SELECT * FROM products) c ON b.id = c.pparent_id";
$queryMenu = "SELECT a.*, 0 AS cat_name, 0 AS cat_url, 0 AS product_name, 0 AS product_url FROM menu a WHERE id NOT IN (
SELECT a.id FROM menu a
JOIN (SELECT * FROM categories) b ON a.id = b.cparent_id
JOIN (SELECT * FROM products) c ON b.id = c.pparent_id)";
$sqlMenu = $pdo->prepare($queryMenu);
$sqlSubmenu = $pdo->prepare($querySubmenu);
$menu = menu_builder($sqlMenu);
$submenu = menu_builder($sqlSubmenu);
$arr = array_merge($menu, $submenu);
usort($arr, function ($a, $b) {
return $a['id'] - $b['id'];
});
function menu_builder($sql)
{
if ($sql->execute()) {
while ($row = $sql->fetch(PDO::FETCH_ASSOC)) {
$array[] = $row;
}
return $array;
}
}
foreach ($arr as $element) {
if ($element['cat_name'] == '0') { ?>
<li class="nav-item">
<a class="nav-link" href="<?php echo $element['url'] ?>"><?php echo $element['title'] ?></a>
</li>
<?php }
if ($element['cat_name'] != '0') { ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><?php echo $element['title'] ?></a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="<?php echo $element['cat_url'] ?>"><?php echo $element['cat_name'] ?></a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="<?php echo $element['product_url'] ?>"><?php echo $element['product_name'] ?></a>
</div>
</div>
</div>
</li>
<?php }
}
?>

Related

PHP Multi-level menu

I have this code that supposed to generate a multi-level menu from my database. My problem I have a specific HTML code when there are/are no child menu.
Here is the HTML Code if the menu does not have a child menu:
<li class="nav-item dropdown">Menu</li>
Here is the HTML code if the menu has a child menu:
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle arrow-none" href="javascript: void(0);" id="topnav-user-access" role="button">
<span key="t-user-access">Parent Menu</span> <div class="arrow-down"></div>
</a>
<div class="dropdown-menu" aria-labelledby="topnav-user-access">
Child Menu 1
Child Menu 2
...
</div>
</li>
Here is my PHP code to generate the menu.
public function generate_multilevel_menu($module_id, $username, $parent_menu = null){
$menu = '';
if(!empty($parent_menu)){
$query = 'SELECT MENU_ID, MENU, PARENT_MENU, IS_LINK, MENU_LINK FROM technical_menu WHERE MODULE_ID = :module_id AND PARENT_MENU = :parent_menu ORDER BY ORDER_SEQUENCE, MENU';
}
else{
$query = 'SELECT MENU_ID, MENU, PARENT_MENU, IS_LINK, MENU_LINK FROM technical_menu WHERE MODULE_ID = :module_id AND (PARENT_MENU IS NULL OR PARENT_MENU = "0") ORDER BY ORDER_SEQUENCE, MENU';
}
$sql = $this->db_connection->prepare($query);
$sql->bindValue(':module_id', $module_id);
if(!empty($parent_menu)){
$sql->bindValue(':parent_menu', $parent_menu);
}
if($sql->execute()){
while($row = $sql->fetch()){
$menu_id = $row['MENU_ID'];
$menu_access_right = $this->check_role_access_rights($username, $menu_id, 'menu');
if($menu_access_right > 0){
if(!empty($row['MENU_LINK'])){
$menu .= ''. $row['MENU'] .'';
#$menu .= '<li class="nav-item dropdown">'. $row['MENU'] .'</li>';
}
else{
$menu .= '<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle arrow-none" href="javascript: void(0);" id="topnav-user-access" role="button">
<span key="t-user-access">'. $row['MENU'] .'</span> <div class="arrow-down"></div>
</a>';
}
$menu .= '<div class="dropdown-menu" aria-labelledby="topnav-user-access">' . $this->generate_multilevel_menu($module_id, $username, $row['MENU_ID']) . '</div>';
$menu .= '</li>';
}
}
return $menu;
}
}
Here is the screenshot of my database structure and details.
Here is the sample output of my code:
My problem is that I can't seem to incorporate the HTML code to fit my code. The code works if the HTML code is a simple HTML list but using my HTML code and conditions I can't seem to fix it. What part of my code do I need to modify to accommodate my if conditions?

Categories are repeating for each subcategory

I am showing categories in Menu. Some categories have subcategories.
function for getting parent categories
function get_parent_category(){
$query="select * from blog_categories where parent_id=0
ORDER BY
CASE id
WHEN '2' THEN 1
WHEN '1' THEN 2
WHEN '3' THEN 3
ELSE id
END";
$rows=array();
$result=$this->query($query);
while($row=$this->fetch_array($result)){
$row['url']=$this->get_cat_url($row);
$rows[]=$row;
}
return $rows;
}
Function for subcategories
function get_child_category(){
$query="select * from blog_categories where parent_id!=0";
$rows=array();
$result=$this->query($query);
while($row=$this->fetch_array($result)){
$row['url']=$this->get_cat_url($row);
$rows[]=$row;
}
return $rows;
}
Showing on the page like this:
<ul class="nav navbar-nav">
<li>Home</li>
<?php
foreach($this->parent_category as $cat){
foreach($this->child_category as $child_cat){
if($cat['id']==$child_cat['parent_id']){
?>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"><?php echo $cat['name'];?>
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><?php echo $child_cat['name']?></li>
</ul>
</li>
<?php
}elseif($cat['parent_id']==0){
?>
<li><span><?php echo $cat['name'];?></span></li>
<?php
}
?>
<?php }}?>
Output and Problem
The Main category circle in red color is seerah which has two subcategories. showing two times for first one in drop down one subcategory and for second time second subcategory is showing.
DB structure
What i wants:
I wants to show each subcategories under each parent category without repetition , how can i achieve this?
Here is how i handled the problem
<?php
foreach($this->parent_category as $cat){
$html = '';
foreach($this->child_category as $child_cat){
if($cat['id']==$child_cat['parent_id']){
$html .= '<li>' . $child_cat['name'] . '</li>';
// here is all child categories are saved in var.
}
}
if ($html == '') {
?>
<li><span><?php echo $cat['name'];?></span></li>
<?php
} else {
?>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"><?php echo $cat['name'];?>
<span class="caret"></span></a>
<ul class="dropdown-menu">
<?php echo $html; ?> // here is displayed under parent category
</ul>
</li>
<?php
}
}
?>
Output
To me it seem you did not split your html and loops properly here:
foreach($this->parent_category as $cat){
foreach($this->child_category as $child_cat){
if($cat['id']==$child_cat['parent_id']){
?>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"><?php echo $cat['name'];?>
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><?php echo $child_cat['name']?></li>
</ul>
</li>
Usually whenever you have a loop you should have some output before any nested loop started. In your case first level loop is about Categories which should become an <li> of parent main menu <ul>.
I think. You need to transform this fragment to:
foreach($this->parent_category as $cat){ ?>
<li ...>
...
<ul ...> <?php
foreach($this->child_category as $child_cat){ ?>
<li>...</li> <?php
} ?>
</ul>
</li> <?php
}
when you do not have repetitive value in $rows, why you use this part again for $cat['name']?
elseif($cat['parent_id']==0){
?>
<li><span><?php echo $cat['name'];?></span></li>
<?php
}
when in first part of foreach you create
<a class="dropdown-toggle" data-toggle="dropdown" href="#"><?php echo $cat['name'];?>

how to Group header meniu category and subcategory mysqli using stmt statement results and display by parent

How I can group category and subcategory and display all the results from misqli DB for header meniu? I made a table called category with 3 column id, category, parent then I grouped the category using parent for the with value parent=0 for the category and the subcatogory value wich corespuning parent with the main category. But the problem is the code show me only the first main category and the first coresponding subcategory, and i want to display all.
so I have 3 category value (fruits id=1 , parent=0, cars id=2 parent=0, clothes id=3 parent=0 with parent=0 that means their are a category header) and e.g. for cars i have 2 subcategory value (moto id=52 parent=2 auto id=34 parent=2 ) you got the idea...
$parentTake = '0';
$categorieDrowdown = "";
$categorieDrowdownLista = "";
$stmt = $con->prepare('SELECT id, category, parent FROM category WHERE parent=?');
$stmt->bind_param('i', $parentTake);
$stmt->execute();
$existCount = $stmt->store_result();
if($existCount == 0){
echo "nU ai nici o categorie adaugata";
exit();
}
$stmt->bind_result($idParent, $categorie, $parent);
while ($stmt->fetch()) {
$idParent;
$categorie;
$categorieDrowdown .= '<li class="dropdown"> '.$categorie.'<span class="caret"></span>
';
$stmt = $con->prepare('SELECT id, category, parent FROM category WHERE parent=?');
$stmt->bind_param('i', $idParent);
$stmt->execute();
$existCount = $stmt->store_result();
if($existCount == 0){
echo "nU ai nici o subcategorie lista adaugata";
exit();
}
$stmt->bind_result($idLista, $categorieLista, $parentLista);
while ($stmt->fetch()) {
$idLista;
$categorieLista;
$categorieDrowdownLista .= ' <ul class="dropdown-menu">
<li>'.$categorieLista.'</li>
</ul>
</li>
';
}//close while subcategorie
}//close while categorie first select
and the display
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active">Home </li>
<li>Admin CMS</li>
<?php echo $categorieDrowdown;//CATEGORIE PRIMA ex: fructe?>
<?php echo $categorieDrowdownLista;//CATEGORIE SECUNDA ex:mere?>
</ul>
Sorry for my english and my problem but I'm newbie
You are overwriting the first $stmt with the second $stmt variable, which is why you are getting only the first menu item and the subcategories for this. I have made some adjustments to your code and added an alternative way of doing it, which doesn't have such a performance hit as the loop queries. Also worth mentioning if you don't have a subcatergory you shouldn't exit the script. Simply make an empty placeholder so you can continue with the page loading.
$stmt->store_result(); doesn't returns the number of rows. You call it to have access to $stmt->num_rows property, which will give you the results of found rows. Notice I haven't stored the result in the second example ( mysqli::store_result() ), I'm just filling the $menu array, without even the need to know if I have results.
$start = microtime( true );
$output = '';
$stmt = $con->prepare( '
SELECT
id, category, parent
FROM category
WHERE parent = 0
' );
$stmt->execute();
$stmt->store_result();
// YOU DON'T WANT TO DO THIS... o.O it's fine for debuging tho
//~ if($stmt->num_rows == 0){
//~ echo "nU ai nici o categorie adaugata";
//~ exit();
//~ }
// INSTEAD
if( $stmt->num_rows > 0 ){
$stmt->bind_result($idParent, $categorie, $parent);
while ($stmt->fetch()){
$output .= '
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
' . $categorie . ' <span class="caret"></span>
</a>
<ul class="dropdown-menu">';
// rename $stmt to $stmt2 so you'll not overwrite the initial statement
$stmt2 = $con->prepare('SELECT id, category, parent FROM category WHERE parent=?');
$stmt2->bind_param('i', $idParent);
$stmt2->execute();
$stmt2->store_result();
// NOR THIS... o.O
//~ if($stmt2->num_rows == 0){
//~ echo "nU ai nici o subcategorie lista adaugata";
//~ exit();
//~ }
// INSTEAD
if( $stmt2->num_rows > 0 ){
$stmt2->bind_result($idLista, $categorieLista, $parentLista);
while ($stmt2->fetch()){
$output .= '
<li>
'.$categorieLista.'
</li>';
} //close while subcategorie
}
$output .= '
</ul>
</li>';
} // close while categorie first select
}
echo $output, "\n\n\n";
echo 'Duration: ', microtime( true ) - $start, "\n\n\n";
//-----------------------------------------------------------
// I LIKE IT THIS WAY... ONLY FOR 1 SUBLEVEL THO
// for unlimited dept sub categories you need different approce
$start = microtime( true );
unset( $output );
$output = '';
$menu = array();
$sel = $con->prepare( '
SELECT
id, category, parent
FROM category
ORDER BY parent, category ASC
' );
$sel->execute();
$sel->bind_result( $id, $category, $parent );
while( $sel->fetch() ){
if( ! $parent ){ // same as $parent == 0
$menu[ $id ] = array(
'name' => $category,
'sub' => array(),
);
} else {
$menu[ $parent ]['sub'][ $id ] = array(
'name' => $category,
);
}
}
$sel->close();
foreach( $menu as $id => $item ){
$output .= '
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
' . $item['name'] . ' <span class="caret"></span>
</a>
<ul class="dropdown-menu">';
foreach( $item['sub'] as $subid => $subitem ){
$output .= '
<li>
' . $subitem['name'] . '
</li>';
}
$output .= '
</ul>
</li>';
}
//~ echo '<pre>', var_dump( $menu ), '</pre>';
echo $output, "\n\n\n";
echo 'Duration: ', microtime( true ) - $start, "\n\n\n";
Sample output:
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
fruits <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
apple
</li>
<li>
orange
</li>
<li>
banana
</li>
<li>
Pen Pineapple Apple Pen
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
cars <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
moto
</li>
<li>
auto
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
clothes <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
skirt
</li>
</ul>
</li>
Duration: 0.0020129680633545
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
cars <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
auto
</li>
<li>
moto
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
clothes <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
skirt
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
fruits <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
apple
</li>
<li>
banana
</li>
<li>
orange
</li>
<li>
Pen Pineapple Apple Pen
</li>
</ul>
</li>
Duration: 0.00058293342590332
Database structure and data
CREATE TABLE `category` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`category` varchar(255) NOT NULL,
`parent` int(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `parent` (`parent`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
INSERT INTO `category` (`id`, `category`, `parent`) VALUES
(1, 'fruits', 0),
(2, 'cars', 0),
(3, 'clothes', 0),
(4, 'moto', 2),
(5, 'auto', 2),
(6, 'apple', 1),
(7, 'orange', 1),
(8, 'banana', 1),
(9, 'skirt', 3),
(10, 'Pen Pineapple Apple Pen', 1);

PHP foreach categories and subcategories

I am working with my project that has a capabality in making categories and subcategories. What I've made in my database is that the subcategory column has a relationship to category_key column
Meaning, all records that doesn't have a value in subcategory column is considered as category, the rest are subcategory.
In the picture below:
b is a subcategory of a
e, g, h is a subcategory of d
And now I am using this code to diplay it in my page:
<?php
$query = "SELECT * FROM `categories`";
$result = mysql_query($query);
while($row = mysql_fetch_array($result)){
if(empty($row['subcategory'])){
echo '
<li class="dd-item dd3-item" id="'.$row['category_key'].'" data-name="'.$row['category_name'].'" data-key="'.$row['category_key'].'">
<div class="dd-handle dd3-handle">
<i class="fa fa-bars" aria-hidden="true"></i>
</div>
<div class="dd-edit other-handle edit">
<i class="fa fa-pencil" aria-hidden="true"></i>
</div>
<div class="dd-edit other-handle delete">
<i class="fa fa-trash" aria-hidden="true"></i>
</div>
<div class="dd3-content">'.$row['category_name'].'</div>
</li>
';
}else{
echo '
<ol class="dd-list">
<li class="dd-item dd3-item" id="'.$row['category_key'].'" data-name="'.$row['category_name'].'" data-key="'.$row['category_key'].'">
<div class="dd-handle dd3-handle">
<i class="fa fa-bars" aria-hidden="true"></i>
</div>
<div class="dd-edit other-handle edit">
<i class="fa fa-pencil" aria-hidden="true"></i>
</div>
<div class="dd-edit other-handle delete">
<i class="fa fa-trash" aria-hidden="true"></i>
</div>
<div class="dd3-content">'.$row['category_name'].'</div>
</li>
</ol>
';
}
}
?>
The problem is <ol> element is repeating as it must be only one and also it must be inside the parent <li> of it's category.
Any help?
You need to structure your result array first:
function appendChildren($input)
{
$output = [];
foreach($input as $value)
{
$key = $value["category_key"];
if(empty($input["subcategory"]))
{
$output[$key] = [];
$output[$key]["value"] = $value;
$output[$key]["children"] = [];
} else {
$parent_key = $value["subcategory"];
$output[$parent_key]["children"][] = $value;
}
}
return $output;
}
then you'll have an array like this:
|-- b
|-- a
|
|-- c
|-- d
|-- e
|-- g
|-- h
|
|-- f
Now, begins the formatting:
$input = [];
$query = "SELECT * FROM `categories`";
$result = mysql_query($query);
while($row = mysql_fetch_array($result)){
$input[] = $row;
}
$result = appendChildren($input);
foreach($result as $row)
{
// open <li> tag
if(count($row["children"]) > 0)
{
foreach($children as $child)
// add ordered list
}
// close <li> tag
}

Materialize dropdown issue

I'm trying to make dynamic dropdown menu. I have database table with dropdown menus.
Table layout
position means position in menu. dropdown (0 - no dropdown, 1 - dropdown "header" with that arrow, 2 - dropdown item). dropdown-parent means id of dropdown header.
Problem is that I can't figure out how to implement it.
$query = $db->query("SELECT * FROM `menu` WHERE
`dropdown` = 0 OR `dropdown` = 1 ORDER BY `position` ASC");
foreach($query as $row) {
if($row['dropdown'] == 0) {
echo '<li>'.$row['name'].'</li>';
}
elseif($row['dropdown'] == 1) {
echo '<li><a class="dropdown-button" href="#" data-activates="dropdown'.$row['position'].'" data-beloworigin="true" data-hover="true">'.$row['name'].'<i class="material-icons right">arrow_drop_down</i></a></li>';
}
}
This code works, but for dropdown items I need to edit this
<ul id="dropdown2" class="dropdown-content">
<li>Test #1</li>
</ul>
Do you have any idea how to implement it?
EDIT
This is what I want
<ul id="dropdown1" class="dropdown-content">
<li>Test item #1 for dropdown1</li>
<li>Test item #2 for dropdown1</li>
<li>Test item #x for dropdown1</li>
</ul>
<ul id="dropdown2" class="dropdown-content">
<li>Test item #1 for dropdown2</li>
<li>Test item #2 for dropdown2</li>
</ul>
<ul id="dropdownx" class="dropdown-content">
<li>Test item for dropdownx</li>
</ul>
<ul class="right hide-on-med-and-down">
<li>Header without dropdown</li>
<li><a class="dropdown-button" href="#" data-activates="dropdown1">Header with dropdown1 items</a></li>
<li><a class="dropdown-button" href="#" data-activates="dropdown2">Header with dropdown2 items</a></li>
<li><a class="dropdown-button" href="#" data-activates="dropdownx">Header with dropdownx items</a></li>
</ul>
I tried this
$query = $db->query("SELECT * FROM `menu` WHERE `dropdown` = 2 ORDER BY position ASC");
foreach($query as $row) {
echo '<ul id="dropdown'.$row['dropdown-parent'].'" class="dropdown-content">
<li>'.$row['name'].'</li>
</ul>';
}
but I'll get
<ul id="dropdown1" class="dropdown-content">
<li>Test item #1 for dropdown1</li>
</ul>
<ul id="dropdown1" class="dropdown-content">
<li>Test item #2 for dropdown1</li>
</ul>
<ul id="dropdown1" class="dropdown-content">
<li>Test item #x for dropdown1</li>
</ul>
<ul id="dropdown2" class="dropdown-content">
<li>Test item #1 for dropdown2</li>
</ul>
<ul id="dropdown2" class="dropdown-content">
<li>Test item #2 for dropdown2</li>
</ul>
<ul id="dropdownx" class="dropdown-content">
<li>Test item #1 for dropdownx</li>
</ul>
There's problem in that foreach, but I don't know hot to fix it.
You need to make the <ul> based on some logic. There are three steps. You are on the first one, you need to end one and create another, you are on the last one. Below is an untested attempt. Should get you close. Also a note I changed $query to $rows for clarity.
$rows = $db->query("SELECT * FROM `menu` WHERE `dropdown` = 2 ORDER BY position ASC");
$start = true; // Just to know we are starting
foreach($rows as $row) {
if($start){ // Need to only create an open ul tag
echo '<ul id="dropdown'.$row['dropdown-parent'].'" class="dropdown-content">';
$start = false; // Never do this again.
}
if(isset($lastRowParent) && $lastRowParent != $row['dropdown-parent']){
echo '</ul>'; //End the last dropdown
// Start the next one
echo '<ul id="dropdown'.$row['dropdown-parent'].'" class="dropdown-content">';
}
echo '<li>'.$row['name'].'</li>'; //Output the content
$lastRowParent = $row['dropdown-parent']; // Update the parent
}
echo '</ul>'; //When the loop ends the last ul will not be closed. close it.
you can use switch case in place of else if.
$query = $db->query("SELECT * FROM `menu` WHERE `dropdown` = 0 OR `dropdown` = 1 ORDER BY `position` ASC");
foreach($query as $row) {
SWITCH($row['dropdown']) {
CASE 0:
echo '<li>'.$row['name'].'</li>';
break;
CASE 1 :
echo '<li><a class="dropdown-button" href="#" data-activates="dropdown'.$row['position'].'" data-beloworigin="true" data-hover="true">'.$row['name'].'<i class="material-icons right">arrow_drop_down</i></a></li>';
break;
}
}

Categories