I have build a tree menu in PHP, using a recursive function, wich works ok.
My menu structure is something like this:
Root
----Categ1
-------Categ11
---------Categ111
---------Categ112
-------Categ12
---------Categ121
---------Categ122
----Categ2
----Categ3
----Categ4
I am using bootstrap.
I need to show only the active nodes and the top level childrens. For my example I need the menu to be opened like this (I clicked on Categ 112 and subcategories of Categ12 to be hidden):
Root
----Categ1
-------Categ11
---------Categ111
---------Categ112
-------Categ12
----Categ2
----Categ3
----Categ4
The php function that generate my menu tree is:
/** show all subcategs of the selected category
* #param $iCategIDSelected
* #param null $arrCategs
* #param bool $bIsOnTheLeaf
* #return string
*/
static function getHTMLCategsForMenuBySelected($iCategIDSelected, $arrCategs=null, $arrAllParents=null){
$bIsVisible = false;
if($arrCategs == null){
$oController = new ShopcategoriesController();
$arrCategs = $oController->getShopCategories(0);
}
//find if we make the current node visible or not
//if the current node id is on the parent's of the selected node then make it visible
//take all nodes starting with root until the selected node
if($arrAllParents==null){
$arrAllParents = ShopcategoriesController::getParentsBySelectedCategID($iCategIDSelected,$arrCategs);
}
$sHTML = '<ul class="nav nav-pills nav-stacked">';
foreach($arrCategs as $oneCateg){
//find if is active or not
$bActiveClass = '';
if(in_array($oneCateg['id'],$arrAllParents)){$bActiveClass = 'active';}
$sHTML .= '<li class="'.$bActiveClass.'">';
$sHTML .= ''.ucfirst($oneCateg['name']).'';
if(count($oneCateg['subCategories'])>0){
$sHTML .= ShopcategoriesController::getHTMLCategsForMenuBySelected($iCategIDSelected, $oneCateg['subCategories'],$arrAllParents);
$sHTML .= '</li>';
}else{
$sHTML .= '';
}
}
$sHTML .= '</ul>';
return $sHTML;
}
The html structure is:
ul.nav > li > a{
padding-left: 30px;
}
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<div id="menuCateg">
<ul class="nav nav-pills nav-stacked">
<li class="active">
<a class="list-group-item" href="http://localhost:8000/showcategory/1">Categ1</a>
<ul class="nav nav-pills nav-stacked">
<li class="active">
<a class="list-group-item" href="http://localhost:8000/showcategory/2">Categ11 </a>
<ul class="nav nav-pills nav-stacked">
<li class=""><a class="list-group-item" href="http://localhost:8000/showcategory/3">Categ111</a></li>
<li class="active"><a class="list-group-item" href="http://localhost:8000/showcategory/4">Categ112</a></li>
</ul>
</li>
<li class=""><a class="list-group-item" href="http://localhost:8000/showcategory/18">Categ12</a></li>
</ul>
</li>
<li class="">
<a class="list-group-item" href="http://localhost:8000/showcategory/6">Categ2</a>
<ul class="nav nav-pills nav-stacked">
<li class=""><a class="list-group-item" href="http://localhost:8000/showcategory/3">Categ121</a></li>
<li class=""><a class="list-group-item" href="http://localhost:8000/showcategory/4">Categ122</a></li>
</ul>
</li>
<li class=""><a class="list-group-item" href="http://localhost:8000/showcategory/8">Categ3</a>
<li class=""><a class="list-group-item" href="http://localhost:8000/showcategory/8">Categ4</a>
</ul>
</div>
I found the solution in order to generate a menu tree with multilevel subcategories and show only the levels that have been selected (without all children from the tree).
THE TRICK were those lines:
if( //if the current subcateg parentid is on the parent that must be active then show his children's otherwise do not
( in_array($oneCateg['parentid'],$arrAllParents) && in_array($oneCateg['id'],$arrAllParents))
)
Thank you for your help!
/** show all subcategs of the selected category
* #param $iCategIDSelected
* #param null $arrCategs
* #param bool $bIsOnTheLeaf
* #return string
*/
static function getHTMLCategsForMenuBySelected($iCategIDSelected, $arrCategs=null, $arrAllParents=null, $iLevel=0){
$bIsVisible = false;
if($arrCategs == null){
$oController = new ShopcategoriesController();
$arrCategs = $oController->getShopCategories(0);
}
//find if we make the current node visible or not
//if the current node id is on the parent's of the selected node then make it visible
//take all nodes starting with root until the selected node
if($arrAllParents==null){
$arrAllParents = ShopcategoriesController::getParentsBySelectedCategID($iCategIDSelected,$arrCategs);
$arrAllParents[]=0;
}
$iLevel++;
$sHTML = '<ul class="nav nav-pills nav-stacked level_'.$iLevel.'">';
foreach($arrCategs as $oneCateg){
//find if is active or not
$bActiveClass = '';
if(in_array($oneCateg['id'],$arrAllParents)){$bActiveClass = 'active';}
$sHTML .= '<li class="'.$bActiveClass.'">';
$sHTML .= ''.ucfirst($oneCateg['name']).'';
if(count($oneCateg['subCategories'])>0){
if( //if the current subcateg parentid is on the parent that must be active then show his children's otherwise do not
( in_array($oneCateg['parentid'],$arrAllParents) && in_array($oneCateg['id'],$arrAllParents))
) {
$sHTML .= ShopcategoriesController::getHTMLCategsForMenuBySelected($iCategIDSelected, $oneCateg['subCategories'], $arrAllParents, $iLevel);
$sHTML .= '</li>';
}
}else{
$sHTML .= '';
}
}
$sHTML .= '</ul>';
return $sHTML;
}
you should add a class when it's not active like .not-active.
.not-active{
display: none;
}
EDIT
I'm not sure to have understood correctly your point but how about this way:
foreach($arrCategs as $oneCateg){
//find if is active or not
$bActiveClass = '';
if(in_array($oneCateg['id'],$arrAllParents)){ //modified
$bActiveClass = 'active';
}else{
$bActiveClass = 'not-active';
}
$sHTML .= '<li class="'.$bActiveClass.'">';
$sHTML .= ''.ucfirst($oneCateg['name']).'';
if(count($oneCateg['subCategories'])>0){
$sHTML .= ShopcategoriesController::getHTMLCategsForMenuBySelected($iCategIDSelected, $oneCateg['subCategories'],$arrAllParents);
$sHTML .= '</li>';
}else{
$sHTML .= '';
}
}
Related
I have a menu in which, on hover, the main submenu appears in full screen, in which the rest of the submenus are shown. I need to add a wrapper for the first level submenu so that there is a container around the ul. I have code but it adds wrapper for all sub-menu and i need to wrap only first level submenu.
class My_Custom_Walker extends Walker_Nav_Menu
{
function start_lvl(&$output, $depth = 0, $args = array())
{
$indent = str_repeat("\t", $depth);
$output .= "\n$indent<div class='sub-menu__wrapper'><ul class='sub-menu'>\n";
}
function end_lvl(&$output, $depth = 0, $args = array())
{
$indent = str_repeat("\t", $depth);
$output .= "$indent</ul></div>\n";
}
}
//Example
<ul>
<li>...</li>
<li>
<div class="sub-menu__wrapper">
<ul class="sub-menu">
<li>...</li>
<li>
<ul class="sub-menu"></ul>
</li>
</ul>
</div>
</li>
<li>...</li>
</ul>
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?
I have tried pretty much every solution that was available online but cannot get the class="active" to change dynamically for each menu section.
When someone clicks the top level menu item it opens up the second level menu and if someone click the on a second level menu item in that section class="active" needs to remain on the top level menu li tag.
I have the following function that generates my bootstrap menu:
function getMenu() {
global $connection;
mysqli_select_db($connection, "c9");
$query = ("SELECT testName, testId FROM testType");
$result_set = mysqli_query($connection, $query);
while ($row = mysqli_fetch_array($result_set)) {
$testId = $row['testId'];
$testName = $row['testName'];
echo '<li>'; //This is where class="active" needs to be added
echo '<span class="nav-label">'.$testName.'</span>';
echo '<ul class="nav nav-second-level collapse">';
echo '<li>Summary Report</li>';
echo '<li>Add Data</li>';
echo '</ul>';
echo '</li>';
}
}
And the menu structure is as follows:
<ul class="nav metismenu" id="side-menu">
<li class="active">
<a href="/">
<span class="nav-label">Summary</span>
<ul class="nav nav-second-level collapse in">
<li>Reports</li>
</ul>
</li>
<li><a href="#">
<span class="nav-label">Temperature</span>
<ul class="nav nav-second-level collapse">
<li>Summary Report</li>
<li>Add Data</li>
</ul>
I created a custom menu called "sub-top-nav" and now I'd like to override the html output. In particular I would like to add an unique class to each item like.
This is how it looks atm:
<div class="clear-block block block-menu" id="block-menu-menu-sub-top-nav">
<div class="content">
<ul class="menu">
<li class="leaf first"><a title="Test 1" href="/test1">Test 1</a></li>
<li class="leaf"><a title="Test 2" href="/test2">Test 2</a></li>
<li class="leaf active-trail"><a class="active" title="Test 3" href="/test3">Test 3</a></li>
<li class="leaf last"><a title="Test 4" href="/test4">Test 4</a></li>
</ul>
</div>
</div>
And I'd like to change it into:
<div class="clear-block block block-menu" id="block-menu-menu-sub-top-nav">
<div class="content">
<ul class="menu">
<li class="leaf test1 first"><a title="Test 1" href="/test1">Test 1</a></li>
<li class="leaf test2"><a title="Test 2" href="/test2">Test 2</a></li>
<li class="leaf test3 active-trail"><a class="active" title="Test 3" href="/test3">Test 3</a></li>
<li class="leaf test4 last"><a title="Test 4" href="/test4">Test 4</a></li>
</ul>
</div>
</div>
This would give me more styling power.
Any idea how that works?
Thanks in advance!
Drupal 7 uses theme_menu_link instead of theme_menu_item
<?php
function theme_menu_link(array $variables) {
$element = $variables['element'];
$sub_menu = '';
if ($element['#below']) {
$sub_menu = drupal_render($element['#below']);
}
$output = l($element['#title'], $element['#href'], $element['#localized_options']);
return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
}
?>
I got it to work now. This piece of code might help someone else as well!
It goes into yourtheme/template.php
function phptemplate_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) {
$class = ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf'));
if (!empty($extra_class))
$class .= ' '. $extra_class;
if ($in_active_trail)
$class .= ' active-trail';
$class .= ' ' . preg_replace('/[^a-zA-Z0-9]/', '', strtolower(strip_tags($link)));
return '<li class="'. $class .'">'. $link . $menu ."</li>\n";
}
After looking through the API I finally found an easy solution to tag the root menu with the same class (this is useful to style only the top level menus uniquely, while maintaining them dynamically friendly). Simply use the plid instead of mlid. I noticed the plid is always 0 for top level menus.
function theme_menu_link(array $variables) {
$element = $variables['element'];
$sub_menu = '';
$element['#attributes']['class'][] = 'menu-' . $element['#original_link']['plid'];
if ($element['#below']) {
$sub_menu = drupal_render($element['#below']);
}
$output = l($element['#title'], $element['#href'], $element['#localized_options']);
$count = 1;
return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
}
You can use the theme_menu_item function in your theme's template.php to do pretty much whatever you want to those menu items, including adding classes, ID's, etc.
I have a problem with closing my li's and ul's at the correct moment.
With the code we have we retrieve all childeren of a specific categorie in the magento shop.
Now the problem is that i want to divide all children in lists. So we can have them sorted by category -> sub-category -> sub-sub category. I want my structure to be;
<ul>
<li>
<a>Head Child</a>
<ul>
<li>
<a>Sub child</a>
<ul>
<li><a>Sub sub child</a></li>
<li><a>Sub sub child</a></li>
</ul>
</li>
</ul>
</li>
<li>
<a>Head Child</a>
<ul>
<li>
<a>Sub child</a>
<ul>
<li><a>Sub sub child</a></li>
<li><a>Sub sub child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
The output im getting now is
<ul>
<a title="View the products for the " href="#">Head child</a>
<li class="sub_cat">
<a title="View the products for the " href="#">Sub child</a>
</li>
<li class="sub_cat">
<a title="View the products for the " href="#">Sub sub child</a>
</li>
</ul>
This is our php code;
<?php
$cat = Mage::getModel('catalog/category')->load(9);
$subcats = $cat->getChildren();
foreach(explode(',',$subcats) as $subCatid)
{
$_category = Mage::getModel('catalog/category')->load($subCatid);
if($_category->getIsActive()) {
echo '<ul>'.$_category->getName().'';
$sub_cat = Mage::getModel('catalog/category')->load($_category->getId());
$sub_subcats = $sub_cat->getChildren();
foreach(explode(',',$sub_subcats) as $sub_subCatid)
{
$_sub_category = Mage::getModel('catalog/category')->load($sub_subCatid);
if($_sub_category->getIsActive()) {
echo '<li class="sub_cat">'.$_sub_category->getName().'';
$sub_sub_cat = Mage::getModel('catalog/category')->load($sub_subCatid);
$sub_sub_subcats = $sub_sub_cat->getChildren();
foreach(explode(',',$sub_sub_subcats) as $sub_sub_subCatid)
{
$_sub_sub_category = Mage::getModel('catalog/category')->load($sub_sub_subCatid);
if($_sub_sub_category->getIsActive()) {
echo '<li class="sub_cat">'.$_sub_sub_category->getName().'';
}
}
}
}
echo '</ul>';
}
}
?>
You should consider using recursive function here
$cat = Mage::getModel('catalog/category')->load(9);
$html = '';
$html = getSubCategoriesHTML($cat, $html);
function getSubCategoriesHTML($cat, $html) {
$html .= ''.$cat->getName().'';
$html .= '<ul>';
$subcats = $cat->getChildren();
foreach(explode(',',$subcats) as $subCatid) {
$_category = Mage::getModel('catalog/category')->load($subCatid);
if($_category->getIsActive()) {
$html .= '<li>';
$html .= getSubCategoriesHTML($_category, $html);
$html .= '</li>;
}
}
$html .= '</ul>';
return $html;
}
You can add this function in a helper or in your category model.
I haven't tested it at all so it may not work correctly. But I hope it can help you.