Post #401

You are currently only viewing posts within the category: Site news
You are here: HomeArchiveSite news2004May19th → this post

The PHP code behind my archive page

19th May 2004, mid-afternoon | Comments (28)

About four times a week I get an email from someone keen to know how I coded my blog archive page — the headings and nested lists approach seems to be a decent way of presenting the information, and people wonder how the page is constructed. I usually reply to these emails saying that it’s a bit too complicated to explain, but today I’m relenting. I’ve posted some example code, below, and explained the basic concept behind it. It’s up to you guys to adapt it for your own purposes.

Getting started

The thing to note about this method is that everything is done with a single database call. All the logic that determines what gets printed where is managed by PHP. I’ve commented the code as heavily as I can, and I’ll hope that as you put it into action you’ll understand exactly what’s going on. If you get a bit confused, don’t worry, I still get a the occasional brain-twinge when I look at this stuff.

A simple example

Let’s start with a pretty simple set of data; we’ll take five posts, each assigned to a category, and we’ll display them using headings (for the categories) and unordered lists (for the posts).

  1. <?php
  2. /* what we're going to do here is produce a set of unordered lists, each topped by an h3 heading of the category. The tricky bit is that we're going to do it with only one SQL call. The logic below does a lot of "If the category name of the current record is the same as the last one then don't print the category name heading" sort of thing.
  3.  
  4. Given these results from our SQL query:
  5.  
  6. ---------------------------
  7. | Category | Post Title |
  8. ---------------------------
  9. | Category 1 | Post 1 |
  10. | Category 1 | Post 3 |
  11. | Category 1 | Post 4 |
  12. | Category 2 | Post 5 |
  13. | Category 3 | Post 2 |
  14. ---------------------------
  15.  
  16. This is the sort of output we can expect:
  17.  
  18. <h3>Category 1</h3>
  19.  
  20. <ul>
  21. <li>Post 1</li>
  22. <li>Post 3</li>
  23. <li>Post 4</li>
  24. </ul>
  25.  
  26. <h3>Category 2</h3>
  27.  
  28. <ul>
  29. <li>Post 5</li>
  30. </ul>
  31.  
  32. <h3>Category 3</h3>
  33.  
  34. <ul>
  35. <li>Post 2</li>
  36. </ul>
  37. */
  38.  
  39. // set query (you'll have to write this yourself, this is just an example)
  40. $query = "SELECT DISTINCT post_title, post_category FROM post ORDER BY post_category ASC, post_title ASC";
  41.  
  42. // run query
  43. $result = @mysql_query($query);
  44.  
  45. // if rows were returned from the query
  46. if (($result) && (mysql_num_rows($result) > 0))
  47. {
  48. // variables explained:
  49. // $current_stored_category -- this will hold the name of the category of the previous post, this will be compared to the category of the current post in order to determine if the category has changed.
  50. //$is_very_first_list -- this will be used to determine if the post in question is the very, very first post in the whole data set, if it isn't then we'll use that knowledge to print tags that will close off the list that was printed before it.
  51.  
  52. // set our variables to their initial values
  53. $current_stored_category = '';
  54. $is_very_first_list = 1;
  55.  
  56. // loop and fetch all the results
  57. while ($row = mysql_fetch_assoc($result))
  58. {
  59. $post_category = $row['post_category'];
  60. $post_title = $row['post_title'];
  61.  
  62. // if this post has a different category to the one stored in $current_stored_category
  63. if ($current_stored_category <> $post_category)
  64. {
  65. // set the stored category to equal this post's category
  66. $current_stored_category = $post_category;
  67.  
  68. // because we're in a new category we need to close off the last category's <ul>. However, we must check that we're not at the very, very beginning of the whole thing, because if we are there won't be a previous list to close off
  69. if ($is_very_first_list == 0)
  70. {
  71. print '<ul>'."\n";
  72. }
  73.  
  74. // we must also print the new category as an <h3> heading and start our new <ul>
  75. print '<h2>'.$post_category.'</h2>'."\n\n";
  76. print '<ul>';
  77. }
  78.  
  79. // now we print out the post item
  80. print '<li>'.$post_title.'</li>'."\n";
  81.  
  82. // now we alter this variable value so we know we're not at the very, very start of the whole thing anymore
  83. $is_very_first_list = 0;
  84. }
  85.  
  86. // now we print a closing <ul> to finish off the last record
  87. print '</ul>'."\n\n";
  88. }
  89.  
  90. // if no rows were returned from the query
  91. else
  92. {
  93. // error message goes here
  94. }
  95. ?>
  96. Download this code: 401a.txt

A more complicated example

You can apply this technique to practically anything that involves grouping of results; and if you need to add in an extra level of grouping (as I do in my archive pages, where I group by Month and then by Day as well) it’s not too hard to insert that extra functionality.

Just to show that, here’s an extension of the previous example, with a few more posts, and a set of sub-categories added in.

  1. <?php
  2. /* what we're going to do here is produce a set of nested lists, each topped by an h3 heading of the category. The tricky bit is that we're going to do it with only one SQL call. The logic below does a lot of "If the category name of the current record is the same as the last one then don't print the category name heading" sort of thing.
  3.  
  4. Given these results from our SQL query:
  5.  
  6. -------------------------------------------
  7. | Category | Sub-category | Post Title |
  8. -------------------------------------------
  9. | Category 1 | SubCat 1a | Post 1 |
  10. | Category 1 | SubCat 1a | Post 3 |
  11. | Category 1 | SubCat 1b | Post 6 |
  12. | Category 1 | SubCat 1c | Post 4 |
  13. | Category 2 | SubCat 2a | Post 5 |
  14. | Category 2 | SubCat 2a | Post 8 |
  15. | Category 2 | SubCat 2b | Post 7 |
  16. | Category 3 | SubCat 3b | Post 2 |
  17. | Category 3 | SubCat 3b | Post 9 |
  18. -------------------------------------------
  19.  
  20. This is the sort of output we can expect:
  21.  
  22. <h3>Category 1</h3>
  23.  
  24. <ul>
  25. <li>SubCat 1a
  26. <ul>
  27. <li>Post 1</li>
  28. <li>Post 3</li>
  29. </ul>
  30. </li>
  31. <li>SubCat 1b
  32. <ul>
  33. <li>Post 6</li>
  34. </ul>
  35. </li>
  36. <li>SubCat 1c
  37. <ul>
  38. <li>Post 4</li>
  39. </ul>
  40. </li>
  41. </ul>
  42.  
  43. <h3>Category 2</h3>
  44.  
  45. <ul>
  46. <li>SubCat 2a
  47. <ul>
  48. <li>Post 5</li>
  49. <li>Post 8</li>
  50. </ul>
  51. </li>
  52. <li>SubCat 2b
  53. <ul>
  54. <li>Post 7</li>
  55. </ul>
  56. </li>
  57. </ul>
  58.  
  59. <h3>Category 3</h3>
  60.  
  61. <ul>
  62. <li>SubCat 3b
  63. <ul>
  64. <li>Post 2</li>
  65. <li>Post 9</li>
  66. </ul>
  67. </li>
  68. </ul>
  69. */
  70.  
  71. // set query (you'll have to write this yourself, this is just an example)
  72. $query = "SELECT DISTINCT post_title, post_category, post_subcategory FROM post ORDER BY post_category ASC, post_subcategory ASC, post_title ASC";
  73.  
  74. // run query
  75. $result = @mysql_query($query);
  76.  
  77. // if rows were returned from the query
  78. if (($result) && (mysql_num_rows($result) > 0))
  79. {
  80. // variables explained:
  81. // $current_stored_category -- this will hold the name of the category of the previous post, this will be compared to the category of the current post in order to determine if the category has changed.
  82. // $is_very_first_list -- this will be used to determine if the post in question is the very, very first post in the whole data set, if it isn't then we'll use that knowledge to print tags that will close off the list that was printed before it.
  83. // $current_stored_subcategory -- this will hold the name of the sub-category of the previous post, this will be compared to the sub-category of the current post in order to determine if the sub-category has changed.
  84. // $is_very_first_sublist -- this will be used to determine if the post in question is the very, very first post in the category data set, if it isn't then we'll use that knowledge to print tags that will close off the list that was printed before it.
  85.  
  86. // set our variables to their initial values
  87. $current_stored_category = '';
  88. $is_very_first_list = 1;
  89. $current_stored_subcategory = '';
  90. $is_very_first_sublist = 1;
  91.  
  92. // loop and fetch all the results
  93. while ($row = mysql_fetch_assoc($result))
  94. {
  95. $post_category = $row['post_category'];
  96. $post_subcategory = $row['post_subcategory'];
  97. $post_title = $row['post_title'];
  98.  
  99. // if this post has a different category to the one stored in $current_stored_category
  100. if ($current_stored_category <> $post_category)
  101. {
  102. // set the stored category to equal this post's category
  103. $current_stored_category = $post_category;
  104.  
  105. // reset the subcategory vars
  106. $current_stored_subcategory = '';
  107. $is_very_first_sublist = 1;
  108.  
  109. // because we're in a new category we need to close off the last category's <ul>. However, we must check that we're not at the very, very beginning of the whole thing, because if we are, there won't be a previous list to close off
  110. if ($is_very_first_list == 0)
  111. {
  112. print '</ul>'."\n";
  113. print '</li>'."\n";
  114. print '</ul>'."\n";
  115. }
  116.  
  117. // we must also print the new category as an <h3> heading and start our new <ul>
  118. print '<h2>'.$post_category.'</h2>'."\n\n";
  119. print '<ul>'."\n";
  120. }
  121.  
  122. // if this post does have the same category as the one stored in $current_stored_category
  123. else
  124. {
  125. // alter this value so we know we're not at the start of a sublist
  126. $is_very_first_sublist = 0;
  127. }
  128.  
  129. // if this post has a different subcategory to the one stored in $current_stored_subcategory
  130. if ($current_stored_subcategory <> $post_subcategory)
  131. {
  132. // set the stored subcategory to equal this post's subcategory
  133. $current_stored_subcategory = $post_subcategory;
  134.  
  135. // because we're in a new subcategory we need to close off the last subcategory's <li>. However, we must check that we're not at the very, very beginning of the whole category, because if we are, there won't be a previous list to close off
  136. if ($is_very_first_sublist == 0)
  137. {
  138. print '</ul>'."\n";
  139. print '</li>'."\n";
  140. }
  141.  
  142. // now we print out the subcategory item and start off its new list
  143. print '<li>'.$post_subcategory'."\n";
  144. print '<ul>'."\n";
  145. }
  146.  
  147. // now we print out the post item
  148. print '<li>'.$post_title.'</li>'."\n";
  149.  
  150. // now we alter this variable value so we know we're not at the start of the whole thing anymore
  151. $is_very_first_list = 0;
  152. $is_very_first_sublist = 1;
  153. }
  154.  
  155. // now we print a closing <ul> to finish off the last record
  156. print '</ul>'."\n";
  157. print '</li>'."\n";
  158. print '</ul>'."\n\n";
  159. }
  160.  
  161. // if no rows were returned from the query
  162. else
  163. {
  164. // error message goes here
  165. }
  166. ?>
  167. Download this code: 401b.txt

Finito

And that’s about it. Just plug in your own database call and variable names and you should be good to go.

Jump up to the start of the post


Comments (28)

Jump down to the comment form ↓

  1. Pointybeard:

    Hey, interesting article. Actaully about 2 weeks ago i was looking thru your archive and i fell in love with the layout, so i borrowed it, and redid my own archive code to use that layout. I do it in a similar way to you, but of course, since i never saw your php i did MY way. Thanks for the idea. ;) Maybe soon everyone will have an archive that looks like yours.

    Posted 35 minutes after the fact
  2. Dunstan:

    I should point out that there's a WordPress hack for this:
    http://wiki.wordpress.org/index.php/MtDewVirus%20Archives

    So if you like my archive format and you run WordPress, check that out.

    Posted 3 hours, 31 minutes after the fact
    Inspired: ↓ Carla
  3. Jim:

    NDT going opensource eh? :) Thanks for sharing, one of these days I'll get my head around this.

    Posted 7 hours, 23 minutes after the fact
  4. Mike P.:

    Cool to see this Dunstan. I'm trying to get my head around how to do categories on our blog. I'd like to do something similar to the way Opera M2 treats e-mail. Sort of have it so that every post can belong to multiple categories and sub-cats etc. I haven't really put a lot of thought into it yet, but it sure would be useful...

    Posted 8 hours, 18 minutes after the fact
  5. Hannes:

    Ouch. Use arrays, and the script is much much easier, faster and shorter.

    Look here: http://dl.magiccards.info/dunstan/dunstansource.php

    -hannes

    Posted 9 hours, 19 minutes after the fact
    Inspired: ↓ Dunstan
  6. Rob Mientjes:

    I love your code, but I use WordPress, and that hack is quite nice. Still, going public with your code... gutsy!

    Posted 9 hours, 48 minutes after the fact
  7. Hans:

    Oooooh! Aaaaah! the master shares his secrets!
    That's a great layout and organization on your arcihve page... I may implement a variation on my site once Dean does a few more things with TextPattern...

    Thank you!

    Posted 12 hours, 54 minutes after the fact
  8. Rahul:

    I have something similar working on my webzine that collects all articles from the issue the current article is in and divides them up by theme, category, and type. It all runs on one query.. a very complicated one, mind you, with several joins - when I look at it without being in SQL mode I have no idea how I got it to work at all.

    Anyway, it's a good archive page but I always noticed one little bug... you have the same text linking to different areas (for instance, "8th" for two seperate months linking to /2004/04/18 or /2004/05/18). Isn't that an accessibility hazard (13.1.2 of the WCAG - http://www.w3.org/TR/WAI-WEBCONTENT/#tech-meaningful-links )?

    I guess that's a conscious design choice on your part?

    Posted 13 hours, 7 minutes after the fact
    Inspired: ↓ Dunstan
  9. Swt GA HunnyB:

    You really do have a wonderful page. I've enjoyed having you on my blogroll and visiting each chance I get! :)

    Posted 17 hours, 43 minutes after the fact
  10. Amit Karmakar:

    Mine is fairly simplistic on similar lines though.
    http://www.karmakars.com/weblog/archives/2004/03/15/customised_archive_page/>
    check is out

    Output here http://www.karmakars.com/weblog/archives.php

    Posted 1 day, 3 hours after the fact
  11. Amit Karmakar:

    Oops that should read http://www.karmakars.com/weblog/archives/2004/03/15/customised_archive_page/

    Posted 1 day, 6 hours after the fact
  12. Kris:

    hey d

    just thought you'd like to know that your pre/code containers don't display properly in opera (7.2, win 2k). we've a left-right scroll bar but not an up-down, and no way of viewing the overflow content.

    (although firefox doesn't seem to have issues, nor netscape 6, ie6...)

    Posted 1 day, 10 hours after the fact
    Inspired: ↓ Dunstan
  13. Rudy:

    oh, you oughta see how drop-dead simple this sort of thing is in coldfusion

    here's your "more complicated" example:

    <cfoutput query="foo"
    group="category">
    <h3>#category#</h3>
    <ul>
    <cfoutput
    group="subcategory">
    <li>subcategory
    <ul>
    <cfoutput>
    <li>#posttitle#</li>
    </cfoutput>
    </ul>
    </li>
    </cfoutput>
    </ul>
    </cfoutput>

    i guess to each his own, eh

    if you like coding all the loops and variables and "if current = previous" doubly nested logic yourself, then i guess you'll prefer php

    personally, i prefer the clean, simple, dare i say elegant coldfusion approach

    the semantics of the GROUP= parameter is *exactly* right for this example

    Posted 2 days after the fact
    Inspired: ↓ Pointybeard
  14. Pointybeard:

    Look like a templating thing to me. Easy enuff todo in php. Most of my site is driven by templates, XHTML and XML templates, driven by a fairly simple engine. I've never used CF tho, i assume the code you've shown is native to CF?

    Posted 2 days, 4 hours after the fact
    Inspired by: ↑ Rudy
    Inspired: ↓ Rudy, ↓ Pointybeard
  15. Rudy:

    native? i guess so (not exactly sure what "native" means)

    Posted 2 days, 8 hours after the fact
    Inspired by: ↑ Pointybeard
    Inspired: ↓ Pointybeard
  16. Pointybeard:

    heh. yea, come to think of it, that is a weird statement. Well i knew what i meant. lol. Just was pointing out that it looks very much like alot of templating i see done in php, but todo that in php you need some sort of template engine like smarty or pear. :p Ignore my ignorance. ;)

    Posted 2 days, 12 hours after the fact
    Inspired by: ↑ Pointybeard, ↑ Rudy
  17. Robert Lofthouse:

    I wouldn't say Cold Fusion has as many advantages as PHP. I've used it extensively for a large database oriented web site, and I still prefer PHP. In my opinion ASP and CF will never compete with PHP.

    Yes you can do things in 5 lines that it would take another language about 20 lines to do, but CF will never be as fast/powerful as PHP.

    Besides, CFML won't help you learn many of the other scripting/programming languages that are "required" in this day and age.

    Anyways, each to their own.

    *I loves ma open-source*

    Posted 2 days, 23 hours after the fact
  18. Dunstan:

    Hannes, that's excellent, thanks very much for posting your code, I think I'll implement that on my site.

    Always happy to be shown the optimum way to code PHP or SQL: most of my code is highly logical, but I often lack the knowledge to really get it running fast. Thanks again!

    Posted 5 days after the fact
    Inspired by: ↑ Hannes
    Inspired: ↓ Hannes, ↓ Joachim Krebs
  19. Dunstan:

    Rahul, it could be an accessibility problem, but I do use quite verbose title text for each link, so I think that gets around the issue. And, yes, it was a conscious design decision; I think the benefits of being able to link to any comment, post, day, month or year, outweigh any problems that duplicated link-text might bring.

    Posted 5 days, 1 hour after the fact
    Inspired by: ↑ Rahul
  20. Dunstan:

    They don't work as intended in Safari either, Kris; I have to do something about that... but thanks for the heads up!

    Posted 5 days, 1 hour after the fact
    Inspired by: ↑ Kris
  21. Hannes:

    You're welcome.

    Sometimes you just don't see the easier solution. I once wrote a similar script and suddenly I realized I could do it easier.

    Posted 5 days, 18 hours after the fact
    Inspired by: ↑ Dunstan
  22. Hans:

    rm, just out of curiosity, is there any reason why you've not posted in a week? (Perhaps Dunstan has finally jumped into the Pacific-- and gone with the dolphins.)

    Posted 1 week after the fact
  23. Kirk:

    Its fancy allright, but not as fancy as your css-skills. I wanna give you credit for having your site optmized for Mozilla rather then IE. Im really impressed that there isnt any tables at all. CSS its really underestimate. Keep it up!

    Posted 1 week after the fact
  24. Joachim Krebs:

    And that's why open-source code is a good thing.

    Posted 1 week, 2 days after the fact
    Inspired by: ↑ Dunstan
  25. Mary:

    Really nice. :)

    if ($is_very_first_list == 0)
    {
    print '<ul>'."n";
    }

    Shouldn't that be a closing tag ('</ul>')?

    Posted 1 week, 3 days after the fact
  26. Franceso Natali:

    Nice code. But to adopt it the PHP skills are needed.

    Posted 2 weeks, 4 days after the fact
  27. Andry:

    Thank you,Mary!

    Posted 1 month, 1 week after the fact
  28. Carla:

    Bless you! I was hoping I'd find something sensible and well-explained re: archives on your site--and am not surprised I did. Thank you!

    Posted 7 months, 3 weeks after the fact
    Inspired by: ↑ Dunstan

Jump up to the start of the post


Add your comment

I'm sorry, but comments can no longer be posted to this blog.