I'm currently doing some work for a client to implement an intranet using SharePoint 2013, and one of their requirements is a simple calendar using overlays to colour code events.
The short
Calendar overlays aren't listed on calendar views when accessing the view via managed navigation unless you explicitly include the view ID in the target URL of the navigation term settings.
The long
This should be pretty straight forward, and works well enough:
They're also using Managed Navigation on their site. No worries, surely calendar overlays and managed navigation don't interfere with each other, right? Oh wait, this is SharePoint, of course something will go wrong!
The above screenshot was taken when accessing the actual URL (~sitecollection/Lists/Events/calendar.aspx), while the below screenshot was taken when accessing the managed navigation term which pointed to the aforementioned URL (i.e. ~sitecollection/events is configured to display ~sitecollection/Lists/Events/calendar.aspx):
Where is the list of calendar overlays?! Everything else works as expected: the events are colour coded, you can add events, use the ribbon to switch views, use the side navigation to change the viewed month. Just the list of overlays is missing.
After poking around a bit (in none other than SPView.ServerRelativeUrl. As the request URL is a friendly URL from managed navigation, it doesn't match any view URLs. As no views are matched, the component can't determine it's looking at calendar view, so doesn't render.
Stepping through the code:
// Microsoft.SharePoint.ApplicationPages.WebControls.CalendarAggregationPanel
protected override void CreateChildControls()
{
this.Visible = this.IsVisible();
if (!this.Visible)
{
return;
}
// …
}
The CalendarAggregationPanel is just the list of overlays. The IsVisible
method determines if the control should be visible (shocking! =P).
// Microsoft.SharePoint.ApplicationPages.WebControls.CalendarAggregationPanel
private bool IsVisible()
{
// …
SPView view = SPContext.Current.ViewContext.View;
if (view == null || SPViewCollection.StringToSPViewType(view.Type) != SPViewCollection.SPViewType.Calendar)
{
return false;
}
// …
}
Ok, so it gets the view and determines the SPView.Type. But my SPView was coming back null
, so let's look at the property definition:
// Microsoft.SharePoint.SPViewContext
public SPView View
{
get
{
if (this.m_view == null && this.ParentContext.List != null && this.ViewId != Guid.Empty)
{
// …
}
return this.m_view;
}
}
And ViewId
was returning Guid.Empty
. And herein lies the problem. Looking at that property:
// Microsoft.SharePoint.SPViewContext
public Guid ViewId
{
get
{
if (!this.m_isViewIdSet)
{
string text = this.ParentContext.HttpRequestContext.Request.QueryString["View"];
if (string.IsNullOrEmpty(text))
{
text = this.ParentContext.HttpRequestContext.Request.QueryString["ShowWebPart"];
}
if (!string.IsNullOrEmpty(text))
{
try
{
this.m_viewId = new Guid(text);
}
// catches setting to Guid.Empty
}
if (this.m_viewId == Guid.Empty && this.ParentContext.ItemId == 0 && this.ParentContext.FormContext.FormMode == SPControlMode.Invalid)
{
SPList list = this.ParentContext.List;
string originalServerRelativeRequestUrl = SPUtility.OriginalServerRelativeRequestUrl;
if (list != null && originalServerRelativeRequestUrl != null)
{
foreach (SPView sPView in list.LightweightViews)
{
if (originalServerRelativeRequestUrl.StartsWith(sPView.ServerRelativeUrl))
{
this.View = sPView;
break;
}
}
}
}
this.m_isViewIdSet = true;
}
return this.m_viewId;
}
}
This attempts to get the view ID from either query string parameters (which aren't set in any case, but we'll come back to that because it's useful to know), and then tries to figure it out based on the request URL. As SPView.ServerRelativeUrl is the actual SharePoint URL (e.g. ~sitecollection/Lists/Events/calendar.aspx), no view is matched.
To test this a bit more, I changed my browser to load http://servername/events?View={view-id}
and like magic the calendar overlays were listed as expected.
We can use this to work around the problem. Having ~sitecollection/events?View={view-id}
is pretty ugly, but we can actually include this is the target URL for the navigation term (e.g. ~sitecollection/Lists/Events/calendar.aspx?View={view-id}), and it will get included in the HttpRequest.QueryString
collection. Brilliant! Of course, that means you need to hard-code a GUID in your term sets, which isn't ideal, but "ideal" and "SharePoint" don't mix!