Blog » Creating event calendars in Movable Type

November 9, 2009

Creating event calendars in Movable Type

Endevver is committed to making Movable Type better for everyone. Virtually every line of code we write is open source and we are engaged heavily in Melody, a collaborative, community-driven fork of Movable Type. Like most engineers we revel in code, we enjoy mashing things together to make new things and old things work better, and delight in people using our software. Also like engineers, we value good documentation. Without it, most of us engineering-types are useless. Without it, we can't learn. And without it, most of us will go somewhere else where can can find it.

Which is why Endevver is also working to provide Movable Type and Melody with a resource its users have been in desperate need of: documentation and instructions on how to build a web site using this software we love. You can find much of this documentation on Melody's web site. We have published a primer on the Melody Templating Language, and the first several introductory chapters on building web sites with Melody (a work in progress):

Our documentation will span the entire spectrum of skill levels, from beginner to expert. For experts, here is a preview of a chapter we just wrote that covers one of the more advanced topics: how to create an event calendar using Movable Type. Enjoy.

Building an Event and Calendar Blog

A blog can actually be a very effective mechanism by which to manage upcoming events. In this model, an event would need to exhibit the following properties:

  • An event must be archived according to its start date, so that a list can easily be constructed of upcoming events, or even for all of the events scheduled for a given time frame (week, month, year, etc).

  • An event must be automatically unpublished and removed from the web site once the event has passed. Of course some people may want certain artifacts from the event to persist even after it has elapsed; we will leave determined that up to the administrator.

To execute against this use case you will need one simple component:

Let's begin.

The Basic Setup

Once the necessary components (Custom Fields and Expire Entries) have been installed, then there is not much for you to do. They key is understanding how to manage dates properly. Remember, every event has two key dates that must be managed in a specific way for this trick to work: the start and end date/time for the event.

The event start date/time field is managed by overloading the entry's publish date. The secret is in knowing that there is nothing wrong with setting an entry's publish date to some date in the future and also having its status set to "Published" (as opposed to scheduling it to be published in the future). Once the published date/time is set, the entry will be archived accordingly. So even if the current date is actually December 15, 2009, and the publish date for an entry is set to February 21, 2010 then your blog will still publish a monthly archive for February 2010 into which your blog post will be placed.

The challenge this hack creates in one of user education. Users engaged in managing the event calendar will need to be properly briefed as to the intended usage of the "Publish On" field, because its name will not really be an accurate reflection of its behavior.

The event's end date, luckily is a little more intuitive. It is achieved by installing the Expire Entries plugin which adds another date field to the Create/Edit Entry interface: "Expire On." Just like you can schedule an entry to be published on a given date and time, this plugin provides the opposite functionality by scheduling an entry to be unpublished as a designated date and time. The Expire On field therefore is used to hold the correspond event's end date/time.

Publishing the Start and End Times for an Event

Once the managers of your event calendar have been properly changed on the proper usage of the Publish On and Expire On fields, then you need to turn your attention to what information is being published to your web site. First up: making it obvious to one of your readers when an event is scheduled to start and end. This is achieved using the date tags provided by Melody and the Expire Entires plugin, as well as a few date formatting rules.

The following recipe will output one of two start/end time formats depending upon whether or not the event starts and ends on the same day or not:

  • "Date: February 14, 2010 - 8:00am-3pm"
  • "Dates: Start: February 14, 2010 8:00am - February 21, 2010 6:00pm"

This output is achieved by comparing just the month, day and year of both the publish on and expire on fields. If they differ, then we know to use the expanded syntax.

<mt:setvarblock name="start"><$mt:EntryDate format="%x"$></mt:setvarblock>
<mt:setvarblock name="end"><$mt:EntryExpireDate format="%x"$></mt:setvarblock>
<mt:if name="start" eq="$end">
  <span class="datalabel">Date:</span> <$mt:var name="start"$>, 
  <$mt:EntryDate format="%X"$> - <$mt:EntryExpireDate format="%X"$>
<mt:else>
  <span class="datalabel">Dates:</span> Start: <$mt:var name="start"$>, 
  <$mt:EntryDate format="%X"$>;  End: <$mt:var name="end"$>, 
  <$mt:EntryExpireDate format="%X"$>
</mt:if>

Displaying Events According to When They Were Added

The default sort order for an entry within a blog is according to the date it was published. However, this default behavior may not be desirable for the front door of your web site, especially if you manage a lot of events. That is because events that happen later in the year will effectively be anchored to the top of your web site. If you want to keep your readers abreast of new events being added to the system, then when they visit your homepage they may not always see what is "newest."

To fix this, you will need to change the default sort key for entries on your homepage. This is done by added one attribute to your <mt:Entries> tag:

<mt:Entries sort_by="created_on">
  <!-- your code here -->
</mt:Entries>

The above will force the events to be sorted in the order they were entered into the system irrespective of the event's start date.

You will need to remember to make this change not only to your homepage, but also your RSS/Atom feed if necessary as well.

Displaying Upcoming Events

In the event that you need to promote events that are scheduled to start soon, then you can use the days parameter on <mt:Entries> to display only those events scheduled to be published within a designated number of days. For example, the following template code will output all events scheduled to start in the next seven days:

<mt:Entries days="7">
  <!-- your code here -->
</mt:Entries>

Summary

As you can hopefully see, managing and publishing an event calendar is not that complex and can be accomplished using some standard off the shelf components. In the next section we will delve into how you can accept events from the community for your calendar.

Accepting User Submitted Calendar Events

This tutorial is for users who have installed the Commercial.pack from Movable Type Pro into Melody as it requires the use of Community Solution. The performance of this combination has not been tested and may be in violation of your Movable Type licensing agreement. Proceed with caution.

Setting up your system to accept events submitted by readers or members of your community is slightly more involved. It still relies on the fundamentals discussed above, but a way for the user to specify a start and end date, and have those values inserted into the proper fields associated with an entry is required.

The bad news is that there is no off-the-shelf system that will do this for you. The good news is that this is a *solved problem," and that is precisely what this guide will help you in doing.

This is what you will need:

  • Community Solution, provided by Movable Type Pro.

  • Expire Entries Plugin - a plugin that will automatically unpublish entries at a scheduled date.

  • The courage to cut and paste and edit Perl code.

Getting Started

First of all, the bulk of the work that needs to be done is already handled for us by the Movable Type Community Solution. This piece of software has within the ability to accept blog posts submitted by the community. The missing piece is how the community is allowed to set the publish and expire on fields from the public submission web interface. Before however we get into that, let's first make sure we can setup a blog to properly accept user submissions.

Setting Up the Community Blog

The most expedient way to get started is to create a new blog and use the "Community Blog" template set in the process. This will setup all the templates for you in just a few clicks. Granted, the look and feel of this blog may not be what you desire for your own site, but it will be far easier to apply this theme and strip away all that you don't like and need, then it will be to build a theme from the ground up for you event calendar.

Once this theme is applied there will be only a few templates you will need to concern yourself with. The rest can be discarded, or better yet, customized to suit the design of your site accordingly. They are:

  • "Create Entry" index template - publishes the actual submission form to your web site. This will need to be customized to look like the rest of your site.

  • "Entry Form" template module - this is a template module that contains just the form elements for your entry submission form.

  • "Entry Response" system template - this template is responsible for displaying a message to the user after they submit an entry to your web site.

About the Entry Response System Template

The "Entry Response" template is the most important template in this scenario because without the system won't work. Movable Type will instead return the following error message, "System template entry_response not found in blog." The core challenge here is that this template cannot be created manually. It can only be created by applying a template set that contains a template with this ID to a blog. The template sets that contain this template for you are:

  • Motion
  • Community Blog

Alternatively of course, you can build your own template set and include this system template within it. The approach you choose will depend largely upon what you think will be easiest and most expedient for you.

Setting Up the Form

The best way to setup your form is to take a form generated by Movable Type and then to customize it. This is because the HTML for this form matters, a lot. You must make sure you include all of the proper hidden HTML form elements, and use the proper input names so that the data submitted is properly submitted and processed by Movable Type.

If you are using Custom Fields, then you can define all of the custom fields you want your visitors to submit along with their event, e.g. event venue, contact info, etc. The custom fields will appear and be published for you automatically if you are using the default "Entry Form" template module that comes with the Community Blog template set.

There will be several fields however that will not be published for you, which you will need to add to your form manually. They are:

  • Start Date
  • Start Time
  • Start AM/PM
  • End Date
  • End Time
  • End AM/PM

The names you assign these fields will be very important because we will need to reference them in our Perl code that will populate the entry with the corresponding information. In addition, the dates and times submitted by your users must be a specific format in order for them to properly process by Movable Type. Here are some recommended names to use in the form, and the required format your visitors will need to use when supplying information to you:

  • Start Date: start_date_d as "YYYY-MM-DD"
  • Start Time: start_date_t as "hh::mm::ss"
  • Start AM/PM: start_ampm as "am" or "pm"
  • End Date: end_date_d as "YYYY-MM-DD"
  • End Time: end_date_t as "hh::mm::ss"
  • End AM/PM: end_ampm as "am" or "pm"

Here is some sample HTML for the end date form elements (the start date is identical except for the form input names):

<input type="hidden" id="end_date_d" name="end_date" value="" />
<input type="hidden" id="end_date_t" name="end_time" value="00:00:00" />
<input type="text" class="date-pick" id="end_date" name="end_date" value="" size="14" />
<select name="end_hour">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
<option>11</option>
<option value="0">12</option>
</select>:
<select name="end_minutes">
<option>00</option>
<option>15</option>
<option>30</option>
<option>45</option>
</select>
<select name="end_ampm">
<option>PM</option>
<option>AM</option>
</select>

Formatting Dates and Times

Asking your community members to enter in date and time according to a specific format can be onerous and error prone. Therefore it is recommended you use a third party calendar library with a little custom javascript to assist users in this process. This example utilizes jQuery, the jQuery date plugin and the jQuery Date Picker plugin to make this happen. Once installed, here is the javascript you will need to add to your Create Entry template to properly reference these javascript libraries:

<script type="text/javascript" src="<mt:StaticWebPath>plugins/MyPlugin/js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="<mt:StaticWebPath>plugins/MyPlugin/js/date.js"></script>
<script type="text/javascript" src="<mt:StaticWebPath>plugins/MyPlugin/js/jquery.datePicker.min-2.1.2.js"></script>

Then this javascript can be added to the same template so to properly initialize your calendar widgets and date fields:

Date.format = 'mm/dd/yyyy';
$(document).ready( function() {
  var today = new Date();
  $('.date-pick').datePicker().val(today.asString()).trigger('change');
  $('#start_date_d').val( today.asString('yyyy-mm-dd') );
  $('#end_date_d').val( today.asString('yyyy-mm-dd') );
  $('#start_date').bind(
    'dpClosed',
    function(e, selectedDates) {
      var d = selectedDates[0];
      if (d) {
        d = new Date(d);
        $('#start_date_d').val( d.asString('yyyy-mm-dd') );
      }
    }
  );
  $('#end_date').bind(
    'dpClosed',
    function(e, selectedDates) {
      var d = selectedDates[0];
      if (d) {
        d = new Date(d);
        $('#end_date_d').val( d.asString('yyyy-mm-dd') );
      }
    }
  );
});

We are getting closer. Hopefully though we have not lost you along the way as the above recipes all require some familiarity with Javascript. If you need help along the way the Melody and jQuery communities are both very helpful resources to call upon for help.

The Movable Type Plugin, a.k.a. "Perl Code"

If you have gotten this far, it will assumed that you have successfully gotten your event submission form working. It may not set the publish on and expire on fields properly, but you will have:

  • Published the Create Entry form and viewed it online.
  • Made the necessary changes to the Entry Form module to surface the start and end dates for your event.
  • Successfully submitted an event using the form above (without properly setting the times of course).

If you have successfully performed the three tasks above, then the rest is really just a bunch of copy and pasting - with perhaps a tweak here and there.

Every Melody plugin first requires you to create a config.yaml file. Let create it in the following location:

$MELODY_HOME/plugins/DatePopulator/config.yaml

In this file let's put the following contents:

name: Date Populator
callbacks:
  MT::Entry::pre_save: $DatePopulator::DatePopulator::entry_presave
  MT::Entry::post_save: $DatePopulator::DatePopulator::entry_postsave

This config.yaml file refers to two subroutines that you must implement (again, by a little copy and paste). These subroutines will live in a perl file you are about to create. Now, create the following file:

$MELODY_HOME/plugins/DatePopulator/lib/DatePopulator.pm

In this file, paste the following:

package DatePopulator;
use strict;
use Carp qw( croak );

# You can edit the values below if you have customized the 
# field names in your HTML
use constant FIELD_START_DATE => "start_date_d";
use constant FIELD_START_TIME => "start_date_t";
use constant FIELD_START_TOD => "start_ampm";
use constant FIELD_END_DATE => "end_date_d";
use constant FIELD_END_TIME => "end_date_t";
use constant FIELD_END_TOD => "end_ampm";

sub entry_presave {
    my ( $cb, $obj ) = @_;
    my $app = MT::App->instance;
    if ( $app && ($app->isa('MT::App::CMS') || 
                  $app->isa('MT::App::Community')) 
              && $app->mode eq "post" ) {
        my $q = $app->{query};
        my $d = $q->param( FIELD_END_DATE );
        my $t = $q->param( FIELD_END_TIME );
        $d = "$3-$1-$2" if ($d =~ m!^(\d{1,2})/(\d{1,2})/(\d{4})$!);
        my $end_hour = $q->param(FIELD_START_TOD) eq "PM" ? 
          $q->param('end_hour') + 12 : $q->param('end_hour');
        $end_hour = $end_hour < 10 ? '0' . $end_hour : $end_hour;
        my $end_time = $end_hour . ':' . 
           $app->param('end_minutes') . ":00";
        $app->param('expire_on_date',$d);
        $app->param('expire_on_time',$end_time);
        require ExpiredEntries::Plugin;
        return ExpiredEntries::Plugin::pre_save($cb, $app, $obj);
    }
    return 1;
}

sub entry_postsave {
    my ( $cb, $obj ) = @_;
    my $app = MT::App->instance;
    return 1 unless ($app->isa('MT::App::CMS') || 
      $app->isa('MT::App::Community'));
    if ( $app->mode eq "post" ) {
        my $q = $app->{query};
        my $d = $q->param( FIELD_START_DATE );
        my $t = $q->param( FIELD_START_TIME );
        # This should never happen                                                                     
        $d = "$3-$1-$2" if ($d =~ m!^(\d{1,2})/(\d{1,2})/(\d{4})$!);
        my $start_hour = $q->param( FIELD_START_TOD ) eq "PM" ? 
          $q->param('start_hour') + 12 : $q->param('start_hour');
        $start_hour = $start_hour < 10 ? 
          '0' . $start_hour : $start_hour;
        my $start_time = $start_hour . ':' . 
          $q->param('start_minutes') . ":00";
        $obj->authored_on($d . " " . $start_time);
        $obj->save();

    }
    return 1;
}
1; # Don't forget this line!

If all goes well, then you will have successful closed the loop and now possess a theme that will allow community members to submit calendar events to your system.

Leave a comment

Toggle formatting help

About

Endevver is a consulting firm specializing in industrial-strength websites powered by Movable Type.

We believe in making things beautiful and bulletproof.

If this sounds like what you need, then drop us a line. We’d love to chat.

Endevver Consulting