Go Back

Displaying hCalendar formatted Sitefinity Events

In my last post, I described Web 3.0 (also known as the Semantic Web).  I also introduced a couple of formats (Microformats & RDF) that allow additional meaning (semantics) to be applied to web content when HTML fails to be descriptive enough. 

In this post, I’ll demonstrate how to modify Sitefinity’s Events module to export events that are formatted using Microformat’s hCalendar format.  Once modified, these events can be easily consumed by services such as Yahoo! Calendar, Google Events or other services.

Understanding the hCalendar Format

Here is some typical HTML that might be used to display an event:

<h2>Google Buzz Launch</h2> 
<p>2/9/2010 11:06 AM</p> 
<p>Come witness the launch of a social media service that isn't limited to 140 characters.</p> 
<p>Gmail</p>
<p>Social Media</p>
<p>Google</p> 

Can you spot the organizer?  The location?  The category? 

If you’re having trouble, imagine how difficult it is for a computer to extract this information.  Here is the same information enhanced with the Microformat hCalendar format:

<div class="vevent">
    <h2 class="summary">Google Buzz Launch</h2> 
    <p class="dtstart">2/9/2010 11:06 AM</p> 
    <p class="description">Come witness the launch of a social media service that isn't limited to 140 characters.</p> 
    <p class="location">Gmail</p>
    <p class="category">Social Media</p>
    <p class="organizer">Google</p>
</div>

These small semantic annotations make it easy to identify the event details

Mapping to an External Control Template

The HTML generated by Sitefinity’s Event Module is driven by Templates.  By default, these templates are embedded inside Sitefinity’s assemblies.  However, these embedded templates can be downloaded, mapped & customized.

Here are instructions for downloading, mapping & customizing Sitefinity’s embedded templates.

1.  Download the External Templates Zip

2.  Copy the /Sitefinity_[version]_ExternalTemplates/Sitefinity/ControlTemplates/Events folder to your Sitefinity web site ~/Custom/Sitefinity/ControlTemplates/Events

3.  Create the following file in your Sitefinity web site:

~/App_Data/Configuration/Telerik.Sitefinity.Configuration.ControlsConfig.xml

<?xml version="1.0" encoding="utf-8"?>
<controlsConfig>
  <viewMap>
    <viewSettings hostType="Telerik.Events.WebControls.EventsView">
      <additionalTemplates>
        <!--Item List-->
        <add key="ItemListTemplate" layoutTemplatePath="~/Custom/Sitefinity/ControlTemplates/Events/ContentViewItemView.ascx" />
        <!--Single Item-->
        <add key="SingleItemTemplate" layoutTemplatePath="~/Custom/Sitefinity/ControlTemplates/Events/ContentViewSingleItemView.ascx" />
      </additionalTemplates>
    </viewSettings>
  </viewMap>
</controlsConfig>

This XML file tells Sitefinity to use external template(s) for the items above instead of the embedded templates.

4.  Restart the web application.

Modifying the Events ControlTemplates

Sitefinity is now using external templates instead of the default embedded templates.  These external templates can be customized to contain hCalendar format. 

Here is new template:

~/Custom/Sitefinity/ControlTemplates/Events/ContentViewSingleItemView.ascx

<%@ Control Language="C#" %>
<%@ Register Assembly="Telerik.Cms.Engine" Namespace="Telerik.Cms.Engine.WebControls" TagPrefix="sfWeb" %>
<%@ Register Assembly="Telerik.Cms.Web.UI" Namespace="Telerik.Web.UI.SpamProtection" TagPrefix="sfWeb" %>
<%@ Register Assembly="App_Code" Namespace="Custom" TagPrefix="custom" %>

<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        HtmlGenericControl link = new HtmlGenericControl("link");
        link.Attributes.Add("rel", "profile");
        link.Attributes.Add("href", "http://microformats.org/profile/hcalendar");
        Page.Header.Controls.Add(link);

        Event_Start.Text = CreateDateAbbr(Event_Start.Text, "dtstart");
        Event_End.Text = CreateDateAbbr(Event_End.Text, "dtend");
        
        string streetStr = Street.Text;
        string cityStr = City.Text;
        string stateStr = State.Text;
        string countryStr = Country.Text;
        LocationAbbr.Attributes["title"] = string.Format("{0}, {1}, {2}, {3}", streetStr, cityStr, stateStr, countryStr);
    }

    /// <summary>
    /// Accepts a date string and converts this data to the ISO format and creates <abbr title="2010-03-10">March 10, 2010</abbr>
    /// </summary>
    private string CreateDateAbbr(string possibleDate, string cssClass)
    {
        try
        {
            string ISOdate;
            DateTime date = DateTime.Parse(possibleDate);
            ISOdate = date.ToString("yyyy-MM-dd");

            var abbrDate = new StringBuilder();
            abbrDate.Append("<abbr title=\"" + ISOdate + "\" class=\"" + cssClass + "\">");
            abbrDate.Append(possibleDate);
            abbrDate.Append("</abbr>");

            return abbrDate.ToString();
        }
        catch
        {
            return possibleDate;
        }
    }
</script>

<telerik:CssFileLink ID="CssFileLink1" EmbeddedFileName="Telerik.Events.Resources.ControlTemplates.Frontend.eventsCommonLayout.css"
    FileName="" Media="screen" runat="server" />
