Template to List Posts By First Letter of Title, 1 Letter Per Page

This template is a variation of the one that shows posts or pages by first letter of title with a selected number of posts per page.

The template is written for the Twenty Ten theme.  You will probably need to modify it to tweak the appearance or work with a different theme.  The embedded CSS should really be moved to your stylesheet.

<?php
/*
Template Name: A-Z Pages by Letter

A WordPress template to list page titles by first letter.

You should modify the CSS to suit your theme and place it in its proper file.
Be sure to set the $posts_per_row and $posts_per_page variables.
*/

// This function should go in functions.php
function mam_posts_where ($where) {
   global $mam_global_where;
   if ($mam_global_where) $where .= " $mam_global_where";
   return $where;
}
add_filter('posts_where','mam_posts_where');

$posts_per_row = 3;
$posts_per_page = -1;
$pageURL = 'http';
$post_type = 'post';

if ($_SERVER["HTTPS"] == "on") {$pageURL .= "s";}
$pageURL .= "://";
if ($_SERVER["SERVER_PORT"] != "80") {
 $pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
} else {
 $pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
}

$letters = $wpdb->get_col(
"SELECT DISTINCT LEFT(post_title,1) AS first_letter FROM $wpdb->posts
WHERE post_type = '$post_type' AND post_status = 'publish'
ORDER BY first_letter ASC"
);

$first_letter = ($_GET['first_letter']) ? $_GET['first_letter'] : $letters[0];
?>

<?php get_header(); ?>

