Grouping a List of Entries by Month

05/02/2005

It is a common scenario to have a list of entries sorted by date of publication. As this list may be rather long, it is reasonable to group entries and mark each group with a distinct group header. If you want to group by day, you can use the MTDateHeader tag for this task. However, what can be done, if you prefer to group by months?

An example

Suppose you have a couple of entries. Then you might want to create a structured list as follows:

Jan 16th, 2005
- First entry from Jan 16th, 2005
- Second entry from Jan 16th, 2005
- Third entry from Jan 16th, 2005
Jan 20th, 2005
- First entry from Jan 20th, 2005
- Second entry from Jan 20th, 2005
Feb 3rd, 2005
- First entry from Feb 3rd, 2005
- Second entry from Feb 3rd, 2005
Feb 6th, 2005
- First entry from Feb 6th, 2005

We can create such a list easily with the MTDateHeader and the MTDateFooter tags. We might use a sequence of tags similar to the following lines.

<MTEntries>
<MTDateHeader>
<p><$MTEntryDate format="%B %e, Y"$></p>
<ul>
</MTDateHeader>
<li><$MTEntryTitle$></li>
<MTDateFooter>
</ul>
</MTDateFooter>
</MTEntries>

The MTDateHeader tag is a container tag. Movable Type will only evaluate its content, if the current entry is the first entry for a new day. In the above code, we use this tag for printing the date and starting the unordered list. In the same way, the MTDateFooter tag is used for ending the unordered list.

Now suppose you want to create a list of entries similar to the above. The difference being, that the entries should be grouped according to their month of publication. Therefore, the list should look as follows.

January 2005
- First entry from Jan 16th, 2005
- Second entry from Jan 16th, 2005
- Third entry from Jan 16th, 2005
- First entry from Jan 20th, 2005
- Second entry from Jan 20th, 2005
February, 2005
- First entry from Feb 3rd, 2005
- Second entry from Feb 3rd, 2005
- First entry from Feb 6th, 2005

The MTDateHeader has no argument for specifying "look at the month of publication for finding out whether a new group has to be started". Therefore, we cannot use it here.

Maybe, there is a special plugin, which allows just that. However, we will be able to code such a list on our own. This has some advantages.

  • We will be able to handle different conditions to be used for the group change. For example, we might want to start a new group, if the initial letter of the entry's title changes.
  • Your Movable Type environment does not depend on too many different plugins.
  • It is much more fun to code this yourself.

So let us try to create the list with only minimal support from plugins.

Prerequisites

We will need two general-purpose plugins. One is the Compare Plugin, which I described in Comparison needed for conditional Generation. The other is the TagInvoke Plugin, which I described in A Tag within a Tag.

I regard both plugins as "general purpose". They can be used in a variety of scenarios and should be installed with each Movable Type. I highly recommend both of them.

If you have not yet done so, please read the above entries and install both plugins.

Programming a grouped list

Programming a grouped list is no big thing. It is a standard scenario in software development. In pseudocode, the basic structure might be illustrated as follows...

prev_group = ""
this_group = ""
For Each elem In list
   this_group = calc_group_from_element(elem)
   If this_group <> prev_group Then
      Print this_group
      prev_group = this_group
   End If
   Print elem
Next

You need two variables, one for the current group name, and the other for the previous group name. The main part of the program is a loop running over all entries.

For each entry, you will calculate the entry's group (here abbreviated by a function call). Then the current group and the previous group are compared. If both expressions are equal, we are still in the same group - nothing has to be done. However, if they are different, a new group has been entered. Then the appropriate header is printed, and the variable for the current group is changed. Finally the entry itself is printed.

For making this work, the list has to be sorted by the group field. If we want to group the list by month, it has to be sorted by date. If we want to group the list by the initial letter of the title, it has to be sorted by the title.

Converting to the Tag Language

Movable Type's tag language is not the first choice for expressing program logic as shown above. However, much is under the hood. You will be able to do more than you might think. Let us try.

The two assignments in the beginning are very easy, as Movable Type offers an assignment tag.

prev_group = ""
this_group = ""

<MTSetVar name="mgs_prev_group" value="">
<MTSetVar name="mgs_this_group" value="">

Even the loop over all entries is simple. We have an MTEntries tag, which can be controlled by additional arguments. Creating a list of all entries sorted by ascending date can be coded as follows.

For Each elem In list
...
Next

<MTEntries sort_order="ascend">
...
</MTEntries>

Now, we have to master the first small problem. Assigning the current group header to a variable is not trivial. You might be tempted to try...

this_group = calc_group_from_element(elem)

<MTSetVar name="mgs_this_group"
          value="<MTEntryDate format="%Y-%m">">

However, as described in A Tag within a Tag, this will not work. Movable Type does not evaluate a tag that is inside another tag. We have to use the MTTagInvoke Plugin for that task.

this_group = calc_group_from_element(elem)

<MTTagInvoke tag_name="MTSetVar" name="mgs_this_group">
<MTTagAttribute name="value"><MTEntryDate
   format="%Y-%m"></MTTagAttribute>
</MTTagInvoke>

After these three lines, the variable »mgs_this_group« stores the current entry's group name. Its pattern will be YYYY-MM, where YYYY is the four-digit year and MM is the two-digit month. For example, if the current entry is from "Feb 3rd, 2005" the variable contains the string "2005-03".

