Exploring SPS Calendar Views

Abstract
So I was asked a rather simple question recently. Here at the office, we have a custom SPS list that has start date / end date columns. Naturally, we take advantage of the ability to create calendar views of the list specifying these columns as the interval for the item. As it turns out, we also have a column that specifies the status of the event (Pending, Confirmed, Aborted, etc). The simple question was: Could we have the value of the status field dictate the color of the box rendered in the calendar view? Sounds simple right? A day and a half later I now have an appreciation of how intricate the calendar interface is within SharePoint. Here is a screen shot of what I was able to accomplish (View Picture).   

Setup
To begin creating a development play area, I first made a copy of the STS template and registered it with a custom WEBTEMPNEW.XML file. Within this template, I created a new list type folder "MTCEVENTS" which was a copy of the custom list one (I deliberately stayed away from EVENTS since it didn't fit the requirements I had been given). I modified the ONET.XML file to include the definition of this list type and gave it the ID of 506 (this will be important later). I also took the existing custom list with a calendar view I had previously created and saved it as a list template. Once the STP file was created, I save it to my desktop and renamed the file to give it a CAB extension. Inside this cab file, was a Manifest.XML file which had a lot of the field and CAML view code I would need to develop my new list type. This is a great technique to keep you from having to write lots of CAML code from scratch!

Oh, and back up every file you modify as you have no idea how many changes we are going to make!!

The Code
Please read this section and the Extensions section of this article before diving in and modifying the code as I did. After I finished and reflected on the mountain I had climbed I have a few thoughts on what I might do differently next time.

So my first task was to get my custom list in shape. First I modified the SCHEMA.XML file to include the few fields that I had to have to make my calendar work:
<Fields>
<Field Type="Text" Name="Title" FromBaseType="TRUE" DisplayName="Customer Name" Required="TRUE" MaxLength="255"/>
<Field Type="Choice" DisplayName="EventStatus" Required="TRUE" Format="Dropdown" FillInChoice="FALSE" Name="EventStatus"><Default>Pending</Default><CHOICES><CHOICE>Aborted</CHOICE><CHOICE>Cancelled</CHOICE><CHOICE>Confirmed</CHOICE><CHOICE>Pending</CHOICE><CHOICE>Postponed (Waiting for Dates)</CHOICE><CHOICE>Tentative</CHOICE></CHOICES></Field>
<Field Type="DateTime" DisplayName="Start Date" Format="DateTime" Name="Start_x0020_Date"/>
<Field Type="DateTime" DisplayName="End Date" Format="DateTime" Name="End_x0020_Date"/>
</Fields>

I then needed my calendar view. To make this happen, I first copied the AllItems.aspx and renamed it to calendar.aspx. I then decided to copy the calendar view in my extracted Manifest.xml file into the SCHEMA.XML file. You'll notice that it currently only has the AllItems view. You'll need to make a few changes as the one in the manifest file already has references to a specific list. The changes really only impact the first line. I also made sure the EventStatus field was included in the view since I would be writing CAML code here that would reference it. Make sure the BaseViewID is a unique number.
<View DefaultView="TRUE" Type="CALENDAR" DisplayName="calendar" Url="calendar.aspx" BaseViewID="2" WebPartZoneID="Main">
<ViewFields>
<FieldRef Name="Start_x0020_Date"/>
<FieldRef Name="End_x0020_Date"/>
<FieldRef Name="Title"/>
<FieldRef Name="EventStatus"/>
</ViewFields>

You will also have to remove the node that specifies it to use the ViewStyleID 9 since we want it to use the CAML code that is actually here. This is one item that I'll address in my Extensions section as since reflecting on it, I have some reservations about this technique. Now to write some new CAML code. As it turns out, the calendar interface is generated largely through javascript in the ows.js file. So, to modify the interface, we will have to pass an additional parameter to the js call to cal.AddFullEvent(). The following CAML deftly passes in a new parameter we'll call bgStyle. I first check for the list type to make sure it is only for my list and I pass the default style used by SPS if I don't find any matches to my conditions:
<Switch>
<Expr><ListProperty Select="ServerTemplate"/></Expr>
<Case Value="506">
<Switch>
<Expr><Column Name="EventStatus"/></Expr>
<Case Value="Confirmed"><HTML>&quot;&quot;, &quot;ms-appt_green&quot;</HTML></Case>
<Case Value="Pending"><HTML>&quot;&quot;, &quot;ms-appt_yellow&quot;</HTML></Case>
<Default><HTML>&quot;&quot;, &quot;ms-appt&quot;</HTML></Default>
</Switch>
</Case>
<Default><HTML>&quot;&quot;, &quot;ms-appt&quot;</HTML></Default>
</Switch><HTML>); </HTML></ViewBody>