<telerik:JsFileLink ID="jsLink" runat="server" ScriptType="jQuery" />
<div class="sf_singleEvent vevent">
    <asp:HyperLink ID="backToList1" Text="All events" CssClass="sf_back" runat="server"></asp:HyperLink>
    <h2 class="sf_eventTitle summary">
        <asp:Literal ID="Title" runat="server" />
    </h2>
    <div class="sf_eventBasicInfo">
        <h3 class="sf_eventSubTitle">
            <asp:Literal ID="ltrWhen" runat="server" Text='<%$Resources:When %>' />
        </h3>
        <p class='sf_eventPriod'>
            From: <asp:Literal ID="Event_Start" runat="server" /></p>
        <p class='sf_eventPriod'>
            To: <asp:Literal ID="Event_End" runat="server" /></p>
        <p class='sf_eventPriod'>
            Publication date:
            <asp:Literal ID="Publication_Date" runat="server" /></p>
    </div>
    <asp:PlaceHolder ID="plhWhere" runat="server">
        <dl id="Location" runat="server" class="sf_eventLocation">
            <dd>Street: <asp:Literal ID="Street" runat="server" /></dd>
            <dd>City: <asp:Literal ID="City" runat="server" /></dd>
            <dd>State: <asp:Literal ID="State" runat="server" /></dd>
            <dd>Country: <asp:Literal ID="Country" runat="server" /></dd>
        </dl>
        <abbr id="LocationAbbr" class="location" runat="server">
        </abbr>
    </asp:PlaceHolder>
    <h3 class="sf_eventSubTitle">
        <asp:Literal ID="ltrWhat" runat="server" Text='<%$Resources:What %>' />
    </h3>
    <div class="sf_eventContent description">
        <asp:Literal ID="content" runat="server" />
    </div>
    <asp:Panel ID="mapPanel" runat="server" CssClass="sf_eventMap">
    </asp:Panel>
    <asp:PlaceHolder ID="plhWho" runat="server">
        <dl class="sf_eventContact">
            <dt><asp:Literal ID="ltrWho" runat="server" Text='<%$Resources:Who %>' /></dt>
            <dd class="organizer"><asp:Literal ID="Contact_Name" runat="server" Text="{0}" /></dd>
            <dd>Email: <asp:Literal ID="Contact_Email" runat="server" Text="<a href='mailto:{1}'>{0}</a>" /></dd>
            <dd>Phone: <asp:Literal ID="Contact_Phone" runat="server" Text="{0}" /></dd>
            <dd>Cell: <asp:Literal ID="Contact_Cell" runat="server" Text="{0}" /></dd>
            <dd>Website: <asp:Literal ID="Contact_Web" runat="server" Text="<a href='http://{1}'>{0}</a>" /></dd>
        </dl>
    </asp:PlaceHolder>
    <dl class="sf_eventCategory">
        <dt><asp:Literal ID="CategoryLiteral" runat="server" Text="<%$Resources:Category %>" /></dt>
        <dd><asp:HyperLink ID="Category" CssClass="category" runat="server" /></dd>
    </dl>
    <asp:Repeater ID="Tags" runat="server">
        <HeaderTemplate>
            <dl class="sf_eventTags">
                <dt><asp:Literal ID="TagsLiteral" runat="server" Text="<%$Resources:Tags %>" /></dt>
        </HeaderTemplate>
        <ItemTemplate>
            <dd><asp:HyperLink ID="tagLink" CssClass="tag" runat="server" /></dd>
        </ItemTemplate>
        <FooterTemplate>
            </dl>
        </FooterTemplate>
    </asp:Repeater>
    <asp:Repeater ID="Bookmarks" runat="server">
        <HeaderTemplate>
            <ul class="sf_socialBookmarks">
        </HeaderTemplate>
        <ItemTemplate>
            <li>
                <asp:HyperLink ID="BookmarkLink" runat="server">
                    <asp:Image ID="BookmarkImage" runat="server" />
                </asp:HyperLink>
            </li>
        </ItemTemplate>
        <FooterTemplate>
            </ul></FooterTemplate>
    </asp:Repeater>
</div>
<div id="comments" class="sf_contentComments">
    <sfWeb:CommentsList ID="commentsList" runat="server" CssClass="sf_commentsList" ValidationGroup="commentInfo">
    </sfWeb:CommentsList>
</div>

Here is what is happening in this template:

  • Sprinkled throughout this template are the semantic annotations specified by hCalendar (vevent, summary, location, dtstart, dtend, description, organizer, etc.)
  • The <link> needed by hCalendar is programmatically added to the page’s <head>
  • The Event_Start & Event_End Labels are being intercepted and re-written with <abbr> to include a machine-readable ISO date/time format.
  • Location is being specified using an empty <abbr> tag.

Did it work?  How to test!

While writing this blog post, this is where this entire adventure slammed to a halt…

I modified the HTML to conform to the hCalendar format, but then couldn’t find a way to validate or test these changes.  If you’re aware of any tools for validating hCalendar or semantic markup, please comment!

The best solution I was able to find is a Firefox add-on called Operator.  Operator will find semantic data embedded in web pages.  This data can then be exported to various services or formats.

Using the Operator Firefox Add-on to test Semantic Annotations

In my case, I exported to Google Calendar to confirm the correct information was being identified:

Microformat hCalendar data in Google Calendar

Comments  1

  • Georgi 23 Feb

    Nice post, Gabe!
Post a comment!
  1. Formatting options
       
     
     
     
     
       
  2. I'm sorry for the CAPTCHA. You have spammers to thank for this: