Friday 22 April 2016

Add an "Out of date" alert to SharePoint content pages

I was at an Intranet Experts conference last year in Geneva, courtesy of Advatera. During the day, the subject of how we can get Site Builders in a federated content model to ensure they keep their content up to date. One suggestion was to add a banner to the top of any page that hasn't been updated within a certain time period, for example, a year.

Trouble is, no one really knew how to do it.

So I had a bit of think about it and figured that if I could come up with some way to retrieve the page's Modified value and put that into a JavaScript variable, then it would be easy to get a message to display on the page ... say, like this.



So ... how do you do it? The key is in the SharePoint control:

<SharePointWebControls:FieldValue ID="Modified" FieldName="Modified" runat="server"/>

So, with a lot of help from my colleague David T., this is what we came up with:

<script>
   $( document ).ready(function() {
      var shareDate = '<SharePointWebControls:FieldValue ID="Modified" FieldName="Modified" runat="server"/>';
      var regex = /(\d+)/g;
      var dateParts = shareDate.match(regex);
      var docDate = new Date(dateParts[2]+"/"+dateParts[1]+"/"+dateParts[0]);
      var prevDate = new Date();
      prevDate.setDate(prevDate.getDate() - 365); //This is the number of days in the past to check

      if(docDate < prevDate){
      $( ".Breadcrm" ).append("<strong class='OutOfDate'>The content on this page has not been updated in over a year and may be unreliable</strong>");
}
   });

</script>

To get this work cleanly, I inserted the script at the end of the <div> block that holds the breadcrumb trail in the content page's MasterPage (see the screengrab above for what the final (active) alert looks like.

It's not a foolproof method, because all a content editor has to do is Edit the page then immediately Publish it and the Alert will disappear. But at least it will concentrate the attention of the more diligent content editor.

Hope this helps someone.

Monday 14 March 2016

Create dynamic list forms with jQuery and SP Designer

In an earlier post, I covered ways to make certain fields in a SharePoint list's input forms invisible, using JavaScript. But recently, I had a request to make input fields appear based on the user selecting a value from a picklist (drop-down list). With quite a bit of help from my colleague David T, I managed to figure out a way to create quite dynamic input forms for a SharePoint list using jQuery. This is how we did it.

You will need SharePoint Designer access in order to do this. If you don't have SharePoint Designer access then this method will not be possible for you.

So, this is what we'll end up with. When the user wants to create a new item, the NewForm.aspx page will come up looking like this:


Select a category and the dynamic input form will change.
The user will then select the category they want and the form will display differently, depending on the category chosen. In this case, the list was designed to hold Service Alerts and Planned Outages for IT systems within our company. Service Alerts happen on an ad hoc basis and don't have Start or End dates. 


This is how the form displays if you choose "Service alert".
But Planned Outages, by definition, will have a Start and an End date. Thus choosing Outage will reveal the fields for the Beginning and End of the Planned Outage.


