Jump to content

Recursive navigation


apeisa
 Share

Recommended Posts

  • 4 years later...
On 2/22/2013 at 9:06 PM, mindplay.dk said:

This is so common - I do things like this all the time, not just for menus.

Does anyone else think this should be part of the core?

For now, I wrote a simple flat function:


/**
 * Recursive traverse and visit every child in a sub-tree of Pages.
 *
 * @param Page $parent root Page from which to traverse
 * @param callable $enter function to call upon visiting a child Page
 * @param callable|null $exit function to call after visiting a child Page (and all of it's children)
 */
function visit(Page $parent, $enter, $exit=null)
{
    foreach ($parent->children() as $child) {
        call_user_func($enter, $child);
        
        if ($child->numChildren > 0) {
            visit($child, $enter, $exit);
        }
        
        if ($exit) {
            call_user_func($exit, $child);
        }
    }
}

With PHP 5.3 you can generate a menu (or whatever else) recursively as simple as this:


visit(
    $pages->get('/menus/main')
    ,
    function(Page $page) {
        echo '<li><a href="' . $page->url . '">' . $page->title . '</a>';
        if ($page->numChildren > 0) {
            echo '<ul>';
        }
    }
    ,
    function(Page $page) {
        echo '</li>';
        if ($page->numChildren > 0) {
            echo '</ul>';
        }
    }
);

Dead simple.

I've seen the options for modules etc. that generate menus - they seem to grow out of control with a million options, and my own helpers seem to evolve the same way, and it doesn't jive with the beautiful, self-contained, simple templates you normally get away with in PW.

Would it make sense to have a standard visit() method in Page in the core?

I realise this post is SUPER old but I'm after something similar that outputs/builds an array, rather than markup.

 Something like...

menu => array(
	main => array(
		1023 => array(
			id => 1023
			title => 'Menu item 1',
			children => null
		),
		1024 => array(
			id => 1024
			title => 'Menu item 1',
			children => array(
				3000 => array(
                  id => 3000
                  title => 'Child menu item 1',
                  children => null
				),
				3001 => array(
                  id => 3001
                  title => 'Child menu item 2',
                  children => null
				)
			)
		),
		1024 => array(
			id => 1024
			title => 'Menu item 1',
			children => array(
				3000 => array(
                	id => 3000
                  	title => 'Child menu item 1',
                  	children => array(
						4001 => array(
                          id => 4001
                          title => 'Child menu item 2',
                          children => null
                        ),
						4002 => array(
                          id => 4002
                          title => 'Child menu item 2',
                          children => null
                        )
					)
				),
				3001 => array(
                  id => 3001
                  title => 'Child menu item 2',
                  children => null
				)
			)
		)
	)
)

 

Link to comment
Share on other sites

On 10/8/2020 at 12:34 PM, a-ok said:

but I'm after something similar that outputs/builds an array, rather than markup.

What's your use case? Deeply nested arrays can get unwieldy and depending on how big they are, consume a lot of memory.  I find it simpler to work with flat(ter) arrays with in-built relationships between the members. I then run that through a recursive function. Something like this:

<?php namespace ProcessWire;

$menu = array(
 1 => array(
  'id' => 1023,
  'title' => 'Menu item 1',
  'parent' => null, // this menu item does not have a parent/top level
 ),
 2 => array(
  'id' => 1024,
  'title' => 'Menu item 2',
  'parent' => null,
 ),
 3 => array(
  'id' => 3000,
  'title' => 'Child 1 of Menu item 2',
  'parent' => 1024, // its parent is item with ID 1024
 ),
 4 => array(
  'id' => 3001,
  'title' => 'Child 2 of Menu item 2',
  'parent' => 1024,
 ),
 5 => array(
  'id' => 3003,
  'title' => 'Child 1 of Child Menu item 2',
  'parent' => 3001,
 ),
 6 => array(
  'id' => 1027,
  'title' => 'Menu item 4',
  'parent' => null,
 ),
 7 => array(
  'id' => 3009,
  'title' => 'Child 1 of Menu item 4',
  'parent' => 1027,
 ),
 8 => array(
  'id' => 4001,
  'title' => 'Child 1 of Child 1 of Menu item 4',
  'parent' => 3009,
 ),
 9 => array(
  'id' => 4002,
  'title' => 'Child 2 of Child 1 of Menu item 4',
  'parent' => 3009,
 ),

);

 

  • Like 1
