Jump to content

Get children of parent page within a function


SamC
 Share

Recommended Posts

Hi, I've been on this for the past couple of hours but can't translate something from a template into a function. I want to print a menu like this:
 

Link 1
Link 2 (active)
 - Link 2.1
 - Link 2.2
Link 3
Link 4

OR

Link 1
Link 2
 - Link 2.1 (active)
 - Link 2.2
Link 3
Link 4

...where the child links are visible when on the parent page AS WELL AS when on a child page.

I had this in a template:
 

// main.php
<div id="main-menu-wrapper">
        <div class="container">
          
            <nav id="main-menu">
                <?php
                    $arr = $home->and($home->children);
                    echo renderMenu($arr);
                ?>
            </nav>
        </div>
    </div>

    <!-- sub navigation -->
    <?php if (!($page->id == 1)): ?>
        
        <?php if ($page->numChildren > 0): ?>
            <div id="sub-menu-wrapper">
                <div class="container">
                    <nav id="sub-menu">
                        <?php echo renderMenu($page->children); ?>
                    </nav>
                </div>
            </div>
        <?php endif; ?>

        <?php if (count($page->parents) > 1): ?>
            <div id="sub-menu-wrapper">
                <div class="container">
                    <nav id="sub-menu">
                        <?php echo renderMenu($page->parent->children); ?>
                    </nav>
                </div>
            </div>
        <?php endif; ?>

    <?php endif; ?>

...and the function was this:
 

/**
 * Render navigation from array of pages
 *
 * @param PageArray $items
 * @param $menuClassName
 * @return string
 *
 */
function renderMenu($items) {

    // if we were given a single Page rather than a group of them, we'll pretend they
    // gave us a group of them (a group/array of 1)
    if($items instanceof Page) {
        $items = array($items);
    }

    $str = '';

    foreach ($items as $item) {

    if ($item->showInMenu == true) {

            $menuText = $item->get('menuLinkTitle|title');
            $currentClass = '';

            if ($item->id == wire('page')->id) {
                $currentClass .= "class=\"current\" ";
            }
            elseif (wire('page')->parents->has($item) && !($item->id == 1)) {
                $currentClass .= "class=\"current-parent\" ";
            }
            else {
                $currentClass .= '';
            } 

            $str .= "<li><a " . $currentClass . "href=\"$item->url\">$menuText</a></li>";
        }
    }

    // if output was generated above, wrap it in a <ul>
    if ($str) {
        $str = "<ul>$str</ul>";
    }

    return $str;
}

Now I've changed my layout and want just a single menu, but with the same functionality. I'm doing it like this:

// main.php
                <nav id="main-menu">
                    <?php
                        $arr = $home->and($home->children);
                        echo renderMenu($arr);
                    ?>
                </nav>

...with a function like this:
 

/**
 * Render navigation from array of pages
 *
 * @param PageArray $items
 * @param $menuClassName
 * @return string
 *
 */
function renderMenu($items) {

    // if we were given a single Page rather than a group of them, we'll pretend they
    // gave us a group of them (a group/array of 1)
    if($items instanceof Page) {
        $items = array($items);
    }

    $str = '';

    foreach ($items as $item) {

    if ($item->showInMenu == true) {

            $menuText = $item->get('menuLinkTitle|title');
            $currentClass = '';

            if (wire('page')->id == $item->id) {
                $currentClass .= "class=\"current\" ";
            }
            elseif (wire('page')->parents->has($item) && !($item->id == 1)) {
                $currentClass .= "class=\"current-parent\" ";
            }
            else {
                $currentClass .= '';
            } 

            $str .= "<li><a " . $currentClass . "href=\"$item->url\">$menuText</a>";

            if (!(wire('page')->id == 1)) {

                if (wire('page')->id == $item->id) {
                    $str .= renderMenu($item->children);
                }
              // if the current page has omre than 1 parent (i.e. on level 2)
                if (count(wire('page')->parents) > 1) {
                    // send array of the children of the current pages parent to renderMenu
                    $str .= renderMenu(wire('page')->parent->children); // this is where the function poos the bed
                }
            }

            $str .= "</li>";
        }
    }

    // if output was generated above, wrap it in a <ul>
    if ($str) {
        $str = "<ul>$str</ul>";
    }

    return $str;
}

So it works ok when on 'Link 2', you get 'Link 2.1' and 'Link 2.2' printed in a nested list. However, clicking on Link 2.1 or Link 2.2, just a spinning wheel in the browser and a timeout, like I'm stuck in an infinite loop.

It's obviously wrong, but can someone please explain what's wrong with it? Why does:

<?php echo renderMenu($page->parent->children); ?>

...work in the template, but:

$str .= renderMenu(wire('page')->parent->children);

...fails in my function? A bit confused here. Any help is much appreciated. Thanks.

Link to comment
Share on other sites

Ok, so this gets me some output when the conditions are correct:

// _func.php
if (wire('page')->id == $item->id) {
    $str .= renderMenu($item->children);
}
if (wire('page')->parents->has($item) && !($item->id == 1)) {
    echo "stuff";
}

Still unsure how to get:
 

renderMenu($page->parent->children)

...into my template.

Link to comment
Share on other sites

Hold the bells! This works:

// main.php
<nav id="main-menu">
    <?php
        $arr = $home->and($home->children);
        echo renderMenu($arr);
	?>
</nav>

// _func.php
/**
 * Render navigation from array of pages
 *
 * @param PageArray $items
 * @param $menuClassName
 * @return string
 *
 */
function renderMenu($items) {

    // if we were given a single Page rather than a group of them, we'll pretend they
    // gave us a group of them (a group/array of 1)
    if($items instanceof Page) {
        $items = array($items);
    }

    $str = '';

    foreach ($items as $item) {

    if ($item->showInMenu == true) {

            $menuText = $item->get('menuLinkTitle|title');
            $currentClass = '';

            if (wire('page')->id == $item->id) {
                $currentClass .= "class=\"current\" ";
            }
            elseif (wire('page')->parents->has($item) && !($item->id == 1)) {
                $currentClass .= "class=\"current-parent\" ";
            }
            else {
                $currentClass .= '';
            } 

            $str .= "<li><a " . $currentClass . "href=\"$item->url\">$menuText</a>";

            if (!(wire('page')->id == 1)) {

                if (wire('page')->id == $item->id) {
                    $str .= renderMenu($item->children);
                }
                if (wire('page')->parents->has($item) && !($item->id == 1)) {
                    $str .= renderMenu($item->children);
                }
            }

            $str .= "</li>";
        }
    }

    // if output was generated above, wrap it in a <ul>
    if ($str) {
        $str = "<ul>$str</ul>";
    }

    return $str;
}

I guess I could combine to two ifs into an || but would make one huge difficult to read line. Maybe just talking this out to myself writing it helped me out. Anyway, any improvements are now welcome :)

I still don't understand what 'wire('page')->parents->has($item)' does. Not just being happy with it working, I now have to know why it works... so testing output, I get this:

// _func.php (in loop)
echo $item . " | ";

// main.php
<nav id="main-menu">
    <?php
        $arr = $home->and($home->children); // this same thing passed on every page
        echo renderMenu($arr);
    ?>
</nav>

// OUTPUT TO WEBPAGE
// when on home, outputs:
1 | 1015 | 1018 | 1019 | 1022 | 1023 |

// when on contact page, outputs:
1 | 1015 | 1018 | 1019 | 1022 | 1023 |

// when on about page outputs:
1 | 1015 | 1016 | 1017 | 1018 | 1019 | 1022 | 1023 |

// when on subpage of about, outputs:
1 | 1015 | 1016 | 1017 | 1018 | 1019 | 1022 | 1023 |

// when on volunteer page, outputs:
1 | 1015 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 |

// when on subpage of volunteer, outputs:
1 | 1015 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 |

I'm a little confused here. I thought the array going into 'renderMenu($items)' was the same every time?!

Link to comment
Share on other sites

A few minor optimizations:

  • the "else" block isn't necessary
  • no need to put the "class=\"something\"" into the variable, you just need to store the class name
  • HTML attributes can also use single quotes, which makes assigning them in interpolated strings easier to read
  • you have two identical if/elseif conditionals for the class and for rendering children, so you can set a flag in the first place
  • storing the result of wire('page') in a variable (I used $page) shortens the code
  • $a != $b is (to me) more readable in complex conditional expressions than !($a == $b)

 

Untested:

<?php

// ...

function renderMenu($items) {
	
    $page = wire('page');

    // if we were given a single Page rather than a group of them, we'll pretend they
    // gave us a group of them (a group/array of 1)
    if($items instanceof Page) {
        $items = array($items);
    }

    $str = '';

    foreach ($items as $item) {

    	if ($item->showInMenu == true) {

            $menuText = $item->get('menuLinkTitle|title');
            $currentClass = '';
            $renderChildren = false;

            if ($page->id == $item->id) {
                $currentClass .= "current";
                $renderChildren = true;
            }
            elseif ($page->parents->has($item) && $item->id != 1) {
                $currentClass .= "current-parent";
                $renderChildren = true;
            }

            $str .= "<li><a class='$currentClass' href='$item->url'>$menuText</a>";

            if ($page->id != 1 && $renderChildren) {
            	$str .= renderMenu($item->children);
            }

            $str .= "</li>";
        }
    }

    // if output was generated above, wrap it in a <ul>
    if ($str) {
        $str = "<ul>$str</ul>";
    }

    return $str;
}

 

  • Like 3
Link to comment
Share on other sites

Awesome, thanks @BitPoet

I'll get there in the end! :)

Can I just say, points 1, 2, and 3 were to avoid this:

<li><a class='' href='/'>Home</a></li>

I prefer double quotes in all my HTML and empty classes seem pointless. That aside, the logic seems more sensible.

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
 Share

×
×
  • Create New...