DiggingIntoWordPress

by Chris Coyier & Jeff Starr

Dynamic Archives

Posted by on

Have you ever seen WordPress archives where you select something (usually a month/year) from a dropdown and it takes you to a page where you can view that? It’s fairly common. WordPress almost has built in functionality for it, since you can specifically tell the wp_get_archives() function that you want the values to be returned as <option>s. We can kick it up a notch though, and make the results show dynamically on the same page as the dropdowns through some Ajaxy JavaScript. We’ll even allow for multiple dropdowns (include the category as well) and make sure it’s flexible for your own alterations.

View Demo

Step 1) Building the Dropdowns

The dropdowns are pretty easy to create. Here I’ll make a div wrapper for both of them, then include a dropdown for month/year, and for category:

<div id="archive-browser">
	<div>
		<h4>Month</h4>
		<select id="month-choice">
			<option val="no-choice"> &mdash; </option>
			<?php wp_get_archives(array(
			
				'type'    => 'monthly',
				'format'  => 'option'
			
			)); ?>
		</select>
	</div>
	<div>
		<h4>Category</h4>
		<?php 

			wp_dropdown_categories('show_option_none= -- ');
					
		?> 
	</div>
</div>

Note that the WordPress functions for the two dropdowns are a bit different. wp_get_archives requires you to bring your own select wrapper, which wp_dropdown_categories does not. For the former, you’ll also need to include your own “no-choice” option. I’m sure the WordPress gang has their reasons for this kind of thing, but I’d prefer to see it standardized over time.

Step 2) Let’s get ourselves a getter.php

We’re going to be dynamically grabbing our archives and plunking them on the screen. That means JavaScript, and as usual with me, that means jQuery. But jQuery can’t do a WordPress query all by itself. JavaScript is a client-side technology and database stuff is a server-side activity. So we’re going to need an intermediary. A URL that our JavaScript can call to return to us exactly what we need. That’s what I’m calling a getter.php file.

Since we’re working with WordPress here, a great way to do this is to create a template of what we need, then publish a page using that template. That gives us a nice URL we can hit. So in our active theme, I have greated a file called “archive-getter.php”, and this is that code:

<?php

    /*
        Template Name: Archives Getter
    */
    
    $year = htmlspecialchars(trim($_POST['digwp_y']));
    $month = htmlspecialchars(trim($_POST['digwp_m']));
    $cat = htmlspecialchars(trim($_POST['digwp_c']));
    
    $querystring = "year=$year&monthnum=$month&cat=$cat&posts_per_page=-1";
    
    query_posts($querystring); 
    
?>

<?php if (($year == '') && ($month == '') && ($cat == '-1')) { ?>

	<table id="archives-table"><tr><td style='text-align: center; font-size: 15px; padding: 5px;'>Please choose from above.</td></tr></table>
	
<?php } else { ?>

	<table id="archives-table">
	    <?php    
	        if (have_posts()) : while (have_posts()) : the_post(); ?>
	            <tr>
	            	<td><img src="<?php echo get_post_meta($post->ID, 'PostThumb', true); ?>" alt="" style="width: 35px;" /></td>
	            	<td><a href='<?php the_permalink(); ?>'><?php the_title(); ?></a></td>
	            	<td><?php comments_popup_link(' ', '1 Comment', '% Comments'); ?></td>
	            	<td><?php the_date('m/j/Y'); ?></td>
	            </tr>
	    <?php 
	        endwhile; else:
	        
	        	echo "<tr><td style='text-align: center; font-size: 15px; padding: 5px;'>Nothing found.</td></tr>";
	        
	        endif; 
	    ?>
	</table>

<?php } ?>

Let’s go through it top to bottom:

  1. Special comment to tell WordPress this is an Template file
  2. Set variables from three POST parameters. Clean them up in the process
  3. Create a $query string in the format WordPress likes them in
  4. Run a query_posts from that query string
  5. If none of the variables are set, return a message telling the user to please select values.
  6. If at least one variable is set…
  7. Run “The Loop”
  8. Output a table of the results, containing the post image, content, date, comments, etc.
  9. If nothing is found, tell the user so

Step 3) Watching for Dropdown Menu Changes