<style type="text/css">
.letter-group { width: 100%; }
.letter-cell { width: 5%; height: 2em; text-align: center; padding-top: 8px; margin-bottom: 8px; background: #e0e0e0; float: left; }
.row-cells { width: 70%; float: right; margin-right: 180px; }
.title-cell { width: 30%;  float: left; overflow: hidden; margin-bottom: 8px; }
.clear { clear: both; }
</style>

<div id="main-background">

   <div id="main-column">
      <h1><?php the_title(); ?></h1>

      <div class="margin-top"></div>

      <div id="a-z">

         <?php
         $mam_global_where = " AND LEFT(post_title,1) = '$first_letter' ";
         $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
         $args = array (
            'posts_per_page' => $posts_per_page,
            'post_type' => $post_type,
            'orderby' => 'title',
            'order' => 'ASC',
            'paged' => $paged,
            'caller_get_posts' => 1
         );
         query_posts($args);
         $mam_global_where = '';  // Turn off filter
         if ( have_posts() ) {
            $in_this_row = 0;
            while ( have_posts() ) {
               the_post();
               $first_letter = strtoupper(substr(apply_filters('the_title',$post->post_title),0,1));
               if ($first_letter != $curr_letter) {
                  if (++$post_count > 1) {
                     end_prev_letter();
                  }
                  start_new_letter($first_letter);
                  $curr_letter = $first_letter;
               }
               if (++$in_this_row > $posts_per_row) {
                  end_prev_row();
                  start_new_row();
                  ++$in_this_row;  // Account for this first post
               } ?>
               <div class="title-cell"><a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title_attribute(); ?>"><?php the_title(); ?></a></div>
            <?php }
            end_prev_letter();
            ?>
            <div class="navigation">
            <?php
            foreach ($letters as $letter) {
               $url = add_query_arg('first_letter',$letter,$pageURL);
               echo "<a href='$url' title='Starting letter $letter' >[ $letter ]&nbsp;&nbsp;</a>";
            }
            ?>
            </div>
         <?php } else {
            echo "<h2>Sorry, no posts were found!</h2>";
         }
         ?>

      </div><!-- End id='a-z' -->

   </div><!-- End class='margin-top -->

</div><!-- End id='rightcolumn' -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

<?php
function end_prev_letter() {
   end_prev_row();
   echo "</div><!-- End of letter-group -->\n";
   echo "<div class='clear'></div>\n";
}
function start_new_letter($letter) {
   echo "<div class='letter-group'>\n";
   echo "\t<div class='letter-cell'>$letter</div>\n";
   start_new_row($letter);
}
function end_prev_row() {
   echo "\t</div><!-- End row-cells -->\n";
}
function start_new_row() {
   global $in_this_row;
   $in_this_row = 0;
   echo "\t<div class='row-cells'>\n";
}

?>

56 Responses to Template to List Posts By First Letter of Title, 1 Letter Per Page

  • Calliope says:

    Hi Mac, this is working very well for me. I am wondering how I can display the full alphabet in the menu, with only the letters that have posts under those letters showing a link.

    Right now it’s looking like this: [8] [D] [P]

    But I would like to have all letters of the alphabet visible, even if there are no posts yet for those letters … [8] A B C [D] E F G H …

    What part of the code should I change to make that happen, is it possible?

    Thanking you in advance,

    Calliope

    • macadmin says:

      I think you can get what you want by replacing this:

                  foreach ($letters as $letter) {
                     $url = add_query_arg('first_letter',$letter,$pageURL);
                     echo "<a href='$url' title='Starting letter $letter' >[ $letter ]  </a>";
                  }
      

      with this:

                  foreach ( range('A', 'Z') as $letter ) {
                     if (in_array($letter, $letters) ) {
                        $url = add_query_arg('first_letter',$letter,$pageURL);
                        echo "<a href='$url' title='Starting letter $letter' >[ $letter ]  </a>";
                     } else {
                        echo "&nbsp; $letter &nbsp;&nbsp;";
                     }
                  }
      
      • Calliope says:

        That works! Thank you Mac.

        Is it possible to include numbers (1 to 9) in the above array? I am making navigation for a directory and some of the post titles start with a number. I have been reading about php arrays using range, and I see that they are either for numbers or for letters.

        Calliope

        • macadmin says:

          Try replacing this:

          foreach ( range('A', 'Z') as $letter ) {
          

          with this:

          $first_chars = array_merge( range(0,9), range('A','Z') );
          foreach ( $first_chars as $letter ) {
          
          • Calliope says:

            Worked! Thanks again for your help, I really appreciate it and wish you a Happy New Year. I’ve credited your code on the child theme I created.

            Calliope

  • Ruriko says:

    I don’t use the Twenty Ten theme I have my own custom theme. The code doesn’t seem to work. I end up getting a blank page. What could be wrong?

  • Rocky says:

    Just another question.. Everything above is working properly, but I want a [ALL] text alongside the [A] [B] that would display all the posts of that category.

    • Mac McDonald says:

      I can’t think of an easy way to do that right now.

      Creating an ALL choice for each letter will make the menu extremely long.

      Creating a single ALL might involve creating a form for the menu with a checkbox for the ALL choice – fairly major modification to the code.

      If I think of an easier way, I will let you know.

  • Rocky says:

    This works like a charm! But i need to tweak it a little bit, I hope you can help me!

    I want the posts of specific categories to be displayed. Suppose, I have two categories “Cats” and “Dogs”. When I click on “Cats” I should get all the posts, 1 letter per page related to that category and its sub-categories! I have made a change. I have put ‘cat’ => 13, in the $args array. I am getting the posts related to category 13 but the problem is the navigation of the letters. In category 13 I have posts starting with letters “a” and “b”, but the code is displaying letters “c” and “d” also in the navigation part

    Could this be done? If yes, what changes need to be made.

    • Mac McDonald says:

      You only need to change the query that selects the letters. Try changing this:

      $letters = $wpdb->get_col(
      "SELECT DISTINCT LEFT(post_title,1) AS first_letter FROM $wpdb->posts
      WHERE post_type = '$post_type' AND post_status = 'publish'
      ORDER BY first_letter ASC"
      );
      

      to this:

      $letters = $wpdb->get_col(
      "SELECT DISTINCT LEFT(post_title,1) AS first_letter FROM $wpdb->posts p
      JOIN $wpdb->term_relationships tr ON (p.ID = tr.object_id AND tr.term_taxonomy_id = 13)
      WHERE post_type = 'post' AND post_status = 'publish'
      ORDER BY first_letter ASC"
      
  • Steve says:

    Hi Guys, nice code.
    I am wondering if it can work with authors’ list. is that possible and how?
    I tried to play with the code by no hope
    Please help me, I need this urgently
    Thank You guys
    Seteve

    • Mac McDonald says:

      Hello Steve,

      Do you have code now that is working to give an author’s list with the information you want? If so, please post it in a pastebin, and use the Contact Me page to send me a link to it.

  • Mike says:

    Well, it was a temporary draft page and probably the drafts are available in wordpress only to admins or users that are allowed to view unpublished content. I do not want to loose your precious time so if you can manage to fix the code and would not mind to share it, It would be wonderful. If not, please do not hesitate as I am sure you have lots of daily tasks and nicer things to do ;)

    Thank you anyway for the help or at least the willing to help :)

  • Mike says:

    btw, I do not know whether I am allowed to post links, but probably it would be easier if you can see the results?

    Here is the link

    • Mac says:

      Hi Mike,

      I see what is causing the problem, but I do not yet have a fix. I will post again when I find an answer.

      Unfortunately, your link gives a 404 error, but I believe I do not need it.

  • Mike says:

    Well it turned out that for 3 years of post writing and using cyrillic in post titles we have managed to get an array of all this letters and symbols:

    ” & 0 1 2 3 4 5 7 8 9 B J K T U Y А Б В Г Д Е З И К Л М Н О П Р С Т У Ф Х Ц Ч Ш Я

    So what I would love to have is to group all the numbers and other symbols different from letters in a group [0-9-&] [А] [Б] etc. until the end of the alphabet. So how to group the posts with all the digits and symbols in one link?

    • Mac says:

      I don’t know if all of the code is multi-byte character compatible and I have not tested this, but it is worth a try. Change this:

                     $first_letter = strtoupper(substr(apply_filters('the_title',$post->post_title),0,1));
                     if ($first_letter != $curr_letter) {
      

      to this:

                     $first_letter = strtoupper(substr(apply_filters('the_title',$post->post_title),0,1));
                     if (strpos('&0123456789',$first_letter) !== false) $first_letter = '&-0-9';
                     if ($first_letter != $curr_letter) {
      
      • Mike says:

        Hi Mac,

        thank you very much for your fast reply. I really really appreciate your willing to help, however something is missing in the code you have kindly provided as it is still showing all the digits and letters even thoug I tested it with English alphabet and digits mix on fresh wordpress installation. Any ideas?

  • Mike says:

    Hi Mac,

    I have tested your piece of code and it worked like a charm. I only need to polish the CSS style now.

    I have a question though how can I show before the letters another link that can search for any posts starting with a digit (for example: [ 0-9 ] ). Otherwise if I have 10 posts starting with the digits from 0 to 9 and 30 posts for all the letters the list would become too long and most likely would overflow one line?

    Thanks in advance for your help and support!

  • Aina says:

    Hi Mac,

    Thk!!!

  • Aina says:

    Hi,

    Fantistic is work perfect :)

    I have a question, it’s possible to list all the letters??, not only the existing first letter.

    Thks!

    Sorry for my english :/

    • Mac says:

      It would not be possible without major changes to the template. Besides, the letters without posts would link to empty pages with ‘No Posts Found’ messages.

  • migo says:

    Sorry for the bump, but the code is missing;

    I mean is for page title: Archive A - Domain.Com , Archive A - Domain.Com and so on.

    Thanks

    • Mac says:

      This has not been exhaustively tested, but I think it will work.

      Add code to get the domain under the line that sets $first_letter:

      $first_letter = ($_GET['first_letter']) ? $_GET['first_letter'] : $letters[0];
      
      $siteurl = get_option('siteurl'); //or home
      $domain_terms = explode('/', str_replace('www.', '', str_replace('http://', '', $siteurl)));
      $domain = $domain_terms[0];
      

      And change this:

         <h1><?php the_title(); ?></h1>
      

      to this:

            <h1><?php echo "Archive $first_letter - $domain"; ?></h1>
      
  • migo says:

    Hello Mac,

    I really like this template. but I would like to customize the url of the page become more search engine friendly like:
    domain.com/archives/a
    domain.com/archives/b

    also on page Archive A – Domain.Com , Archive A – Domain.Com and so on.

    Please help me,
    Thank You

  • Majed says:

    Could you help me regarding the Arabic letters.see my previous replay.

    • Mac says:

      Try changing this:

      function start_new_letter($letter) {
         echo "<div class='letter-group'>\n";
         echo "\t<div class='letter-cell'>$letter</div>\n";
         start_new_row($letter);
      }
      

      to this:

      function start_new_letter($letter) {   
         echo "<div class='letter-group'>\n";   
         echo "\t<div class='letter-cell'>" . mb_convert_encoding($letter,'UTF-8', mb_detect_encoding($letter) ) . "</div>\n";
         start_new_row($letter);
      }
      
  • Majed says:

    Good day,

    I have problem with letter-cell.when I tray to use Arabic letters for example it shows others samples could you help? while I have no problem letter row (where all letters listed as links)

  • Majed says:

    Good day,

    I would like to customize every letter ( [A] [B [C]….etc] which means I need to give a .css properties to every letter , how can I do that?

    • Mac says:

      Try changing this line (line 98):

       
                     echo "<a href='$url' title='Starting letter $letter' >[ $letter ]&nbsp;&nbsp;</a>";
      

      to this:

                     echo "<a href='$url' title='Starting letter $letter' ><span class='letter-$letter'>[ $letter ]</span>&nbsp;&nbsp;</a>";
      
  • Majed says:

    Thank you very much for the excellent support

  • Mac says:

    Did you set $post_type = ‘post’ at the top?

    This has caused a lot of confusion, so I will change it now in the source.

  • Majed says:

    I am using itheme2. when I use this code in my new page template it show the pages not the posts!!

  • Poli says:

    Yes. I’ve done that. It has to do with moving to another page so I’m looking for something to do

    Thanks all the same.

  • Poli says:

    I did tried the unedited code but got the same result. Still if you’re happy with the solution then I’ll keep it. Another issue, but which has nothing to do with the script itself, is that I’ve been trying to attach a :focus to the $letter in order to style it when on the page related to a letter. However, nothing works for me. Any ideas?

    By the way, are you for hire? Sometimes I wouldn’t mind paying for help with bits and pieces.

    Thanks

    • Mac says:

      Did you notice that there is sample CSS in the code? You should remove that and place it in your style.css. Then you can modify it to suit.

      Sorry, I don’t do work for hire. This is only a hobby for me, and I don’t want it to be anything more.

  • Poli says:

    Hello Mac,

    It worked perfectly! Now i have it displaying by meta_value also. The only bit I couldn’t make work was

    $first_letter = strtoupper(substr($post->meta_value,0,1)); in order to display the first letter.

    I managed to display the first letter with

    $business = get_post_meta( $post->ID, ‘business’, true );
    $first_letter = strtoupper(substr($business,0,1));

    Do you have a neater solution?

    Thanks
    Poli

  • Poli says:

    Now it does. Thanks. I will send you a link when the site is live so you can see how I’m using your template.

    Many thanks

    Poli

  • Poli says:

    That’s fantastic! And thank you for your quick reply – although Pastebin doesn’t seem to recognize the paste’s ID so I couldn’t look at the code. Why that may be?

    Poli

    • Mac says:

      During testing, I discovered a bug and had to replace the pastebin. I have updated the comment now with the correct link.

  • Poli says:

    Hello Mac,

    I really like this template and it works great. The display of one letter per page is a big plus. I have been trying to make it work for displaying by custom field rather than by title but without success. How will be possible to achieve this? It was easy enough to select the letters by meta_value but I’m stuck on the filter. I’d really appreciate some help.

    Thanks
    Poli

  • Adam says:

    Been looking for this functionality for a while.

    One question though; is there anyway I can move the position of the navigation list above both the current letter and post list?

    • Mac says:

      I have not tested it, but I think you can put this just after line 74 ($in_this_row = 0;):

               ?>
               <div class="navigation">
                  <?php
                  foreach ($letters as $letter) {
                     $url = add_query_arg('first_letter',$letter,$pageURL);
                     echo "<a href='$url' title='Starting letter $letter' >[ $letter ]&nbsp;&nbsp;</a>";
                  }
                  ?>
               </div>
               <?php
      
  • Daniel says:

    I got it working (maybe it is not correct php but it works) :
    echo "";
    echo " $letter ";
    echo "";

    and css for this is:
    .letter { display:inline; text-align: center; padding: 2px 5px; border-top: 1px solid #48465B; border-left: 1px solid #48465B; background: #18181C; margin: 0 5px;}
    working example here:
    http://www.4ella.com/clubs/regione-e-lettera/?first_letter=P

  • Daniel says:

    I start to love this site , thank you for very quick and as always in previous tips very good working snippet.

    • Daniel says:

      Mac one more question : If I want to customize every letter , instead of [A] I would like to give a .css properties to every letter , how can I call the class?
      let’s say I will want to style it :
      .letter { text-align: center; padding: 2px 5px; border-top: 1px solid #48465B; border-left: 1px solid #48465B;background: #18181C; margin-right: 10px;}

Leave a Reply

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


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>