Link to comment
Share on other sites

2 hours ago, kongondo said:

What's your use case? Deeply nested arrays can get unwieldy and depending on how big they are, consume a lot of memory.  I find it simpler to work with flat(ter) arrays with in-built relationships between the members. I then run that through the recursive function. Something like this:


<?php namespace ProcessWire;

$menu = array(
 1 => array(
  'id' => 1023,
  'title' => 'Menu item 1',
  'parent' => null, // this menu item does not have a parent/top level
 ),
 2 => array(
  'id' => 1024,
  'title' => 'Menu item 2',
  'parent' => null,
 ),
 3 => array(
  'id' => 3000,
  'title' => 'Child 1 of Menu item 2',
  'parent' => 1024, // its parent is item with ID 1024
 ),
 4 => array(
  'id' => 3001,
  'title' => 'Child 2 of Menu item 2',
  'parent' => 1024,
 ),
 5 => array(
  'id' => 3003,
  'title' => 'Child 1 of Child Menu item 2',
  'parent' => 3001,
 ),
 6 => array(
  'id' => 1027,
  'title' => 'Menu item 4',
  'parent' => null,
 ),
 7 => array(
  'id' => 3009,
  'title' => 'Child 1 of Menu item 4',
  'parent' => 1027,
 ),
 8 => array(
  'id' => 4001,
  'title' => 'Child 1 of Child 1 of Menu item 4',
  'parent' => 3009,
 ),
 9 => array(
  'id' => 4002,
  'title' => 'Child 2 of Child 1 of Menu item 4',
  'parent' => 3009,
 ),

);

 

@kongondo This is super interesting. My set up in PW is the following:

  • Works
    • Categories
      • Artworks
        • Digital
        • Painting
      • Books
      • Editions
    • Work
    • Work
    • Work
  • Activities
    • Categories
      • Events
        • Reading
        • Talks
        • Workshops
      • Exhibitions
        • Curated
        • Group
        • Solo
      • Publications
        • Anthologisations
        • Books
        • Editing
        • Essays
    • Activity
    • Activity
    • Activity

And what I'm wanting to do on the front end (with JS) is when the user clicks 'Works' then it shows the first category level ('Artworks', 'Books', 'Editions'), then if the user clicks 'Artworks' it would show 'Digital' and 'Painting' while keeping the previous 'parents' active.

I'll figure out the JS somehow for the menu but I'm guessing your method (which I think is a super smart way of doing it) would structurally allow for this sort of functionality?

Link to comment
Share on other sites

32 minutes ago, a-ok said:

would structurally allow for this sort of functionality?

Yes, it should. As long as you tell the JS what criteria to use to filter things. I don't know if you are using vanilla JS or some library but the principle is the same. You will filter out/in items to either remove/hide OR add/display depending on whether you filtered out or in. 

32 minutes ago, a-ok said:

I'm guessing your method (which I think is a super smart way of doing it)

This is how we do it in Menu Builder, giving us flexibility to create all sorts of menus.

If you have more questions specific to your use case (including the JS), please open a new thread so that we stay on topic in this thread. Thanks.

Edited by kongondo
  • Like 1
Link to comment
Share on other sites

2 minutes ago, kongondo said:

Yes, it should. As long as you tell the JS what criteria to use to filter things. I don't know if you are using vanilla JS or some library but the principle is the same. You will filter out/in items to either remove/hide OR add/display depending on whether you filtered out or in. 

This is how we do it in Menu Builder, giving us flexibility to create all sorts of menus.

If you have more questions specific to your use case (including the JS), please open a new thread so that we stay on topic in this thread. Thanks.

@kongondo Thanks! Using vanilla/alpine/react sort of thing but yeah principle should be the same ? 

Okay great. Will be in touch if I have any more questions but super appreciate the help/advice.

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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...