However, Outages require an Start Date/Time and and End Date/Time ...
So, in the browser, set up your list with all the fields you need for either (or all, if you're having more than two choices) situation, and be sure to include a Category field as a Choice, so you can switch between the options in the NewForm.aspx. I started with an Announcements list, as this was essentially a news feed, and customised it slightly.


Here's the Announcements list with the added custom fields.
Make sure you have the columns in the order you want them to appear in the NewForm.aspx. If you don't, and you change the order later, this will affect the way the jQuery script works and you'll end up with a broken form.

Next, create a document library in your site, and name it "siteJS". In that, place a blank Notepad document, named "NewForm.js". You'll use this later to hold the script that controls the NewForm.aspx. 

Now, fire up SharePoint Designer and navigate to the Site where you created the List. In the Folder List, find the Lists section and expand it. Now find your target list, and expand that.


This is where all the out-of-the-box list forms are stored.
Find the NewForm.aspx under the List and open it. If you get an alert box asking if you want to check the page out, click Yes.

Now somewhere around line 15 in the code for the page, you should see a line that says:

<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">

Under that, you'll place your calls to the jQuery files. I prefer to store the jQuery files locally (as you can see from the paths in the screengrab). But you can use calls to the remote jQuery files if you want - a quick Google search will throw up a slew of examples.


This is what the code looks like in SP Designer. Make sure you put your JavaScript in the right place or the function won't work.
Once you done that, you can also add a call to your blank Notepad document you saved earlier.


Add the call to the text file that will hold your custom JavaScript. Relative pathways are fine.
Now, we're more or less ready to start on the real meat of this task. But first, you'll need to fetch the ID's of all the fields in the NewForm.aspx page. You can do this most effectively by bringing the page up in your browser then Viewing Source. Each field will have an ID associated with it. These IDs also reflect the order of the fields in the NewForm page. If you change the order of the fields, the IDs will change. Use Find to speed though the source code of the NewForm page.

It would save you time if you load each ID into the NewForm.js file as you go along, as this is where they'll end up. I prefer to use NotePad++ over regular Notepad, but either will do.  So, open the NewForm.js file and begin entering the field IDs as you copy them from the View Source window. To save yourself a little time, enter them in this format:

var trBody = $("#ctl00_m_g_6c609f0f_5ff0_49e3_b8da_0653643109f9_ctl00_ctl04_ctl04_ctl00_ctl00_ctl04_ctl00_ctl00_TextField").closest("tr");  // Field = Body Text

You're setting each field up as a variable, which you can use later in the process to control the visibility of the field. For text fields, the above format is fine, but date fields are trickier to manage, because date fields are made up of more than one component. So they require two variables to be set to manage them correctly. I set up the date fields variables like this:

var trExpires = $("#ctl00_m_g_6c609f0f_5ff0_49e3_b8da_0653643109f9_ctl00_ctl04_ctl08_ctl00_ctl00_ctl04_ctl00_ctl00_DateTimeField_DateTimeFieldDate").parent().parent().parent().parent().parent().parent().parent(); // Row = Expires 
var fncExpires = $("#ctl00_m_g_6c609f0f_5ff0_49e3_b8da_0653643109f9_ctl00_ctl04_ctl08_ctl00_ctl00_ctl04_ctl00_ctl00_DateTimeField_DateTimeFieldDate").closest("tr");  //Field = Expires

The first variable above controls the tables row that the field appears in, and the second variable controls the date function itself.

When you have all the field IDs added in this way, you're ready to add the rest of the scripting.

Before your list of field variables add this scripting:

$(document).ready(function() {
// *** START HIDE-SHOW FIELDS BASED ON "CATEGORY" DROP-DOWN SELECTION ***

// FORM FIELD VARIABLES
var selectList1 = $('#ctl00_m_g_6c609f0f_5ff0_49e3_b8da_0653643109f9_ctl00_ctl04_ctl01_ctl00_ctl00_ctl04_ctl00_DropDownChoice'); // Field = Select category

Commenting is good practice that helps you keep track of what's going on. Notice that I put the Category drop-down variable at the top of the listing of variables and named it slightly differently from the others.

Now ... below the list of variables, add this script:

//resets the form
function clearAll() {
  $('*[id*=5ff0_49e3_b8da_0653643109f9_ctl00_ctl04]:visible').each(function() {
      $(this).closest("tr").hide();
      });
      $(selectList1).closest("tr").show(); //show dropdown
      $(trStartDate).hide();  // Field = Start Date
      $(trEndDate).hide();  // Field = End Date
      $(trExpires).hide();  // Field = Expires Date
}
clearAll(); // hides all rows upon loading the page

This hides all the fields except for the Category drop-down. The line that begins $('*[id*= finds every field with an ID that contains "5ff0_49e3_b8da_0653643109f9_ctl00_ctl04" and the next line hides them all. The next four lines that begin with "$" shows the drop down and hides the table rows that contain the date fields.

Now, beneath the above script, add this:

//Category > Alert
function alert() {
clearAll(); // call the Alert form function
$(trCategory).show();  // Field = Category
$(trTitle).show();  // Field = Title
$(trStatus).show();  // Field = Status
$(trIncident).show();  // Field = Incident number
$(trBody).show();  // Field = Body text
$(trBodyTool).show(); // show text field options
$(trUpdate).show();  // Field = Update Test / Reason
// Next two lines required to make Expired date field show
$(trExpires).show();  // Row = Expires Date
$(fncExpires).show();  // Row = Expires Date
}

This block of script makes the required fields for the Alert category visible.

Next, add this script, which makes the other category (Outage) visible:

//Category > Outage
function outage() {
clearAll(); // call the Outage form function
$(trCategory).show();  // Field = Category
$(trTitle).show();  // Field = Title
$(trStatus).show();  // Field = Status
$(trIncident).show();  // Field = Incident number
$(trBody).show();  // Field = Body text
$(trBodyTool).show();  // show text field options
$(trUpdate).show();  // Field = Update Test / Reason
// Next two lines required to make Start date field show
$(trStartDate).show();  // Row = Start Date
$(fncStartDate).show();  // Row = Start Date
// Next two lines required to make End date field show
$(trEndDate).show();  // Row = End Date
$(fncEndDate).show();  // Row = End Date
// Next two lines required to make Expired date field show
$(trExpires).show();  // Row = Expires Date
$(fncExpires).show();  // Row = Expires Date
}

Finally, add the block of script that controls the whole function:

// calls function if selectlist1 is changed
$(selectList1).change(function() {
var funcCall = "";
var primarySelect = $(selectList1).val().toLowerCase().replace(/-|\s/g, '');
var funcCall = primarySelect;
//alert("Segment: "+ primarySelect +"\nRequestType: "+secondarySelect +"\nFunction Name: "+funcCall);
eval(funcCall+'()');
});

You don't need the Alert line. I was using this for testing a version of this script that had two different category drop-downs.

Finally, make sure the whole script block is closed by adding:

});

Save the file, and you're ready to test.

That should be it. Hope this helps someone.

Thursday 28 January 2016

Use DDWRT to format your dates

The ddwrt date functions in SharePoint are not the easiest things to get to grips with. And it's quite difficult to find a definitive list of all the functions When I have found lists in other people's blogs, the functions posted don't always coincide with what I'm seeing in my site collection. This might be a result of us all using slightly different builds of SharePoint, but I'm going to list the ddwrt variations that I have observed on my SharePoint Site Collection by actually using them.

If you are going to use ddwrt date formatting in your Data View Web Part, you must include the line:

xmlns:ddwrt=http://schemas.microsoft.com/WebParts/v2/DataView/runtime

... inside the <xsl:stylesheet ...> tag, like this:

<xsl:stylesheet 
   version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" 
   xmlns:x="http://www.w3.org/2001/XMLSchema" 
   xmlns:dsp="http://schemas.microsoft.com/sharepoint/dsp" 
   xmlns:asp="http://schemas.microsoft.com/ASPNET/20" 
   xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" 
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
   xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
   xmlns:SharePoint="Microsoft.SharePoint.WebControls" 
   xmlns:ddwrt2="urn:frontpage:internal"
   xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime">

The 1033 LCID is used for US date formatting and the 2057 LCID is for UK date formatting. Where there's a hyphen in the Result column below, it means that the relevant ddwrt value produced no result for me.

DDWRT valueResult
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,1)" />2/21/2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,2)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,3)" />Sunday, February 21, 2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,4)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,5)" />2/21/2016 1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,6)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,7)" />Sunday, February 21, 2016 1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,8)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,9)" />2/21/2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,10)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,11)" />Sunday, February 21, 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,12)" />1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,13)" />2/21/2016 1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,14)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,15)" />Sunday, February 21, 2016 1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,16)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,17)" />2/21/2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,18)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,19)" />Sunday, February 21, 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,20)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,21)" />2/21/2016 1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,22)" />1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,23)" />Sunday, February 21, 2016 1:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,25)" />2/21/2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),1033,27)" />Sunday, February 21, 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,28)" />1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,29)" />2/21/2016 1:00:00 PM
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),1033,30)" />1:00:00 PM
DDWRT valueResult
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,1)" />21/02/2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,2)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,3)" />21 February 2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,4)" />13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,5)" />21/02/2016 13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,6)" />13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,7)" />21 February 2016 13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,8)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,9)" />21/02/2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,10)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,11)" />21 February 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,12)" />13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,13)" />21/02/2016 13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,14)" />13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,15)" />21 February 2016 13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,16)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,17)" />21/02/2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,18)" />-
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,19)" />21 February 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,20)" />13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,21)" />21/02/2016 13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,22)" />13:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,23)" />21 February 2016 13:00
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,25)" />21/02/2016
<xsl:value-of select="ddwrt:FormatDate(string(@StartDate),2057,27)" />21 February 2016
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,28)" />13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,29)" />21/02/2016 13:00:00
<xsl:value-of select="ddwrt:FormatDate(string(@EventDate),2057,30)" />13:00:00

If you're using the FormatDateTime specifier, it doesn't matter whether you define the LCID as 1033 or 2057.

DDWRT valueResult
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'dd-MMM-yy')" />21-Jan-15
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'dd/MM/yy')" />21/01/15
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'dd/M/yy')" />21/1/15
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'ddd, dd/M/yy')" />Sun, 21/1/15
<xsl:value-of select="ddwrt:FormatDateTime(string(@EventDate),1033,'dddd, dd/M/yy')" />Sunday, 21/1/15

The full list of specifiers is as follows:

SpecifierResult
dDay of the month, single digit 1-9, double digit 10 up
ddDay of the month, leading zero 1-9, double digit 10 up
dddAbbreviated day of the week, ie, Wed
ddddFull name of the day of the week
Specifier Result
MThe month, 1 to 12
MMThe month, 01 to 12
MMMAbbreviated name of the month
MMMMFull name of the month
Specifier Result
yThe year, from 0 to 99
yyThe year, from 00 to 99
yyyThe year with a minumum of three digits, ie 900
yyyyThe year as a four-digit number

You can also add the following, if you want to display the time, as well:

Specifier Result
h12-hour clock, 1 to 12
hh12-hour clock, 01 to 12
H24 hour clock, 1 to 24
HH24 hour clock, 01 to 24
mMinutes, 0 to 59
mmMinutes, 00 to 59
sSeconds, 0 to 59
ssSeconds, 00 to 59

Use anything you like to separate the components ... a dash, a slash or wordpsaces and/or commas.

Hope this helps someone.


Tuesday 19 January 2016

SharePoint Calendar – view as a year

The one thing SharePoint doesn’t do out of the box … well, there’s many things … but the one that has given me the biggest challenge is its inability to let me display a calendar in year-view. I’m sure there’s valid reasons for this. I’ve found that the Month View of the SharePoint calendar is a snake-pit of embedded tables and weird, week-long table rows. (Go “View Source” on a Month View if you want to be horrified.)

Yes, you can go to various sites and buy an enhanced Calendar web part, and admittedly, that might well save you time. But if, like me, you work in a corporate environment, you’ll no doubt find that installing alien software on your SharePoint server will involve much sighing and sucking of teeth from the IT Department.

So I thought I’d look into using a DataView Web Part to display a year’s-worth of events. It took me quite a while (I think I started this in 2013), but I’ve finally come up with something that’s at least workable, though by no means perfect. If anyone smarter than me (essentially just about everyone) reading this is able to suggest improvements, I would be hugely grateful.

So here’s what I ended up with:


This is where we want to be at the end of the process - rendering a calendar in year view. The colours can be changed in the code.
This is going to need SharePoint Designer access level, so if you haven’t got that then this method isn’t for you.

So the first thing you need to do is to create a SharePoint Calendar and populate it with some events. The one you see here is tracking our company Board and Committee meetings throughout the year. Prior to this, our senior PAs put this information into an Excel spreadsheet and emailed it around. My version at least gives users the ability to click on an event and be taken to a detail page, courtesy of the Calendar’s DispForm.aspx page.