Now we have to deal with the comparison. Again, Movable Type is not able to handle such an expression. We will use the Compare Plugin for that. Please have a look at Comparison needed for conditional Generation, if you are not familiar with that plugin.

If this_group <> prev_group Then
...
End If

<MTIfNotEqual a="[MTGetVar name='mgs_this_group']"
              b="[MTGetVar name='mgs_prev_group']">
...
</MTIfNotEqual>

Printing the current group's header is simple.

Print this_group

<p><b><MTGetVar name="mgs_this_group"></b></p>

Assigning the current group to the previous group variable is another scenario, where the MTTagInvoke Plugin will help.

prev_group = this_group

<MTTagInvoke tag_name="MTSetVar" name="mgs_prev_group">
<MTTagAttribute name="value"><MTGetVar
   name="mgs_this_group"></MTTagAttribute>
</MTTagInvoke>

Just one more statement has to be converted. It is simple.

Print elem

<p><MTEntryTitle></p>

Joining all of the above segments will produce the result. It is as follows.

<MTSetVar name="mgs_prev_group" value="">
<MTSetVar name="mgs_this_group" value="">

<MTEntries sort_order="ascend">

<MTTagInvoke tag_name="MTSetVar" name="mgs_this_group">
<MTTagAttribute name="value">
<MTEntryDate format="%Y-%m">
</MTTagAttribute>
</MTTagInvoke>

<MTIfNotEqual a="[MTGetVar name='mgs_this_group']"
              b="[MTGetVar name='mgs_prev_group']">
<p><b><MTGetVar name="mgs_this_group"></b></p>
<MTTagInvoke tag_name="MTSetVar" name="mgs_prev_group">
<MTTagAttribute name="value"><MTGetVar
   name="mgs_this_group"></MTTagAttribute>
</MTTagInvoke>
</MTIfNotEqual>

<p><MTEntryTitle></p>
</MTEntries>

If you place the above lines into a template, the result will be as expected. A list of entries will be created. Each month will started with a group header showing the month abbreviation.

Modifications

Of course, the above is simplified code, showing only the structural parts. It is very easy to extend the code. Possible modifications are...

  • Currently we only print an entry's title. Maybe we also want to show the entry's excerpt.
  • The group's name is built as a string made up of the four-digit year and the two-digit month. We might want to print the month name, so "February 2005" is printed instead of "2005-02".
  • The MTEntries tag might be modified, so the list of entries is filtered.

mgs | 05/02/2005

Feedback is welcome!

What do you think about this entry? Was it interesting or boring? I would like to hear your comments. If the text was helpful, please consider setting a link to http://www.movable-type-weblog.com/.

No spam please!

For protecting this weblog I have installed the MT-Approval Plugin. You have to view a new comment in preview mode, before it is saved on the server. Moreover, I will view your comment manually, before it is published. You can find more information on the subject in the entry Weblog Spamming Basics.

With an active TypeKey session, your comment will be published immediately.

Post a new comment

TypeKey has temporarily been disabled at this location. Please create your comment without using TypeKey or log in from the preview dialog.




Remember Me?


Comment

jayseae | May 2, 2005 10:08 PM

While this is definitely more fun, I've noticed that the Compare plugin can consume some rather hefty resources. That isn't to say that I don't use it - I do. But it is something to keep in mind.

With that in mind, something like my MT-SomeDays plugin might fit the bill better for those who don't enjoy the fun part of it. :)

Comment

mgs [TypeKey Profile Page] | May 3, 2005 07:58 AM

I do not develop software with Perl, as I did not yet learn it. So I am not the best person to look at some Perl code and judge whether it is computational expensive. If you look at the Compare Plugin function "_hdlr_equality", which of those statements do you regard resource intensive?

I do not yet use MTSomeDays, but just had a quick look at the documentation. It is a long list of date-related functions. However, I do not understand, how it would help us here. Does MTSomeDays have a tag for specifying "only evaluate this tag's content, if it is the first entry for a new month"?

Comment

Dan Wolfgang [TypeKey Profile Page] | May 8, 2005 12:11 AM

Another good approach is to use the MTCollate plugin.

Comment

Jeff | July 1, 2005 10:48 PM

I am trying to do a small variation of this in which the entries would be sorted into categories by month, then appear in a list preceded by their date and followed by "filed under: (category)."

Something like this:

June 2005
14 SAMPLE ENTRY TITLE filed under: category name
21 SAMPLE ENTRY TITLE filed under: category name
21 SAMPLE ENTRY TITLE filed under: category name
28 SAMPLE ENTRY TITLE filed under: category name

July 2005
etc...

I've been trying to work this out starting with this code, but i always end up with it either failing to start a new month header for each new month or making a new month header for each individual entry.

Any ideas?

Comment

mgs | July 2, 2005 07:13 AM

What do you mean with "the entries would be sorted into categories by month"? If you look at the entries from one month, should they be sorted by category or by date of publication?

Your sample lines look, as if they were sorted by date.

Then you should just use the original code from above, and change the line

<p><MTEntryTitle></p>

into

<p><MTEntryTitle> filed under <MTEntryCategory></p>

plus adding the day of publication.

Michael G. Schneider

Comment

IshMEL | August 11, 2005 09:57 PM

Thanks, this was very helpful!

Comment

Computer Forensics [TypeKey Profile Page] | July 18, 2008 01:16 AM

These are very helpful tags. I've even used portions of them in my blogs with ad servers such as Chitika.