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";
}

?>

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

  • kevin says:

    hello mac
    I want the subcategories of specific category to be displayed. Suppose, I have one categories “Animals” . When I click on “Animals” I should get all the subcategories,
    /#comment-206 is fine but i show subcategories title, not post title.
    I hope you can help me!

  • Jhonny says:

    I wast use custom type posts. I tried changing post_type. But it does not work. Any help?

    • Mac McDonald says:

      Just be sure you are using the post type, not the name. The post type cannot contain any capital letters or spaces.

      • Jhonny says:

        This is how I have

        $post_type = 'staff-member';

        • Mac McDonald says:

          Please put your template in a pastebin and reply with a link to it using the Contact Me form.

          • Jhonny says:

            Hi, Mac thanks for you reply. This what I have http://pastebin.com/gHq29qrY
            Basically I modified the bottom part for the taxonomy but the nav on top only generating all taxonomy terms. I want it to specify only pull from staff meme

            —Taxonomy – Staff Members
            — Term -> Full-time, staff, part time.

          • Mac McDonald says:

            I want it to specify only pull from staff meme

            —Taxonomy – Staff Members
            — Term -> Full-time, staff, part time.

            You did not make that clear at first! I still don’t know whether you want to pull all Staff Members terms, or only ‘staff’. Assuming you want all terms, try this for your variables:

            $post_type = 'staff-member';
            $taxonomy = 'staff-members';  // Use slug, not name
            $terms = array( 'full-time', 'staff', 'part-time' );  // Use slugs, not names
            $terms_string = "'" . implode("', ", $terms) . "'";
            

            and this for your query:

            $letters = $wpdb->get_col(
            "SELECT DISTINCT LEFT(p.post_title,1) AS last_letter
            FROM $wpdb->posts p
            JOIN $wpdb->term_relationships tr ON p.ID = tr.object_id
            JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
            JOIN $wpdb->terms t ON tt.term_id = t.term_id
            WHERE p.post_type = '$post_type'
            AND p.post_status = 'publish'
            AND tt.taxonomy = '$taxonomy'
            AND t.slug IN ($terms_string)
            ORDER BY last_letter ASC
            ");
            

            If you need any further help, do not reply to this post – use the Contact Me form!

  • Naama says:

    I was so thrilled to find your code which just WORKED as it was pasted in after 2 weeks of searching for a working solution! Thank you for posting this!

    I need to make a change to the query for the alphabet links so that it queries a specific post-type with a specific pair of meta_key and meta_value. I tried changing the SELECT but it is coming out empty.

    This is what I tried

    $letters = $wpdb->get_col(
    "SELECT DISTINCT LEFT($wpdb->posts.ID_title,1) AS first_letter FROM $wpdb->posts, $wpdb->postmeta
    WHERE $wpdb->posts.ID = $wpdb->postmeta.post_id AND
    $wpdb->posts.post_status = 'publish' AND
    $wpdb->posts.post_type = '$post_type' AND
    $wpdb->postmeta.meta_key = '$meta_key' AND
    meta_value = '$meta_value'
    ORDER BY first_letter ASC"
    );

    Can you help me get this working otherwise my whole attempt is again useless.

    Thank you so much!

    • Mac McDonald says:

      Your code looks correct. How did you set $post_type, $meta_key and $meta_value?

      • Naama says:

        $post_type = ‘directory-listing';
        $meta_key = ‘services';
        $meta_value = ‘Cloud Storage';

        I also changed the query lower down to

        $posts_per_page,
        'meta_query' => array(
        array(
        'key' => $meta_key,
        'value' => $meta_value,
        'compare' => 'LIKE') ),
        'post_type' => $post_type,
        'orderby' => 'title',
        'order' => 'ASC',
        'paged' => $paged,
        'caller_get_posts' => 1
        );

        This query works fine by itself but after I modified the first one this returns no results.

        It still prints out the “ABCDE….” but none of them are linked and it says no results found.

        Any ideas?? Thank you so much for your time!

  • Marie says:

    Hi Mac,

    thank you for that useful snippet!
    I’m trying to highlight the current letter that’s being displayed in the navigation – could you please help me with that? I thought about adding a class (e.g “current”) to the navigation item that’s currently being displayed, but I don’t know how to do that…

    • Mac McDonald says:

      Hi Marie,

      You are on the right track with adding the class. Try changing the navigation code to this (untested):

                  foreach ($letters as $letter) {
                     $url = add_query_arg('first_letter',$letter,$pageURL);
                     $class = ($letter == $first_letter) ? 'class="current"' : '';
                     echo "<a $class href='$url' title='Starting letter $letter' >[ $letter ]&nbsp;&nbsp;</a>";
                  }
      
  • 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

          • Camila Guimarães says:

            Hi Mac, in this last code, how to modify code to group numbers into same link, like: [0-9] [A] [B] [C]… Thanks a lot!

          • Mac McDonald says:

            @Camila,

            This is not a simple change. See this pastebin for some sample code: http://pastebin.com/cbcDgxaD

          • Camila Guimarães says:

            Thanks, Mac! I followed your code and now it’s working, just changed > by < on line 116.

            Can you give me another help? In this new code, how can I put a "class" to the current item on navigation?

          • Camila Guimarães says:

            Hey Mac, I found solution: $class = (mb_substr($this_grp,-1) == mb_substr($pageURL,-1)) ? ‘class=”current”‘ : ”;

            Thank you! :)

  • 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 http://www.recipemagician.com/?page_id=5814&preview=true

    • 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>