I’ve kept the data in the Title field of each Calendar event as short as possible – three characters – as by necessity the columns in your year calendar are going to be narrow. Users can always click on the event to see more details if they don’t understand a three character acronym.

You should also consider adding a Category column (drop-down list) to your calendar. This will come in handy later when we colour code the types of events in the calendar.

An additional "Category" column can be added in the browser view of the Calendar's Settings area.
Right, let’s crack on … create a page in your subsite to hold the year view. You’ll need a page that has a web part zone running the width of the page. Check the page in as a Shared Draft.


I create the page in the browser so I can be sure of getting the right layout quickly and easily.
Now you’ll need to switch to SharePoint Designer and open the page you just created. (You could create the page in SPD, but I find it easier and quicker to use the browser for that.) Select the Web Part Zone and then bring up the Calendar in the Data Source Details sidebar. You can drop in any column you feel like – we’re going to overwrite most of the presentation code anyhow.


It doesn't really matter what data you insert in the Data View Web Part. We'll be changing the code as we go along and build a completely different view.
The Web Part you just added will look something like this.


Oh, how I love the "Common Data View Tasks" panel.
There are some adjustments to be made in the Common Data View Tasks panel, so we might as well make them now. First, set the Paging to Display All Items. Then, in Sort and Group, make the Sort Order “Start Time” and “Ascending”.  This latter one will be needed so that events on the same day appear in time order.

Use this control panel to determine the order in which events appear in each day cell. This is important as, left to their own devices, events will appear in the order they were entered in the calendar (ie in order of ID).
At this point you might want to review Christophe Humbert’s article on adding colour coding to a SharePoint 2007 calendar on his Path to SharePoint site. It only takes fifteen minutes and it will keep your monthly calendar view consistent with the Year View we’re building here. Indeed, this Year View uses some of Christophe’s techniques, so credit is due to M. Humbert for that. 

Once you’re done with that, switch back to SP Designer and locate the line:

<xsl:variable name="dvt_1_automode">0</xsl:variable>

Directly below, type the following:

<xsl:param name="Today" />

We’ll be using that variable further along. The next thing to do is to add the XSL code that will render the months and the days of the month. This was especially tricky, and most of the heavy lifting here was done by my friend Steve M. who is a Jedi Master when it comes to all kinds of code. So first the Months template, which is added below the dvt_1 template.  (We’ll be ripping out the dvt_1 template in a moment, so don’t worry about it too much.) Here’s the code to add:

<xsl:template name="Months">
  <xsl:param name="MonthNo" />
    <tr>
      <xsl:choose>
        <xsl:when test="$MonthNo= 0">
          <th width="32px" style="border:1px solid #666666; height=30px;background-color:#009fe3;color:#ffffff;">Month</th>
        </xsl:when>
        <xsl:otherwise>
        <td width="32px" class="ms-vb" style="border:1px solid #666666; height=30px;background-color:#009fe3;color:#ffffff;"><strong>
        <xsl:choose>
          <xsl:when test="$MonthNo= 1">Jan</xsl:when>
          <xsl:when test="$MonthNo= 2">Feb</xsl:when>
          <xsl:when test="$MonthNo= 3">Mar</xsl:when>
          <xsl:when test="$MonthNo= 4">Apr</xsl:when>
          <xsl:when test="$MonthNo= 5">May</xsl:when>
          <xsl:when test="$MonthNo= 6">Jun</xsl:when>
          <xsl:when test="$MonthNo= 7">Jul</xsl:when>
          <xsl:when test="$MonthNo= 8">Aug</xsl:when>
          <xsl:when test="$MonthNo= 9">Sep</xsl:when>
          <xsl:when test="$MonthNo= 10">Oct</xsl:when>
          <xsl:when test="$MonthNo= 11">Nov</xsl:when>
          <xsl:when test="$MonthNo= 12">Dec</xsl:when>
          <xsl:otherwise></xsl:otherwise>
        </xsl:choose>
        </strong></td>
        </xsl:otherwise>
        </xsl:choose>
        <xsl:call-template name="Days">
          <xsl:with-param name="MonthNo" select="$MonthNo" />
          <xsl:with-param name="DayNo" select="1" />
        </xsl:call-template>
    </tr>
    <xsl:if test="$MonthNo &lt; 12">
      <xsl:call-template name="Months">
        <xsl:with-param name="MonthNo" select="$MonthNo +1" />
      </xsl:call-template>
     </xsl:if>
