Dynamic 3 column sitemap for WordPress

Recently had a job that the client wanted a WordPress sitmap page that was dynamic showing only the pages (no posts). Easy enough with the wp_list_pages() function in WordPress but the client also wanted this in 3 well spaced columns even when pages have different numbers of children. Also, the children of the top level page had to be grouped with the parent. This is where it gets harder for WP.

My first thought was to split the list up using JavaScript but the client wanted to reduce the amount of JavaScript on the site and wanted the list to be read properly by screen readers. Fair enough, I think there’s a little too much done with JS these days anyway. So no JS.

Next on the solutions list was the CSS3 columns property which automatically puts content into as many columns as you like. A little test later shows that it doesn’t work quite as nicely as it should, splits up groups of items and older browsers (I’m looking at you, Internet Explorer) wouldn’t understand the property and just show a single column. So no CSS3.

Iffy columns using CSS3

I couldn’t find anyone else who had come up with a good solution to this using PHP, so I wrote my own.

I’m building it into a Custom Theme here but could be made into a Child Theme template or a Plugin perhaps.

In my functions.php file goes:

/* get a 3 column sitemap */
function get_three_col_sitemap () {
  $current_page_id = get_the_ID(); // this page ID – so that we don't show it
  $cols_desired = 3; // number of columns wanted
  $before_text = "<ul id='sitemap-col'>"; // type of tag to use
  $after_text = "</ul>";
  $spacer_text = $after_text.$before_text;
  $current_col_total = 0;
  $top_pages_loop = 1;
  $exlcuded_pages = $current_page_id; // exclude this page
  $all_pages_count = count(get_pages(array( 'exclude' => $exlcuded_pages )));
  $min_per_col = intval($all_pages_count / $cols_desired); // min entries per col
  $max_per_col = intval($all_pages_count / ($cols_desired – 1)); // max entries per col
  $top_pages = get_pages(array( 'parent' => 0, 'sort_column' => 'menu_order', 'sort_order' => 'asc' , 'exclude' => $exlcuded_pages )); // sort order and exclude pages
  $top_pages_count = count($top_pages);
  // before lists
  echo $before_text;
  foreach ($top_pages as $this_page)
  {
    // count of this page
    $this_page_count = count(get_pages('child_of='.$this_page->ID)) + 1;
    // list pages of this page and add to col total
    echo '<li><a href="'.get_page_link($this_page->ID).'">'.$this_page->post_title.'</a></li>';
    echo '<ul>';
    wp_list_pages('child_of='.$this_page->ID.'&title_li=');
    $current_col_total+=$this_page_count;
    echo '</ul>';
    // if >max and not first in col OR if >min and not last in loop = gap and reset
    if (($current_col_total > $max_per_col && $current_col_total > 0 ) || ($current_col_total > $min_per_col && $top_pages_loop < $top_pages_count )) {
      echo $spacer_text;
      $current_col_total = 0;
    }
    $top_pages_loop++;
  }
  // after lists
  echo $after_text;
}

The first few lines sort out all the variables. Main ones to consider are the minimum and maximum entries per column and the top pages, which are all the pages with no parent. The main loop then goes through each of the top pages, listing all the sub pages in each, then decides if the column is long enough. If it is, then it puts in a spacer and starts on the next column.

You could change this to use div tags or (*shudder*) tables to do the splitting of the list. I went for unordered lists (ul tags) because it creates a big list that screenreaders can easily see but can still be positioned by CSS.

Now it can just be called from a template file in the theme:

<?php 
if ( function_exists( 'get_three_col_sitemap' ) ) { 
    get_three_col_sitemap(); 
}
?>

And the style.css just needs a few extra entries:

#sitemap-col { /* each column */
 float: left;
 width: 210px;
 padding: 16px 15px 1px 0px;
}
#sitemap-col li { /* top pages */
 text-transform: uppercase;
 font-weight: normal;
 margin-bottom: 0px;
 list-style-image: none;
 list-style-type: none;
}
#sitemap-col ul li { /* sub pages */
 text-transform: none;
 list-style-type: square;
 color: #2EB1DA;
}
#sitemap-col ul li ul li { /* sub sub pages */
 list-style-type: none;
}
#sitemap-col a { /* link style */
 color: #2EB1DA;
 font-weight: normal;
}

This is just an idea of what you can do. Put what ever CSS you like in there.

You should now have a Template that creates something like this.

Dynamic 3 column sitemap complete

Now this won’t sort everything perfectly if you have low page numbers or loads of pages under one top page. It will do the best it can but won’t rearrange the page order for you. This uses the page order rather than the menu order, so that the menu order can be different.

If I get time, I might make this into a plugin with shortcodes and admin menus. In the meantime, please feel free to use the code here for your own use.

3 Replies to “Dynamic 3 column sitemap for WordPress”

  1. Hi, this looks like a great bit of code but I’m not sure its compatible with WP 3.4.2?

    When I add it to my theme’s functions.php file I just get a blank white screen. I’m using GoPress theme and I was hoping to use it in the footer widget.

    1. Hi KevEd,

      It is working on a WP 3.5.1 site right now and should be just fine on yours, although I haven’t played with GoPress themes.

      If you don’t already have a functions.php file, you will need to wrap the code with < ?php at the start and ?> at the end.

Leave a Reply

Your email address will not be published. Required fields are marked *