Jump to content

Menu Builder


kongondo

Recommended Posts

I don't know if MSN can use a WireArray to build menus. It certainly can take a PageArray and build a menu out of that. The key here is that MSN relies on true/natural PW child-parent relationships (unless something has changed that I don't know about) to create menus. So, $page->children will make sense to it. You won't get $page->children in MB...at least not in that original sense.

My bad. It is PageArray, not WireArray.

Anyway, could you consider the scenario of somehow making it possible for MenuBuilder and MarkupSimpleNavigation to work together? Maybe providing PageArray output with some dynamically-made-up pages for external links?

I really like the idea of separating concerns. It seems to me very handy to have one module to build menu markup from anywhere (MarkupSimpleNavigation) and one module to be able to generate contents for it from custom user input (MenuBuilder). So you do not have to learn two ways of doing the same thing on one site.

What do you think? Could this be possible?

Link to comment
Share on other sites

OK...sigh...because you asked nicely...
 
@note: 

  • Using this method, it makes no sense to create a menu with more than 1 level  in Menu Builder. The levels will be built using natural child-parent relationship in MSN
  • The CSS attritubes that can be set in MB have no meaning. I see no way of passing those to MSN
  • This was a one-time exercise. Currently, I am not able to support more features for this technique :-)

Step 1: Create a 1-level menu in MB
 
post-894-0-73568400-1464003482_thumb.png
 
Step 2:  Grab menu items from the menu created in MB in Step #1

$itemIDs = '';

$mb = $modules->get("MarkupMenuBuilder");
 
// options 1: get menu items as array
$menuItemsArray = $mb->getMenuItems('Ivan Gretsky', 1);
foreach($menuItemsArray as $m) $itemIDs .= $m['pages_id'] . '|'; 
$itemIDs = rtrim($itemIDs, '|');
 
        #### OR ####
 
//option 2: get menu items as object (WireArray)
$menuItemsObj = $mb->getMenuItems('Ivan Gretsky'); 
// implode the WireArray the cool way (PW 2.4+)
$itemIDs = $menuItemsObj->implode('|', 'pagesID');
 
// get a PageArray using menu item IDs
$itemsPageArray = $pages->find('id=' . $itemIDs);

 
Step 3: Output the menu using MSN

// pass the PageArray to MSN (optionally passing it MSN $options) to output your menu
$menu = $modules->get("MarkupSimpleNavigation");
echo $menu->render($options, null, $itemsPageArray);
  • Like 3
Link to comment
Share on other sites

  • 2 weeks later...

Hi,

I got a problem, which makes me scratch my head a lot.

I have a menu item with two children, which are 'hidden', but not 'unpublished'. (A newsletter opt in page with landing pages for the double-opt-in-process.)

The menu generated looks like this (just the last 3 items):

                <li>
                    <a href='/pw/de/impressum/'>Impressum</a>
                </li>
                <li>
                    <a href='/pw/de/kontakt/'>Kontakt</a>
                </li>
                <li class="current">
                    <a href='/pw/de/newsletter/'>Newsletter</a>
                    <ul class="submenu">
                    </ul>
                </li>

As one can see, there is an empty ul in the last li. This causes some trouble with some jQuery - mobile - navigation generator.

What is puzzling me is, that in a second menu, which just contains this last 3 items (a footer in the non-mobile version) this ul does not appear. I could not find any differences in the menu settings. 

FYI: runs otherwise smoohtly on pw 3.08 dev.

Link to comment
Share on other sites

Thanks for your answer.

I can indeed make the empty ul make disappear by setting

'include_children' => 0

in the options. But then, also those items, which shall have their submenu, don't have one. 

Also, i can switch on an unintended empty ul in another menu, when i set include_children to 1 there.

Link to comment
Share on other sites

My intent was not to suggest that you use include_children as a solution. What I needed is more information to reproduce this. I have been unable to replicate you issue so the more information I have the better :-)...How the menu is constructed, the options, your environment, etc...basically, the whole code if that is possible....

Edited by kongondo
Link to comment
Share on other sites

Hi, I found a possible cause for the issue when tampering with my own menu function and running into the very same problem.

In line 351 of MarkupMenuBuilder.module 

if($p->numChildren) $hasNativeChildren = 1;// has children to add

is the function numChildren used. This function counts in default mode also hidden children. 

To make it count only visible children, it has to be used with a parameter:

if($p->numChildren(true)) $hasNativeChildren = 1;// has children to add

I tried it and it seems to work! :-)

Link to comment
Share on other sites

Ok, this might just be a dumb question...But, has anyone had any success adding "<span class="caret"></span>" into a menu yet using bootstrap? My dropdown works great, save for the the fact that for the life of me I can not figure out how to get the dropdown li to have a caret.

Link to comment
Share on other sites

Hi, I found a possible cause for the issue when tampering with my own menu function and running into the very same problem.

In line 351 of MarkupMenuBuilder.module 

if($p->numChildren) $hasNativeChildren = 1;// has children to add

is the function numChildren used. This function counts in default mode also hidden children. 

To make it count only visible children, it has to be used with a parameter:

if($p->numChildren(true)) $hasNativeChildren = 1;// has children to add

I tried it and it seems to work! :-)

OK, I'll try and replicate. As for the files, seems I wasn't clear, sorry. I just needed to see how you were calling menubuilder methods + the options you were passing to it :-)

Ok, this might just be a dumb question...But, has anyone had any success adding "<span class="caret"></span>" into a menu yet using bootstrap? My dropdown works great, save for the the fact that for the life of me I can not figure out how to get the dropdown li to have a caret.

Have a look here: https://processwire.com/talk/topic/4451-menu-builder/?p=120639

Link to comment
Share on other sites

Example 1b is for buildMenuFromArray() actually...buildMenuFromObject() is for example 1a. Whichever you choose, you need to define the function first before you can call it...(i.e. the function code first, then echo it...)

Edited by kongondo
Link to comment
Share on other sites

Thank you so much (and sorry for my confusion). I thought I had it, but now I am just getting 6 errors:

Error: Exception: Method MarkupMenuBuilder::getMenuItems does not exist or is not callable in this context (in Desktop/_Staging/test/wire/core/Wire.php line 409)

#0 [internal function]: ProcessWire\Wire->___callUnknown('getMenuItems', Array)
#1 Desktop/_Staging/test/wire/core/Wire.php(347): call_user_func_array(Array, Array)
#2 Desktop/_Staging/test/wire/core/WireHooks.php(555): ProcessWire\Wire->_callMethod('___callUnknown', Array)
#3 Desktop/_Staging/test/wire/core/Wire.php(370): ProcessWire\WireHooks->runHooks(Object(MarkupMenuBuilder), 'callUnknown', Array)
#4 Desktop/_Staging/test/wire/core/Wire.php(371): ProcessWire\Wire->__call('callUnknown', Array)
#5 Desktop/_Staging/test/site/templates/navTwo.inc(11): ProcessWire\Wire->__call('getMenuItems', Array)
#6 /Desktop/_Staging/test/site/templates/head.inc(85): include('/Us

I am a bit lost on this now, though I am trying to dig through them and understand what they mean.

What I have thus far (all using example 1b):

head.inc

   <div class="collapse navbar-collapse container" id="bs-example-navbar-collapse-1">
     <?php //include('./navigation.inc');?>
     <?php include('./navTwo.inc');?>
     <?php  echo buildMenuFromArray(0, $menuItems2); ?>
   </div><!-- /.navbar-collapse -->

navTwo.inc

<?php
$mb = $modules->get('MarkupMenuBuilder');// get Menu Builder

$jsonStr = $pages->get(1033)->menu_items;
$arrayFromJSON = json_decode($jsonStr, true);
#$menu = $arrayFromJSON;// pass an array
$menu = 'main';// pass a title

/** grab menu items as WireArray with Menu objects **/
// for examples 1a, 2 and 3
$menuItems = $mb->getMenuItems($menu, 2, $options);// called with options and 2nd argument = 2 {return Menu (WireArray object)}
#$menuItems = $mb->getMenuItems($menu);// called without options; 2nd argument defaults to 2

/** grab menu items as Normal Array with Menu items **/
// only for example 1b below
$menuItems2 = $mb->getMenuItems($menu, 1);// called without options; 2nd argument is 1 so return array

/**
* Builds a nested list (menu items) of a single menu from an Array of menu items.
*
* A recursive function to display nested list of menu items.
*
* @access private
* @param Int $parent ID of menu item.
* @param Array $menu Array of menu items to display.
* @param Int $first Helper variable to designate first menu item.
* @return string $out.
*
*/
function buildMenuFromArray($parent = 0, $menu, $first = 0) {

  $out = '';
  $has_child = false;

  foreach ($menu as $id => $m) {

    $parentID = isset($m['parent_id']) ? $m['parent_id'] : 0;
    $newtab = isset($m['newtab']) && $m['newtab'] ? " target='_blank'" : '';
    // if this menu item is a parent; create the sub-items/child-menu-items
    if ($parentID == $parent) {// if this menu item is a parent; create the inner-items/child-menu-items

        // if this is the first child
        if ($has_child === false) {
            $has_child = true;// This is a parent
            if ($first == 0){
              $out .= "<ul class='main-menu cf'>\n";
              $first = 1;
            }
            else $out .= "\n<ul class='sub-menu'>\n";
        }

        $class = isset($m['is_current']) && $m['is_current'] ? ' class="current"' : '';

        // a menu item
        $out .= '<li' . $class . '><a href="' . $m['url'] . '"' . $newtab . '>' . $m['title'];
        // if menu item has children
        if (isset($m['is_parent']) && $m['is_parent']) {
          $out .= '<span class="drop-icon">▼</span>' .
              '<label title="Toggle Drop-down" class="drop-icon" for="' . wire('sanitizer')->pageName($m['title']) . '" onclick>▼</label>' .
          '</a>' .
          '<input type="checkbox" id="' . wire('sanitizer')->pageName($m['title']) . '">';
        }

        else $out .= '</a>';

        // call function again to generate nested list for sub-menu items belonging to this menu item.
        $out .= buildMenuFromArray($id, $menu, $first);
        $out .= "</li>\n";

    }// end if parent

  }// end foreach

  if ($has_child === true) $out .= "</ul>\n";

  return $out;

}



?>

Did I miss something crucial here?

Link to comment
Share on other sites

I see you copied and pasted more code than necessary. I have used your code and it works for me...

First, you need to make sure you have the latest version of Menu Builder which has the method getMenuItems(), i.e. version 0.1.5

head.inc

<div class="collapse navbar-collapse container" id="bs-example-navbar-collapse-1">
  <?php include('./navTwo.inc');?>
  <?php  echo buildMenuFromArray(0, $menuItems); ?>
</div><!-- /.navbar-collapse -->

navTwo.inc

<?php
$mb = $modules->get('MarkupMenuBuilder');// get Menu Builder
$menu = 'Main';// pass a title of an existing menu (i.e. a menu already built and published in ProcessMenuBuilder)

// grab menu items as Normal Array with Menu items (argument #2 = 1)
$menuItems = $mb->getMenuItems($menu, 1);

/**
* Builds a nested list (menu items) of a single menu from an Array of menu items.
*
* A recursive function to display nested list of menu items.
*
* @access private
* @param Int $parent ID of menu item.
* @param Array $menu Array of menu items to display.
* @param Int $first Helper variable to designate first menu item.
* @return string $out.
*
*/
function buildMenuFromArray($parent = 0, $menu, $first = 0) {

  $out = '';
  $has_child = false;

  foreach ($menu as $id => $m) {

    $parentID = isset($m['parent_id']) ? $m['parent_id'] : 0;
    $newtab = isset($m['newtab']) && $m['newtab'] ? " target='_blank'" : '';
    // if this menu item is a parent; create the sub-items/child-menu-items
    if ($parentID == $parent) {// if this menu item is a parent; create the inner-items/child-menu-items

        // if this is the first child
        if ($has_child === false) {
            $has_child = true;// This is a parent
            if ($first == 0){
              $out .= "<ul class='main-menu cf'>\n";
              $first = 1;
            }
            else $out .= "\n<ul class='sub-menu'>\n";
        }

        $class = isset($m['is_current']) && $m['is_current'] ? ' class="current"' : '';

        // a menu item
        $out .= '<li' . $class . '><a href="' . $m['url'] . '"' . $newtab . '>' . $m['title'];
        // if menu item has children
        if (isset($m['is_parent']) && $m['is_parent']) {
          $out .= '<span class="drop-icon">▼</span>' .
              '<label title="Toggle Drop-down" class="drop-icon" for="' . wire('sanitizer')->pageName($m['title']) . '" onclick>▼</label>' .
          '</a>' .
          '<input type="checkbox" id="' . wire('sanitizer')->pageName($m['title']) . '">';
        }

        else $out .= '</a>';

        // call function again to generate nested list for sub-menu items belonging to this menu item.
        $out .= buildMenuFromArray($id, $menu, $first);
        $out .= "</li>\n";

    }// end if parent

  }// end foreach

  if ($has_child === true) $out .= "</ul>\n";

  return $out;

}

?>
Link to comment
Share on other sites

Thanks again.. Finally starting to understand what is going on, but now it is time to figure out how to give the "second nested ul" a different class than the wrapper ul. I thought I could pass that through with $options, but that doesnt seem to work. (sorry to drag this thread out).

Link to comment
Share on other sites

Actually, my bad; the README and my post above wasn't very clear (have now updated them). Not all options apply to getMenuItems(). The reason is that if using MarkupMenuBuilder to output the menu for you, i..e using the method render(), the structuring of the menu (the <ul>, their CSS classes, etc) are taken care of by the method. In contrast, if using getMenuItems() what you get back are the menu items with all their relevant properties. The reasoning here is that you will use those properties as well as any other logic and/or options you want in your own custom recursive function (nested loops) where you have full control of the markup. That said, only 3 options (from the list of those applicable to render()) apply to getMenuItems(). These are default_title, default_class and current_class_level. default_class is applied to the  item's property $m['ccss_itemclass'].

Here is a revised navTwo.inc showing how you can pass custom options to the recursive function and applying the same as well as the 3 options that can be passed to getMenuItems(). head.inc is also slightly revised to pass $options to buildMenuFromArray()

- @note: The new 4th argument in the function

- @note: The custom options array

- @note: Complete code for previous examples 1-3 can be found in these gists

head.inc

<div class="collapse navbar-collapse container" id="bs-example-navbar-collapse-1">
 <?php include('./navTwo.inc');
  // your CUSTOM options to pass to the recursive function buildMenuFromArray()
  $options = array (
   'submenu_css_class' => 'nested-ul-css-class',//CSS Class for sub-menus; you can use whatever index you want
   'other_custom_option' => 'foo',
   'first_class' => 'first',
   'last_class' => 'last',
   'current_class' => 'current',
   'has_children_class' => 'parent',
 );

  echo buildMenuFromArray(0, $menuItems, 0, $options);

 ?>
</div><!-- /.navbar-collapse -->

navTwo.inc

<?php
$mb = $modules->get('MarkupMenuBuilder');// get Menu Builder
$menu = 'Main';// pass a title of an existing menu (i.e. a menu already built and published in ProcessMenuBuilder)

// @note: only these 3 options will work with getMenuItems(); Only pass options if you need to; it is not mandatory 
$options4getMenuItems = array('default_title' => 1, 'default_class' => 'list-group', 'current_class_level' => 4);

// grab menu items as Normal Array with Menu items (argument #2 = 1)
#$menuItems = $mb->getMenuItems($menu, 1); OR
$menuItems = $mb->getMenuItems($menu, 1, $options4getMenuItems);// if passing options