</xsl:template>

All we’re doing here is writing the first column of the table to iterate the names of the months downwards. For the sake of clarity, it’s best to convert the SharePoint data, which holds the value of the month as a number, into the name of the month.

Next, we need to write the days across the page as columns. So add this template code directly beneath the Months template:

<xsl:template name="Days">
    <xsl:param name="MonthNo" />
    <xsl:param name="DayNo" />
    <xsl:variable name="Year" select="substring-before($Today, '-')" />
    <xsl:variable name="isLeapYear">
        <xsl:choose>
            <xsl:when test="($Year mod 4 = 0 and $Year mod 100 != 0) or $Year mod 400 = 0">True</xsl:when>
            <xsl:otherwise>False</xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="Month">
        <xsl:choose>
            <xsl:when test="$MonthNo &lt; 10">0<xsl:value-of select="$MonthNo" /></xsl:when>
            <xsl:otherwise><xsl:value-of select="$MonthNo" /></xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="Day">
        <xsl:choose>
            <xsl:when test="$DayNo &lt; 10">0<xsl:value-of select="$DayNo " /></xsl:when>
            <xsl:otherwise><xsl:value-of select="$DayNo " /></xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="backColor">
        <xsl:choose>
            <xsl:when test="contains(' 4 6 9 11 ', concat(' ', $MonthNo, ' ')) and $DayNo = 31">gray</xsl:when>
            <xsl:when test="$MonthNo = 2 and ($DayNo &gt;= 30 or ($isLeapYear = 'False' and $DayNo = 29))">gray</xsl:when>
            <xsl:otherwise></xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="thisDay" select="concat($Year, '-', $Month, '-', $Day)" />
    <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[substring-before(@EventDate, 'T') = $thisDay]"/>

    <xsl:choose>
        <xsl:when test="$MonthNo = 0">
        <th width="32px" style="border:1px solid #666666; height=30px;background-color:#83d0f5;"><xsl:value-of select="$DayNo" /></th>
        </xsl:when>
        <xsl:otherwise>
            <td class="ms-vb" width="32px" style="border:1px solid #666666; height=30px;">
            <xsl:attribute name="bgcolor"><xsl:value-of select="$backColor" /></xsl:attribute>
                <xsl:choose>
                    <xsl:when test="count($Rows) &gt; 0">
                        <xsl:for-each select="$Rows">
                            <xsl:call-template name="rowview" />
                        </xsl:for-each>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:text />
                    </xsl:otherwise>
                </xsl:choose>
            </td>
        </xsl:otherwise>
    </xsl:choose>

    <xsl:if test="$DayNo &lt; 31">
        <xsl:call-template name="Days">
            <xsl:with-param name="MonthNo" select="$MonthNo" />
            <xsl:with-param name="DayNo" select="$DayNo +1" />
        </xsl:call-template>
    </xsl:if>
</xsl:template>

So the first section is testing for a Leap Year. Obviously, we need to know how many days February has in the current year.

The next two sections are placing a zero in front of any single digit Month or Day.

The following section beginning with variable name=”backColour” is determining which months have less than 31 days and setting a background colour for the excess days.

Then the next section is rendering the rows and applying the background colour to the “non-existent” days (for example days 30 and 31 in Feb, 31 in April and so on) … and calling the rowview template. So we’ll need to add that right after this.

<xsl:template name="Days">
    <xsl:param name="MonthNo" />
    <xsl:param name="DayNo" />                               
    <xsl:variable name="Year" select="substring-before($Today, '-')" />
    <xsl:variable name="isLeapYear">
        <xsl:choose>
            <xsl:when test="($Year mod 4 = 0 and $Year mod 100 != 0) or $Year mod 400 = 0">True</xsl:when>
            <xsl:otherwise>False</xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="Month">
        <xsl:choose>
            <xsl:when test="$MonthNo &lt; 10">0<xsl:value-of select="$MonthNo" /></xsl:when>
            <xsl:otherwise><xsl:value-of select="$MonthNo" /></xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="Day">
        <xsl:choose>
            <xsl:when test="$DayNo &lt; 10">0<xsl:value-of select="$DayNo " /></xsl:when>
            <xsl:otherwise><xsl:value-of select="$DayNo " /></xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="backColor">
        <xsl:choose>
            <xsl:when test="contains(' 4 6 9 11 ', concat(' ', $MonthNo, ' ')) and $DayNo = 31">gray</xsl:when>
            <xsl:when test="$MonthNo = 2 and ($DayNo &gt;= 30 or ($isLeapYear = 'False' and $DayNo = 29))">gray</xsl:when>
            <xsl:otherwise></xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="thisDay" select="concat($Year, '-', $Month, '-', $Day)" />
    <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row[substring-before(@EventDate, 'T') = $thisDay]"/>

    <xsl:choose>
        <xsl:when test="$MonthNo = 0">
        <th width="32px" style="border:1px solid #666666; height=30px;background-color:#83d0f5;"><xsl:value-of select="$DayNo" /></th>
        </xsl:when>
        <xsl:otherwise>
            <td class="ms-vb" width="32px" style="border:1px solid #666666; height=30px;">
                <xsl:attribute name="bgcolor"><xsl:value-of select="$backColor" /></xsl:attribute>
                <xsl:choose>
                    <xsl:when test="count($Rows) &gt; 0">
                        <xsl:for-each select="$Rows">
                            <xsl:call-template name="rowview" />
                        </xsl:for-each>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:text />
                    </xsl:otherwise>
                </xsl:choose>
            </td>
        </xsl:otherwise>
    </xsl:choose>

    <xsl:if test="$DayNo &lt; 31">
        <xsl:call-template name="Days">
            <xsl:with-param name="MonthNo" select="$MonthNo" />
            <xsl:with-param name="DayNo" select="$DayNo +1" />
        </xsl:call-template>
    </xsl:if>
</xsl:template>

So, now we add the rowview template.  This will render the events into the appropriate table cells, and apply colour.

<xsl:template name="rowview"> <xsl:variable name="color"> <xsl:choose> <xsl:when test="@Category = 'Boards'">Blue</xsl:when> <xsl:when test="@Category = 'Formal Committees'">HotPink</xsl:when> <xsl:when test="@Category = 'Divisional Boards'">Purple</xsl:when> <xsl:when test="@Category = 'Proposed Trips'">Red</xsl:when> <xsl:when test="@Category = 'Visits'">Green</xsl:when> <xsl:when test="@Category = 'Group Committees/Boards'">Indigo</xsl:when> <xsl:when test="@Category = 'Public Holiday'">Grey</xsl:when> <xsl:when test="@Category = 'Group'">Brown</xsl:when> <xsl:when test="@Category = 'Other'">Darkorange</xsl:when> </xsl:choose> </xsl:variable> <xsl:variable name="EventDate" select="(number(ddwrt:DateTimeTick(ddwrt:GenDisplayName(string(@EventDate))))) div 864000000000" /> <xsl:variable name="EndDate" select="(number(ddwrt:DateTimeTick(ddwrt:GenDisplayName(string(@EndDate))))) div 864000000000" /> <xsl:variable name="width"> <xsl:choose> <xsl:when test="$EndDate - $EventDate = 1">143%</xsl:when> <xsl:when test="$EndDate - $EventDate = 2">178%</xsl:when> <xsl:when test="$EndDate - $EventDate = 3">217%</xsl:when> <xsl:when test="$EndDate - $EventDate = 4">232%</xsl:when> <xsl:otherwise>95%</xsl:otherwise> </xsl:choose> </xsl:variable> <a href="http://yourintranet/departments/management/Lists/Main%20Boards%20%20Committees/DispForm.aspx?ID={@ID}&amp;Source=http://yourintranet/departments/management/Pages/BoardsCommitteesYearView.aspx"> <span style="position:relative;display:inline-block;width:{$width};"> <span style="display:inline-block;width:100%;cursor:pointer;text-align:center;border:1px solid {$color};position:absolute;color:{$color};"><xsl:value-of select="@Title" /></span> <span style="display:inline-block;width:100%;background-color:{$color};text-align:center;border:1px solid;z-index:-1;filter:alpha(opacity=30);opacity:0.3;"><xsl:value-of select="@Title" /></span> </span> </a><br /> </xsl:template>