We’re going to be using jQuery, so make sure jQuery is loaded in your theme. Then we’ll need a JavaScript file from which to work. So I’ve created a file called archives.js in the js folder of my theme. I similarly made a CSS file for styling this, and then conditionally load both of them in the header.php file:

<?php if (is_page_template("archives.php")) { ?>
   <link rel="stylesheet" href="<?php bloginfo('template_url'); ?>/css/archives.css" type="text/css" />
   <script src="<?php bloginfo('template_url'); ?>/js/archives.js" type="text/javascript"></script>
<?php } ?>

Now that we have a working JavaScript enviornment, we can watch those select dropdown menus we built for changes:

jQuery(function() {
  jQuery("#archive-browser select").change(function() {
     // dynamically load the archives
  });
});

Step 4) Dynamically Load New Archives

Using the great base we already have going here, let’s write out all the code we need to dynamically load in those archives. One thing we haven’t covered yet is, where on the page do we load in these said archives? We’ll need a target. We could append it with JavaScript, but what they hey, I just stuck it into the template itself instead:

The #archive-pot is where we’ll load in new markup as we get it. The wrapper is there because that way we can dynamically adjust the height and do it really nice and smoothly.

Now here’s the complete JavaScript:

jQuery(function() {

	jQuery("#archive-wrapper").height(jQuery("#archive-pot").height());

	jQuery("#archive-browser select").change(function() {
	
		jQuery("#archive-pot")
			.empty()
			.html("<div style='text-align: center; padding: 30px;'><img src='/wp-content/themes/DiggingIntoWordPress-2/images/ajax-loader.gif' /></div>");
	
		var dateArray = jQuery("#month-choice").val().split("/");
		var y = dateArray[3];
		var m = dateArray[4];
		var c = jQuery("#cat").val();
		
		jQuery.ajax({
		
			url: "/archives-getter/",
			dataType: "html",
			type: "POST",
			data: ({
				"digwp_y": y,
				"digwp_m" : m,
				"digwp_c" : c
			}),
			success: function(data) {
				jQuery("#archive-pot").html(data);
				
				jQuery("#archive-wrapper").animate({
					height: jQuery("#archives-table tr").length * 50
				});
			
			}
			
		});
			
	});

});
  1. Set height of wrapper to height of inside. Seems weird, but now that the wrapper has a static height and hidden overflow, it won’t blast open when new content is added, we can animate it’s heigh accordingly.
  2. Watch for dropdown changes
  3. When a change happens, empty out the current showing archives and add an Ajax spinner graphic
  4. Figure out the year and date that we need by tinkering with the default WordPress values in the <option>
  5. Grab the selected category
  6. Launch an Ajax request to our getter file, POSTING the values we just collected
  7. Upon success, drop the markup into the #archive-pot
  8. Animate the wrapper to the new height needed

Little Important Note

Notice how the data that we POST is prefixed with “digwp_”? I had a little brainbender on this, while I was trying to POST data as like “m” and “month” and things like that. Apparently WordPress doesn’t like that. It can 404 an otherwise perfectly good URL for you when you pass it parameters (GET or POST) in that format. Check it out: http://digwp.com/?m=2007&y=3849&cat=w34we. So anyway, using your own prefixed parameters will save you that headache.

Taking it further

This is by no means limited to what we have done here. All were are doing it passing a getter file parameters that we want built into a query string. We could make checkboxes so you could pick multiple categories. We could include authors. We could include tags. The sky is the limit really.

Demo

I just did it right on this site, so you can see our own archives.

