I have looked at printing an intelligent list of WordPress pages in a previous blog post, but I wanted to revisit this topic and use the Walker classes that are part of WordPress. The Walker class is an abstract class that sorts out a lot of the basic functionality needed to extract and render a hierarchical list of items from a database. It is essentially an iterator class that understands lists of items that contain lists of items and can be used anywhere in WordPress where this structure is needed. The Walker class can be found in the file wp-includes/classes.php along with four other classes that extend Walker, these are.
- Walker_Page - Constructs a string containing an unordered HTML hierarchical list of pages.
- Walker_PageDropdown - Constructs a string containing a list of pages as options in a single select element.
- Walker_Category - Constructs a string containing an unordered HTML hierarchical list of categories.
- Walker_CategoryDropdown - Constructs a string containing a list of categories as options in a single select element.
A quick search through the source code of WordPress 3.0.1 reveals a couple of other classes that use the Walker class to print hierarchical lists of items. These are as follows.
- Walker_Comment (found in wp-includes/comment_template.php) - Constructs a string containing a hierarchical list of comments.
- Walker_Nav_Menu (found in wp-includes/nav-menu-template.php) - Constructs a unordered HTML hierarchical list of menu items. This can be seen when using the WordPress menu system.
- Walker_Category_Checklist (found in wp-admin/includes/template.php) - Created for the admin section of WordPress and is used to create a hierarchical list of checkboxes. This list can be seen when assigning a post to a category.
To create your own list of items you can either extend the base Walker class, or if you just want to override some of the functionality you can extend any of the sub classes. One thing to realise with these classes is that they are not meant to be used directly, they are used by WordPress to iterate over content that is created by other function calls. This is best explained in an example.
The following example extends the Walker_Page class so that our list of pages is printed out as a ordered HTML list (the default is an unordered list) by overriding the start_lvl() and end_lvl() methods.
class OrderedListOfPages extends Walker_Page {
/**
* @see Walker::start_lvl()
* @since 2.1.0
*
* @param string $output Passed by reference. Used to append additional content.
* @param int $depth Depth of page. Used for padding.
*/
function start_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent\n";
}
/**
* @see Walker::end_lvl()
* @since 2.1.0
*
* @param string $output Passed by reference. Used to append additional content.
* @param int $depth Depth of page. Used for padding.
*/
function end_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "$indent\n";
}
}
To use this class in your page template you need to include this class somewhere that WordPress can see (either in a the function.php file or as a plugin) and then use the wp_list_pages() function, passing the class as a parameter. The following example will print out our page hierarchy using the OrderedListOfPages class above.
wp_list_pages(array('walker' => new OrderedListOfPages()));
Alternatively, rather than add code to your template you can change the way the pages widget works by adding a filter. The following code snippet will force the pages widget to use our new OrderedListOfPages class.
function my_page_filter($args) {
$args = array_merge($args, array('walker' => new OrderedListOfPages()));
return $args;
}
add_filter('widget_pages_args', 'my_page_filter');
Revisiting my previous example of a dynamic menu creator I wanted to create a class that would do the same thing, but this time use a WordPress walker class. To do this I only need to override the start_el() method from the Walker_Page class, checking each page (ie. each call to start_el()) to see where the page lies in the page hierarchy. The start_el() method also creates a bunch of CSS classes that can be used to style the elements in different ways and this functionality was reimplemented when I rewrote the method. The strategy I used here was to allow the element to be constructed in the normal way if it was needed as part of the page hierarchy, otherwise just return so that the element is not created.
Only thing that this class doesn't do that the dynamic menu creator does is that when going really far down into the tree some the siblings of top elements start to get cut off, but the path to the current page and the levels above are always kept open.
class DynamicPageMenu extends Walker_Page {
/**
* @see Walker::start_el()
* @since 2.1.0
*
* @param string $output Passed by reference. Used to append additional content.
* @param object $page Page data object.
* @param int $depth Depth of page. Used for padding.
* @param int $current_page Page ID.
* @param array $args
*/
function start_el(&$output, $page, $depth, $args, $current_page) {
if ( $depth ) {
$indent = str_repeat("\t", $depth);
} else {
$indent = '';
}
extract($args, EXTR_SKIP);
$css_class = array('page_item', 'page-item-'.$page->ID);
$_current_page = null;
$_tmp_page = null;
if (!empty($current_page)) {
$_current_page = get_page( $current_page );
$_tmp_page = get_page( $_current_page->post_parent );
}
if ($page->post_parent == 0) {
// this is a top level page, so it must be printed
} elseif (isset($_current_page->ancestors) && in_array($page->ID, (array) $_current_page->ancestors)) {
// this is in the path for our current page so it must be printed
$css_class[] = 'current_page_ancestor';
} elseif ($page->ID == $current_page) {
// this is the current page to it must be printed
$css_class[] = 'current_page_item';
} elseif (!is_null($_current_page) && $page->ID == $_current_page->post_parent) {
// this page is the current page parent
$css_class[] = 'current_page_parent';
} elseif (!is_null($_current_page) && $page->post_parent == $_current_page->ID) {
// this page sits just below the current page so it must also be printed
} elseif (!is_null($_tmp_page) && $_tmp_page->post_parent == $page->post_parent) {
// this is a sibling of the current page parent so it must also be printed
} elseif (!is_null($_current_page) && $_current_page->post_parent == $page->post_parent) {
// this is a sibling of the current page so it must be printed
} else {
// reject everything else.
return true;
}
$css_class = implode(' ', apply_filters('page_css_class', $css_class, $page));
$output .= $indent . '' . $link_before . apply_filters( 'the_title', $page->post_title, $page->ID ) . $link_after . '';
if ( !empty($show_date) ) {
if ( 'modified' == $show_date ) {
$time = $page->post_modified;
} else {
$time = $page->post_date;
}
$output .= " " . mysql2date($date_format, $time);
}
}
}
Use this class in the same way as before, as follows:
wp_list_pages(array('walker' => new DynamicPageMenu()));
Or, to use it to override the output of the page widget use the following code.
function my_page_filter($args) {
$args = array_merge($args, array('walker' => new DynamicPageMenu()));
return $args;
}
add_filter('widget_pages_args', 'my_page_filter');
Comments
Submitted by Anonymous on Tue, 11/30/2010 - 15:18
PermalinkSubmitted by kiwus on Sun, 01/23/2011 - 16:37
PermalinkJust what I needed. You certainly saved me quite a bit of time. Thanks a million.
Submitted by Pete on Mon, 04/18/2011 - 19:56
PermalinkDid I do something wrong?
The pages are listed but nothing is linked?
Submitted by Anonymous on Wed, 05/11/2011 - 22:39
PermalinkAdd new comment