/**
* Builds a nested list (menu items) of a single menu from an Array of menu items.
*
* A recursive function to display nested list of menu items.
*
* @access private
* @param Int $parent ID of menu item.
* @param Array $menu Array of menu items to display.
* @param Int $first Helper variable to designate first menu item.
* @return string $out.
*
*/
function buildMenuFromArray($parent = 0, $menu, $first = 0, $options = array()) {

  $out = '';
  $has_child = false;
  $subMenuCSSClass = isset($options['submenu_css_class']) ?  'class="' . $options['submenu_css_class'] . '"' :  '';

  foreach ($menu as $id => $m) {

    $parentID = isset($m['parent_id']) ? $m['parent_id'] : 0;
    $newtab = isset($m['newtab']) && $m['newtab'] ? " target='_blank'" : '';
    // if this menu item is a parent; create the sub-items/child-menu-items
    if ($parentID == $parent) {// if this menu item is a parent; create the inner-items/child-menu-items

        // if this is the first child
        if ($has_child === false) {
            $has_child = true;// This is a parent
            if ($first == 0){
              $out .= "<ul class='main-menu cf'>\n";
              $first = 1;
            }
            else $out .= "\n<ul ". $subMenuCSSClass .">\n";
        }

	// item CSS
	$itemCSSID = isset($m['css_itemid'])? ' id="' . $m['css_itemid'] . '"' : '';				
	$itemCSSClass = isset($m['css_itemclass']) ? $m['css_itemclass'] . ' ' : '';
	$itemFirst = isset($m['is_first']) && isset($options['first_class']) ? $options['first_class'] : '';				
	$itemHasChildren = isset($m['is_parent']) && isset($options['has_children_class']) ? $options['has_children_class'] . ' ' : '';				
	$itemLast = isset($m['is_last']) && isset($options['last_class']) ? $options['last_class'] . ' ' : '';
	$itemCurrent = 	isset($m['is_current']) && isset($options['current_class']) ? $options['current_class'] . ' ' : '';

	$classes = $itemCSSClass . $itemHasChildren . $itemLast . $itemCurrent . $itemFirst;
	$classes = trim(preg_replace('/\s+/', ' ', $classes));
	$class = strlen($classes) ? ' class="' . $classes . '"' : '';

        // a menu item
        $out .= '<li' . $itemCSSID . $class . '><a href="' . $m['url'] . '"' . $newtab . '>' . $m['title'];
        // if menu item has children
        if (isset($m['is_parent']) && $m['is_parent']) {
          $out .= '<span class="drop-icon">▼</span>' .
              '<label title="Toggle Drop-down" class="drop-icon" for="' . wire('sanitizer')->pageName($m['title']) . '" onclick>▼</label>' .
          '</a>' .
          '<input type="checkbox" id="' . wire('sanitizer')->pageName($m['title']) . '">';
        }

        else $out .= '</a>';

        // call function again to generate nested list for sub-menu items belonging to this menu item.
        $out .= buildMenuFromArray($id, $menu, $first, $options);
        $out .= "</li>\n";

    }// end if parent

  }// end foreach

  if ($has_child === true) $out .= "</ul>\n";

  return $out;

}

?>
Link to comment
Share on other sites

Thanks again Kongondo! I appreciate all the help. I actually figured it out about 5 mins ago (forgot to check back on the forums). It feel good to have actually learned through your help and the various comments what actually is occurring and not blindly copying. Your module has made things so much easier (once I learned).

Link to comment
Share on other sites

  • 3 weeks later...

Hey kongondo,

thanks for thi great module again. A few days ago I figured out, that the output of the main menu builder header is not formated in html. Maybe it has to do with some other modules installed, because I never had the issue, that the output is not "rendered" as HTML.

Does anyone else have this issue, too?

 

menubuilder.jpg

Link to comment
Share on other sites

Hey, 

 

thanks for your fast reply. I attached a screenshot with my current setup. You are right, the page is multilingual. 

Maybe you cna give me a hint, how I can solve this?

screenshot-2016-06-29 10-55-17.png

It seems, that the HTML is not rendered correctly. I tried to edit the HTML of the decription field and put Markdon tags in the description, which are displayed correct.

 

Edited by pmichaelis
Description Field HTML
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...