24 Responses

  1. Superb trick Chris! Will use this for sure!

  2. A great piece of functionality, which I’ll probably use as the basis for my own archive pages. I have both a suggestion and a question:

    As well as adding differant ways to filter, such as by author or a range of dates etc, which as you say, would be simple enough to extend, why not have the dynamic content area default to the current months archive. Probably ommited from your example for simplicity, but another nice way to extend it

    Also, is there a reason you’re using an intirely seperate template file and creating a page for the getter? Why not have it as a function in functions.php and use a template_redirect (or some other early hook)?

  3. Nice idea, I always like the live filtering. Information Architects used to have a search somewhat like Google instant that whittled down the archives results as you typed.

    Also, a great tip on prefixing post variables, I was hitting my head against the wall a while back trying to figure out why a simple contact form was failing…so, now everything gets a prefix.

  4. cooljaz124 October 29, 2010

    Nice ! But, will be much great if you add this functionality too.

    If we select a month, say September and then we click the category drop down -> The categories which have no post in September should grey out and can’t be selected.

    This way, I can save a lot of time if there are no real posts in some of the categories in a particular month.

  5. I’m not sure why htmlentities is used here. Presumably becuase of the &ampersand in the querystring, but htmlentities makes & into &, so not sure if that helps.

    You can use an array instead of a querystring to avoid this issue all together, in the same go saving wordpress from having to convert it.
    See more:
    http://codex.wordpress.org/Function_Reference/query_posts#Example_4

  6. Cool.
    How to do it for post types and custom taxonomies?

  7. how bout giving a download link? ;)

    • It’s just not something that makes much sense as a download. There is too many custom things you need to do that a set of downloaded files can’t do for you. However there it may be possible to pluginize this and I’ve looked at a few options, so we’ll see how that goes =) Glad you got it working.

  8. where does the dropdown code go in?

    trying it on my end, but doesnt seem to work.

  9. I’m trying to replicate and coming up short. Dropdown code (archive-browser) apparently goes in the archive.php template, yes? Their also appears to be some code missing after:
    “I just stuck it into the template itself instead:”
    Maybe:
    <div id="archive-wrapper"></div>

  10. Nice tutorial. It took a while …. but I managed to set it up on one of my blogs. Thanks (+1 Stumble).

  11. got it working finally ;)

  12. This is great, Chris! Thanks for posting. I would love to see how this would work with custom post types/taxonomies.

  13. ++1 for custom post-types/taxonomies

    I also like the idea of if you select a month or post-type, to only show the categories or taxonomies in the dropdown that are available for the first selection.

    Either way, great tutorial!

  14. Not working. Can you please fix code as Jim said. There is something missing from your code in this tutorial.

  15. Ok, i find out how to make this to work, but now, when i select category from drop down, webpage goes to that category, but not loading archive page with list of articles from that category.

  16. I’m a noob here, and although I get most of the parts right, I’m just a WP freak and not a PHP programmer.
    The question: how do you assemble all the parts together so you can display the page properly?

    I also see that you don’t communicate much with your commenters, I don’t want to waste my time and your time here, so please just let me know if there’s something I don’t get right.

    Much appreciated!

  17. Hello there, and thank you for a great trick!

    I have made a little modification and use it on my photo albums, and instead of fetching several posts, it just fetches one post and displays the whole post. Or, at least that’s the thought.

    I’ve made it all work as it should, except for one thing:

    Since I’m fetching and displaying the whole post (a photo gallery) with the_content, not in rows as in your example, I would like to change the following function so that it checks how tall the post are, and then give the archive-wrapper that height. As it is now, it becomes 50 pixels high, and nothing else shows.

    jQuery("#archive-wrapper").animate({
    height: jQuery("#archives-table tr").length * 50
    });

    I’ve tried experimenting with the overflow on #archive-wrapper, which works half way, the problem then is that my footer doesn’t get pushed down, and the content continues down under the page bottom.

    Hope there is a solution to this, I’m not much of a coder myself, and has come this far with trial, error and a bit of christmas luck perhaps.

    Take care and thank you again!

    // Jens.

  18. And of course I find the solution 2 minutes after I have posted my comment.
    Since I don’t know how these things work, I can’t really tell why it worked, but here’s the code I used to make it work! :)

    The archive pot height defines the height now I guess.

    jQuery("#archive-wrapper").animate({
    height: jQuery("#archive-pot").height()
    });

    Thanks again, if I remember I will post the result when I put the site up online!

  19. Grate tutorial Chris! Thanks for that. I am still working on it, trying to use check-boxes instead of drop-down.

  20. Do we have any chance to paginate the results of the dynamic content? If the result of the query is for example 60 posts, we’ll have a very, very long page, no? Many thanks.

  21. Hi, I tried to insert author’s dropdown but I didn’t be able to do that. Infact I inserted the dropdown authors list and I also tried to edit archive-getter but when I select an author it shows all posts…

  22. Jamal July 4, 2012

    Hi, i have tried this tutorial and its work good, but when i click on a category, than all posts were displayed… i have no idea were the mistake is.

Comments are closed. Contact us with any critical information. Thank you!

Code is poetry