This is where I’ve swiped some of Christophe’s code to render the different categories of events in different colours, using it to set a variable, color. 

Then from Marc D Anderson’s blog, I swiped a bit of XSLT arithmetic and used it to check whether an event is more than one day. This bit gave me a bit of a problem, until I realised that a one day event gives a value of 0, a two day event returns 1, a three day event gives 2 and so on. I was then able to set the width of multiple day events accordingly, using a “width” variable.

NOTE: This is completely different to how SharePoint renders events in the Calendar’s Month View. To be honest, it’s a bit of a clumsy work-around. But the thought of trying to follow how the out-of-the-box month view works was more than a bit scary. You may have a better way of doing this, and if you do, feel free to share it. But this was the best I could manage.

The final section renders the actual event in the table cell and applies the colour. I freely admit I couldn’t done this without Christophe’s and Marc's excellent work.

We still have a couple of things to do to get this all to work. So next, remove all the XLST code that the SharePoint Designer wizard dropped in. That means delete the dvt_body template and the dvt_1.rowview template, then go back to the top and replace the two sections:

<xsl:template match …>

… and the dvt_1 template with this code.

    <xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls"> <table width="100%" cellpadding="2" cellspacing="0" style="border:1px solid #666666;border-collapse: collapse;"> <xsl:call-template name="Months"> <xsl:with-param name="MonthNo" select="0" /> </xsl:call-template> <xsl:choose> <xsl:when test="ddwrt:IfHasRights(2)"> <tr> <td class="ms-addnew" style="padding: 4px" colspan="99"><img src="/_layouts/images/rect.gif" /> <a class="ms-addnew" ID="idHomePageNewEvent" href="/departments/management/Lists/Main%20Boards%20%20Committees/NewForm.aspx?Source=http://yourintranet/departments/management/Lists/Main%20Boards%20%20Committees/calendar.aspx" onclick="javascript:NewItem('/departments/management/Lists/Main%20Boards%20%20Committees/NewForm.aspx?Source=http://yourintranet/departments/management/Lists/Main%20Boards%20%20Committees/calendar.aspx', true);javascript:return false;" target="_self"> Add new meeting</a></td> </tr> </xsl:when> <xsl:otherwise></xsl:otherwise> </xsl:choose> </table> </xsl:template>

This is what pulls the whole thing together. I also added a text link at the bottom of the table to allow Site Editors to add new events without having to go the whole Site Actions > View All Site Content route.

Admittedly, this may not be the most elegant solution. But I still haven’t been able to find an alternative approach by searching the Internet. 

My least favourite aspect of this solution is how a multiple day event will simply overlay any first event in any of the other days in the date range. I wasn’t able to find a way of getting the overlaid event to sit below the multiple day event.


In this case, the green event (a three-dayer) is overlaid on top of the event that is scheduled for the second day, making for an ugly display.
All I could do was input the multiple day events into the calendar as a series of single day events so that everything would display correctly.


The only workaround I could think of was to split the three-day event into two separate sections, forcing the event on the second day to sit below the three day event. It's not great, but it's better than what I first had.
If you’re able to offer any improvements of a better way of doing any of the above, not just the imperfect display of events, then it would be great if you could share by leaving a comment below.

I hope this helps someone.