Selective Page Hierarchy for wp_list_pages()

Posted on

By Michael Fields

It’s been a while since I’ve stepped foot inside the forrst and today I decided that it might be a good idea to drop in and see what wonderful things I could find. I came upon a link to an article titled Creating a hierarchical submenu in WordPress in which Roger Johansson presents a solution to creating a list of pages where only the active hierarchy is shown. I have always wondered why WordPress didn’t come pre-installed with such functionality myself. Reading Roger’s post inspired me to clean up a bit of code that I wrote previous this year which accomplishes this by extending the Walker_Page class.

Add to functions.php

/**
 * Create HTML list of pages.
 *
 * @package Razorback
 * @subpackage Walker
 * @author Michael Fields <michael@mfields.org>
 * @copyright Copyright (c) 2010, Michael Fields
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 *
 * @uses Walker_Page
 *
 * @since 2010-05-28
 * @alter 2010-10-09
 */
class Razorback_Walker_Page_Selective_Children extends Walker_Page {
	/**
	 * Walk the Page Tree.
	 *
	 * @global stdClass WordPress post object.
	 * @uses Walker_Page::$db_fields
	 * @uses Walker_Page::display_element()
	 *
	 * @since 2010-05-28
	 * @alter 2010-10-09
	 */
	function walk( $elements, $max_depth ) {
		global $post;
		$args = array_slice( func_get_args(), 2 );
		$output = '';

		/* invalid parameter */
		if ( $max_depth < -1 ) {
			return $output;
		}

		/* Nothing to walk */
		if ( empty( $elements ) ) {
			return $output;
		}

		/* Set up variables. */
		$top_level_elements = array();
		$children_elements  = array();
		$parent_field = $this->db_fields['parent'];
		$child_of = ( isset( $args[0]['child_of'] ) ) ? (int) $args[0]['child_of'] : 0;

		/* Loop elements */
		foreach ( (array) $elements as $e ) {
			$parent_id = $e->$parent_field;
			if ( isset( $parent_id ) ) {
				/* Top level pages. */
				if( $child_of === $parent_id ) {
					$top_level_elements[] = $e;
				}
				/* Only display children of the current hierarchy. */
				else if (
					( isset( $post->ID ) && $parent_id == $post->ID ) ||
					( isset( $post->post_parent ) && $parent_id == $post->post_parent ) ||
					( isset( $post->ancestors ) && in_array( $parent_id, (array) $post->ancestors ) )
				) {
					$children_elements[ $e->$parent_field ][] = $e;
				}
			}
		}

		/* Define output. */
		foreach ( $top_level_elements as $e ) {
			$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
		}
		return $output;
	}
}

Add to any template file with a php extension:

$walker = new Razorback_Walker_Page_Selective_Children();
wp_list_pages( array(
	'title_li' => '',
	'walker' => $walker,
	) );

Will recognize the child_of parameter.

$walker = new Razorback_Walker_Page_Selective_Children();
wp_list_pages( array(
	'title_li' => '',
	'child_of' => 1283,
	'walker' => $walker,
	) );

14 Comments Comments are closed

  1. Joseph Carrington October 19, 2010 at 11:42 pm

    Really great. Thanks so much!

  2. shawn November 6, 2010 at 6:53 am

    I know it may be a lot to ask, but would you consider adapting this walker class to work with adaptive post hierarchy?

    I’m working on a photo gallery project and want to add a widget that does exactly what this walker does for pages. I’ve tried adapting it myself, but keep running into roadblocks.

    Either way, thanks for such an amazing walker addition.

  3. Armando Estrada November 23, 2010 at 1:49 am

    Great script. I have been searching for something like this for a while. Question: What if I would like to exclude a page form being listed? For example, I have many pages that link form other pages. Currently they are all listed. In other words, they are orphan pages. I am still getting my feet wet with WP custom functions etc., so i am thinking maybe there is a way to add a custom field that i can have a value of “no-list” and exclude those. Any help would be appreciated.

    Thanks!

  4. Michael Fields November 23, 2010 at 2:01 am

    Thanks. Is the exclude parameter working?

    $walker = new Razorback_Walker_Page_Selective_Children();
    wp_list_pages( array(
    	'title_li' => '',
    	'walker' => $walker,
    	'exclude' => '1,2,3,4,5,6'
    	) );
    

    Honestly, I did not test this functionality, but then again… I didn’t need it. I did however test the ‘child_of’ argument which should allow you to isolate a single branch of the page tree.

  5. Armando Estrada November 23, 2010 at 2:16 am

    Thanks for the speedy reply! That worked. What I did was create a page called “EXCLUDE PAGES” and added the ID to ‘exclude’. This and any child pages will not show up. That way the client can add or remove pages without the need to hard code excluded pages. Again, thanks a million!

  6. Matt December 10, 2010 at 1:58 pm

    Nice work. I have a small question though, is it possible to exclude the all top level pages?

    So instead of:
    - Section 1
    – sub page (current)
    – sub sub page
    – sub page 2
    - Section 2
    - Section 3

    It outputs:
    - sub page (current)
    – sub sub page
    - sub page 2

    At present it outputs the whole tree from the top, I have been trying to get it to only output and walk from a “section” (e.g.: 1 level deep). I know i could hide/remove the top level pages with CSS but it would be great if I could exclude top level pages from the query.

  7. Michael Fields December 10, 2010 at 6:11 pm

    Thanks Matt! It should be relatively easy to do this. Have you tried something like this?

    if ( is_page() ) {
    	$get_children_of = ( isset( $post->ID ) ) ? (int) $post->ID : 0;
    	$walker = new Razorback_Walker_Page_Selective_Children();
    	wp_list_pages( array(
    		'title_li' => '',
    		'child_of' => $get_children_of,
    		'walker' => $walker,
    		) );
    }
    
  8. Paul December 10, 2010 at 6:20 pm

    Utter genius. Exclude pages works perfectly. Finally a sensible & easily implemented page menu structure for WP. Thanks!

  9. Matt December 11, 2010 at 11:44 am

    Thanks Michael, I gave that snippet a go and it kind of works but parent and sibling pages seem to disappear as I move down the tree.

    Ideally I was trying to keep these pages in the tree and only remove the very top level? e.g.:

    - second level page
    —- third level page (current)
    - second level page 2

  10. Michael Fields December 13, 2010 at 1:46 am

    Right on… In that case you will want to use the same idea, but instead of passing $post->ID to the child_of argument, you will want to get the ID of the root page and pass that instead. A good place to start is the code posted here.

  11. Debbie June 17, 2011 at 6:24 pm

    Thank you many times over! I’ve been searching the web and trying out solutions with no luck for days. Your solution was elegant and worked. Just what I needed. Why aren’t more people making use of the Walker-Page class? This is the first I’ve heard of it.
    Thanks again.

  12. Joel Fish October 19, 2011 at 9:43 am

    Hi… Is it possible to see this functionality implemented somewhere? I don’t know really know how to code, (although I can experiment) but I’d like to have a better idea of what the above code achieves.

    Best,
    Joel.

  13. Ján Bočínec November 21, 2011 at 11:45 am

    Nice walker ;). I have done something similar for custom menus http://wordpress.org/extend/plugins/advanced-menu-widget/ – maybe somebody will find it useful.

Fork me on GitHub