Driving on MT: Properly Punctuated Entry Categories List

Tuesday, October 30, 2007

I think I’ve figured out why I like Movable Type—it lets me be as anal as I want to be. If I want to micromanage how the list of categories on an entry are displayed, it responds with, “Well, ok, let’s sit down and think about this. I’m sure there’s a way we can do what you want to do.”

WordPress, on the other hand, responds with, “What? That’s stupid. Why would anybody do it that way? That makes no sense. The default version is fine.”

I appreciate software that indulges my obsessions.

This is the first in a series of posts I’ll be doing of MT hacks. Clearly, no sane person would want to do what I’m doing. But hopefully you’ll find some useful information about how MT works.

In this case, I wanted to present a list of categories in a conversational way. Typically, blog software gives you something like this:

Filed under: announcements, Movable Type, News Goat, Web Design

Which certainly provides you with information, but it seems a bit cold, and hardly worthy of being wrapped in a paragraph tag. I wanted something more like this:

Posted October 30, 2007 in the announcements, Movable Type, News Goat, and Web Design categories.

More importantly, I wanted the grammar and punctuation to be correct, no matter how many categories a post had.

Posted October 30, 2007 in the announcements category. Posted October 30, 2007 in the Movable Type and News Goat categories.

For those that just want the code, here it is:

<mt:SetVar name="lastcat" value="">
<p>
   Posted <$MTEntryDate format="%x"$> in the 
   <a href="<MTEntryLink archive_type="Category">"><MTEntryCategory></a>
   <MTEntryAdditionalCategories>
      <mt:SetVarBlock name="lastcat"><MTCategoryLabel></mt:SetVarBlock>
   </MTEntryAdditionalCategories>
   <mt:SetVarBlock name="catcount">
      <MTEntryAdditionalCategories glue="1"></MTEntryAdditionalCategories>
   </mt:setvarblock>
   <mt:if name="lastcat">
      <mt:ifnonzero name="catcount">,</mt:ifnonzero> 
      <MTEntryAdditionalCategories glue=", ">
         <mt:SetVarBlock name="curcat"><MTCategoryLabel></mt:SetVarBlock>
         <mt:if name="lastcat" eq="$curcat">and </mt:if>
         <a href="<MTCategoryArchiveLink>"><MTCategoryLabel></a>
      </MTEntryAdditionalCategories>
   </mt:if>
   <mt:if name="lastcat">categories<mt:else>category</mt:else></mt:if>.
</p>

Now, let’s go through and see what this does. The first line:

<mt:SetVar name="lastcat" value="">

Clears the lastcat variable. Otherwise, on pages with multiple entries that value would carry from one entry to the next.

   Posted <$MTEntryDate format="%x"$> in the 
   <a href="<MTEntryLink archive_type="Category">"><MTEntryCategory></a>

We print the primary category first, separate from the rest. One reason we do this is so that the primary category is first. The other reason I’ll explain in a moment. Now, we need to gather some data. First, we’ll get the name of the last category in the list of categories:

   <MTEntryAdditionalCategories>
      <mt:SetVarBlock name="lastcat"><MTCategoryLabel></mt:SetVarBlock>
   </MTEntryAdditionalCategories>

<MTEntryAdditionalCategories> allows us to loop through only secondary categories. We also need a count, of sorts:

   <mt:SetVarBlock name="catcount">
      <MTEntryAdditionalCategories glue="1"></MTEntryAdditionalCategories>
   </mt:setvarblock>

The variable catcount will get a value that has one less 1 digit than the number of secondary categories. So if there’s one secondary category, catcount will be empty. If there’s two it will be “1,” three “11,” etc. This is hardly exact, but it’s good enough for our needs.

(You may be wondering why we don’t use the template loop meta variables. In MT 4.01, these do not work for <MTEntryCategories> or <MTEntryAdditionalCategories>. You can use them, but you end up with the values from the <MTEntries> loop you are in. But if they did work, they would be quite handy.)

Now we get to the real meat of things:

   <mt:if name="lastcat">

If lastcat has a value, we have at least one secondary category.

      <mt:ifnonzero name="catcount">,</mt:ifnonzero>

If `catcount’ is a non-zero value (1, 11, 111, etc.) then we have at least two secondary categories, and therefore need a comma after the first category. We then loop through all the secondary categories:

      <MTEntryAdditionalCategories glue=", ">
         <mt:SetVarBlock name="curcat"><MTCategoryLabel></mt:SetVarBlock>
         <mt:if name="lastcat" eq="$curcat">and </mt:if>
         <a href="<MTCategoryArchiveLink>"><MTCategoryLabel></a>
      </MTEntryAdditionalCategories>

As you can see, we set a variable, curcat, to the name of the current category, then compare that to the lastcat we found earlier. When we find the last category, we throw “and ” in front of it. This is the other reason we output the primary category separately. Since <MTEntryCategories> loops through categories alphabetically, lastcat might not necessarily be the last category listed. In our example at the beginning, if “announcements” is the primary category it would be at the end in an alphabetical list because of the way capitals and lower-case letters are ordered. This can put “and” in the strangest of places.

Finally, we produce an appropriately pluralized “category:”

   <mt:if name="lastcat">categories<mt:else>category</mt:else></mt:if>.

And there you have it. Of course, I’ve formatted this code so it’s easier to read. If you want your spacing to be right (and if you’re willing to go to this much trouble for a category list, you do), everything from the output of the primary category to the end of the last loop needs to be on a single line with no extra spaces.

You could tighten up this code a little bit. While writing this post I noticed a couple of minor things that could be improved. Those are left as an exercise for the reader. You could also do this with PHP—I don’t think the code would be significantly cleaner, but it would make publishing faster. I like producing static XHTML, though, and my publishing is fast enough, so I’m happy with this method.

This really does highlight what I like about Movable Type: It’s so easy to get to the raw data and format it however you want. I realize the code above doesn’t look easy, but compared to a lot of other CMSes that insist on mixing markup with data, it really is.

Next time, I’ll have a method for improving the look of titles that has all kinds of other applications.