ORIGINAL POST
I want to create a navigation menu in PHP with Bootstrap 4. Problem is that one of the <li>
's is not right (the one from dropdown
, it doesn't become a dropdown but just a normal nav-item
). This code works alright if you want to make a normal menu with <ul>
and <li>
but with bootstrap you need to have a nav-item dropdown
on the <li>
of id 2
named Dropdown
. How would I do this?
I hope this is enough information.
This is the array():
array (size=3)
0 =>
array (size=5)
0 =>
array (size=3)
'id' => string '1' (length=1)
'menu_naam' => string 'Home' (length=4)
'parent_id' => string '0' (length=1)
1 =>
array (size=3)
'id' => string '2' (length=1)
'menu_naam' => string 'Dropdown' (length=4)
'parent_id' => string '0' (length=1)
2 =>
array (size=3)
'id' => string '3' (length=1)
'menu_naam' => string 'Winkelwagen' (length=11)
'parent_id' => string '0' (length=1)
3 =>
array (size=3)
'id' => string '4' (length=1)
'menu_naam' => string 'Contact' (length=7)
'parent_id' => string '0' (length=1)
4 =>
array (size=3)
'id' => string '5' (length=1)
'menu_naam' => string 'Feedback' (length=8)
'parent_id' => string '0' (length=1)
2 =>
array (size=1)
0 =>
array (size=3)
'id' => string '6' (length=1)
'menu_naam' => string 'Sub Menu' (length=8)
'parent_id' => string '2' (length=1)
6 =>
array (size=1)
0 =>
array (size=3)
'id' => string '7' (length=1)
'menu_naam' => string 'Sub Sub Menu' (length=12)
'parent_id' => string '6' (length=1)
This is the PHP I use to build the menu:
<?php
function menu_builder() {
global $pdo;
$sql = $pdo->prepare("SELECT * FROM menus");
if ($sql->execute()) {
while ($row = $sql->fetch(PDO::FETCH_ASSOC)) {
$array[$row['parent_id']][] = $row;
}
loop_array($array);
}
}
function loop_array($array = array(), $parent_id = 0) {
if (!empty($array[$parent_id])) {
echo "<ul class=\"navbar-nav mr-auto\">";
foreach ($array[$parent_id] as $item) {
echo "<li class=\"nav-item\">";
echo "<a href=\"#\" class=\"nav-link\">" . $item['menu_naam'] . "</a>";
loop_array2($array, $item['id']);
echo "</li>";
}
echo "</ul>";
}
}
function loop_array2($array = array(), $parent_id = 0) {
if (!empty($array[$parent_id])) {
echo "<li class=\"nav-item dropdown\">";
foreach ($array[$parent_id] as $item) {
echo "<a href=\"#\" class=\"nav-link dropdown-toggle\" id=\"navbarDropdown\" role=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">" . $item['menu_naam'] . "</a>";
loop_array3($array, $item['id']);
}
echo "</li>";
}
}
function loop_array3($array = array(), $parent_id = 0) {
if (!empty($array[$parent_id])) {
echo "<div class=\"dropdown-menu\" aria-labelledby=\"navbarDropdown\">";
foreach ($array[$parent_id] as $item) {
echo "<a class=\"dropdown-item\" href=\"#\">" . $item['menu_naam'] . "</a>";
}
echo "</div>";
}
}
I really hope someone can help me with this, should I add something to the database so it will know it's a dropdown? I think my code is too big and to complicated, there should be a easyer way but I don't know how. I think I need a whole other approach. If you could only help me in the right direction it would be fine too.
Also some credit to the guy who made the sub menu tutorial (here you can also see how the menu is build with <ul>
and <li>
and does exactly what it needs to do, but not for bootstrap
): https://www.youtube.com/watch?v=Ol63V4R-TdI
EDIT:
I found a kind of a solution over here: Dynamic menu php bootstrap mysql
What I have now is:
function drawMenu($pdo, $parent_id, $level = null) {
$sql = $pdo->prepare("SELECT * FROM menus where parent_id = $parent_id");
$sql->execute();
foreach ($sql->fetchAll() as $row) {
$sql = $pdo->prepare("SELECT count(*) FROM menus where parent_id = " . $row['id'] . "");
$sql->execute();
// The item is parent, so do recursion again
//var_dump($sql->fetchAll()[0][0]);
if($sql->fetchAll()[0][0] !== '0' && $level !== 0){
echo "<li class=\"nav-item dropdown\"><a href=\"" . $row['url'] . "\" class=\"nav-link dropdown-toggle\" id=\"navbarDropdownMenuLink\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">" . $row['menu_naam'] . "</a><div class=\"dropdown-menu\" aria-labelledby=\"navbarDropdownMenuLink\">\n";
drawMenu($pdo, $row[0], $level - 1);
echo "</div></li>\n";
}
else { // The item is a leaf or we reach the end level, i.e. base case, so do print the item label
echo "<li class=\"nav-item\"><a href=\"#\" class=\"nav-link\">" . $row['menu_naam'] . "</a></li>\n";
}
}
}
?>
<header class="navbar navbar-dark bg-dark fixed-top navbar-expand-sm">
<a class="navbar-brand" href="#">Webshop</a>
<button class="navbar-toggler" style="background: #000000" type="button" data-toggle="collapse" data-target="#navbar-header" aria-controls="navbar-header">
☰
</button>
<div class="navbar-collapse collapse show" id="navbar-header">
<ul class="navbar-nav mr-auto">
<?php
drawMenu($pdo, 0, null);
?>
</ul>
</div>
</header>
But the problem now is that it prints multiple
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
The HTML looks like this now:
<div class="navbar-collapse collapse show" id="navbar-header">
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a href="#" class="nav-link"><span class="fas fa-home"></span> Home</a></li>
<li class="nav-item dropdown"><a href="#" class="nav-link dropdown-toggle" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a><div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<li class="nav-item dropdown"><a href="#" class="nav-link dropdown-toggle" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Sub Menu</a><div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<li class="nav-item"><a href="#" class="nav-link">Sub-sub Menu</a></li>
</div></li>
</div></li>
<li class="nav-item"><a href="#" class="nav-link"><span class="fas fa-shopping-cart"> </span> Winkelwagen</a></li>
<li class="nav-item"><a href="#" class="nav-link">Contact</a></li>
<li class="nav-item"><a href="#" class="nav-link">Feedback</a></li>
</ul>
</div>
I added menu
to the database and check if it's 0 or 1. I have dropped the sub-sub menu's, but I will update this post if I add them.
function menu_builder($pdo, $parent_id) {
$sql = $pdo->prepare("SELECT * FROM menus");
if ($sql->execute()) {
while ($row = $sql->fetch(PDO::FETCH_ASSOC)) {
$array[$row['parent_id']][] = $row;
}
main_menu($array);
}
}
function main_menu ($array, $parent_id = 0) {
if (!empty($array[$parent_id])) {
foreach ($array[$parent_id] as $item) {
if ($item['menu'] == '0') {
echo " <li class=\"nav-item\">" . PHP_EOL;
echo " <a class=\"nav-link\" href=\"#\">" . $item['menu_naam'] . "</a>" . PHP_EOL;
main_menu($array, $item['id']);
echo " </li>" . PHP_EOL;
}
elseif ($item['menu'] == '1') {
echo " <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\">" . $item['menu_naam'] . "</a>" . PHP_EOL;
sub_menu($array, $item['id']);
echo " </li>" . PHP_EOL;
}
}
//echo "</div>" . PHP_EOL;
echo "</li>" . PHP_EOL;
}
}
function sub_menu ($array, $parent_id) {
if (!empty($array[$parent_id])) {
echo " <div class=\"dropdown-menu\" aria-labelledby=\"navbarDropdown\">" . PHP_EOL;
foreach ($array[$parent_id] as $item) {
echo " <a class=\"dropdown-item\" href=\"" . $item['url'] . "\">" . $item['menu_naam'] . "</a>" . PHP_EOL;
}
echo " </div>" . PHP_EOL;
}
}
?>
<header class="navbar navbar-dark bg-dark fixed-top navbar-expand-sm">
<a class="navbar-brand" href="#">Webshop</a>
<button class="navbar-toggler" style="background: #000000" type="button" data-toggle="collapse" data-target="#navbar-header" aria-controls="navbar-header">
☰
</button>
<div class="navbar-collapse collapse show" id="navbar-header">
<?php
echo "<ul class=\"navbar-nav mr-auto\">";
echo menu_builder($pdo, 0);
echo "</ul>" . PHP_EOL;
?>
</div>
</header>
EDIT: To have sub-menu's the code looks like this and you need the following css
too.
function menu_builder($pdo, $parent_id) {
$sql = $pdo->prepare("SELECT * FROM menus");
if ($sql->execute()) {
while ($row = $sql->fetch(PDO::FETCH_ASSOC)) {
$array[$row['parent_id']][] = $row;
}
main_menu($array);
}
}
function main_menu ($array, $parent_id = 0) {
if (!empty($array[$parent_id])) {
foreach ($array[$parent_id] as $item) {
if ($item['menu'] == '0') {
echo " <li class=\"nav-item\">" . PHP_EOL;
echo " <a class=\"nav-link\" href=\"" . $item['url'] . "\">" . $item['menu_naam'] . "</a>" . PHP_EOL;
main_menu($array, $item['id']);
echo " </li>" . PHP_EOL;
}
elseif ($item['menu'] == '1') {
echo " <li class=\"nav-item dropdown\">". PHP_EOL;
echo " <a class=\"nav-link dropdown-toggle\" href=\"" . $item['url'] . "\" id=\"navbarDropdown\" role=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">" . $item['menu_naam'] . "</a>" . PHP_EOL;
sub_menu($array, $item['id']);
echo " </li>" . PHP_EOL;
}
}
}
}
function sub_menu ($array, $parent_id) {
if (!empty($array[$parent_id])) {
echo " <div class=\"dropdown-menu\" aria-labelledby=\"navbarDropdown\">" . PHP_EOL;
foreach ($array[$parent_id] as $item) {
if ($item['sub_menu'] == '0') {
echo " <a class=\"dropdown-item\" href=\"" . $item['url'] . "\">" . $item['menu_naam'] . "</a>" . PHP_EOL;
}
elseif ($item['sub_menu'] == '1') {
echo " <div class=\"dropdown-submenu\">" . PHP_EOL;
echo " <a class=\"dropdown-item dropdown-toggle\" href=\"" . $item['url'] . "\">" . $item['menu_naam'] . "</a>" . PHP_EOL;
sub_sub_menu($array, $item['id']);
echo " </div>" . PHP_EOL;
}
}
echo " </div>" . PHP_EOL;
}
}
function sub_sub_menu ($array, $parent_id) {
if (!empty($array[$parent_id])) {
echo " <div class=\"dropdown-menu\" aria-labelledby=\"navbarDropdown\">" . PHP_EOL;
foreach ($array[$parent_id] as $item) {
echo " <a class=\"dropdown-item\" href=\"" . $item['url'] . "\">" . $item['menu_naam'] . "</a>" . PHP_EOL;
}
}
echo " </div>" . PHP_EOL;
}
The CSS you need for the sub-menu's because bootstrap doesn't have support for it by default (https://stackoverflow.com/a/45755948/2877035):
.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;
}
and the 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;
});
I have maked some changes for bootstrap 4.1
<style type="text/css">
.navbar .dropdown-toggle, .navbar .dropdown-menu a {
cursor: pointer;
}
.navbar .dropdown-item.active, .navbar .dropdown-item:active {
color: inherit;
text-decoration: none;
background-color: inherit;
}
.navbar .dropdown-item:focus, .navbar .dropdown-item:hover {
color: #16181b;
text-decoration: none;
background-color: #f8f9fa;
}
@media (min-width: 767px) {
.navbar .dropdown-toggle:not(.nav-link)::after {
display: inline-block;
width: 0;
height: 0;
margin-left: .5em;
vertical-align: 0;
border-bottom: .3em solid transparent;
border-top: .3em solid transparent;
border-left: .3em solid;
}
}
</style>
<script type="text/javascript">
$(document).ready(function () {
$('.navbar .dropdown-item').on('click', function (e) {
var $el = $(this).children('.dropdown-toggle');
var $parent = $el.offsetParent(".dropdown-menu");
$(this).parent("li").toggleClass('open');
if (!$parent.parent().hasClass('navbar-nav')) {
if ($parent.hasClass('show')) {
$parent.removeClass('show');
$el.next().removeClass('show');
$el.next().css({"top": -999, "left": -999});
} else {
$parent.parent().find('.show').removeClass('show');
$parent.addClass('show');
$el.next().addClass('show');
$el.next().css({"top": $el[0].offsetTop, "left": $parent.outerWidth() - 4});
}
e.preventDefault();
e.stopPropagation();
}
});
$('.navbar .dropdown').on('hidden.bs.dropdown', function () {
$(this).find('li.dropdown').removeClass('show open');
$(this).find('ul.dropdown-menu').removeClass('show open');
});
});
</script>
<?php function menu_builder1($db, $parent_id) {
$sql = $db->prepare("SELECT * FROM menu WHERE status = 1 ORDER BY position ASC");
if($sql->execute()) {
while ($row = $sql->fetch(PDO::FETCH_ASSOC)) {
$array[$row['menu_sub_id']][] = $row;
}
main_menu1($array);
}
}
function main_menu1($array, $parent_id = false) {
if(!empty($array[$parent_id])) {
foreach ($array[$parent_id] as $item) {
if ($item['dropdown'] == false) {
echo '<li class="nav-item active"><a class="nav-link" href="' . $item['href'] . '">' . $item['name'] . '</a></li>';
}
elseif ($item['dropdown'] == true) {
echo '<li class="nav-item dropdown"><a class="nav-link dropdown-toggle" id="dropdown2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' . $item['name'] . '</a>';
sub_menu1($array, $item['menu_id']);
echo '</li>';
}
}
}
}
function sub_menu1($array = array(), $parent_id = false) {
if(!empty($array[$parent_id])) {
echo '<ul class="dropdown-menu" aria-labelledby="dropdown2">';
foreach ($array[$parent_id] as $item) {
if ($item['dropdown'] == false) {
echo '<li class="dropdown-item"><a href="' . $item['href'] . '">' . $item['name'] . '</a></li>';
}
elseif ($item['dropdown'] == true) {
echo '<li class="dropdown-item dropdown"><a class="dropdown-toggle" id="dropdown2-1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' . $item['name'] . '</a>';
sub_sub_menu1($array, $item['menu_id']);
echo '</li>';
}
}
echo "</ul>";
}
}
function sub_sub_menu1($array = array(), $parent_id = false) {
if(!empty($array[$parent_id])) {
echo '<ul class="dropdown-menu" aria-labelledby="dropdown2-1">';
foreach ($array[$parent_id] as $item) {
if ($item['dropdown'] == false) {
echo '<li class="dropdown-item"><a href="' . $item['href'] . '">' . $item['name'] . '</a></li>';
}
}
echo "</ul>";
}
}
?>
<div class="navbar navbar-expand-md navbar-dark bg-dark mb-4" role="navigation">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<?=menu_builder1($db, 0)?>
</ul>
</div>
</div>
also some SQL info
CREATE TABLE `menu` (
`menu_id` int(11) NOT NULL,
`menu_sub_id` int(11) NOT NULL,
`added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` int(1) NOT NULL DEFAULT '1',
`href` varchar(150) NOT NULL,
`class` varchar(150) NOT NULL,
`position` int(3) NOT NULL,
`name` varchar(150) NOT NULL,
`description` varchar(500) NOT NULL,
`dropdown` int(11) NOT NULL,
`sub_menu` int(1) NOT NULL,
`sub_sub_menu` int(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `menu`
ADD PRIMARY KEY (`menu_id`);
ALTER TABLE `menu`
MODIFY `menu_id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;