Modify OWS.CSS
So you'll need to add the _green and _yellow versions of the ms-appt styles to the OWS.CSS file. Basically just copy the ms-appt definition twice and modify to get the yellow and green version. Append these to the end of the css file. Here is a copy of my _green definition:
.ms-appt_green a {
color: #2D425F;
}
.ms-appt_green a:hover {
color: red;
}
.ms-appt_green, .ms-GRCellSelect {
border:2px solid black;
text-align:center;
vertical-align: middle;
font-size:8pt;
height:18px;
overflow:hidden;
text-overflow:ellipsis;
background-color: #00FF00;
color: black;
}

Modify OWS.JS
Now here is where it gets really interesting. MAKE SURE YOU BACKUP THIS FILE BEFORE MODIFYING IT. In fact, I wouldn't recommend this as it might be simpler just to copy all the calendar js functions, rename them, and put them in a custom js file just so you don't have to modify it. But here goes nothing:
Locate the CallAddFullEvent function and add a new parameter:
function CalAddFullEvent(stDateStart, stDateEnd, stLocation, stDesc, stTitle, stURL, rgIcons, bgStyle)

In this funtion body, just pass the bgStyle parameter into creation of the event:
var evt = new CalEvent(displayDateStart, displayDateEnd, dateStart, dateEnd, stLocation, stDesc, stTitle, stURL, rgIcons, bgStyle);

Of course, you will then need to add this new parameter to the CalEvent definition:
function CalEvent(dateStart, dateEnd, actualDateStart, actualDateEnd, stLocation, stDesc, stTitle, stURL, rgIcons, bgStyle)
{
this.dateStart = dateStart;
this.dateEnd = dateEnd;
this.actualDateStart = actualDateStart;
this.actualDateEnd = actualDateEnd;
this.stLocation = stLocation;
this.stDesc = stDesc;
this.stTitle = stTitle;
if (stTitle.length == 0)
this.stTitle = L_NoTitle_Text;
this.stURL = stURL;
this.rgIcons = rgIcons;
this.bgStyle = bgStyle;
var start;
var end;

Now find CalStBuildDHTML(). This is the function that generates most of the interface. I didn't try to find all the cases that I was interested in modifying. Specifically I was only really interested in events that span multiple days. Eventually I found where the TD tag for this case was being generated. All I did was comment out the hard-coded style and then used my new parameter:
span = this.mpSpan[irw + "." + iday + "." + ichan];
if (span != null)
{
// var stClass = "ms-appt";
var stClass = span.evt.bgStyle;
if (!span.evt.multiDay)
stClass = "ms-apptsingle" + this.DayStyle(dateCur, false, false, fWeekly);
st += "<td class=" + stClass +
" colspan=" + span.cbucket + " TITLE=" + StAttrQuote(span.evt.StTip(this.dopt)) + "><nobr>"+
StURL(span.evt.stURL, span.evt.stTitle) + "</nobr></td>\r";
iday += span.cbucket - 1;
}
else
...

So that is where I stopped and was able to get what you see in the above screen shot. I figured this was enough to prove it was possible. And it works quite nicely, change the EventStatus field and the event box changes back and forth from green (confirmed) to yellow (pending).      

Extensions
Before I went the route of adding my own view to the SCHEMA.XML file, I first started modifying the VWSTYLES.XML file. This is the reason for the check for the List Type of 506. In this file there are three view styles that are used by calendar views. These not given a description which I guess it what keeps them from being hidden for standard views. They are IDs 7, 8, and 9. Each represents the day, week, and month views. Since I was only interested in a month calendar, I started playing with view style 9. I abandoned this as I really didn't want to have to have a custom STS template. In retrospect, this was probably the right way to go. The reason is that as the user created new calendar views off of my current solution's list, it uses these styles. This is frequent in my example, as we uses different calendar views of the same list with filters to make the data easier to navigate.

I've also explored adding another parameter for additional information. What I want is some additional fields to be displayed below the title link. Usuing this same framework makes this an easy task.

I did find one other discussion on this topic that helped my go down this road (https://www.sharepointu.com/forums/Columns_in_Calendar_View/m_6556/tm.htm). In this discussion, it is mentioned that you could use XMLHTTP posts to grab the additional information. I didn't even explore that option since I thought it could be a performance issue with a large number of events and seemed a bit redundant if I already had those fields when generating the call to the js function. Now if you could accomplish this without changing ows.js then that would be something!